Compare commits
No commits in common. "improved" and "master" have entirely different histories.
@ -1,3 +1,2 @@
|
|||||||
# Rogue101
|
# Rogue101
|
||||||
|
|
||||||
Originally a programming assignment in INF101. Added here for archival.
|
Originally a programming assignment in INF101. Added here for archival.
|
@ -1,38 +1,38 @@
|
|||||||
package inf101.v18.gfx;
|
package inf101.v18.gfx;
|
||||||
|
|
||||||
public interface IPaintLayer {
|
public interface IPaintLayer {
|
||||||
/**
|
/**
|
||||||
* Clear the layer.
|
* Clear the layer.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Everything on the layer is removed, leaving only transparency.
|
* Everything on the layer is removed, leaving only transparency.
|
||||||
*/
|
*/
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send this layer to the back, so it will be drawn behind any other layers.
|
* Send this layer to the back, so it will be drawn behind any other layers.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* There will still be background behind this layer. You may clear it or draw to
|
* There will still be background behind this layer. You may clear it or draw to
|
||||||
* it using {@link Screen#clearBackground()},
|
* it using {@link Screen#clearBackground()},
|
||||||
* {@link Screen#setBackground(javafx.scene.paint.Color)} and
|
* {@link Screen#setBackground(javafx.scene.paint.Color)} and
|
||||||
* {@link Screen#getBackgroundContext()}.
|
* {@link Screen#getBackgroundContext()}.
|
||||||
*/
|
*/
|
||||||
void layerToBack();
|
void layerToBack();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send this layer to the front, so it will be drawn on top of any other layers.
|
* Send this layer to the front, so it will be drawn on top of any other layers.
|
||||||
*/
|
*/
|
||||||
void layerToFront();
|
void layerToFront();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Width (in pixels) of graphics layer
|
* @return Width (in pixels) of graphics layer
|
||||||
*/
|
*/
|
||||||
double getWidth();
|
double getWidth();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Height (in pixels) of graphics layer
|
* @return Height (in pixels) of graphics layer
|
||||||
*/
|
*/
|
||||||
double getHeight();
|
double getHeight();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -2,196 +2,204 @@ package inf101.v18.gfx.gfxmode;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @author anya
|
* @author anya
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public class Direction {
|
public class Direction {
|
||||||
|
/**
|
||||||
|
* Construct direction from an angle
|
||||||
|
*
|
||||||
|
* @param degrees
|
||||||
|
* Angle in degrees, where 0 is (1,0)
|
||||||
|
*/
|
||||||
|
public static Direction fromDegrees(double degrees) {
|
||||||
|
return new Direction(degrees);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct direction from an angle
|
* Construct direction from a vector
|
||||||
*
|
*
|
||||||
* @param degrees Angle in degrees, where 0 is (1,0)
|
* @param x
|
||||||
*/
|
* X direction
|
||||||
public static Direction fromDegrees(double degrees) {
|
* @param y
|
||||||
return new Direction(degrees);
|
* Y direction
|
||||||
}
|
*/
|
||||||
|
public static Direction fromVector(double x, double y) {
|
||||||
|
return new Direction(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
private double xDir;
|
||||||
* Construct direction from a vector
|
|
||||||
*
|
|
||||||
* @param x X direction
|
|
||||||
* @param y Y direction
|
|
||||||
*/
|
|
||||||
public static Direction fromVector(double x, double y) {
|
|
||||||
return new Direction(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
private double xDir;
|
private double yDir;
|
||||||
|
|
||||||
private double yDir;
|
/**
|
||||||
|
* Create a new direction.
|
||||||
|
*
|
||||||
|
* The direction vector will be normalised to a vector of length 1.
|
||||||
|
*
|
||||||
|
* @param degrees
|
||||||
|
* Angle of direction in degrees
|
||||||
|
*/
|
||||||
|
public Direction(double degrees) {
|
||||||
|
double radians = Math.toRadians(degrees);
|
||||||
|
this.xDir = Math.cos(radians);
|
||||||
|
this.yDir = Math.sin(radians);
|
||||||
|
normalize();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new direction.
|
* Create a new direction.
|
||||||
* <p>
|
*
|
||||||
* The direction vector will be normalised to a vector of length 1.
|
* The direction vector will be normalised to a vector of length 1.
|
||||||
*
|
*
|
||||||
* @param degrees Angle of direction in degrees
|
* @param xDir
|
||||||
*/
|
* X-component of direction vector
|
||||||
public Direction(double degrees) {
|
* @param yDir
|
||||||
double radians = Math.toRadians(degrees);
|
* Y-component of direction vector
|
||||||
this.xDir = Math.cos(radians);
|
*/
|
||||||
this.yDir = Math.sin(radians);
|
public Direction(double xDir, double yDir) {
|
||||||
normalize();
|
this.xDir = xDir;
|
||||||
}
|
this.yDir = yDir;
|
||||||
|
normalize();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new direction.
|
* Multiply direction by distance
|
||||||
* <p>
|
*
|
||||||
* The direction vector will be normalised to a vector of length 1.
|
* @param distance
|
||||||
*
|
* @return Position delta
|
||||||
* @param xDir X-component of direction vector
|
*/
|
||||||
* @param yDir Y-component of direction vector
|
public Point getMovement(double distance) {
|
||||||
*/
|
return new Point(xDir * distance, -yDir * distance);
|
||||||
public Direction(double xDir, double yDir) {
|
}
|
||||||
this.xDir = xDir;
|
|
||||||
this.yDir = yDir;
|
|
||||||
normalize();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Multiply direction by distance
|
* @return X-component of direction vector
|
||||||
*
|
*
|
||||||
* @param distance
|
* Same as the Math.cos(toRadians())
|
||||||
* @return Position delta
|
*/
|
||||||
*/
|
public double getX() {
|
||||||
public Point getMovement(double distance) {
|
return xDir;
|
||||||
return new Point(xDir * distance, -yDir * distance);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return X-component of direction vector
|
* @return Y-component of direction vector
|
||||||
* <p>
|
*
|
||||||
* Same as the Math.cos(toRadians())
|
* Same as the Math.sin(toRadians())
|
||||||
*/
|
*/
|
||||||
public double getX() {
|
public double getY() {
|
||||||
return xDir;
|
return yDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void normalize() {
|
||||||
* @return Y-component of direction vector
|
double l = Math.sqrt(xDir * xDir + yDir * yDir);
|
||||||
* <p>
|
if (l >= 0.00001) {
|
||||||
* Same as the Math.sin(toRadians())
|
xDir = xDir / l;
|
||||||
*/
|
yDir = yDir / l;
|
||||||
public double getY() {
|
} else if (xDir > 0) {
|
||||||
return yDir;
|
xDir = 1;
|
||||||
}
|
yDir = 0;
|
||||||
|
} else if (xDir < 0) {
|
||||||
|
xDir = -1;
|
||||||
|
yDir = 0;
|
||||||
|
} else if (yDir > 0) {
|
||||||
|
xDir = 0;
|
||||||
|
yDir = 1;
|
||||||
|
} else if (yDir < 0) {
|
||||||
|
xDir = 0;
|
||||||
|
yDir = -1;
|
||||||
|
} else {
|
||||||
|
xDir = 1;
|
||||||
|
yDir = 0;
|
||||||
|
}
|
||||||
|
|
||||||
private void normalize() {
|
}
|
||||||
double l = Math.sqrt(xDir * xDir + yDir * yDir);
|
|
||||||
if (l >= 0.00001) {
|
|
||||||
xDir = xDir / l;
|
|
||||||
yDir = yDir / l;
|
|
||||||
} else if (xDir > 0) {
|
|
||||||
xDir = 1;
|
|
||||||
yDir = 0;
|
|
||||||
} else if (xDir < 0) {
|
|
||||||
xDir = -1;
|
|
||||||
yDir = 0;
|
|
||||||
} else if (yDir > 0) {
|
|
||||||
xDir = 0;
|
|
||||||
yDir = 1;
|
|
||||||
} else if (yDir < 0) {
|
|
||||||
xDir = 0;
|
|
||||||
yDir = -1;
|
|
||||||
} else {
|
|
||||||
xDir = 1;
|
|
||||||
yDir = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
/**
|
||||||
|
* Translate to angle (in degrees)
|
||||||
|
*
|
||||||
|
* @return Angle in degrees, -180 .. 180
|
||||||
|
*/
|
||||||
|
public double toDegrees() {
|
||||||
|
return Math.toDegrees(Math.atan2(yDir, xDir));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translate to angle (in degrees)
|
* Translate to angle (in radians)
|
||||||
*
|
*
|
||||||
* @return Angle in degrees, -180 .. 180
|
* @return Angle in radians, -2π .. 2π
|
||||||
*/
|
*/
|
||||||
public double toDegrees() {
|
public double toRadians() {
|
||||||
return Math.toDegrees(Math.atan2(yDir, xDir));
|
return Math.atan2(yDir, xDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Translate to angle (in radians)
|
public String toString() {
|
||||||
*
|
return String.format("%.2f", toDegrees());
|
||||||
* @return Angle in radians, -2π .. 2π
|
}
|
||||||
*/
|
|
||||||
public double toRadians() {
|
|
||||||
return Math.atan2(yDir, xDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public String toString() {
|
* Turn (relative)
|
||||||
return String.format("%.2f", toDegrees());
|
*
|
||||||
}
|
* @param deltaDir
|
||||||
|
*/
|
||||||
|
public Direction turn(Direction deltaDir) {
|
||||||
|
return new Direction(xDir + deltaDir.xDir, yDir + deltaDir.yDir);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turn (relative)
|
* Turn angle degrees
|
||||||
*
|
*
|
||||||
* @param deltaDir
|
* @param angle
|
||||||
*/
|
*/
|
||||||
public Direction turn(Direction deltaDir) {
|
public Direction turn(double angle) {
|
||||||
return new Direction(xDir + deltaDir.xDir, yDir + deltaDir.yDir);
|
return turnTo(toDegrees() + angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turn angle degrees
|
* Turn around 180 degrees
|
||||||
*
|
*/
|
||||||
* @param angle
|
public Direction turnBack() {
|
||||||
*/
|
return turn(180.0);
|
||||||
public Direction turn(double angle) {
|
}
|
||||||
return turnTo(toDegrees() + angle);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turn around 180 degrees
|
* Turn left 90 degrees
|
||||||
*/
|
*/
|
||||||
public Direction turnBack() {
|
public Direction turnLeft() {
|
||||||
return turn(180.0);
|
return turn(90.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turn left 90 degrees
|
* Turn right 90 degrees
|
||||||
*/
|
*/
|
||||||
public Direction turnLeft() {
|
public Direction turnRight() {
|
||||||
return turn(90.0);
|
return turn(-90.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turn right 90 degrees
|
* Absolute turn
|
||||||
*/
|
*
|
||||||
public Direction turnRight() {
|
* @param degrees
|
||||||
return turn(-90.0);
|
* Angle in degrees, where 0 is (1,0)
|
||||||
}
|
*/
|
||||||
|
public Direction turnTo(double degrees) {
|
||||||
/**
|
return new Direction(degrees);
|
||||||
* Absolute turn
|
}
|
||||||
*
|
|
||||||
* @param degrees Angle in degrees, where 0 is (1,0)
|
|
||||||
*/
|
|
||||||
public Direction turnTo(double degrees) {
|
|
||||||
return new Direction(degrees);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Turn slightly towards a directions
|
|
||||||
*
|
|
||||||
* @param dir A direction
|
|
||||||
* @param percent How much to turn (100.0 is the same as turnTo())
|
|
||||||
*/
|
|
||||||
public Direction turnTowards(Direction dir, double percent) {
|
|
||||||
return new Direction(xDir * (1.00 - percent / 100.0) + dir.xDir * (percent / 100.0),
|
|
||||||
yDir * (1.00 - percent / 100.0) + dir.yDir * (percent / 100.0));
|
|
||||||
// double thisAngle = toAngle();
|
|
||||||
// double otherAngle = dir.toAngle();
|
|
||||||
// turnTo(thisAngle*(1.00 - percent/100.0) +
|
|
||||||
// otherAngle*(percent/100.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn slightly towards a directions
|
||||||
|
*
|
||||||
|
* @param dir
|
||||||
|
* A direction
|
||||||
|
* @param percent
|
||||||
|
* How much to turn (100.0 is the same as turnTo())
|
||||||
|
*/
|
||||||
|
public Direction turnTowards(Direction dir, double percent) {
|
||||||
|
return new Direction(xDir * (1.00 - percent / 100.0) + dir.xDir * (percent / 100.0),
|
||||||
|
yDir * (1.00 - percent / 100.0) + dir.yDir * (percent / 100.0));
|
||||||
|
// double thisAngle = toAngle();
|
||||||
|
// double otherAngle = dir.toAngle();
|
||||||
|
// turnTo(thisAngle*(1.00 - percent/100.0) +
|
||||||
|
// otherAngle*(percent/100.0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,5 @@
|
|||||||
package inf101.v18.gfx.gfxmode;
|
package inf101.v18.gfx.gfxmode;
|
||||||
|
|
||||||
public enum Gravity {
|
public enum Gravity {
|
||||||
|
NORTH, NORTHWEST, WEST, SOUTHWEST, SOUTH, SOUTHEAST, EAST, NORTHEAST, CENTER
|
||||||
NORTH,
|
|
||||||
NORTHWEST,
|
|
||||||
WEST,
|
|
||||||
SOUTHWEST,
|
|
||||||
SOUTH,
|
|
||||||
SOUTHEAST,
|
|
||||||
EAST,
|
|
||||||
NORTHEAST,
|
|
||||||
CENTER
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,45 +5,45 @@ import javafx.scene.paint.Paint;
|
|||||||
|
|
||||||
public interface IPainter extends IPaintLayer {
|
public interface IPainter extends IPaintLayer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restore graphics settings previously stored by {@link #save()}.
|
* Restore graphics settings previously stored by {@link #save()}.
|
||||||
*
|
*
|
||||||
* @return {@code this}, for sending more draw commands
|
* @return {@code this}, for sending more draw commands
|
||||||
*/
|
*/
|
||||||
IPainter restore();
|
IPainter restore();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store graphics settings.
|
* Store graphics settings.
|
||||||
*
|
*
|
||||||
* @return {@code this}, for sending more draw commands
|
* @return {@code this}, for sending more draw commands
|
||||||
*/
|
*/
|
||||||
IPainter save();
|
IPainter save();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set colour used to drawing and filling.
|
* Set colour used to drawing and filling.
|
||||||
*
|
*
|
||||||
* @param ink A colour or paint
|
* @param ink A colour or paint
|
||||||
* @return {@code this}, for sending more draw commands
|
* @return {@code this}, for sending more draw commands
|
||||||
*/
|
*/
|
||||||
IPainter setInk(Paint ink);
|
IPainter setInk(Paint ink);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start drawing a shape.
|
* Start drawing a shape.
|
||||||
*
|
*
|
||||||
* @return An IShape for sending shape drawing commands
|
* @return An IShape for sending shape drawing commands
|
||||||
*/
|
*/
|
||||||
IShape shape();
|
IShape shape();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start drawing with a turtle.
|
* Start drawing with a turtle.
|
||||||
*
|
*
|
||||||
* @return An ITurtle for sending turtle drawing commands
|
* @return An ITurtle for sending turtle drawing commands
|
||||||
*/
|
*/
|
||||||
ITurtle turtle();
|
ITurtle turtle();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Current ink, as set by {@link #setInk(Paint)}
|
* @return Current ink, as set by {@link #setInk(Paint)}
|
||||||
*/
|
*/
|
||||||
Paint getInk();
|
Paint getInk();
|
||||||
|
|
||||||
}
|
}
|
@ -6,226 +6,234 @@ import javafx.scene.shape.Shape;
|
|||||||
|
|
||||||
public interface IShape {
|
public interface IShape {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add another point to the line path
|
* Add another point to the line path
|
||||||
*
|
*
|
||||||
* @param x
|
* @param xy
|
||||||
* @param y
|
* @return
|
||||||
* @return
|
*/
|
||||||
*/
|
IShape addPoint(double x, double y);
|
||||||
IShape addPoint(double x, double y);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add another point to the line path
|
* Add another point to the line path
|
||||||
*
|
*
|
||||||
* @param xy
|
* @param xy
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
IShape addPoint(Point xy);
|
IShape addPoint(Point xy);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the arc angle for the subsequent draw commands
|
* Set the arc angle for the subsequent draw commands
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* For use with {@link #arc()}
|
* For use with {@link #arc()}
|
||||||
*
|
*
|
||||||
* @param a The angle, in degrees
|
* @param a
|
||||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
* The angle, in degrees
|
||||||
* draw command
|
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||||
*/
|
* draw command
|
||||||
IShape angle(double a);
|
*/
|
||||||
|
IShape angle(double a);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draw an arc with the current drawing parameters
|
* Draw an arc with the current drawing parameters
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Relevant parameters:
|
* Relevant parameters:
|
||||||
* <li>{@link #at(Point)}, {@link #x(double)}, {@link #gravity(double)}
|
* <li>{@link #at(Point)}, {@link #x(double)}, {@link #gravity(double)}
|
||||||
* <li>{@link #length(double)}
|
* <li>{@link #length(double)}
|
||||||
* <li>{@link #angle(double)}
|
* <li>{@link #angle(double)}
|
||||||
* <li>{@link #gravity(Gravity)}
|
* <li>{@link #gravity(Gravity)}
|
||||||
* <li>{@link #stroke(Paint)}, {@link #fill(Paint)}
|
* <li>{@link #stroke(Paint)}, {@link #fill(Paint)}
|
||||||
* <li>{@link #rotation(double)}
|
* <li>{@link #rotation(double)}
|
||||||
*
|
*
|
||||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||||
* draw command
|
* draw command
|
||||||
*/
|
*/
|
||||||
IShape arc();
|
IShape arc();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the (x,y)-coordinates of the next draw command
|
* Set the (x,y)-coordinates of the next draw command
|
||||||
*
|
*
|
||||||
* @param p Coordinates
|
* @param p
|
||||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
* Coordinates
|
||||||
* draw command
|
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||||
*/
|
* draw command
|
||||||
IShape at(Point p);
|
*/
|
||||||
|
IShape at(Point p);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the line path, turning it into a polygon.
|
* Close the line path, turning it into a polygon.
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
IShape close();
|
IShape close();
|
||||||
|
|
||||||
void draw();
|
void draw();
|
||||||
|
|
||||||
void draw(GraphicsContext context);
|
void draw(GraphicsContext context);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draw an ellipse with the current drawing parameters
|
* Draw an ellipse with the current drawing parameters
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Relevant parameters:
|
* Relevant parameters:
|
||||||
* <li>{@link #at(Point)}, {@link #x(double)}, {@link #gravity(double)}
|
* <li>{@link #at(Point)}, {@link #x(double)}, {@link #gravity(double)}
|
||||||
* <li>{@link #width(double)}, {@link #height(double)}
|
* <li>{@link #width(double)}, {@link #height(double)}
|
||||||
* <li>{@link #gravity(Gravity)}
|
* <li>{@link #gravity(Gravity)}
|
||||||
* <li>{@link #stroke(Paint)}, {@link #fill(Paint)}
|
* <li>{@link #stroke(Paint)}, {@link #fill(Paint)}
|
||||||
* <li>{@link #rotation(double)}
|
* <li>{@link #rotation(double)}
|
||||||
*
|
*
|
||||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||||
* draw command
|
* draw command
|
||||||
*/
|
*/
|
||||||
IShape ellipse();
|
IShape ellipse();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fill the current shape
|
* Fill the current shape
|
||||||
*
|
*
|
||||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||||
* draw command
|
* draw command
|
||||||
*/
|
*/
|
||||||
IShape fill();
|
IShape fill();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set fill colour for the subsequent draw commands
|
* Set fill colour for the subsequent draw commands
|
||||||
*
|
*
|
||||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||||
* draw command
|
* draw command
|
||||||
*/
|
*/
|
||||||
IShape fillPaint(Paint p);
|
IShape fillPaint(Paint p);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set gravity for the subsequent draw commands
|
* Set gravity for the subsequent draw commands
|
||||||
* <p>
|
*
|
||||||
* Gravity determines the point on the shape that will be used for positioning
|
* Gravity determines the point on the shape that will be used for positioning
|
||||||
* and rotation.
|
* and rotation.
|
||||||
*
|
*
|
||||||
* @param g The gravity
|
* @param g
|
||||||
* @return
|
* The gravity
|
||||||
*/
|
* @return
|
||||||
IShape gravity(Gravity g);
|
*/
|
||||||
|
IShape gravity(Gravity g);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the height of the next draw command
|
* Set the height of the next draw command
|
||||||
*
|
*
|
||||||
* @param h The height
|
* @param h
|
||||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
* The height
|
||||||
* draw command
|
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||||
*/
|
* draw command
|
||||||
IShape height(double h);
|
*/
|
||||||
|
IShape height(double h);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the length of the following draw commands
|
* Set the length of the following draw commands
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* For use with {@link #line()} and {@link #arc()}
|
* For use with {@link #line()} and {@link #arc()}
|
||||||
*
|
*
|
||||||
* @param l The length
|
* @param l
|
||||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
* The length
|
||||||
* draw command
|
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||||
*/
|
* draw command
|
||||||
IShape length(double l);
|
*/
|
||||||
|
IShape length(double l);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draw a line with the current drawing parameters
|
* Draw a line with the current drawing parameters
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Relevant parameters:
|
* Relevant parameters:
|
||||||
* <li>{@link #at(Point)}, {@link #x(double)}, {@link #gravity(double)}
|
* <li>{@link #at(Point)}, {@link #x(double)}, {@link #gravity(double)}
|
||||||
* <li>{@link #length(double)}
|
* <li>{@link #length(double)}
|
||||||
* <li>{@link #angle(double)}
|
* <li>{@link #angle(double)}
|
||||||
* <li>{@link #gravity(Gravity)} (flattened to the horizontal axis, so, e.g.,
|
* <li>{@link #gravity(Gravity)} (flattened to the horizontal axis, so, e.g.,
|
||||||
* {@link Gravity#NORTH} = {@link Gravity#SOUTH} = {@link Gravity#CENTER})
|
* {@link Gravity#NORTH} = {@link Gravity#SOUTH} = {@link Gravity#CENTER})
|
||||||
* <li>{@link #stroke(Paint)}
|
* <li>{@link #stroke(Paint)}
|
||||||
* <li>{@link #rotation(double)}
|
* <li>{@link #rotation(double)}
|
||||||
*
|
*
|
||||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||||
* draw command
|
* draw command
|
||||||
*/
|
*/
|
||||||
IShape line();
|
IShape line();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draw a rectangle with the current drawing parameters
|
* Draw a rectangle with the current drawing parameters
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Relevant parameters:
|
* Relevant parameters:
|
||||||
* <li>{@link #at(Point)}, {@link #x(double)}, {@link #gravity(double)}
|
* <li>{@link #at(Point)}, {@link #x(double)}, {@link #gravity(double)}
|
||||||
* <li>{@link #width(double)}, {@link #height(double)}
|
* <li>{@link #width(double)}, {@link #height(double)}
|
||||||
* <li>{@link #gravity(Gravity)}
|
* <li>{@link #gravity(Gravity)}
|
||||||
* <li>{@link #stroke(Paint)}, {@link #fill(Paint)}
|
* <li>{@link #stroke(Paint)}, {@link #fill(Paint)}
|
||||||
* <li>{@link #rotation(double)}
|
* <li>{@link #rotation(double)}
|
||||||
*
|
*
|
||||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||||
* draw command
|
* draw command
|
||||||
*/
|
*/
|
||||||
IShape rectangle();
|
IShape rectangle();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets rotation for subsequent draw commands.
|
* Sets rotation for subsequent draw commands.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Shapes will be rotate around the {@link #gravity(Gravity)} point.
|
* Shapes will be rotate around the {@link #gravity(Gravity)} point.
|
||||||
*
|
*
|
||||||
* @param angle Rotation in degrees
|
* @param angle
|
||||||
* @return
|
* Rotation in degrees
|
||||||
*/
|
* @return
|
||||||
IShape rotation(double angle);
|
*/
|
||||||
|
IShape rotation(double angle);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stroke the current shape
|
* Stroke the current shape
|
||||||
*
|
*
|
||||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||||
* draw command
|
* draw command
|
||||||
*/
|
*/
|
||||||
IShape stroke();
|
IShape stroke();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set stroke colour for the subsequent draw commands
|
* Set stroke colour for the subsequent draw commands
|
||||||
*
|
*
|
||||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||||
* draw command
|
* draw command
|
||||||
*/
|
*/
|
||||||
IShape strokePaint(Paint p);
|
IShape strokePaint(Paint p);
|
||||||
|
|
||||||
Shape toFXShape();
|
Shape toFXShape();
|
||||||
|
|
||||||
String toSvg();
|
String toSvg();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the width of the next draw command
|
* Set the width of the next draw command
|
||||||
*
|
*
|
||||||
* @param w The width
|
* @param w
|
||||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
* The width
|
||||||
* draw command
|
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||||
*/
|
* draw command
|
||||||
IShape width(double w);
|
*/
|
||||||
|
IShape width(double w);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the x-coordinate of the next draw command
|
* Set the x-coordinate of the next draw command
|
||||||
*
|
*
|
||||||
* @param x Coordinate
|
* @param x
|
||||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
* Coordinate
|
||||||
* draw command
|
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||||
*/
|
* draw command
|
||||||
IShape x(double x);
|
*/
|
||||||
|
IShape x(double x);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the y-coordinate of the next draw command
|
* Set the y-coordinate of the next draw command
|
||||||
*
|
*
|
||||||
* @param y Coordinate
|
* @param y
|
||||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
* Coordinate
|
||||||
* draw command
|
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||||
*/
|
* draw command
|
||||||
IShape y(double y);
|
*/
|
||||||
|
IShape y(double y);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,253 +2,269 @@ package inf101.v18.gfx.gfxmode;
|
|||||||
|
|
||||||
public interface ITurtle extends IPainter {
|
public interface ITurtle extends IPainter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is used to convert the turtle to another type, determined by the
|
* This method is used to convert the turtle to an other type, determined by the
|
||||||
* class object given as an argument.
|
* class object given as an argument.
|
||||||
* <p>
|
* <p>
|
||||||
* This can be used to access extra functionality not provided by this
|
* This can be used to access extra functionality not provided by this
|
||||||
* interface, such as direct access to the underlying graphics context.
|
* interface, such as direct access to the underlying graphics context.
|
||||||
*
|
*
|
||||||
* @param clazz
|
* @param clazz
|
||||||
* @return This object or an appropriate closely related object of the given
|
* @return This object or an appropriate closely related object of the given
|
||||||
* time; or <code>null</code> if no appropriate object can be found
|
* time; or <code>null</code> if no appropriate object can be found
|
||||||
*/
|
*/
|
||||||
<T> T as(Class<T> clazz);
|
<T> T as(Class<T> clazz);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move to the given position while drawing a curve
|
* Move to the given position while drawing a curve
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* The resulting curve is a cubic Bézier curve with the control points located
|
* The resulting curve is a cubic Bézier curve with the control points located
|
||||||
* at <code>getPos().move(getDirection, startControl)</code> and
|
* at <code>getPos().move(getDirection, startControl)</code> and
|
||||||
* <code>to.move(Direction.fromDegrees(endAngle+180), endControl)</code>.
|
* <code>to.move(Direction.fromDegrees(endAngle+180), endControl)</code>.
|
||||||
* <p>
|
* <p>
|
||||||
* The turtle is left at point <code>to</code>, facing <code>endAngle</code>.
|
* The turtle is left at point <code>to</code>, facing <code>endAngle</code>.
|
||||||
* <p>
|
* <p>
|
||||||
* The turtle will start out moving in its current direction, aiming for a point
|
* The turtle will start out moving in its current direction, aiming for a point
|
||||||
* <code>startControl</code> pixels away, then smoothly turning towards its
|
* <code>startControl</code> pixels away, then smoothly turning towards its
|
||||||
* goal. It will approach the <code>to</code> point moving in the direction
|
* 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°
|
* <code>endAngle</code> (an absolute bearing, with 0° pointing right and 90°
|
||||||
* pointing up).
|
* pointing up).
|
||||||
*
|
*
|
||||||
* @param to Position to move to
|
* @param to
|
||||||
* @param startControl Distance to the starting control point.
|
* Position to move to
|
||||||
* @return {@code this}, for sending more draw commands
|
* @param startControl
|
||||||
*/
|
* Distance to the starting control point.
|
||||||
ITurtle curveTo(Point to, double startControl, double endAngle, double endControl);
|
* @return {@code this}, for sending more draw commands
|
||||||
|
*/
|
||||||
|
ITurtle curveTo(Point to, double startControl, double endAngle, double endControl);
|
||||||
|
|
||||||
void debugTurtle();
|
void debugTurtle();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move forward the given distance while drawing a line
|
* Move forward the given distance while drawing a line
|
||||||
*
|
*
|
||||||
* @param dist Distance to move
|
* @param dist
|
||||||
* @return {@code this}, for sending more draw commands
|
* Distance to move
|
||||||
*/
|
* @return {@code this}, for sending more draw commands
|
||||||
ITurtle draw(double dist);
|
*/
|
||||||
|
ITurtle draw(double dist);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move to the given position while drawing a line
|
* Move to the given position while drawing a line
|
||||||
*
|
*
|
||||||
* @param x X-position to move to
|
* @param x
|
||||||
* @param y Y-position to move to
|
* X-position to move to
|
||||||
* @return {@code this}, for sending more draw commands
|
* @param y
|
||||||
*/
|
* Y-position to move to
|
||||||
ITurtle drawTo(double x, double y);
|
* @return {@code this}, for sending more draw commands
|
||||||
|
*/
|
||||||
|
ITurtle drawTo(double x, double y);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move to the given position while drawing a line
|
* Move to the given position while drawing a line
|
||||||
*
|
*
|
||||||
* @param to Position to move to
|
* @param to
|
||||||
* @return {@code this}, for sending more draw commands
|
* Position to move to
|
||||||
*/
|
* @return {@code this}, for sending more draw commands
|
||||||
ITurtle drawTo(Point to);
|
*/
|
||||||
|
ITurtle drawTo(Point to);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The current angle of the turtle, with 0° pointing to the right and
|
* @return The current angle of the turtle, with 0° pointing to the right and
|
||||||
* 90° pointing up. Same as {@link #getDirection()}.getAngle()
|
* 90° pointing up. Same as {@link #getDirection()}.getAngle()
|
||||||
*/
|
*/
|
||||||
double getAngle();
|
double getAngle();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The current direction of the turtle. Same as calling
|
* @return The current direction of the turtle. Same as calling
|
||||||
* <code>new Direction(getAngle())</code>
|
* <code>new Direction(getAngle())</code>
|
||||||
*/
|
*/
|
||||||
Direction getDirection();
|
Direction getDirection();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The current position of the turtle.
|
* @return The current position of the turtle.
|
||||||
*/
|
*/
|
||||||
Point getPos();
|
Point getPos();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move a distance without drawing.
|
* Move a distance without drawing.
|
||||||
*
|
*
|
||||||
* @param dist Distance to move
|
* @param dist
|
||||||
* @return {@code this}, for sending more draw commands
|
* Distance to move
|
||||||
*/
|
* @return {@code this}, for sending more draw commands
|
||||||
ITurtle jump(double dist);
|
*/
|
||||||
|
ITurtle jump(double dist);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move a position without drawing.
|
* Move a position without drawing.
|
||||||
*
|
*
|
||||||
* @param x X position to move to
|
* @param x
|
||||||
* @param y Y position to move to
|
* X position to move to
|
||||||
* @return {@code this}, for sending more draw commands
|
* @param y
|
||||||
*/
|
* Y position to move to
|
||||||
ITurtle jumpTo(double x, double y);
|
* @return {@code this}, for sending more draw commands
|
||||||
|
*/
|
||||||
|
ITurtle jumpTo(double x, double y);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move a position without drawing.
|
* Move a position without drawing.
|
||||||
*
|
*
|
||||||
* @param to X,Y position to move to
|
* @param to
|
||||||
* @return {@code this}, for sending more draw commands
|
* X,Y position to move to
|
||||||
*/
|
* @return {@code this}, for sending more draw commands
|
||||||
ITurtle jumpTo(Point to);
|
*/
|
||||||
|
ITurtle jumpTo(Point to);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draw a line from the current position to the given position.
|
* Draw a line from the current position to the given position.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This method does not change the turtle position.
|
* This method does not change the turtle position.
|
||||||
*
|
*
|
||||||
* @param to Other end-point of the line
|
* @param to
|
||||||
* @return {@code this}, for sending more draw commands
|
* Other end-point of the line
|
||||||
*/
|
* @return {@code this}, for sending more draw commands
|
||||||
ITurtle line(Point to);
|
*/
|
||||||
|
ITurtle line(Point to);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the size of the turtle's pen
|
* Set the size of the turtle's pen
|
||||||
*
|
*
|
||||||
* @param pixels Line width, in pixels
|
* @param pixels
|
||||||
* @return {@code this}, for sending more draw commands
|
* Line width, in pixels
|
||||||
* @requires pixels >= 0
|
* @return {@code this}, for sending more draw commands
|
||||||
*/
|
* @requires pixels >= 0
|
||||||
ITurtle setPenSize(double pixels);
|
*/
|
||||||
|
ITurtle setPenSize(double pixels);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start drawing a shape at the current turtle position.
|
* Start drawing a shape at the current turtle position.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* The shape's default origin and rotation will be set to the turtle's current
|
* 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
|
* position and direction, but can be modified with {@link IShape#at(Point)} and
|
||||||
* {@link IShape#rotation(double)}.
|
* {@link IShape#rotation(double)}.
|
||||||
* <p>
|
* <p>
|
||||||
* The turtle's position and attributes are unaffected by drawing the shape.
|
* The turtle's position and attributes are unaffected by drawing the shape.
|
||||||
*
|
*
|
||||||
* @return An IDrawParams object for setting up and drawing the shape
|
* @return An IDrawParams object for setting up and drawing the shape
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
IShape shape();
|
IShape shape();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change direction the given number of degrees (relative to the current
|
* Change direction the given number of degrees (relative to the current
|
||||||
* direction).
|
* direction).
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Positive degrees turn <em>left</em> while negative degrees turn
|
* Positive degrees turn <em>left</em> while negative degrees turn
|
||||||
* <em>right</em>.
|
* <em>right</em>.
|
||||||
*
|
*
|
||||||
* @param degrees
|
* @param degrees
|
||||||
* @return {@code this}, for sending more draw commands
|
* @return {@code this}, for sending more draw commands
|
||||||
*/
|
*/
|
||||||
ITurtle turn(double degrees);
|
ITurtle turn(double degrees);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turn 180°.
|
* Turn 180°.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Same as <code>turn(180)</code> and <code>turn(-180)</code>.
|
* Same as <code>turn(180)</code> and <code>turn(-180)</code>.
|
||||||
*
|
*
|
||||||
* @return {@code this}, for sending more draw commands
|
* @return {@code this}, for sending more draw commands
|
||||||
*/
|
*/
|
||||||
ITurtle turnAround();
|
ITurtle turnAround();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turn left 90°.
|
* Turn left 90°.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Same as <code>turn(90)</code>.
|
* Same as <code>turn(90)</code>.
|
||||||
*
|
*
|
||||||
* @return {@code this}, for sending more draw commands
|
* @return {@code this}, for sending more draw commands
|
||||||
*/
|
*/
|
||||||
ITurtle turnLeft();
|
ITurtle turnLeft();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turn left.
|
* Turn left.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Same as <code>turn(degrees)</code>.
|
* Same as <code>turn(degrees)</code>.
|
||||||
*
|
*
|
||||||
* @return {@code this}, for sending more draw commands
|
* @return {@code this}, for sending more draw commands
|
||||||
* @requires degrees >= 0
|
* @requires degrees >= 0
|
||||||
*/
|
*/
|
||||||
ITurtle turnLeft(double degrees);
|
ITurtle turnLeft(double degrees);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turn right 90°.
|
* Turn right 90°.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Same as <code>turn(-90)</code>.
|
* Same as <code>turn(-90)</code>.
|
||||||
*
|
*
|
||||||
* @return {@code this}, for sending more draw commands
|
* @return {@code this}, for sending more draw commands
|
||||||
*/
|
*/
|
||||||
ITurtle turnRight();
|
ITurtle turnRight();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turn left.
|
* Turn left.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Same as <code>turn(-degrees)</code>.
|
* Same as <code>turn(-degrees)</code>.
|
||||||
*
|
*
|
||||||
* @return {@code this}, for sending more draw commands
|
* @return {@code this}, for sending more draw commands
|
||||||
* @requires degrees >= 0
|
* @requires degrees >= 0
|
||||||
*/
|
*/
|
||||||
ITurtle turnRight(double degrees);
|
ITurtle turnRight(double degrees);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turn to the given bearing.
|
* Turn to the given bearing.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* 0° is due right, 90° is up.
|
* 0° is due right, 90° is up.
|
||||||
*
|
*
|
||||||
* @param degrees Bearing, in degrees
|
* @param degrees
|
||||||
* @return {@code this}, for sending more draw commands
|
* Bearing, in degrees
|
||||||
*/
|
* @return {@code this}, for sending more draw commands
|
||||||
ITurtle turnTo(double degrees);
|
*/
|
||||||
|
ITurtle turnTo(double degrees);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turn towards the given bearing.
|
* Turn towards the given bearing.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Use this method to turn slightly towards something.
|
* Use this method to turn slightly towards something.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* 0° is due right, 90° is up.
|
* 0° is due right, 90° is up.
|
||||||
*
|
*
|
||||||
* @param degrees Bearing, in degrees
|
* @param degrees
|
||||||
* @param percent How far to turn, in degrees.
|
* Bearing, in degrees
|
||||||
* @return {@code this}, for sending more draw commands
|
* @param percent
|
||||||
*/
|
* How far to turn, in degrees.
|
||||||
ITurtle turnTowards(double degrees, double percent);
|
* @return {@code this}, for sending more draw commands
|
||||||
|
*/
|
||||||
|
ITurtle turnTowards(double degrees, double percent);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Jump (without drawing) to the given relative position.
|
* Jump (without drawing) to the given relative position.
|
||||||
* <p>
|
* <p>
|
||||||
* The new position will be equal to getPos().move(relPos).
|
* The new position will be equal to getPos().move(relPos).
|
||||||
*
|
*
|
||||||
* @param relPos A position, interpreted relative to current position
|
* @param relPos
|
||||||
* @return {@code this}, for sending more draw commands
|
* A position, interpreted relative to current position
|
||||||
*/
|
* @return {@code this}, for sending more draw commands
|
||||||
ITurtle jump(Point relPos);
|
*/
|
||||||
|
ITurtle jump(Point relPos);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move to the given relative position while drawing a line
|
* Move to the given relative position while drawing a line
|
||||||
* <p>
|
* <p>
|
||||||
* The new position will be equal to getPos().move(relPos).
|
* The new position will be equal to getPos().move(relPos).
|
||||||
*
|
*
|
||||||
* @return {@code this}, for sending more draw commands
|
* @return {@code this}, for sending more draw commands
|
||||||
*/
|
*/
|
||||||
ITurtle draw(Point relPos);
|
ITurtle draw(Point relPos);
|
||||||
|
|
||||||
}
|
}
|
@ -1,113 +1,116 @@
|
|||||||
package inf101.v18.gfx.gfxmode;
|
package inf101.v18.gfx.gfxmode;
|
||||||
|
|
||||||
public class Point {
|
public class Point {
|
||||||
private final double x;
|
private final double x;
|
||||||
private final double y;
|
private final double y;
|
||||||
|
|
||||||
public Point(double x, double y) {
|
public Point(double x, double y) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate direction towards other position
|
* Calculate direction towards other position
|
||||||
*
|
*
|
||||||
* @param otherPos
|
* @param otherPos
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Direction directionTo(Point otherPos) {
|
public Direction directionTo(Point otherPos) {
|
||||||
return new Direction(otherPos.x - x, otherPos.y - y);
|
return new Direction(otherPos.x - x, otherPos.y - y);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate distance to other position
|
* Calculate distance to other position
|
||||||
*
|
*
|
||||||
* @param otherPos
|
* @param otherPos
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public double distanceTo(Point otherPos) {
|
public double distanceTo(Point otherPos) {
|
||||||
return Math.sqrt(Math.pow(x - otherPos.x, 2) + Math.pow(y - otherPos.y, 2));
|
return Math.sqrt(Math.pow(x - otherPos.x, 2) + Math.pow(y - otherPos.y, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The X coordinate
|
* @return The X coordinate
|
||||||
*/
|
*/
|
||||||
public double getX() {
|
public double getX() {
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The Y coordinate
|
* @return The Y coordinate
|
||||||
*/
|
*/
|
||||||
public double getY() {
|
public double getY() {
|
||||||
return y;
|
return y;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Relative move
|
* Relative move
|
||||||
*
|
*
|
||||||
* @param dir Direction
|
* @param dir
|
||||||
* @param distance Distance to move
|
* Direction
|
||||||
*/
|
* @param distance
|
||||||
public Point move(Direction dir, double distance) {
|
* Distance to move
|
||||||
return new Point(x + dir.getX() * distance, y - dir.getY() * distance);
|
*/
|
||||||
}
|
public Point move(Direction dir, double distance) {
|
||||||
|
return new Point(x + dir.getX() * distance, y - dir.getY() * distance);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Relative move
|
* Relative move
|
||||||
*
|
*
|
||||||
* @param deltaX
|
* @param deltaX
|
||||||
* @param deltaY
|
* @param deltaY
|
||||||
* @return A new point at x+deltaX, y+deltaY
|
* @return A new point at x+deltaX, y+deltaY
|
||||||
*/
|
*/
|
||||||
public Point move(double deltaX, double deltaY) {
|
public Point move(double deltaX, double deltaY) {
|
||||||
return new Point(x + deltaX, y + deltaY);
|
return new Point(x + deltaX, y + deltaY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Relative move
|
* Relative move
|
||||||
*
|
*
|
||||||
* @param deltaPos
|
* @param deltaPos
|
||||||
*/
|
*/
|
||||||
public Point move(Point deltaPos) {
|
public Point move(Point deltaPos) {
|
||||||
return new Point(x + deltaPos.x, y + deltaPos.y);
|
return new Point(x + deltaPos.x, y + deltaPos.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change position
|
* Change position
|
||||||
*
|
*
|
||||||
* @param newX the new X coordinate
|
* @param newX
|
||||||
* @param newY the new Y coordinate
|
* the new X coordinate
|
||||||
* @return A new point at newX, newY
|
* @param newY
|
||||||
*/
|
* the new Y coordinate
|
||||||
public Point moveTo(double newX, double newY) {
|
* @return A new point at newX, newY
|
||||||
return new Point(newX, newY);
|
*/
|
||||||
}
|
public Point moveTo(double newX, double newY) {
|
||||||
|
return new Point(newX, newY);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format("(%.2f,%.2f)", x, y);
|
return String.format("(%.2f,%.2f)", x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Multiply this point by a scale factor.
|
* Multiply this point by a scale factor.
|
||||||
*
|
*
|
||||||
* @param factor A scale factor
|
* @param factor A scale factor
|
||||||
* @return A new Point, (getX()*factor, getY()*factor)
|
* @return A new Point, (getX()*factor, getY()*factor)
|
||||||
*/
|
*/
|
||||||
public Point scale(double factor) {
|
public Point scale(double factor) {
|
||||||
return new Point(x * factor, y * factor);
|
return new Point(x*factor, y*factor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find difference between points.
|
* Find difference between points.
|
||||||
* <p>
|
* <p>
|
||||||
* The returned value will be such that <code>this.move(deltaTo(point)).equals(point)</code>.
|
* The returned value will be such that <code>this.move(deltaTo(point)).equals(point)</code>.
|
||||||
*
|
* @param point Another point
|
||||||
* @param point Another point
|
* @return A new Point, (point.getX()-getX(), point.getY()-getY())
|
||||||
* @return A new Point, (point.getX()-getX(), point.getY()-getY())
|
*/
|
||||||
*/
|
public Point deltaTo(Point point) {
|
||||||
public Point deltaTo(Point point) {
|
return new Point(point.x-x, point.y-y);
|
||||||
return new Point(point.x - x, point.y - y);
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,330 +1,329 @@
|
|||||||
package inf101.v18.gfx.gfxmode;
|
package inf101.v18.gfx.gfxmode;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javafx.scene.canvas.GraphicsContext;
|
import javafx.scene.canvas.GraphicsContext;
|
||||||
import javafx.scene.paint.Paint;
|
import javafx.scene.paint.Paint;
|
||||||
import javafx.scene.shape.Shape;
|
import javafx.scene.shape.Shape;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class ShapePainter implements IShape {
|
public class ShapePainter implements IShape {
|
||||||
private abstract static class DrawCommand {
|
private abstract static class DrawCommand {
|
||||||
protected double calcX(Gravity g, double w) {
|
protected double calcX(Gravity g, double w) {
|
||||||
switch (g) {
|
switch (g) {
|
||||||
default:
|
default:
|
||||||
case CENTER:
|
case CENTER:
|
||||||
case SOUTH:
|
return w / 2;
|
||||||
case NORTH:
|
case EAST:
|
||||||
return w / 2;
|
return w;
|
||||||
case EAST:
|
case NORTH:
|
||||||
case NORTHEAST:
|
return w / 2;
|
||||||
case SOUTHEAST:
|
case NORTHEAST:
|
||||||
return w;
|
return w;
|
||||||
case NORTHWEST:
|
case NORTHWEST:
|
||||||
case WEST:
|
return 0;
|
||||||
case SOUTHWEST:
|
case SOUTH:
|
||||||
return 0;
|
return w / 2;
|
||||||
}
|
case SOUTHEAST:
|
||||||
}
|
return w;
|
||||||
|
case SOUTHWEST:
|
||||||
|
return 0;
|
||||||
|
case WEST:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected double calcY(Gravity g, double h) {
|
protected double calcY(Gravity g, double h) {
|
||||||
switch (g) {
|
switch (g) {
|
||||||
default:
|
default:
|
||||||
case CENTER:
|
case CENTER:
|
||||||
case EAST:
|
return h / 2;
|
||||||
case WEST:
|
case EAST:
|
||||||
return h / 2;
|
return h / 2;
|
||||||
case NORTH:
|
case NORTH:
|
||||||
case NORTHWEST:
|
return 0;
|
||||||
case NORTHEAST:
|
case NORTHEAST:
|
||||||
return 0;
|
return 0;
|
||||||
case SOUTH:
|
case NORTHWEST:
|
||||||
case SOUTHWEST:
|
return 0;
|
||||||
case SOUTHEAST:
|
case SOUTH:
|
||||||
return h;
|
return h;
|
||||||
}
|
case SOUTHEAST:
|
||||||
}
|
return h;
|
||||||
|
case SOUTHWEST:
|
||||||
|
return h;
|
||||||
|
case WEST:
|
||||||
|
return h / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void fill(GraphicsContext ctx, ShapePainter p) {
|
public void fill(GraphicsContext ctx, ShapePainter p) {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.setFill(p.fill);
|
ctx.setFill(p.fill);
|
||||||
ctx.translate(p.x, p.y);
|
ctx.translate(p.x, p.y);
|
||||||
if (p.rot != 0) {
|
if (p.rot != 0)
|
||||||
ctx.rotate(-p.rot);
|
ctx.rotate(-p.rot);
|
||||||
}
|
fillIt(ctx, p);
|
||||||
fillIt(ctx, p);
|
ctx.restore();
|
||||||
ctx.restore();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void fillIt(GraphicsContext ctx, ShapePainter p);
|
protected abstract void fillIt(GraphicsContext ctx, ShapePainter p);
|
||||||
|
|
||||||
// public abstract Shape toFXShape(DrawParams p);
|
// public abstract Shape toFXShape(DrawParams p);
|
||||||
//
|
//
|
||||||
// public abstract String toSvg(DrawParams p);
|
// public abstract String toSvg(DrawParams p);
|
||||||
|
|
||||||
public void stroke(GraphicsContext ctx, ShapePainter p) {
|
public void stroke(GraphicsContext ctx, ShapePainter p) {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.setStroke(p.stroke);
|
ctx.setStroke(p.stroke);
|
||||||
if (p.strokeWidth != 0) {
|
if (p.strokeWidth != 0)
|
||||||
ctx.setLineWidth(p.strokeWidth);
|
ctx.setLineWidth(p.strokeWidth);
|
||||||
}
|
ctx.translate(p.x, p.y);
|
||||||
ctx.translate(p.x, p.y);
|
if (p.rot != 0)
|
||||||
if (p.rot != 0) {
|
ctx.rotate(-p.rot);
|
||||||
ctx.rotate(-p.rot);
|
strokeIt(ctx, p);
|
||||||
}
|
ctx.restore();
|
||||||
strokeIt(ctx, p);
|
}
|
||||||
ctx.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void strokeIt(GraphicsContext ctx, ShapePainter p);
|
protected abstract void strokeIt(GraphicsContext ctx, ShapePainter p);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DrawEllipse extends DrawCommand {
|
private static class DrawEllipse extends DrawCommand {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fillIt(GraphicsContext ctx, ShapePainter p) {
|
public void fillIt(GraphicsContext ctx, ShapePainter p) {
|
||||||
ctx.fillOval(-calcX(p.gravity, p.w), -calcY(p.gravity, p.h), p.w, p.h);
|
ctx.fillOval(-calcX(p.gravity, p.w), -calcY(p.gravity, p.h), p.w, p.h);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void strokeIt(GraphicsContext ctx, ShapePainter p) {
|
public void strokeIt(GraphicsContext ctx, ShapePainter p) {
|
||||||
ctx.strokeOval(-calcX(p.gravity, p.w), -calcY(p.gravity, p.h), p.w, p.h);
|
ctx.strokeOval(-calcX(p.gravity, p.w), -calcY(p.gravity, p.h), p.w, p.h);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DrawLine extends DrawCommand {
|
private static class DrawLine extends DrawCommand {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fillIt(GraphicsContext ctx, ShapePainter p) {
|
public void fillIt(GraphicsContext ctx, ShapePainter p) {
|
||||||
if (p.lineSegments != null) {
|
if (p.lineSegments != null) {
|
||||||
int nPoints = (p.lineSegments.size() / 2) + 1;
|
int nPoints = (p.lineSegments.size() / 2) + 1;
|
||||||
double[] xs = new double[nPoints];
|
double xs[] = new double[nPoints];
|
||||||
double[] ys = new double[nPoints];
|
double ys[] = new double[nPoints];
|
||||||
xs[0] = -calcX(p.gravity, p.w);
|
xs[0] = -calcX(p.gravity, p.w);
|
||||||
ys[0] = -calcY(p.gravity, p.h);
|
ys[0] = -calcY(p.gravity, p.h);
|
||||||
for (int i = 0; i < p.lineSegments.size(); i++) {
|
for (int i = 0; i < p.lineSegments.size(); i++) {
|
||||||
xs[i] = p.lineSegments.get(i * 2) - p.x;
|
xs[i] = p.lineSegments.get(i * 2) - p.x;
|
||||||
ys[i] = p.lineSegments.get(i * 2 + 1) - p.y;
|
ys[i] = p.lineSegments.get(i * 2 + 1) - p.y;
|
||||||
}
|
}
|
||||||
ctx.fillPolygon(xs, ys, nPoints);
|
ctx.fillPolygon(xs, ys, nPoints);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void strokeIt(GraphicsContext ctx, ShapePainter p) {
|
public void strokeIt(GraphicsContext ctx, ShapePainter p) {
|
||||||
if (p.lineSegments == null) {
|
if (p.lineSegments == null) {
|
||||||
double x = -calcX(p.gravity, p.w);
|
double x = -calcX(p.gravity, p.w);
|
||||||
double y = -calcY(p.gravity, p.h);
|
double y = -calcY(p.gravity, p.h);
|
||||||
ctx.strokeLine(x, y, x + p.w, y + p.h);
|
ctx.strokeLine(x, y, x + p.w, y + p.h);
|
||||||
} else {
|
} else {
|
||||||
int nPoints = (p.lineSegments.size() / 2) + 1;
|
int nPoints = (p.lineSegments.size() / 2) + 1;
|
||||||
double[] xs = new double[nPoints];
|
double xs[] = new double[nPoints];
|
||||||
double[] ys = new double[nPoints];
|
double ys[] = new double[nPoints];
|
||||||
xs[0] = -calcX(p.gravity, p.w);
|
xs[0] = -calcX(p.gravity, p.w);
|
||||||
ys[0] = -calcY(p.gravity, p.h);
|
ys[0] = -calcY(p.gravity, p.h);
|
||||||
for (int i = 0; i < p.lineSegments.size(); i++) {
|
for (int i = 0; i < p.lineSegments.size(); i++) {
|
||||||
xs[i] = p.lineSegments.get(i * 2) - p.x;
|
xs[i] = p.lineSegments.get(i * 2) - p.x;
|
||||||
ys[i] = p.lineSegments.get(i * 2 + 1) - p.y;
|
ys[i] = p.lineSegments.get(i * 2 + 1) - p.y;
|
||||||
}
|
}
|
||||||
if (p.closed) {
|
if (p.closed)
|
||||||
ctx.strokePolygon(xs, ys, nPoints);
|
ctx.strokePolygon(xs, ys, nPoints);
|
||||||
} else {
|
else
|
||||||
ctx.strokePolyline(xs, ys, nPoints);
|
ctx.strokePolyline(xs, ys, nPoints);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static class DrawRectangle extends DrawCommand {
|
private static class DrawRectangle extends DrawCommand {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fillIt(GraphicsContext ctx, ShapePainter p) {
|
public void fillIt(GraphicsContext ctx, ShapePainter p) {
|
||||||
ctx.fillRect(-calcX(p.gravity, p.w), -calcY(p.gravity, p.h), p.w, p.h);
|
ctx.fillRect(-calcX(p.gravity, p.w), -calcY(p.gravity, p.h), p.w, p.h);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void strokeIt(GraphicsContext ctx, ShapePainter p) {
|
public void strokeIt(GraphicsContext ctx, ShapePainter p) {
|
||||||
ctx.strokeRect(-calcX(p.gravity, p.w), -calcY(p.gravity, p.h), p.w, p.h);
|
ctx.strokeRect(-calcX(p.gravity, p.w), -calcY(p.gravity, p.h), p.w, p.h);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private double x = 0;
|
private double x = 0, y = 0, w = 0, h = 0, rot = 0, strokeWidth = 0;
|
||||||
private double y = 0;
|
private List<Double> lineSegments = null;
|
||||||
private double w = 0;
|
private Paint fill = null;
|
||||||
private double h = 0;
|
private Paint stroke = null;
|
||||||
private double rot = 0;
|
|
||||||
private final double strokeWidth = 0;
|
|
||||||
private final List<Double> lineSegments = null;
|
|
||||||
private Paint fill = null;
|
|
||||||
private Paint stroke = null;
|
|
||||||
|
|
||||||
private Gravity gravity = Gravity.CENTER;
|
private Gravity gravity = Gravity.CENTER;
|
||||||
|
|
||||||
private DrawCommand cmd = null;
|
private DrawCommand cmd = null;
|
||||||
|
|
||||||
private boolean closed = false;
|
private boolean closed = false;
|
||||||
|
|
||||||
private final GraphicsContext context;
|
private final GraphicsContext context;
|
||||||
|
|
||||||
public ShapePainter(GraphicsContext context) {
|
public ShapePainter(GraphicsContext context) {
|
||||||
super();
|
super();
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IShape addPoint(double x, double y) {
|
public IShape addPoint(double x, double y) {
|
||||||
lineSegments.add(x);
|
lineSegments.add(x);
|
||||||
lineSegments.add(y);
|
lineSegments.add(y);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IShape addPoint(Point xy) {
|
public IShape addPoint(Point xy) {
|
||||||
lineSegments.add(xy.getX());
|
lineSegments.add(xy.getX());
|
||||||
lineSegments.add(xy.getY());
|
lineSegments.add(xy.getY());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ShapePainter angle(double a) {
|
public ShapePainter angle(double a) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IShape arc() {
|
public IShape arc() {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ShapePainter at(Point p) {
|
public ShapePainter at(Point p) {
|
||||||
if (p != null) {
|
if (p != null) {
|
||||||
this.x = p.getX();
|
this.x = p.getX();
|
||||||
this.y = p.getY();
|
this.y = p.getY();
|
||||||
} else {
|
} else {
|
||||||
this.x = 0;
|
this.x = 0;
|
||||||
this.y = 0;
|
this.y = 0;
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IShape close() {
|
public IShape close() {
|
||||||
closed = true;
|
closed = true;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw() {
|
public void draw() {
|
||||||
draw(context);
|
draw(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(GraphicsContext context) {
|
public void draw(GraphicsContext context) {
|
||||||
if (cmd != null && context != null) {
|
if (cmd != null && context != null) {
|
||||||
if (fill != null) {
|
if (fill != null)
|
||||||
cmd.fill(context, this);
|
cmd.fill(context, this);
|
||||||
}
|
if (stroke != null)
|
||||||
if (stroke != null) {
|
cmd.stroke(context, this);
|
||||||
cmd.stroke(context, this);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IShape ellipse() {
|
public IShape ellipse() {
|
||||||
cmd = new DrawEllipse();
|
cmd = new DrawEllipse();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ShapePainter fill() {
|
public ShapePainter fill() {
|
||||||
if (cmd != null && context != null) {
|
if (cmd != null && context != null)
|
||||||
cmd.fill(context, this);
|
cmd.fill(context, this);
|
||||||
}
|
return this;
|
||||||
return this;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ShapePainter fillPaint(Paint p) {
|
public ShapePainter fillPaint(Paint p) {
|
||||||
fill = p;
|
fill = p;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ShapePainter gravity(Gravity g) {
|
public ShapePainter gravity(Gravity g) {
|
||||||
gravity = g;
|
gravity = g;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ShapePainter height(double h) {
|
public ShapePainter height(double h) {
|
||||||
this.h = h;
|
this.h = h;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ShapePainter length(double l) {
|
public ShapePainter length(double l) {
|
||||||
w = l;
|
w = l;
|
||||||
h = l;
|
h = l;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IShape line() {
|
public IShape line() {
|
||||||
cmd = new DrawLine();
|
cmd = new DrawLine();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IShape rectangle() {
|
public IShape rectangle() {
|
||||||
cmd = new DrawRectangle();
|
cmd = new DrawRectangle();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ShapePainter rotation(double angle) {
|
public ShapePainter rotation(double angle) {
|
||||||
rot = angle;
|
rot = angle;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ShapePainter stroke() {
|
public ShapePainter stroke() {
|
||||||
if (cmd != null && context != null) {
|
if (cmd != null && context != null)
|
||||||
cmd.stroke(context, this);
|
cmd.stroke(context, this);
|
||||||
}
|
return this;
|
||||||
return this;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ShapePainter strokePaint(Paint p) {
|
public ShapePainter strokePaint(Paint p) {
|
||||||
stroke = p;
|
stroke = p;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Shape toFXShape() {
|
public Shape toFXShape() {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toSvg() {
|
public String toSvg() {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ShapePainter width(double w) {
|
public ShapePainter width(double w) {
|
||||||
this.w = w;
|
this.w = w;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ShapePainter x(double x) {
|
public ShapePainter x(double x) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ShapePainter y(double y) {
|
public ShapePainter y(double y) {
|
||||||
this.y = y;
|
this.y = y;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package inf101.v18.gfx.gfxmode;
|
package inf101.v18.gfx.gfxmode;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import inf101.v18.gfx.IPaintLayer;
|
import inf101.v18.gfx.IPaintLayer;
|
||||||
import inf101.v18.gfx.Screen;
|
import inf101.v18.gfx.Screen;
|
||||||
import javafx.scene.canvas.Canvas;
|
import javafx.scene.canvas.Canvas;
|
||||||
@ -9,382 +12,362 @@ import javafx.scene.paint.Paint;
|
|||||||
import javafx.scene.shape.StrokeLineCap;
|
import javafx.scene.shape.StrokeLineCap;
|
||||||
import javafx.scene.shape.StrokeLineJoin;
|
import javafx.scene.shape.StrokeLineJoin;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class TurtlePainter implements IPaintLayer, ITurtle {
|
public class TurtlePainter implements IPaintLayer, ITurtle {
|
||||||
|
|
||||||
static class TurtleState {
|
static class TurtleState {
|
||||||
protected Point pos;
|
protected Point pos;
|
||||||
protected Direction dir;
|
protected Direction dir;
|
||||||
protected Direction inDir;
|
protected Direction inDir;
|
||||||
protected double penSize = 1.0;
|
protected double penSize = 1.0;
|
||||||
protected Paint ink = Color.BLACK;
|
protected Paint ink = Color.BLACK;
|
||||||
|
|
||||||
public TurtleState() {
|
public TurtleState() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public TurtleState(TurtleState s) {
|
public TurtleState(TurtleState s) {
|
||||||
pos = s.pos;
|
pos = s.pos;
|
||||||
dir = s.dir;
|
dir = s.dir;
|
||||||
inDir = s.inDir;
|
inDir = s.inDir;
|
||||||
penSize = s.penSize;
|
penSize = s.penSize;
|
||||||
ink = s.ink;
|
ink = s.ink;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Screen screen;
|
private final Screen screen;
|
||||||
private final double width;
|
private final double width;
|
||||||
private final double height;
|
private final double height;
|
||||||
private final GraphicsContext context;
|
private final GraphicsContext context;
|
||||||
private final List<TurtleState> stateStack = new ArrayList<>();
|
private final List<TurtleState> stateStack = new ArrayList<>();
|
||||||
|
|
||||||
private TurtleState state = new TurtleState();
|
private TurtleState state = new TurtleState();
|
||||||
private final Canvas canvas;
|
private final Canvas canvas;
|
||||||
private boolean path = false;
|
private boolean path = false;
|
||||||
|
|
||||||
public TurtlePainter(double width, double height) {
|
public TurtlePainter(double width, double height) {
|
||||||
this.screen = null;
|
this.screen = null;
|
||||||
this.canvas = null;
|
this.canvas = null;
|
||||||
this.context = null;
|
this.context = null;
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
stateStack.add(new TurtleState());
|
stateStack.add(new TurtleState());
|
||||||
state.dir = new Direction(1.0, 0.0);
|
state.dir = new Direction(1.0, 0.0);
|
||||||
state.pos = new Point(width / 2, height / 2);
|
state.pos = new Point(width / 2, height / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TurtlePainter(Screen screen, Canvas canvas) {
|
public TurtlePainter(Screen screen, Canvas canvas) {
|
||||||
if (screen == null || canvas == null) {
|
if (screen == null || canvas == null)
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
}
|
this.screen = screen;
|
||||||
this.screen = screen;
|
this.canvas = canvas;
|
||||||
this.canvas = canvas;
|
this.context = canvas.getGraphicsContext2D();
|
||||||
this.context = canvas.getGraphicsContext2D();
|
this.width = screen.getWidth();
|
||||||
this.width = screen.getWidth();
|
this.height = screen.getHeight();
|
||||||
this.height = screen.getHeight();
|
stateStack.add(new TurtleState());
|
||||||
stateStack.add(new TurtleState());
|
state.dir = new Direction(1.0, 0.0);
|
||||||
state.dir = new Direction(1.0, 0.0);
|
state.pos = new Point(screen.getWidth() / 2, screen.getHeight() / 2);
|
||||||
state.pos = new Point(screen.getWidth() / 2, screen.getHeight() / 2);
|
context.setLineJoin(StrokeLineJoin.BEVEL);
|
||||||
context.setLineJoin(StrokeLineJoin.BEVEL);
|
context.setLineCap(StrokeLineCap.SQUARE);
|
||||||
context.setLineCap(StrokeLineCap.SQUARE);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T> T as(Class<T> clazz) {
|
public <T> T as(Class<T> clazz) {
|
||||||
if (clazz == GraphicsContext.class) {
|
if (clazz == GraphicsContext.class)
|
||||||
return (T) context;
|
return (T) context;
|
||||||
}
|
if (clazz == getClass())
|
||||||
if (clazz == getClass()) {
|
return (T) this;
|
||||||
return (T) this;
|
else
|
||||||
} else {
|
return null;
|
||||||
return null;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
if (context != null) {
|
if (context != null)
|
||||||
context.clearRect(0, 0, getWidth(), getHeight());
|
context.clearRect(0, 0, getWidth(), getHeight());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ITurtle curveTo(Point to, double startControl, double endAngle, double endControl) {
|
public ITurtle curveTo(Point to, double startControl, double endAngle, double endControl) {
|
||||||
Point c1 = state.pos.move(state.dir, startControl);
|
Point c1 = state.pos.move(state.dir, startControl);
|
||||||
Point c2 = to.move(Direction.fromDegrees(endAngle + 180), endControl);
|
Point c2 = to.move(Direction.fromDegrees(endAngle + 180), endControl);
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
if (!path) {
|
if (!path) {
|
||||||
// context.save();
|
// context.save();
|
||||||
context.setStroke(state.ink);
|
context.setStroke(state.ink);
|
||||||
context.setLineWidth(state.penSize);
|
context.setLineWidth(state.penSize);
|
||||||
context.beginPath();
|
context.beginPath();
|
||||||
context.moveTo(state.pos.getX(), state.pos.getY());
|
context.moveTo(state.pos.getX(), state.pos.getY());
|
||||||
}
|
}
|
||||||
context.bezierCurveTo(c1.getX(), c1.getY(), c2.getX(), c2.getY(), to.getX(), to.getY());
|
context.bezierCurveTo(c1.getX(), c1.getY(), c2.getX(), c2.getY(), to.getX(), to.getY());
|
||||||
}
|
}
|
||||||
state.inDir = state.dir;
|
state.inDir = state.dir;
|
||||||
state.pos = to;
|
state.pos = to;
|
||||||
state.dir = Direction.fromDegrees(endAngle);
|
state.dir = Direction.fromDegrees(endAngle);
|
||||||
|
|
||||||
if (!path && context != null) {
|
if (!path && context != null) {
|
||||||
context.stroke();
|
context.stroke();
|
||||||
// context.restore();
|
// context.restore();
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void debugTurtle() {
|
public void debugTurtle() {
|
||||||
System.err.println("[" + state.pos + " " + state.dir + "]");
|
System.err.println("[" + state.pos + " " + state.dir + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ITurtle draw(double dist) {
|
public ITurtle draw(double dist) {
|
||||||
Point to = state.pos.move(state.dir, dist);
|
Point to = state.pos.move(state.dir, dist);
|
||||||
return drawTo(to);
|
return drawTo(to);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ITurtle draw(Point relPos) {
|
public ITurtle draw(Point relPos) {
|
||||||
Point to = state.pos.move(relPos);
|
Point to = state.pos.move(relPos);
|
||||||
return drawTo(to);
|
return drawTo(to);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ITurtle drawTo(double x, double y) {
|
public ITurtle drawTo(double x, double y) {
|
||||||
Point to = new Point(x, y);
|
Point to = new Point(x, y);
|
||||||
return drawTo(to);
|
return drawTo(to);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ITurtle drawTo(Point to) {
|
public ITurtle drawTo(Point to) {
|
||||||
if (path && context != null) {
|
if (path && context != null) {
|
||||||
context.setStroke(state.ink);
|
context.setStroke(state.ink);
|
||||||
context.setLineWidth(state.penSize);
|
context.setLineWidth(state.penSize);
|
||||||
context.lineTo(to.getX(), to.getY());
|
context.lineTo(to.getX(), to.getY());
|
||||||
} else {
|
} else {
|
||||||
line(to);
|
line(to);
|
||||||
}
|
}
|
||||||
state.inDir = state.dir;
|
state.inDir = state.dir;
|
||||||
state.pos = to;
|
state.pos = to;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public double getAngle() {
|
public double getAngle() {
|
||||||
return state.dir.toDegrees();
|
return state.dir.toDegrees();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Direction getDirection() {
|
public Direction getDirection() {
|
||||||
return state.dir;
|
return state.dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public double getHeight() {
|
public double getHeight() {
|
||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Point getPos() {
|
public Point getPos() {
|
||||||
return state.pos;
|
return state.pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Screen getScreen() {
|
public Screen getScreen() {
|
||||||
return screen;
|
return screen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public double getWidth() {
|
public double getWidth() {
|
||||||
return width;
|
return width;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ITurtle jump(double dist) {
|
public ITurtle jump(double dist) {
|
||||||
state.inDir = state.dir;
|
state.inDir = state.dir;
|
||||||
state.pos = state.pos.move(state.dir, dist);
|
state.pos = state.pos.move(state.dir, dist);
|
||||||
if (path && context != null) {
|
if (path && context != null)
|
||||||
context.moveTo(state.pos.getX(), state.pos.getY());
|
context.moveTo(state.pos.getX(), state.pos.getY());
|
||||||
}
|
return this;
|
||||||
return this;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ITurtle jump(Point relPos) {
|
public ITurtle jump(Point relPos) {
|
||||||
// TODO: state.inDir = state.dir;
|
// TODO: state.inDir = state.dir;
|
||||||
state.pos = state.pos.move(relPos);
|
state.pos = state.pos.move(relPos);
|
||||||
if (path && context != null) {
|
if (path && context != null)
|
||||||
context.moveTo(state.pos.getX(), state.pos.getY());
|
context.moveTo(state.pos.getX(), state.pos.getY());
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ITurtle jumpTo(double x, double y) {
|
public ITurtle jumpTo(double x, double y) {
|
||||||
state.inDir = state.dir;
|
state.inDir = state.dir;
|
||||||
state.pos = new Point(x, y);
|
state.pos = new Point(x, y);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ITurtle jumpTo(Point to) {
|
public ITurtle jumpTo(Point to) {
|
||||||
state.inDir = state.dir;
|
state.inDir = state.dir;
|
||||||
state.pos = to;
|
state.pos = to;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void layerToBack() {
|
public void layerToBack() {
|
||||||
if (screen != null) {
|
if (screen != null)
|
||||||
screen.moveToBack(this);
|
screen.moveToBack(this);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void layerToFront() {
|
public void layerToFront() {
|
||||||
if (screen != null) {
|
if (screen != null)
|
||||||
screen.moveToFront(this);
|
screen.moveToFront(this);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ITurtle line(Point to) {
|
public ITurtle line(Point to) {
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
// context.save();
|
// context.save();
|
||||||
context.setStroke(state.ink);
|
context.setStroke(state.ink);
|
||||||
context.setLineWidth(state.penSize);
|
context.setLineWidth(state.penSize);
|
||||||
context.strokeLine(state.pos.getX(), state.pos.getY(), to.getX(), to.getY());
|
context.strokeLine(state.pos.getX(), state.pos.getY(), to.getX(), to.getY());
|
||||||
// context.restore();
|
// context.restore();
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IPainter restore() {
|
public IPainter restore() {
|
||||||
if (stateStack.size() > 0) {
|
if (stateStack.size() > 0) {
|
||||||
state = stateStack.remove(stateStack.size() - 1);
|
state = stateStack.remove(stateStack.size() - 1);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IPainter save() {
|
public IPainter save() {
|
||||||
stateStack.add(new TurtleState(state));
|
stateStack.add(new TurtleState(state));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IPainter setInk(Paint ink) {
|
public IPainter setInk(Paint ink) {
|
||||||
state.ink = ink;
|
state.ink = ink;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ITurtle setPenSize(double pixels) {
|
public ITurtle setPenSize(double pixels) {
|
||||||
if (pixels < 0) {
|
if (pixels < 0)
|
||||||
throw new IllegalArgumentException("Negative: " + pixels);
|
throw new IllegalArgumentException("Negative: " + pixels);
|
||||||
}
|
state.penSize = pixels;
|
||||||
state.penSize = pixels;
|
return this;
|
||||||
return this;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IShape shape() {
|
public IShape shape() {
|
||||||
ShapePainter s = new ShapePainter(context);
|
ShapePainter s = new ShapePainter(context);
|
||||||
return s.at(getPos()).rotation(getAngle()).strokePaint(state.ink);
|
return s.at(getPos()).rotation(getAngle()).strokePaint(state.ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ITurtle turn(double degrees) {
|
public ITurtle turn(double degrees) {
|
||||||
state.dir = state.dir.turn(degrees);
|
state.dir = state.dir.turn(degrees);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ITurtle turnAround() {
|
public ITurtle turnAround() {
|
||||||
return turn(180);
|
return turn(180);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ITurtle turnLeft() {
|
public ITurtle turnLeft() {
|
||||||
return turn(90);
|
return turn(90);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ITurtle turnLeft(double degrees) {
|
public ITurtle turnLeft(double degrees) {
|
||||||
if (degrees < 0) {
|
if (degrees < 0)
|
||||||
throw new IllegalArgumentException("Negative: " + degrees + " (use turn())");
|
throw new IllegalArgumentException("Negative: " + degrees + " (use turn())");
|
||||||
}
|
state.dir = state.dir.turn(degrees);
|
||||||
state.dir = state.dir.turn(degrees);
|
return this;
|
||||||
return this;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ITurtle turnRight() {
|
public ITurtle turnRight() {
|
||||||
return turn(-90);
|
return turn(-90);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ITurtle turnRight(double degrees) {
|
public ITurtle turnRight(double degrees) {
|
||||||
if (degrees < 0) {
|
if (degrees < 0)
|
||||||
throw new IllegalArgumentException("Negative: " + degrees + " (use turn())");
|
throw new IllegalArgumentException("Negative: " + degrees + " (use turn())");
|
||||||
}
|
state.dir = state.dir.turn(-degrees);
|
||||||
state.dir = state.dir.turn(-degrees);
|
return this;
|
||||||
return this;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ITurtle turnTo(double degrees) {
|
public ITurtle turnTo(double degrees) {
|
||||||
state.dir = state.dir.turnTo(degrees);
|
state.dir = state.dir.turnTo(degrees);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ITurtle turnTowards(double degrees, double percent) {
|
public ITurtle turnTowards(double degrees, double percent) {
|
||||||
state.dir = state.dir.turnTowards(new Direction(degrees), percent);
|
state.dir = state.dir.turnTowards(new Direction(degrees), percent);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ITurtle turtle() {
|
public ITurtle turtle() {
|
||||||
TurtlePainter painter = screen != null ? new TurtlePainter(screen, canvas) : new TurtlePainter(width, height);
|
TurtlePainter painter = screen != null ? new TurtlePainter(screen, canvas) : new TurtlePainter(width, height);
|
||||||
painter.stateStack.set(0, new TurtleState(state));
|
painter.stateStack.set(0, new TurtleState(state));
|
||||||
return painter;
|
return painter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ITurtle beginPath() {
|
public ITurtle beginPath() {
|
||||||
if (path) {
|
if (path)
|
||||||
throw new IllegalStateException("beginPath() after beginPath()");
|
throw new IllegalStateException("beginPath() after beginPath()");
|
||||||
}
|
path = true;
|
||||||
path = true;
|
if (context != null) {
|
||||||
if (context != null) {
|
context.setStroke(state.ink);
|
||||||
context.setStroke(state.ink);
|
context.beginPath();
|
||||||
context.beginPath();
|
context.moveTo(state.pos.getX(), state.pos.getY());
|
||||||
context.moveTo(state.pos.getX(), state.pos.getY());
|
}
|
||||||
}
|
return this;
|
||||||
return this;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public ITurtle closePath() {
|
public ITurtle closePath() {
|
||||||
if (!path) {
|
if (!path)
|
||||||
throw new IllegalStateException("closePath() without beginPath()");
|
throw new IllegalStateException("closePath() without beginPath()");
|
||||||
}
|
if (context != null)
|
||||||
if (context != null) {
|
context.closePath();
|
||||||
context.closePath();
|
return this;
|
||||||
}
|
}
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ITurtle endPath() {
|
public ITurtle endPath() {
|
||||||
if (!path) {
|
if (!path)
|
||||||
throw new IllegalStateException("endPath() without beginPath()");
|
throw new IllegalStateException("endPath() without beginPath()");
|
||||||
}
|
path = false;
|
||||||
path = false;
|
if (context != null)
|
||||||
if (context != null) {
|
context.stroke();
|
||||||
context.stroke();
|
return this;
|
||||||
}
|
}
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ITurtle fillPath() {
|
public ITurtle fillPath() {
|
||||||
if (!path) {
|
if (!path)
|
||||||
throw new IllegalStateException("fillPath() without beginPath()");
|
throw new IllegalStateException("fillPath() without beginPath()");
|
||||||
}
|
path = false;
|
||||||
path = false;
|
if (context != null) {
|
||||||
if (context != null) {
|
context.save();
|
||||||
context.save();
|
context.setFill(state.ink);
|
||||||
context.setFill(state.ink);
|
context.fill();
|
||||||
context.fill();
|
context.restore();
|
||||||
context.restore();
|
}
|
||||||
}
|
return this;
|
||||||
return this;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Paint getInk() {
|
public Paint getInk() {
|
||||||
return state.ink;
|
return state.ink;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,200 +6,196 @@ import java.util.List;
|
|||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
public class BlocksAndBoxes {
|
public class BlocksAndBoxes {
|
||||||
public enum PixelOrder implements Iterable<Integer> {
|
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,
|
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);
|
4), RIGHT_TO_LEFT_UPWARDS(1, 2, 4, 8);
|
||||||
|
|
||||||
private List<Integer> order;
|
private List<Integer> order;
|
||||||
|
|
||||||
PixelOrder(int a, int b, int c, int d) {
|
PixelOrder(int a, int b, int c, int d) {
|
||||||
order = Arrays.asList(a, b, c, d);
|
order = Arrays.asList(a, b, c, d);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<Integer> iterator() {
|
public Iterator<Integer> iterator() {
|
||||||
return order.iterator();
|
return order.iterator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String[] unicodeBlocks = {" ", "▗", "▖", "▄", "▝", "▐", "▞", "▟", "▘", "▚", "▌", "▙", "▀", "▜",
|
public static final String[] unicodeBlocks = { " ", "▗", "▖", "▄", "▝", "▐", "▞", "▟", "▘", "▚", "▌", "▙", "▀", "▜",
|
||||||
"▛", "█", "▒"};
|
"▛", "█", "▒" };
|
||||||
|
|
||||||
public static final int[] unicodeBlocks_NumPixels = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 2};
|
public static final int[] unicodeBlocks_NumPixels = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 2 };
|
||||||
public static final String unicodeBlocksString = String.join("", unicodeBlocks);
|
public static final String unicodeBlocksString = String.join("", unicodeBlocks);
|
||||||
public static final String BLOCK_EMPTY = " ";
|
public static final String BLOCK_EMPTY = " ";
|
||||||
public static final String BLOCK_BOTTOM_RIGHT = "▗";
|
public static final String BLOCK_BOTTOM_RIGHT = "▗";
|
||||||
public static final String BLOCK_BOTTOM_LEFT = "▖";
|
public static final String BLOCK_BOTTOM_LEFT = "▖";
|
||||||
public static final String BLOCK_BOTTOM = "▄";
|
public static final String BLOCK_BOTTOM = "▄";
|
||||||
public static final String BLOCK_TOP_RIGHT = "▝";
|
public static final String BLOCK_TOP_RIGHT = "▝";
|
||||||
public static final String BLOCK_RIGHT = "▐";
|
public static final String BLOCK_RIGHT = "▐";
|
||||||
public static final String BLOCK_DIAG_FORWARD = "▞";
|
public static final String BLOCK_DIAG_FORWARD = "▞";
|
||||||
public static final String BLOCK_REVERSE_TOP_LEFT = "▟";
|
public static final String BLOCK_REVERSE_TOP_LEFT = "▟";
|
||||||
public static final String BLOCK_TOP_LEFT = "▘";
|
public static final String BLOCK_TOP_LEFT = "▘";
|
||||||
public static final String BLOCK_DIAG_BACKWARD = "▚";
|
public static final String BLOCK_DIAG_BACKWARD = "▚";
|
||||||
public static final String BLOCK_LEFT = "▌";
|
public static final String BLOCK_LEFT = "▌";
|
||||||
public static final String BLOCK_REVERSE_TOP_RIGHT = "▙";
|
public static final String BLOCK_REVERSE_TOP_RIGHT = "▙";
|
||||||
public static final String BLOCK_TOP = "▀";
|
public static final String BLOCK_TOP = "▀";
|
||||||
public static final String BLOCK_REVERSE_BOTTOM_LEFT = "▜";
|
public static final String BLOCK_REVERSE_BOTTOM_LEFT = "▜";
|
||||||
public static final String BLOCK_REVERSE_BOTTOM_RIGHT = "▛";
|
public static final String BLOCK_REVERSE_BOTTOM_RIGHT = "▛";
|
||||||
public static final String BLOCK_FULL = "█";
|
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) {
|
public static String blockAddOne(String s, PixelOrder order) {
|
||||||
int i = BlocksAndBoxes.unicodeBlocksString.indexOf(s);
|
int i = BlocksAndBoxes.unicodeBlocksString.indexOf(s);
|
||||||
if (i >= 0) {
|
if (i >= 0) {
|
||||||
for (int bit : order) {
|
for (int bit : order) {
|
||||||
if ((i & bit) == 0) {
|
if ((i & bit) == 0)
|
||||||
return unicodeBlocks[i | bit];
|
return unicodeBlocks[i | bit];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return s;
|
||||||
return s;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a string into a Unicode block graphics character.
|
* Convert a string into a Unicode block graphics character.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* The block characters corresponds to 2x2-pixel images, and can be used, e.g.,
|
* The block characters corresponds to 2x2-pixel images, and can be used, e.g.,
|
||||||
* to draw a 80x44 pixel image on a 40x22 character text screen.
|
* to draw a 80x44 pixel image on a 40x22 character text screen.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* The blocks are specified by four-character strings of spaces and asterisks,
|
* The blocks are specified by four-character strings of spaces and asterisks,
|
||||||
* with space indicating an open space and asterisk indicating a filled "pixel",
|
* with space indicating an open space and asterisk indicating a filled "pixel",
|
||||||
* with the pixels arranged in a left-to-right, top-to-bottom order:
|
* with the pixels arranged in a left-to-right, top-to-bottom order:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* 01
|
* 01
|
||||||
* 23
|
* 23
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* So <code>"* **"</code> corresponds to the block character <code>"▚"</code>,
|
* So <code>"* **"</code> corresponds to the block character <code>"▚"</code>,
|
||||||
* with this layout:
|
* with this layout:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* *
|
* *
|
||||||
* *
|
* *
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* The special codes <code>"++++"</code> and <code>"+"</code> corresponds to the
|
* The special codes <code>"++++"</code> and <code>"+"</code> corresponds to the
|
||||||
* "grey" block <code>"▒"</code>, and <code>"*"</code> corresponds to the
|
* "grey" block <code>"▒"</code>, and <code>"*"</code> corresponds to the
|
||||||
* "black" block <code>"█"</code>.
|
* "black" block <code>"█"</code>.
|
||||||
*
|
*
|
||||||
* @param s A four character string, indicating which block character to
|
* @param s
|
||||||
* select.
|
* A four character string, indicating which block character to
|
||||||
* @return A Unicode block character
|
* select.
|
||||||
* @throws IllegalArgumentException if the string isn't of the expected form
|
* @return A Unicode block character
|
||||||
*/
|
* @throws IllegalArgumentException
|
||||||
public static String blockChar(String s) {
|
* if the string isn't of the expected form
|
||||||
switch (s.replaceAll("\n", s)) {
|
*/
|
||||||
case " ":
|
public static String blockChar(String s) {
|
||||||
return unicodeBlocks[0];
|
switch (s.replaceAll("\n", s)) {
|
||||||
case " *":
|
case " ":
|
||||||
return unicodeBlocks[1];
|
return unicodeBlocks[0];
|
||||||
case " * ":
|
case " *":
|
||||||
return unicodeBlocks[2];
|
return unicodeBlocks[1];
|
||||||
case " **":
|
case " * ":
|
||||||
return unicodeBlocks[3];
|
return unicodeBlocks[2];
|
||||||
case " * ":
|
case " **":
|
||||||
return unicodeBlocks[4];
|
return unicodeBlocks[3];
|
||||||
case " * *":
|
case " * ":
|
||||||
return unicodeBlocks[5];
|
return unicodeBlocks[4];
|
||||||
case " ** ":
|
case " * *":
|
||||||
return unicodeBlocks[6];
|
return unicodeBlocks[5];
|
||||||
case " ***":
|
case " ** ":
|
||||||
return unicodeBlocks[7];
|
return unicodeBlocks[6];
|
||||||
case "* ":
|
case " ***":
|
||||||
return unicodeBlocks[8];
|
return unicodeBlocks[7];
|
||||||
case "* *":
|
case "* ":
|
||||||
return unicodeBlocks[9];
|
return unicodeBlocks[8];
|
||||||
case "* * ":
|
case "* *":
|
||||||
return unicodeBlocks[10];
|
return unicodeBlocks[9];
|
||||||
case "* **":
|
case "* * ":
|
||||||
return unicodeBlocks[11];
|
return unicodeBlocks[10];
|
||||||
case "** ":
|
case "* **":
|
||||||
return unicodeBlocks[12];
|
return unicodeBlocks[11];
|
||||||
case "** *":
|
case "** ":
|
||||||
return unicodeBlocks[13];
|
return unicodeBlocks[12];
|
||||||
case "*** ":
|
case "** *":
|
||||||
return unicodeBlocks[14];
|
return unicodeBlocks[13];
|
||||||
case "****":
|
case "*** ":
|
||||||
return unicodeBlocks[15];
|
return unicodeBlocks[14];
|
||||||
case "++++":
|
case "****":
|
||||||
return unicodeBlocks[16];
|
return unicodeBlocks[15];
|
||||||
case ".":
|
case "++++":
|
||||||
return BLOCK_BOTTOM_LEFT;
|
return unicodeBlocks[16];
|
||||||
case "_":
|
case ".":
|
||||||
return BLOCK_BOTTOM;
|
return BLOCK_BOTTOM_LEFT;
|
||||||
case "/":
|
case "_":
|
||||||
return BLOCK_DIAG_FORWARD;
|
return BLOCK_BOTTOM;
|
||||||
case "\\":
|
case "/":
|
||||||
return BLOCK_DIAG_BACKWARD;
|
return BLOCK_DIAG_FORWARD;
|
||||||
case "|":
|
case "\\":
|
||||||
return BLOCK_LEFT;
|
return BLOCK_DIAG_BACKWARD;
|
||||||
case "#":
|
case "|":
|
||||||
return BLOCK_FULL;
|
return BLOCK_LEFT;
|
||||||
case "`":
|
case "#":
|
||||||
return BLOCK_TOP_LEFT;
|
return BLOCK_FULL;
|
||||||
case "'":
|
case "`":
|
||||||
return BLOCK_TOP_RIGHT;
|
return BLOCK_TOP_LEFT;
|
||||||
}
|
case "'":
|
||||||
throw new IllegalArgumentException(
|
return BLOCK_TOP_RIGHT;
|
||||||
"Expected length 4 string of \" \" and \"*\", or \"++++\", got \"" + s + "\"");
|
}
|
||||||
}
|
throw new IllegalArgumentException(
|
||||||
|
"Expected length 4 string of \" \" and \"*\", or \"++++\", got \"" + s + "\"");
|
||||||
|
}
|
||||||
|
|
||||||
public static String blockCompact(String s) {
|
public static String blockCompact(String s) {
|
||||||
int i = BlocksAndBoxes.unicodeBlocksString.indexOf(s);
|
int i = BlocksAndBoxes.unicodeBlocksString.indexOf(s);
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
int lower = i & 3;
|
int lower = i & 3;
|
||||||
int upper = (i >> 2) & 3;
|
int upper = (i >> 2) & 3;
|
||||||
i = (lower | upper) | ((lower & upper) << 2);
|
i = (lower | upper) | ((lower & upper) << 2);
|
||||||
// System.out.println("Compact: " + s + " -> " + BlocksAndBoxes.unicodeBlocks[i]
|
// System.out.println("Compact: " + s + " -> " + BlocksAndBoxes.unicodeBlocks[i]
|
||||||
// + "\n");
|
// + "\n");
|
||||||
return BlocksAndBoxes.unicodeBlocks[i];
|
return BlocksAndBoxes.unicodeBlocks[i];
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String blockCompose(String b1, String b2, BiFunction<Integer, Integer, Integer> op) {
|
public static String blockCompose(String b1, String b2, BiFunction<Integer, Integer, Integer> op) {
|
||||||
int i1 = unicodeBlocksString.indexOf(b1);
|
int i1 = unicodeBlocksString.indexOf(b1);
|
||||||
if (i1 < 0) {
|
if (i1 < 0)
|
||||||
throw new IllegalArgumentException("Not a block character: " + b1);
|
throw new IllegalArgumentException("Not a block character: " + b1);
|
||||||
}
|
int i2 = unicodeBlocksString.indexOf(b2);
|
||||||
int i2 = unicodeBlocksString.indexOf(b2);
|
if (i2 < 0)
|
||||||
if (i2 < 0) {
|
throw new IllegalArgumentException("Not a block character: " + b1);
|
||||||
throw new IllegalArgumentException("Not a block character: " + b1);
|
if (i1 == 16 || i2 == 16)
|
||||||
}
|
return b2;
|
||||||
if (i1 == 16 || i2 == 16) {
|
else
|
||||||
return b2;
|
return unicodeBlocks[op.apply(i1, i2)];
|
||||||
} else {
|
}
|
||||||
return unicodeBlocks[op.apply(i1, i2)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String blockComposeOrOverwrite(String b1, String b2, BiFunction<Integer, Integer, Integer> op) {
|
public static String blockComposeOrOverwrite(String b1, String b2, BiFunction<Integer, Integer, Integer> op) {
|
||||||
int i1 = unicodeBlocksString.indexOf(b1);
|
int i1 = unicodeBlocksString.indexOf(b1);
|
||||||
int i2 = unicodeBlocksString.indexOf(b2);
|
int i2 = unicodeBlocksString.indexOf(b2);
|
||||||
if (i1 < 0 || i2 < 0 || i1 == 16 || i2 == 16) {
|
if (i1 < 0 || i2 < 0 || i1 == 16 || i2 == 16)
|
||||||
return b2;
|
return b2;
|
||||||
} else {
|
else
|
||||||
return unicodeBlocks[op.apply(i1, i2)];
|
return unicodeBlocks[op.apply(i1, i2)];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static String blockRemoveOne(String s, PixelOrder order) {
|
public static String blockRemoveOne(String s, PixelOrder order) {
|
||||||
int i = BlocksAndBoxes.unicodeBlocksString.indexOf(s);
|
int i = BlocksAndBoxes.unicodeBlocksString.indexOf(s);
|
||||||
if (i >= 0) {
|
if (i >= 0) {
|
||||||
for (int bit : order) {
|
for (int bit : order) {
|
||||||
if ((i & bit) != 0) {
|
if ((i & bit) != 0)
|
||||||
return unicodeBlocks[i & ~bit];
|
return unicodeBlocks[i & ~bit];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return s;
|
||||||
return s;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package inf101.v18.gfx.textmode;
|
package inf101.v18.gfx.textmode;
|
||||||
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@ -13,271 +11,266 @@ import java.util.function.Consumer;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
public class ControlSequences {
|
public class ControlSequences {
|
||||||
private static final boolean DEBUG = false;
|
private static final boolean DEBUG = false;
|
||||||
|
|
||||||
public static class CsiPattern {
|
public static class CsiPattern {
|
||||||
public static CsiPattern compile0(String pat, String desc, Consumer<Printer> handler) {
|
public static CsiPattern compile0(String pat, String desc, Consumer<Printer> handler) {
|
||||||
CsiPattern csiPattern = new CsiPattern(pat, 0, 0, desc, handler, null, null);
|
CsiPattern csiPattern = new CsiPattern(pat, 0, 0, desc, handler, null, null);
|
||||||
patterns.put(csiPattern.getCommandLetter(), csiPattern);
|
patterns.put(csiPattern.getCommandLetter(), csiPattern);
|
||||||
return csiPattern;
|
return csiPattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CsiPattern compile1(String pat, int defaultArg, String desc,
|
public static CsiPattern compile1(String pat, int defaultArg, String desc,
|
||||||
BiConsumer<Printer, Integer> handler) {
|
BiConsumer<Printer, Integer> handler) {
|
||||||
CsiPattern csiPattern = new CsiPattern(pat, defaultArg, 1, desc, null, handler, null);
|
CsiPattern csiPattern = new CsiPattern(pat, defaultArg, 1, desc, null, handler, null);
|
||||||
patterns.put(csiPattern.getCommandLetter(), csiPattern);
|
patterns.put(csiPattern.getCommandLetter(), csiPattern);
|
||||||
return csiPattern;
|
return csiPattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CsiPattern compileN(String pat, int defaultArg, int numArgs, String desc,
|
public static CsiPattern compileN(String pat, int defaultArg, int numArgs, String desc,
|
||||||
BiConsumer<Printer, List<Integer>> handler) {
|
BiConsumer<Printer, List<Integer>> handler) {
|
||||||
CsiPattern csiPattern = new CsiPattern(pat, defaultArg, numArgs, desc, null, null, handler);
|
CsiPattern csiPattern = new CsiPattern(pat, defaultArg, numArgs, desc, null, null, handler);
|
||||||
patterns.put(csiPattern.getCommandLetter(), csiPattern);
|
patterns.put(csiPattern.getCommandLetter(), csiPattern);
|
||||||
return csiPattern;
|
return csiPattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String patStr;
|
private String patStr;
|
||||||
private Pattern pattern;
|
private Pattern pattern;
|
||||||
private int defaultArg = 0;
|
private int defaultArg = 0;
|
||||||
private String desc;
|
private String desc;
|
||||||
private Consumer<Printer> handler0;
|
private Consumer<Printer> handler0;
|
||||||
|
|
||||||
private BiConsumer<Printer, Integer> handler1;
|
private BiConsumer<Printer, Integer> handler1;
|
||||||
|
|
||||||
private BiConsumer<Printer, List<Integer>> handlerN;
|
private BiConsumer<Printer, List<Integer>> handlerN;
|
||||||
|
|
||||||
private int numArgs;
|
private int numArgs;
|
||||||
|
|
||||||
public CsiPattern(String pat, int defaultArg, int numArgs, String desc, Consumer<Printer> handler0,
|
public CsiPattern(String pat, int defaultArg, int numArgs, String desc, Consumer<Printer> handler0,
|
||||||
BiConsumer<Printer, Integer> handler1, BiConsumer<Printer, List<Integer>> handlerN) {
|
BiConsumer<Printer, Integer> handler1, BiConsumer<Printer, List<Integer>> handlerN) {
|
||||||
this.patStr = pat;
|
this.patStr = pat;
|
||||||
this.pattern = Pattern.compile(pat);
|
this.pattern = Pattern.compile(pat);
|
||||||
this.defaultArg = defaultArg;
|
this.defaultArg = defaultArg;
|
||||||
this.numArgs = numArgs;
|
this.numArgs = numArgs;
|
||||||
this.desc = desc;
|
this.desc = desc;
|
||||||
this.handler0 = handler0;
|
this.handler0 = handler0;
|
||||||
this.handler1 = handler1;
|
this.handler1 = handler1;
|
||||||
this.handlerN = handlerN;
|
this.handlerN = handlerN;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCommandLetter() {
|
public String getCommandLetter() {
|
||||||
return patStr.substring(patStr.length() - 1);
|
return patStr.substring(patStr.length() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
return desc;
|
return desc;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean match(Printer printer, String input) {
|
public boolean match(Printer printer, String input) {
|
||||||
Matcher matcher = pattern.matcher(input);
|
Matcher matcher = pattern.matcher(input);
|
||||||
if (matcher.matches()) {
|
if (matcher.matches()) {
|
||||||
String argStr = matcher.groupCount() > 0 ? matcher.group(1) : "";
|
String argStr = matcher.groupCount() > 0 ? matcher.group(1) : "";
|
||||||
String[] args = argStr.split(";");
|
String[] args = argStr.split(";");
|
||||||
if (handler0 != null) {
|
if (handler0 != null) {
|
||||||
if (DEBUG) {
|
if(DEBUG)
|
||||||
System.out.println("Handling " + getDescription() + ".");
|
System.out.println("Handling " + getDescription() + ".");
|
||||||
}
|
handler0.accept(printer);
|
||||||
handler0.accept(printer);
|
} else if (handler1 != null) {
|
||||||
} else if (handler1 != null) {
|
int arg = args.length > 0 && !args[0].equals("") ? Integer.valueOf(args[0]) : defaultArg;
|
||||||
int arg = args.length > 0 && !args[0].equals("") ? Integer.valueOf(args[0]) : defaultArg;
|
if(DEBUG)
|
||||||
if (DEBUG) {
|
System.out.println("Handling " + getDescription() + ": " + arg);
|
||||||
System.out.println("Handling " + getDescription() + ": " + arg);
|
handler1.accept(printer, arg);
|
||||||
}
|
} else if (handlerN != null) {
|
||||||
handler1.accept(printer, arg);
|
List<Integer> argList = new ArrayList<>();
|
||||||
} else if (handlerN != null) {
|
for (String s : args) {
|
||||||
List<Integer> argList = new ArrayList<>();
|
if (s.equals(""))
|
||||||
for (String s : args) {
|
argList.add(defaultArg);
|
||||||
if (s.equals("")) {
|
else
|
||||||
argList.add(defaultArg);
|
argList.add(Integer.valueOf(s));
|
||||||
} else {
|
}
|
||||||
argList.add(Integer.valueOf(s));
|
while (argList.size() < numArgs) {
|
||||||
}
|
argList.add(defaultArg);
|
||||||
}
|
}
|
||||||
while (argList.size() < numArgs) {
|
if(DEBUG)
|
||||||
argList.add(defaultArg);
|
System.out.println("Handling " + getDescription() + ": " + argList);
|
||||||
}
|
handlerN.accept(printer, argList);
|
||||||
if (DEBUG) {
|
}
|
||||||
System.out.println("Handling " + getDescription() + ": " + argList);
|
return true;
|
||||||
}
|
}
|
||||||
handlerN.accept(printer, argList);
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final Map<String, CsiPattern> patterns = new HashMap<>();
|
public static final Map<String, CsiPattern> patterns = new HashMap<>();
|
||||||
public static final CsiPattern CUU = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)A", 1, "cursor up",
|
public static final CsiPattern CUU = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)A", 1, "cursor up",
|
||||||
(Printer p, Integer i) -> {
|
(Printer p, Integer i) -> {
|
||||||
p.move(0, -i);
|
p.move(0, -i);
|
||||||
});
|
});
|
||||||
public static final CsiPattern CUD = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)B", 1, "cursor down",
|
public static final CsiPattern CUD = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)B", 1, "cursor down",
|
||||||
(Printer p, Integer i) -> {
|
(Printer p, Integer i) -> {
|
||||||
p.move(0, i);
|
p.move(0, i);
|
||||||
});
|
});
|
||||||
public static final CsiPattern CUF = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)C", 1, "cursor forward",
|
public static final CsiPattern CUF = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)C", 1, "cursor forward",
|
||||||
(Printer p, Integer i) -> {
|
(Printer p, Integer i) -> {
|
||||||
p.move(i, 0);
|
p.move(i, 0);
|
||||||
});
|
});
|
||||||
public static final CsiPattern CUB = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)D", 1, "cursor back",
|
public static final CsiPattern CUB = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)D", 1, "cursor back",
|
||||||
(Printer p, Integer i) -> {
|
(Printer p, Integer i) -> {
|
||||||
p.move(-i, 0);
|
p.move(-i, 0);
|
||||||
});
|
});
|
||||||
public static final CsiPattern CNL = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)E", 1, "cursor next line",
|
public static final CsiPattern CNL = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)E", 1, "cursor next line",
|
||||||
(Printer p, Integer i) -> {
|
(Printer p, Integer i) -> {
|
||||||
p.move(0, i);
|
p.move(0, i);
|
||||||
p.beginningOfLine();
|
p.beginningOfLine();
|
||||||
});
|
});
|
||||||
public static final CsiPattern CPL = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)F", 1, "cursor previous line",
|
public static final CsiPattern CPL = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)F", 1, "cursor previous line",
|
||||||
(Printer p, Integer i) -> {
|
(Printer p, Integer i) -> {
|
||||||
p.move(0, -i);
|
p.move(0, -i);
|
||||||
p.beginningOfLine();
|
p.beginningOfLine();
|
||||||
});
|
});
|
||||||
public static final CsiPattern CHA = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)G", 1,
|
public static final CsiPattern CHA = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)G", 1,
|
||||||
"cursor horizontal absolute", (Printer p, Integer i) -> {
|
"cursor horizontal absolute", (Printer p, Integer i) -> {
|
||||||
p.moveTo(i, p.getY());
|
p.moveTo(i, p.getY());
|
||||||
});
|
});
|
||||||
public static final CsiPattern CUP = CsiPattern.compileN("\u001b\\\u005b([0-9;]*)H", 1, 2, "cursor position",
|
public static final CsiPattern CUP = CsiPattern.compileN("\u001b\\\u005b([0-9;]*)H", 1, 2, "cursor position",
|
||||||
(Printer p, List<Integer> i) -> {
|
(Printer p, List<Integer> i) -> {
|
||||||
p.moveTo(i.get(1), i.get(0));
|
p.moveTo(i.get(1), i.get(0));
|
||||||
});
|
});
|
||||||
public static final CsiPattern ED = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)J", 0, "erase in display",
|
public static final CsiPattern ED = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)J", 0, "erase in display",
|
||||||
(Printer p, Integer i) -> {
|
(Printer p, Integer i) -> {
|
||||||
if (i == 2 || i == 3) {
|
if (i == 2 || i == 3)
|
||||||
p.clear();
|
p.clear();
|
||||||
} else {
|
else
|
||||||
System.err.println("Unimplemented: ED");
|
System.err.println("Unimplemented: ED");
|
||||||
}
|
});
|
||||||
});
|
public static final CsiPattern EK = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)K", 0, "erase in line",
|
||||||
public static final CsiPattern EK = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)K", 0, "erase in line",
|
(Printer p, Integer i) -> {
|
||||||
(Printer p, Integer i) -> {
|
System.err.println("Unimplemented: EK");
|
||||||
System.err.println("Unimplemented: EK");
|
});
|
||||||
});
|
public static final CsiPattern SU = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)S", 1, "scroll up",
|
||||||
public static final CsiPattern SU = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)S", 1, "scroll up",
|
(Printer p, Integer i) -> {
|
||||||
(Printer p, Integer i) -> {
|
p.scroll(i);
|
||||||
p.scroll(i);
|
});
|
||||||
});
|
public static final CsiPattern SD = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)T", 1, "scroll down",
|
||||||
public static final CsiPattern SD = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)T", 1, "scroll down",
|
(Printer p, Integer i) -> {
|
||||||
(Printer p, Integer i) -> {
|
p.scroll(-i);
|
||||||
p.scroll(-i);
|
});
|
||||||
});
|
public static final CsiPattern HVP = CsiPattern.compileN("\u001b\\\u005b([0-9;]*)f", 1, 2,
|
||||||
public static final CsiPattern HVP = CsiPattern.compileN("\u001b\\\u005b([0-9;]*)f", 1, 2,
|
"horizontal vertical position", (Printer p, List<Integer> l) -> {
|
||||||
"horizontal vertical position", (Printer p, List<Integer> l) -> {
|
p.moveTo(l.get(1), l.get(0));
|
||||||
p.moveTo(l.get(1), l.get(0));
|
});
|
||||||
});
|
public static final CsiPattern AUX_ON = CsiPattern.compile0("\u001b\\\u005b5i", "aux port on", (Printer p) -> {
|
||||||
public static final CsiPattern AUX_ON = CsiPattern.compile0("\u001b\\\u005b5i", "aux port on", (Printer p) -> {
|
System.err.println("Unimplemented: AUX on");
|
||||||
System.err.println("Unimplemented: AUX on");
|
});
|
||||||
});
|
public static final CsiPattern AUX_OFF = CsiPattern.compile0("\u001b\\\u005b4i", "aux port off", (Printer p) -> {
|
||||||
public static final CsiPattern AUX_OFF = CsiPattern.compile0("\u001b\\\u005b4i", "aux port off", (Printer p) -> {
|
System.err.println("Unimplemented: AUX off");
|
||||||
System.err.println("Unimplemented: AUX off");
|
});
|
||||||
});
|
public static final CsiPattern DSR = CsiPattern.compile0("\u001b\\\u005b6n", "device status report",
|
||||||
public static final CsiPattern DSR = CsiPattern.compile0("\u001b\\\u005b6n", "device status report",
|
(Printer p) -> {
|
||||||
(Printer p) -> {
|
System.out.println("ESC[" + p.getY() + ";" + p.getX() + "R");
|
||||||
System.out.println("ESC[" + p.getY() + ";" + p.getX() + "R");
|
});
|
||||||
});
|
public static final CsiPattern SCP = CsiPattern.compile0("\u001b\\\u005bs", "save cursor position", (Printer p) -> {
|
||||||
public static final CsiPattern SCP = CsiPattern.compile0("\u001b\\\u005bs", "save cursor position", (Printer p) -> {
|
p.saveCursor();
|
||||||
p.saveCursor();
|
});
|
||||||
});
|
public static final CsiPattern RCP = CsiPattern.compile0("\u001b\\\u005bu", "restore cursor position",
|
||||||
public static final CsiPattern RCP = CsiPattern.compile0("\u001b\\\u005bu", "restore cursor position",
|
(Printer p) -> {
|
||||||
(Printer p) -> {
|
p.restoreCursor();
|
||||||
p.restoreCursor();
|
});
|
||||||
});
|
public static final int F = 0xFF, H = 0xAA, L = 0x55, OFF = 0x00;
|
||||||
public static final int F = 0xFF, H = 0xAA, L = 0x55, OFF = 0x00;
|
public static final Color[] PALETTE_CGA = { //
|
||||||
public static final Color[] PALETTE_CGA = { //
|
Color.rgb(0, 0, 0), Color.rgb(0, 0, H), Color.rgb(0, H, 0), Color.rgb(0, H, H), //
|
||||||
Color.rgb(0, 0, 0), Color.rgb(0, 0, H), Color.rgb(0, H, 0), Color.rgb(0, H, H), //
|
Color.rgb(H, 0, 0), Color.rgb(H, 0, H), Color.rgb(H, L, 0), Color.rgb(H, H, H), //
|
||||||
Color.rgb(H, 0, 0), Color.rgb(H, 0, H), Color.rgb(H, L, 0), Color.rgb(H, H, H), //
|
Color.rgb(L, L, L), Color.rgb(L, L, F), Color.rgb(L, F, L), Color.rgb(L, F, F), //
|
||||||
Color.rgb(L, L, L), Color.rgb(L, L, F), Color.rgb(L, F, L), Color.rgb(L, F, F), //
|
Color.rgb(F, L, L), Color.rgb(F, L, F), Color.rgb(F, F, L), Color.rgb(F, F, F), };
|
||||||
Color.rgb(F, L, L), Color.rgb(F, L, F), Color.rgb(F, F, L), Color.rgb(F, F, F),};
|
public static final Color[] PALETTE_VGA = { //
|
||||||
public static final Color[] PALETTE_VGA = { //
|
Color.rgb(0, 0, 0), Color.rgb(H, 0, 0), Color.rgb(0, H, 0), Color.rgb(H, H, 0), //
|
||||||
Color.rgb(0, 0, 0), Color.rgb(H, 0, 0), Color.rgb(0, H, 0), Color.rgb(H, H, 0), //
|
Color.rgb(0, 0, H), Color.rgb(H, 0, H), Color.rgb(0, H, H), Color.rgb(H, H, H), //
|
||||||
Color.rgb(0, 0, H), Color.rgb(H, 0, H), Color.rgb(0, H, H), Color.rgb(H, H, H), //
|
Color.rgb(L, L, L), Color.rgb(F, L, L), Color.rgb(L, F, L), Color.rgb(F, F, L), //
|
||||||
Color.rgb(L, L, L), Color.rgb(F, L, L), Color.rgb(L, F, L), Color.rgb(F, F, L), //
|
Color.rgb(L, L, F), Color.rgb(F, L, F), Color.rgb(L, F, F), Color.rgb(F, F, F), };
|
||||||
Color.rgb(L, L, F), Color.rgb(F, L, F), Color.rgb(L, F, F), Color.rgb(F, F, F),};
|
|
||||||
|
|
||||||
public static final CsiPattern SGR = CsiPattern.compileN("\u001b\\\u005b([0-9;]*)m", 0, -1,
|
public static final CsiPattern SGR = CsiPattern.compileN("\u001b\\\u005b([0-9;]*)m", 0, -1,
|
||||||
"select graphics rendition", (Printer p, List<Integer> l) -> {
|
"select graphics rendition", (Printer p, List<Integer> l) -> {
|
||||||
if (l.size() == 0) {
|
if (l.size() == 0) {
|
||||||
l.add(0);
|
l.add(0);
|
||||||
}
|
}
|
||||||
int[] attrs = {0, TextFont.ATTR_BRIGHT, TextFont.ATTR_FAINT, TextFont.ATTR_ITALIC,
|
int[] attrs = { 0, TextFont.ATTR_BRIGHT, TextFont.ATTR_FAINT, TextFont.ATTR_ITALIC,
|
||||||
TextFont.ATTR_UNDERLINE, TextFont.ATTR_BLINK, TextFont.ATTR_BLINK, TextFont.ATTR_INVERSE, 0,
|
TextFont.ATTR_UNDERLINE, TextFont.ATTR_BLINK, TextFont.ATTR_BLINK, TextFont.ATTR_INVERSE, 0,
|
||||||
TextFont.ATTR_LINE_THROUGH};
|
TextFont.ATTR_LINE_THROUGH };
|
||||||
|
|
||||||
Iterator<Integer> it = l.iterator();
|
Iterator<Integer> it = l.iterator();
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
int i = it.next();
|
int i = it.next();
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
p.setVideoAttrs(0);
|
p.setVideoAttrs(0);
|
||||||
p.setInk(PALETTE_VGA[7]);
|
p.setInk(PALETTE_VGA[7]);
|
||||||
p.setBackground(PALETTE_VGA[0]);
|
p.setBackground(PALETTE_VGA[0]);
|
||||||
} else if (i < 10) {
|
} else if (i < 10) {
|
||||||
p.setVideoAttrEnabled(attrs[i]);
|
p.setVideoAttrEnabled(attrs[i]);
|
||||||
} else if (i >= 20 && i < 30) {
|
} else if (i >= 20 && i < 30) {
|
||||||
p.setVideoAttrDisabled(attrs[i] - 20);
|
p.setVideoAttrDisabled(attrs[i] - 20);
|
||||||
} else if (i >= 30 && i < 38) {
|
} else if (i >= 30 && i < 38) {
|
||||||
p.setInk(PALETTE_VGA[i - 30]);
|
p.setInk(PALETTE_VGA[i - 30]);
|
||||||
} else if (i == 38) {
|
} else if (i == 38) {
|
||||||
p.setInk(decode256(it));
|
p.setInk(decode256(it));
|
||||||
} else if (i == 29) {
|
} else if (i == 29) {
|
||||||
p.setInk(Color.WHITE);
|
p.setInk(Color.WHITE);
|
||||||
} else if (i >= 40 && i < 48) {
|
} else if (i >= 40 && i < 48) {
|
||||||
p.setBackground(PALETTE_VGA[i - 40]);
|
p.setBackground(PALETTE_VGA[i - 40]);
|
||||||
} else if (i == 48) {
|
} else if (i == 48) {
|
||||||
p.setInk(decode256(it));
|
p.setInk(decode256(it));
|
||||||
} else if (i == 49) {
|
} else if (i == 49) {
|
||||||
p.setBackground(Color.BLACK);
|
p.setBackground(Color.BLACK);
|
||||||
} else if (i >= 90 && i < 98) {
|
} else if (i >= 90 && i < 98) {
|
||||||
p.setInk(PALETTE_VGA[8 + i - 90]);
|
p.setInk(PALETTE_VGA[8 + i - 90]);
|
||||||
} else if (i >= 100 && i < 108) {
|
} else if (i >= 100 && i < 108) {
|
||||||
p.setBackground(PALETTE_VGA[8 + i - 100]);
|
p.setBackground(PALETTE_VGA[8 + i - 100]);
|
||||||
} else if (i == 53) {
|
} else if (i == 53) {
|
||||||
p.setVideoAttrEnabled(TextFont.ATTR_OVERLINE);
|
p.setVideoAttrEnabled(TextFont.ATTR_OVERLINE);
|
||||||
} else if (i == 55) {
|
} else if (i == 55) {
|
||||||
p.setVideoAttrEnabled(TextFont.ATTR_OVERLINE);
|
p.setVideoAttrEnabled(TextFont.ATTR_OVERLINE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
public static boolean applyCsi(Printer printer, String csi) {
|
public static boolean applyCsi(Printer printer, String csi) {
|
||||||
CsiPattern csiPattern = patterns.get(csi.substring(csi.length() - 1));
|
CsiPattern csiPattern = patterns.get(csi.substring(csi.length() - 1));
|
||||||
// System.out.println("Applying CSI: " + csi.replaceAll("\u001b", "ESC"));
|
// System.out.println("Applying CSI: " + csi.replaceAll("\u001b", "ESC"));
|
||||||
|
|
||||||
if (csiPattern != null) {
|
if (csiPattern != null) {
|
||||||
if (csiPattern.match(printer, csi)) {
|
if (csiPattern.match(printer, csi))
|
||||||
return true;
|
return true;
|
||||||
} else {
|
else
|
||||||
System.err.println("Handler failed for escape sequence: " + csi.replaceAll("\u001b", "ESC"));
|
System.err.println("Handler failed for escape sequence: " + csi.replaceAll("\u001b", "ESC"));
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
System.err.println("No handler for escape sequence: " + csi.replaceAll("\u001b", "ESC"));
|
System.err.println("No handler for escape sequence: " + csi.replaceAll("\u001b", "ESC"));
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Color decode256(Iterator<Integer> it) {
|
private static Color decode256(Iterator<Integer> it) {
|
||||||
int i;
|
int i;
|
||||||
try {
|
try {
|
||||||
i = it.next();
|
i = it.next();
|
||||||
if (i == 5) {
|
if (i == 5) {
|
||||||
i = it.next();
|
i = it.next();
|
||||||
if (i < 16) {
|
if (i < 16)
|
||||||
return PALETTE_VGA[i];
|
return PALETTE_VGA[i];
|
||||||
} else if (i < 232) {
|
else if (i < 232)
|
||||||
return Color.rgb(i / 36, (i / 6) % 6, i % 6);
|
return Color.rgb(i / 36, (i / 6) % 6, i % 6);
|
||||||
} else {
|
else
|
||||||
return Color.gray((i - 232) / 23.0);
|
return Color.gray((i - 232) / 23.0);
|
||||||
}
|
} else if (i == 2) {
|
||||||
} else if (i == 2) {
|
int r = it.next();
|
||||||
int r = it.next();
|
int g = it.next();
|
||||||
int g = it.next();
|
int b = it.next();
|
||||||
int b = it.next();
|
return Color.rgb(r, g, b);
|
||||||
return Color.rgb(r, g, b);
|
}
|
||||||
}
|
} catch (NoSuchElementException e) {
|
||||||
} catch (NoSuchElementException e) {
|
}
|
||||||
}
|
return null;
|
||||||
return null;
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,161 +1,153 @@
|
|||||||
package inf101.v18.gfx.textmode;
|
package inf101.v18.gfx.textmode;
|
||||||
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
public class DemoPages {
|
public class DemoPages {
|
||||||
public static void printAnsiArt(Printer printer) {
|
public static void printAnsiArt(Printer printer) {
|
||||||
printer.moveTo(1, 1);
|
printer.moveTo(1, 1);
|
||||||
printer.setAutoScroll(false);
|
printer.setAutoScroll(false);
|
||||||
printer.clear();
|
printer.clear();
|
||||||
|
|
||||||
try (InputStream stream = DemoPages.class.getResourceAsStream("flower.txt")) {
|
try (InputStream stream = DemoPages.class.getResourceAsStream("flower.txt")) {
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
|
BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
|
||||||
for (String s = reader.readLine(); s != null; s = reader.readLine()) {
|
for (String s = reader.readLine(); s != null; s = reader.readLine()) {
|
||||||
printer.println(s);
|
printer.println(s);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void printBlockPlotting(Printer printer) {
|
public static void printBlockPlotting(Printer printer) {
|
||||||
printer.clear();
|
printer.clear();
|
||||||
printer.setAutoScroll(false);
|
printer.setAutoScroll(false);
|
||||||
printer.setVideoAttrs(0);
|
printer.setVideoAttrs(0);
|
||||||
int topLine = 8;
|
int topLine = 8;
|
||||||
for (int x = 0; x < 16; x += 1) {
|
for (int x = 0; x < 16; x += 1) {
|
||||||
if ((x & 1) > 0) {
|
if ((x & 1) > 0)
|
||||||
printer.plot(4 * x + 1, 1 + (topLine - 1) * 2);
|
printer.plot(4 * x + 1, 1 + (topLine - 1) * 2);
|
||||||
}
|
if ((x & 2) > 0)
|
||||||
if ((x & 2) > 0) {
|
printer.plot(4 * x, 1 + (topLine - 1) * 2);
|
||||||
printer.plot(4 * x, 1 + (topLine - 1) * 2);
|
if ((x & 4) > 0)
|
||||||
}
|
printer.plot(4 * x + 1, (topLine - 1) * 2);
|
||||||
if ((x & 4) > 0) {
|
if ((x & 8) > 0)
|
||||||
printer.plot(4 * x + 1, (topLine - 1) * 2);
|
printer.plot(4 * x, (topLine - 1) * 2);
|
||||||
}
|
printer.printAt(1 + 2 * x, topLine + 2, BlocksAndBoxes.unicodeBlocks[x]);
|
||||||
if ((x & 8) > 0) {
|
printer.printAt(1 + 2 * x, topLine + 4, BlocksAndBoxes.unicodeBlocks[15]);
|
||||||
printer.plot(4 * x, (topLine - 1) * 2);
|
printer.printAt(1 + 2 * x, topLine + 6, BlocksAndBoxes.unicodeBlocks[~x & +0xf]);
|
||||||
}
|
printer.printAt(1 + 2 * x, topLine + 7, String.format("%X", x));
|
||||||
printer.printAt(1 + 2 * x, topLine + 2, BlocksAndBoxes.unicodeBlocks[x]);
|
if ((x & 1) > 0)
|
||||||
printer.printAt(1 + 2 * x, topLine + 4, BlocksAndBoxes.unicodeBlocks[15]);
|
printer.unplot(4 * x + 1, 1 + (4 + topLine - 1) * 2);
|
||||||
printer.printAt(1 + 2 * x, topLine + 6, BlocksAndBoxes.unicodeBlocks[~x & +0xf]);
|
if ((x & 2) > 0)
|
||||||
printer.printAt(1 + 2 * x, topLine + 7, String.format("%X", x));
|
printer.unplot(4 * x, 1 + (4 + topLine - 1) * 2);
|
||||||
if ((x & 1) > 0) {
|
if ((x & 4) > 0)
|
||||||
printer.unplot(4 * x + 1, 1 + (4 + topLine - 1) * 2);
|
printer.unplot(4 * x + 1, (4 + topLine - 1) * 2);
|
||||||
}
|
if ((x & 8) > 0)
|
||||||
if ((x & 2) > 0) {
|
printer.unplot(4 * x, (4 + topLine - 1) * 2);
|
||||||
printer.unplot(4 * x, 1 + (4 + topLine - 1) * 2);
|
}
|
||||||
}
|
printer.printAt(1, 1,
|
||||||
if ((x & 4) > 0) {
|
"Plotting with Unicode Block Elements\n(ZX81-like Graphics)\n\nThe plot/print and unplot/inverse\nlines should be equal:");
|
||||||
printer.unplot(4 * x + 1, (4 + topLine - 1) * 2);
|
printer.printAt(33, topLine, "plot");
|
||||||
}
|
printer.printAt(33, topLine + 2, "print");
|
||||||
if ((x & 8) > 0) {
|
printer.printAt(33, topLine + 4, "unplot");
|
||||||
printer.unplot(4 * x, (4 + topLine - 1) * 2);
|
printer.printAt(33, topLine + 6, "inverse");
|
||||||
}
|
printer.printAt(0, topLine + 9, String.format("Full blocks:\n Clear[%s] Shaded[%s] Opaque[%s]",
|
||||||
}
|
BlocksAndBoxes.unicodeBlocks[0], BlocksAndBoxes.unicodeBlocks[16], BlocksAndBoxes.unicodeBlocks[15]));
|
||||||
printer.printAt(1, 1,
|
printer.printAt(41, topLine + 9, "(ZX81 inverted shade and half block");
|
||||||
"Plotting with Unicode Block Elements\n(ZX81-like Graphics)\n\nThe plot/print and unplot/inverse\nlines should be equal:");
|
printer.printAt(41, topLine + 10, "shades are missing in Unicode and");
|
||||||
printer.printAt(33, topLine, "plot");
|
printer.printAt(41, topLine + 11, "therefore not supported)");
|
||||||
printer.printAt(33, topLine + 2, "print");
|
printer.println();
|
||||||
printer.printAt(33, topLine + 4, "unplot");
|
}
|
||||||
printer.printAt(33, topLine + 6, "inverse");
|
|
||||||
printer.printAt(0, topLine + 9, String.format("Full blocks:\n Clear[%s] Shaded[%s] Opaque[%s]",
|
|
||||||
BlocksAndBoxes.unicodeBlocks[0], BlocksAndBoxes.unicodeBlocks[16], BlocksAndBoxes.unicodeBlocks[15]));
|
|
||||||
printer.printAt(41, topLine + 9, "(ZX81 inverted shade and half block");
|
|
||||||
printer.printAt(41, topLine + 10, "shades are missing in Unicode and");
|
|
||||||
printer.printAt(41, topLine + 11, "therefore not supported)");
|
|
||||||
printer.println();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void printBoxDrawing(Printer printer) {
|
public static void printBoxDrawing(Printer printer) {
|
||||||
printer.clear();
|
printer.clear();
|
||||||
printer.setAutoScroll(false);
|
printer.setAutoScroll(false);
|
||||||
printer.println(" Latin-1 Boxes & Blocks");
|
printer.println(" Latin-1 Boxes & Blocks");
|
||||||
printer.println(" U+0000..00FF U+2500..257F..259F");
|
printer.println(" U+0000..00FF U+2500..257F..259F");
|
||||||
printer.println(" ");
|
printer.println(" ");
|
||||||
printer.println(" 0123456789ABCDEF 0123456789ABCDEF");
|
printer.println(" 0123456789ABCDEF 0123456789ABCDEF");
|
||||||
for (int y = 0; y < 16; y++) {
|
for (int y = 0; y < 16; y++) {
|
||||||
printer.print(String.format(" %X", y));
|
printer.print(String.format(" %X", y));
|
||||||
int c = y * 0x010;
|
int c = y * 0x010;
|
||||||
for (int x = 0; x < 16; x++) {
|
for (int x = 0; x < 16; x++) {
|
||||||
printer.print(c >= 0x20 ? Character.toString((char) (c + x)) : " ");
|
printer.print(c >= 0x20 ? Character.toString((char) (c + x)) : " ");
|
||||||
}
|
}
|
||||||
printer.print(" ");
|
printer.print(" ");
|
||||||
|
|
||||||
if (y < 10) {
|
if (y < 10) {
|
||||||
printer.print(String.format("%X", y));
|
printer.print(String.format("%X", y));
|
||||||
c = 0x2500 + y * 0x010;
|
c = 0x2500 + y * 0x010;
|
||||||
for (int x = 0; x < 16; x++) {
|
for (int x = 0; x < 16; x++) {
|
||||||
printer.print(Character.toString((char) (c + x)));
|
printer.print(Character.toString((char) (c + x)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
printer.println();
|
printer.println();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void printVideoAttributes(Printer printer) {
|
public static void printVideoAttributes(Printer printer) {
|
||||||
printer.clear();
|
printer.clear();
|
||||||
printer.setAutoScroll(false);
|
printer.setAutoScroll(false);
|
||||||
printer.setVideoAttrs(0);
|
printer.setVideoAttrs(0);
|
||||||
printer.setInk(Color.BLACK);
|
printer.setInk(Color.BLACK);
|
||||||
printer.setStroke(Color.WHITE);
|
printer.setStroke(Color.WHITE);
|
||||||
|
|
||||||
String demoLine = "Lorem=ipsum-dolor$sit.ametÆØÅå*,|▞&Jumps Over\\the?fLat Dog{}()#\"!";
|
String demoLine = "Lorem=ipsum-dolor$sit.ametÆØÅå*,|▞&Jumps Over\\the?fLat Dog{}()#\"!";
|
||||||
printer.println("RIBU|" + demoLine);
|
printer.println("RIBU|" + demoLine);
|
||||||
for (int i = 1; i < 16; i++) {
|
for (int i = 1; i < 16; i++) {
|
||||||
printer.setVideoAttrs(i);
|
printer.setVideoAttrs(i);
|
||||||
String s = (i & 1) != 0 ? "X" : " ";
|
String s = (i & 1) != 0 ? "X" : " ";
|
||||||
s += (i & 2) != 0 ? "X" : " ";
|
s += (i & 2) != 0 ? "X" : " ";
|
||||||
s += (i & 4) != 0 ? "X" : " ";
|
s += (i & 4) != 0 ? "X" : " ";
|
||||||
s += (i & 8) != 0 ? "X" : " ";
|
s += (i & 8) != 0 ? "X" : " ";
|
||||||
printer.println(s + "|" + demoLine);
|
printer.println(s + "|" + demoLine);
|
||||||
}
|
}
|
||||||
printer.setVideoAttrs(0);
|
printer.setVideoAttrs(0);
|
||||||
printer.println();
|
printer.println();
|
||||||
printer.println("Lines: under, through, over");
|
printer.println("Lines: under, through, over");
|
||||||
printer.setVideoAttrs(TextFont.ATTR_UNDERLINE);
|
printer.setVideoAttrs(TextFont.ATTR_UNDERLINE);
|
||||||
printer.println(" " + demoLine + " ");
|
printer.println(" " + demoLine + " ");
|
||||||
printer.setVideoAttrs(TextFont.ATTR_LINE_THROUGH);
|
printer.setVideoAttrs(TextFont.ATTR_LINE_THROUGH);
|
||||||
printer.println(" " + demoLine + " ");
|
printer.println(" " + demoLine + " ");
|
||||||
printer.setVideoAttrs(TextFont.ATTR_OVERLINE);
|
printer.setVideoAttrs(TextFont.ATTR_OVERLINE);
|
||||||
printer.println(" " + demoLine + " ");
|
printer.println(" " + demoLine + " ");
|
||||||
printer.setVideoAttrs(0);
|
printer.setVideoAttrs(0);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void printZX(Printer printer) {
|
public static void printZX(Printer printer) {
|
||||||
printer.moveTo(1, 1);
|
printer.moveTo(1, 1);
|
||||||
printer.setAutoScroll(false);
|
printer.setAutoScroll(false);
|
||||||
printer.clear();
|
printer.clear();
|
||||||
printer.println(" ▄▄▄ ▄ ▄ ▄");
|
printer.println(" ▄▄▄ ▄ ▄ ▄");
|
||||||
printer.println(" █ █ █ █ █ █ █");
|
printer.println(" █ █ █ █ █ █ █");
|
||||||
printer.println(" █ █ █ █ █ █ █");
|
printer.println(" █ █ █ █ █ █ █");
|
||||||
printer.println(" █ █ █ █ █ █ █");
|
printer.println(" █ █ █ █ █ █ █");
|
||||||
printer.println(" ▀ ▀ ▀ ▀ ▀▀▀");
|
printer.println(" ▀ ▀ ▀ ▀ ▀▀▀");
|
||||||
printer.println(" ▄▄▄ ▄▄");
|
printer.println(" ▄▄▄ ▄▄");
|
||||||
printer.println(" █ █");
|
printer.println(" █ █");
|
||||||
printer.println(" █ █");
|
printer.println(" █ █");
|
||||||
printer.println(" █ █");
|
printer.println(" █ █");
|
||||||
printer.println(" ▀▀▀ ▀▀");
|
printer.println(" ▀▀▀ ▀▀");
|
||||||
printer.println(" ▄▄ ▄ ▄ ▄");
|
printer.println(" ▄▄ ▄ ▄ ▄");
|
||||||
printer.println(" █ █ █ █ █ █");
|
printer.println(" █ █ █ █ █ █");
|
||||||
printer.println(" █ █ █ █ █ █");
|
printer.println(" █ █ █ █ █ █");
|
||||||
printer.println(" █ █ █ █ █ █");
|
printer.println(" █ █ █ █ █ █");
|
||||||
printer.println(" ▀▀ ▀ ▀ ▀▀▀");
|
printer.println(" ▀▀ ▀ ▀ ▀▀▀");
|
||||||
printer.println("ON █████ █ █ ███ █");
|
printer.println("ON █████ █ █ ███ █");
|
||||||
printer.println("THE █ █ █ █ █ ██");
|
printer.println("THE █ █ █ █ █ ██");
|
||||||
printer.println("SINCLAIR █ ███ █");
|
printer.println("SINCLAIR █ ███ █");
|
||||||
printer.println(" █ █ █ █ █ WITH");
|
printer.println(" █ █ █ █ █ WITH");
|
||||||
printer.println(" █ █ █ █ █ █ 16K");
|
printer.println(" █ █ █ █ █ █ 16K");
|
||||||
printer.println(" █████ █ █ ███ ███ RAM");
|
printer.println(" █████ █ █ ███ ███ RAM");
|
||||||
printer.moveTo(1, 1);
|
printer.moveTo(1, 1);
|
||||||
printer.setAutoScroll(true);
|
printer.setAutoScroll(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,7 @@
|
|||||||
package inf101.v18.gfx.textmode;
|
package inf101.v18.gfx.textmode;
|
||||||
|
|
||||||
import inf101.v18.gfx.Screen;
|
import inf101.v18.gfx.Screen;
|
||||||
|
import inf101.v18.gfx.textmode.Printer;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.scene.input.KeyCode;
|
import javafx.scene.input.KeyCode;
|
||||||
import javafx.scene.input.KeyEvent;
|
import javafx.scene.input.KeyEvent;
|
||||||
@ -10,227 +11,226 @@ import javafx.stage.Stage;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class TextFontAdjuster extends Application {
|
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);
|
// new TextFont(FONT_NAME, 22.2, TextModes.CHAR_BOX_SIZE, 0.0, 0.0, 1.0, 1.0);
|
||||||
private static TextFontAdjuster demo;
|
private static TextFontAdjuster demo;
|
||||||
|
|
||||||
public static TextFontAdjuster getInstance() {
|
public static TextFontAdjuster getInstance() {
|
||||||
return demo;
|
return demo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
launch(args);
|
launch(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TextFont textFont = Printer.FONT_SYMBOLA;//
|
private TextFont textFont = Printer.FONT_SYMBOLA;//
|
||||||
// new TextFont("ZXSpectrum-7.otf", 22.00, TextMode.CHAR_BOX_SIZE, 3.1000,
|
// new TextFont("ZXSpectrum-7.otf", 22.00, TextMode.CHAR_BOX_SIZE, 3.1000,
|
||||||
// -3.8000, 1.0000, 1.0000, true);
|
// -3.8000, 1.0000, 1.0000, true);
|
||||||
private Screen screen;
|
private Screen screen;
|
||||||
|
|
||||||
private boolean paused;
|
private boolean paused;
|
||||||
private Printer printer;
|
private Printer printer;
|
||||||
|
|
||||||
private boolean grid = true;
|
private boolean grid = true;
|
||||||
|
|
||||||
private final double adjustAmount = 0.1;
|
private double adjustAmount = 0.1;
|
||||||
|
|
||||||
private double adjustX(KeyCode code) {
|
private double adjustX(KeyCode code) {
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case LEFT:
|
case LEFT:
|
||||||
return -1 * adjustAmount;
|
return -1 * adjustAmount;
|
||||||
case RIGHT:
|
case RIGHT:
|
||||||
return 1 * adjustAmount;
|
return 1 * adjustAmount;
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private double adjustY(KeyCode code) {
|
private double adjustY(KeyCode code) {
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case UP:
|
case UP:
|
||||||
return 1 * adjustAmount;
|
return 1 * adjustAmount;
|
||||||
case DOWN:
|
case DOWN:
|
||||||
return -1 * adjustAmount;
|
return -1 * adjustAmount;
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawBackgroundGrid() {
|
private void drawBackgroundGrid() {
|
||||||
if (grid) {
|
if (grid) {
|
||||||
printer.drawCharCells();
|
printer.drawCharCells();
|
||||||
/*
|
/*
|
||||||
* painter.turnTo(0); for (int y = 0; y < printer.getPageHeight(); y++) {
|
* painter.turnTo(0); for (int y = 0; y < printer.getPageHeight(); y++) {
|
||||||
* painter.jumpTo(0, y * printer.getCharHeight()); for (int x = 0; x <
|
* painter.jumpTo(0, y * printer.getCharHeight()); for (int x = 0; x <
|
||||||
* printer.getLineWidth(); x++) { painter.setInk( (x + y) % 2 == 0 ?
|
* printer.getLineWidth(); x++) { painter.setInk( (x + y) % 2 == 0 ?
|
||||||
* Color.CORNFLOWERBLUE : Color.CORNFLOWERBLUE.brighter().brighter());
|
* Color.CORNFLOWERBLUE : Color.CORNFLOWERBLUE.brighter().brighter());
|
||||||
* painter.fillRectangle(printer.getCharWidth(), printer.getCharHeight());
|
* painter.fillRectangle(printer.getCharWidth(), printer.getCharHeight());
|
||||||
* painter.jump(printer.getCharWidth()); } }
|
* painter.jump(printer.getCharWidth()); } }
|
||||||
*/
|
*/
|
||||||
} else {
|
} else {
|
||||||
screen.clearBackground();
|
screen.clearBackground();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void printHelp() {
|
private void printHelp() {
|
||||||
printer.moveTo(1, 1);
|
printer.moveTo(1, 1);
|
||||||
printer.setAutoScroll(false);
|
printer.setAutoScroll(false);
|
||||||
printer.println(" " + Printer.center("TextFontAdjuster", 36) + " ");
|
printer.println(" " + Printer.center("TextFontAdjuster", 36) + " ");
|
||||||
printer.println(" ");
|
printer.println(" ");
|
||||||
printer.println(" ");
|
printer.println(" ");
|
||||||
printer.println(" ");
|
printer.println(" ");
|
||||||
printer.println("________________________________________");
|
printer.println("________________________________________");
|
||||||
printer.println("Adjust letter parameters: ");
|
printer.println("Adjust letter parameters: ");
|
||||||
printer.println(" Font size: CTRL +, CTRL - ");
|
printer.println(" Font size: CTRL +, CTRL - ");
|
||||||
printer.println(" Position: LEFT, RIGHT, UP, DOWN ");
|
printer.println(" Position: LEFT, RIGHT, UP, DOWN ");
|
||||||
printer.println(" Scaling: CTRL-(LEFT, RIGHT, UP, DOWN)");
|
printer.println(" Scaling: CTRL-(LEFT, RIGHT, UP, DOWN)");
|
||||||
printer.println("Commands / options (with CTRL key): ");
|
printer.println("Commands / options (with CTRL key): ");
|
||||||
printer.println(" Hires (R) – Grid (G) – Fullscreen (F) ");
|
printer.println(" Hires (R) – Grid (G) – Fullscreen (F) ");
|
||||||
printer.println(" Help (H) – Quit (Q) ");
|
printer.println(" Help (H) – Quit (Q) ");
|
||||||
printer.println("Write text with any other key. ");
|
printer.println("Write text with any other key. ");
|
||||||
printer.println("_-*-_-*-_-*-_-*-_-*-_-*-_-*-_-*-_-*-_-*-");
|
printer.println("_-*-_-*-_-*-_-*-_-*-_-*-_-*-_-*-_-*-_-*-");
|
||||||
printer.println(" ");
|
printer.println(" ");
|
||||||
printer.println("Sample text: ");
|
printer.println("Sample text: ");
|
||||||
printer.println("the quick brown fox jumps over the lazy ");
|
printer.println("the quick brown fox jumps over the lazy ");
|
||||||
printer.println(" dog, THE QUICK BROWN FOX JUMPS OVER THE");
|
printer.println(" dog, THE QUICK BROWN FOX JUMPS OVER THE");
|
||||||
printer.println("LAZY DOG den vågale røde reven værer den");
|
printer.println("LAZY DOG den vågale røde reven værer den");
|
||||||
printer.println("sinte hunden DEN VÅGALE RØDE REVEN VÆRER");
|
printer.println("sinte hunden DEN VÅGALE RØDE REVEN VÆRER");
|
||||||
printer.println("DEN SINTE HUNDEN !\"#%&/()?,._-@£${[]}?|^");
|
printer.println("DEN SINTE HUNDEN !\"#%&/()?,._-@£${[]}?|^");
|
||||||
|
|
||||||
// printer.print(" ");
|
// printer.print(" ");
|
||||||
printer.moveTo(1, 15);
|
printer.moveTo(1, 15);
|
||||||
printer.setAutoScroll(true);
|
printer.setAutoScroll(true);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void printInfo() {
|
private void printInfo() {
|
||||||
printer.moveTo(1, 3);
|
printer.moveTo(1, 3);
|
||||||
printer.println(String.format("Font: %s at %1.1fpt ", textFont.getFont().getName(),
|
printer.println(String.format("Font: %s at %1.1fpt ", textFont.getFont().getName(),
|
||||||
textFont.getFont().getSize()));
|
textFont.getFont().getSize()));
|
||||||
printer.println(String.format(" xTr=%-1.1f yTr=%-1.1f xSc=%-1.1f ySc=%-1.1f ", textFont.getxTranslate(),
|
printer.println(String.format(" xTr=%-1.1f yTr=%-1.1f xSc=%-1.1f ySc=%-1.1f ", textFont.getxTranslate(),
|
||||||
textFont.getyTranslate(), textFont.getxScale(), textFont.getyScale()));
|
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",
|
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.getFont().getName(), textFont.getSize(), textFont.getxTranslate(), textFont.getyTranslate(),
|
||||||
textFont.getxScale(), textFont.getyScale());
|
textFont.getxScale(), textFont.getyScale());
|
||||||
|
|
||||||
printer.moveTo(1, 15);
|
printer.moveTo(1, 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setup() {
|
private void setup() {
|
||||||
drawBackgroundGrid();
|
drawBackgroundGrid();
|
||||||
printHelp();
|
printHelp();
|
||||||
printInfo();
|
printInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage stage) {
|
public void start(Stage stage) {
|
||||||
demo = this;
|
demo = this;
|
||||||
|
|
||||||
screen = Screen.startPaintScene(stage);
|
screen = Screen.startPaintScene(stage);
|
||||||
|
|
||||||
printer = screen.createPrinter();
|
printer = screen.createPrinter();
|
||||||
printer.setInk(Color.BLACK);
|
printer.setInk(Color.BLACK);
|
||||||
printer.setFont(textFont);
|
printer.setFont(textFont);
|
||||||
screen.setKeyOverride((KeyEvent event) -> {
|
screen.setKeyOverride((KeyEvent event) -> {
|
||||||
KeyCode code = event.getCode();
|
KeyCode code = event.getCode();
|
||||||
// System.out.println(event);
|
// System.out.println(event);
|
||||||
if (event.isControlDown() || event.isShortcutDown()) {
|
if (event.isControlDown() || event.isShortcutDown()) {
|
||||||
if (code == KeyCode.Q) {
|
if (code == KeyCode.Q) {
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
} else if (code == KeyCode.P) {
|
} else if (code == KeyCode.P) {
|
||||||
paused = !paused;
|
paused = !paused;
|
||||||
return true;
|
return true;
|
||||||
} else if (code == KeyCode.R) {
|
} else if (code == KeyCode.R) {
|
||||||
printer.cycleMode(true);
|
printer.cycleMode(true);
|
||||||
drawBackgroundGrid();
|
drawBackgroundGrid();
|
||||||
return true;
|
return true;
|
||||||
} else if (code == KeyCode.S) {
|
} else if (code == KeyCode.S) {
|
||||||
if (event.isAltDown()) {
|
if (event.isAltDown())
|
||||||
screen.fitScaling();
|
screen.fitScaling();
|
||||||
} else {
|
else
|
||||||
screen.zoomCycle();
|
screen.zoomCycle();
|
||||||
}
|
drawBackgroundGrid();
|
||||||
drawBackgroundGrid();
|
return true;
|
||||||
return true;
|
} else if (code == KeyCode.A) {
|
||||||
} else if (code == KeyCode.A) {
|
screen.cycleAspect();
|
||||||
screen.cycleAspect();
|
return true;
|
||||||
return true;
|
} else if (code == KeyCode.G) {
|
||||||
} else if (code == KeyCode.G) {
|
grid = !grid;
|
||||||
grid = !grid;
|
drawBackgroundGrid();
|
||||||
drawBackgroundGrid();
|
return true;
|
||||||
return true;
|
} else if (code == KeyCode.H) {
|
||||||
} else if (code == KeyCode.H) {
|
printHelp();
|
||||||
printHelp();
|
printInfo();
|
||||||
printInfo();
|
return true;
|
||||||
return true;
|
} else if (code == KeyCode.F) {
|
||||||
} else if (code == KeyCode.F) {
|
stage.setFullScreen(!stage.isFullScreen());
|
||||||
stage.setFullScreen(!stage.isFullScreen());
|
return true;
|
||||||
return true;
|
} else if (code == KeyCode.M) {
|
||||||
} else if (code == KeyCode.M) {
|
printer.print("\r");
|
||||||
printer.print("\r");
|
return true;
|
||||||
return true;
|
} else if (code == KeyCode.L) {
|
||||||
} else if (code == KeyCode.L) {
|
printer.redrawTextPage();
|
||||||
printer.redrawTextPage();
|
return true;
|
||||||
return true;
|
} else if (code == KeyCode.DIGIT1) {
|
||||||
} else if (code == KeyCode.DIGIT1) {
|
DemoPages.printBoxDrawing(printer);
|
||||||
DemoPages.printBoxDrawing(printer);
|
return true;
|
||||||
return true;
|
} else if (code == KeyCode.DIGIT2) {
|
||||||
} else if (code == KeyCode.DIGIT2) {
|
DemoPages.printZX(printer);
|
||||||
DemoPages.printZX(printer);
|
return true;
|
||||||
return true;
|
} else if (code == KeyCode.DIGIT3) {
|
||||||
} else if (code == KeyCode.DIGIT3) {
|
DemoPages.printBlockPlotting(printer);
|
||||||
DemoPages.printBlockPlotting(printer);
|
return true;
|
||||||
return true;
|
} else if (code == KeyCode.DIGIT4) {
|
||||||
} else if (code == KeyCode.DIGIT4) {
|
DemoPages.printVideoAttributes(printer);
|
||||||
DemoPages.printVideoAttributes(printer);
|
return true;
|
||||||
return true;
|
} else if (code == KeyCode.DIGIT5) {
|
||||||
} else if (code == KeyCode.DIGIT5) {
|
DemoPages.printAnsiArt(printer);
|
||||||
DemoPages.printAnsiArt(printer);
|
return true;
|
||||||
return true;
|
} else if (code == KeyCode.PLUS) {
|
||||||
} else if (code == KeyCode.PLUS) {
|
textFont = textFont.adjust(adjustAmount, 0.0, 0.0, 0.0, 0.0);
|
||||||
textFont = textFont.adjust(adjustAmount, 0.0, 0.0, 0.0, 0.0);
|
printer.setFont(textFont);
|
||||||
printer.setFont(textFont);
|
printer.redrawTextPage();
|
||||||
printer.redrawTextPage();
|
printInfo();
|
||||||
printInfo();
|
return true;
|
||||||
return true;
|
} else if (code == KeyCode.MINUS) {
|
||||||
} else if (code == KeyCode.MINUS) {
|
textFont = textFont.adjust(-adjustAmount, 0.0, 0.0, 0.0, 0.0);
|
||||||
textFont = textFont.adjust(-adjustAmount, 0.0, 0.0, 0.0, 0.0);
|
printer.setFont(textFont);
|
||||||
printer.setFont(textFont);
|
printer.redrawTextPage();
|
||||||
printer.redrawTextPage();
|
printInfo();
|
||||||
printInfo();
|
return true;
|
||||||
return true;
|
} else if (code == KeyCode.LEFT || code == KeyCode.RIGHT || code == KeyCode.UP
|
||||||
} else if (code == KeyCode.LEFT || code == KeyCode.RIGHT || code == KeyCode.UP
|
|| code == KeyCode.DOWN) {
|
||||||
|| code == KeyCode.DOWN) {
|
textFont = textFont.adjust(0.0, 0.0, 0.0, adjustX(code), adjustY(code));
|
||||||
textFont = textFont.adjust(0.0, 0.0, 0.0, adjustX(code), adjustY(code));
|
printer.setFont(textFont);
|
||||||
printer.setFont(textFont);
|
printer.redrawTextPage();
|
||||||
printer.redrawTextPage();
|
printInfo();
|
||||||
printInfo();
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
} else if (code == KeyCode.LEFT || code == KeyCode.RIGHT || code == KeyCode.UP || code == KeyCode.DOWN) {
|
||||||
} else if (code == KeyCode.LEFT || code == KeyCode.RIGHT || code == KeyCode.UP || code == KeyCode.DOWN) {
|
textFont = textFont.adjust(0.0, adjustX(code), adjustY(code), 0.0, 0.0);
|
||||||
textFont = textFont.adjust(0.0, adjustX(code), adjustY(code), 0.0, 0.0);
|
printer.setFont(textFont);
|
||||||
printer.setFont(textFont);
|
printer.redrawTextPage();
|
||||||
printer.redrawTextPage();
|
printInfo();
|
||||||
printInfo();
|
return true;
|
||||||
return true;
|
} else if (code == KeyCode.ENTER) {
|
||||||
} else if (code == KeyCode.ENTER) {
|
|
||||||
|
|
||||||
printer.print("\n");
|
printer.print("\n");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
screen.setKeyTypedHandler((KeyEvent event) -> {
|
screen.setKeyTypedHandler((KeyEvent event) -> {
|
||||||
if (!Objects.equals(event.getCharacter(), KeyEvent.CHAR_UNDEFINED)) {
|
if (!Objects.equals(event.getCharacter(), KeyEvent.CHAR_UNDEFINED)) {
|
||||||
printer.print(event.getCharacter());
|
printer.print(event.getCharacter());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
setup();
|
setup();
|
||||||
|
|
||||||
stage.show();
|
stage.show();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,169 +3,156 @@ package inf101.v18.gfx.textmode;
|
|||||||
import inf101.v18.gfx.Screen;
|
import inf101.v18.gfx.Screen;
|
||||||
|
|
||||||
public enum TextMode {
|
public enum TextMode {
|
||||||
/**
|
/** Low resolution, wide screen (20:11, fits 16:9) text mode 40x22 */
|
||||||
* Low resolution, wide screen (20:11, fits 16:9) text mode 40x22
|
MODE_40X22(Constants.H40, Constants.V22, Screen.ASPECT_WIDE),
|
||||||
*/
|
/** Low resolution, 16:10 aspect text mode 40x25 */
|
||||||
MODE_40X22(Constants.H40, Constants.V22, Screen.ASPECT_WIDE),
|
MODE_40X25(Constants.H40, Constants.V25, Screen.ASPECT_MEDIUM),
|
||||||
/**
|
/** Low resolution, 4:3 aspect text mode 40x30 */
|
||||||
* Low resolution, 16:10 aspect text mode 40x25
|
MODE_40X30(Constants.H40, Constants.V30, Screen.ASPECT_CLASSIC),
|
||||||
*/
|
/** High resolution, wide screen (20:11, fits 16:9) text mode 80x22 */
|
||||||
MODE_40X25(Constants.H40, Constants.V25, Screen.ASPECT_MEDIUM),
|
MODE_80X22(Constants.H80, Constants.V22, Screen.ASPECT_WIDE),
|
||||||
/**
|
/** High resolution, 16:10 aspect text mode 80x25 */
|
||||||
* Low resolution, 4:3 aspect text mode 40x30
|
MODE_80X25(Constants.H80, Constants.V25, Screen.ASPECT_MEDIUM),
|
||||||
*/
|
/** High resolution, 4:3 aspect text mode 80x30 */
|
||||||
MODE_40X30(Constants.H40, Constants.V30, Screen.ASPECT_CLASSIC),
|
MODE_80X30(Constants.H80, Constants.V30, Screen.ASPECT_CLASSIC);
|
||||||
/**
|
|
||||||
* High resolution, wide screen (20:11, fits 16:9) text mode 80x22
|
|
||||||
*/
|
|
||||||
MODE_80X22(Constants.H80, Constants.V22, Screen.ASPECT_WIDE),
|
|
||||||
/**
|
|
||||||
* High resolution, 16:10 aspect text mode 80x25
|
|
||||||
*/
|
|
||||||
MODE_80X25(Constants.H80, Constants.V25, Screen.ASPECT_MEDIUM),
|
|
||||||
/**
|
|
||||||
* High resolution, 4:3 aspect text mode 80x30
|
|
||||||
*/
|
|
||||||
MODE_80X30(Constants.H80, Constants.V30, Screen.ASPECT_CLASSIC);
|
|
||||||
|
|
||||||
protected static class Constants {
|
protected static class Constants {
|
||||||
protected static final int H40 = 0, H80 = 1;
|
protected static final int H40 = 0, H80 = 1;
|
||||||
protected static final int[] HREZ = {40, 80};
|
protected static final int[] HREZ = { 40, 80 };
|
||||||
protected static final int V22 = 0, V25 = 1, V30 = 2;
|
protected static final int V22 = 0, V25 = 1, V30 = 2;
|
||||||
protected static final int[] VREZ = {22, 25, 30};
|
protected static final int[] VREZ = { 22, 25, 30 };
|
||||||
private static TextMode[] MODES = null;
|
private static TextMode[] MODES = null;
|
||||||
|
|
||||||
// work around initialization order for statics and enums
|
// work around initialization order for statics and enums
|
||||||
protected static TextMode getMode(int i) {
|
protected static TextMode getMode(int i) {
|
||||||
if (MODES == null) {
|
if (MODES == null)
|
||||||
MODES = new TextMode[]{MODE_40X22, MODE_40X25, MODE_40X30, MODE_80X22, MODE_80X25, MODE_80X30};
|
MODES = new TextMode[] { MODE_40X22, MODE_40X25, MODE_40X30, MODE_80X22, MODE_80X25, MODE_80X30 };
|
||||||
}
|
return MODES[(i + MODES.length) % MODES.length];
|
||||||
return MODES[(i + MODES.length) % MODES.length];
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Size of the square-shaped "box" bounds of character cells.
|
* Size of the square-shaped "box" bounds of character cells.
|
||||||
* <p>
|
*
|
||||||
* For "high" resolution, characters will be squeezed horizontally to fit half
|
* For "high" resolution, characters will be squeezed horizontally to fit half
|
||||||
* the width.
|
* the width.
|
||||||
*/
|
*/
|
||||||
public static final double CHAR_BOX_SIZE = 32;
|
public static final double CHAR_BOX_SIZE = 32;
|
||||||
/**
|
/**
|
||||||
* Maximum length of a line in any resolution mode
|
* Maximum length of a line in any resolution mode
|
||||||
*/
|
*/
|
||||||
public static final int LINE_WIDTH_MAX = Constants.HREZ[Constants.HREZ.length - 1];
|
public static final int LINE_WIDTH_MAX = Constants.HREZ[Constants.HREZ.length - 1];
|
||||||
/**
|
/**
|
||||||
* Maximum height of a page in any resolution mode
|
* Maximum height of a page in any resolution mode
|
||||||
*/
|
*/
|
||||||
public static final int PAGE_HEIGHT_MAX = Constants.VREZ[Constants.VREZ.length - 1];
|
public static final int PAGE_HEIGHT_MAX = Constants.VREZ[Constants.VREZ.length - 1];
|
||||||
private int aspect;
|
private int aspect;
|
||||||
private int w;
|
private int w;
|
||||||
|
|
||||||
private int h;
|
private int h;
|
||||||
|
|
||||||
private int hIndex;
|
private int hIndex;
|
||||||
|
|
||||||
private int vIndex;
|
private int vIndex;
|
||||||
|
|
||||||
TextMode(int w, int h, int aspect) {
|
TextMode(int w, int h, int aspect) {
|
||||||
this.hIndex = w;
|
this.hIndex = w;
|
||||||
this.vIndex = h;
|
this.vIndex = h;
|
||||||
this.aspect = aspect;
|
this.aspect = aspect;
|
||||||
this.w = Constants.HREZ[w];
|
this.w = Constants.HREZ[w];
|
||||||
this.h = Constants.VREZ[h];
|
this.h = Constants.VREZ[h];
|
||||||
}
|
}
|
||||||
|
|
||||||
private TextMode findMode(int hIndex, int vIndex) {
|
private TextMode findMode(int hIndex, int vIndex) {
|
||||||
hIndex = (hIndex + Constants.HREZ.length) % Constants.HREZ.length;
|
hIndex = (hIndex + Constants.HREZ.length) % Constants.HREZ.length;
|
||||||
vIndex = (vIndex + Constants.VREZ.length) % Constants.VREZ.length;
|
vIndex = (vIndex + Constants.VREZ.length) % Constants.VREZ.length;
|
||||||
return Constants.getMode(vIndex + hIndex * Constants.VREZ.length);
|
return Constants.getMode(vIndex + hIndex * Constants.VREZ.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get aspect ration descriptor for use with {@link Screen#setAspect()}
|
* Get aspect ration descriptor for use with {@link Screen#setAspect()}
|
||||||
*
|
*
|
||||||
* @return One of {@link Screen#ASPECT_WIDE}, {@link Screen#ASPECT_MEDIUM} or
|
* @return One of {@link Screen#ASPECT_WIDE}, {@link Screen#ASPECT_MEDIUM} or
|
||||||
* {@link Screen#ASPECT_CLASSIC}
|
* {@link Screen#ASPECT_CLASSIC}
|
||||||
*/
|
*/
|
||||||
public int getAspect() {
|
public int getAspect() {
|
||||||
return aspect;
|
return aspect;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getCharBoxSize() {
|
public double getCharBoxSize() {
|
||||||
return CHAR_BOX_SIZE;
|
return CHAR_BOX_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getCharHeight() {
|
public double getCharHeight() {
|
||||||
return CHAR_BOX_SIZE;
|
return CHAR_BOX_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getCharWidth() {
|
public double getCharWidth() {
|
||||||
return w == 80 ? CHAR_BOX_SIZE / 2 : CHAR_BOX_SIZE;
|
return w == 80 ? CHAR_BOX_SIZE / 2 : CHAR_BOX_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getLineWidth() {
|
public int getLineWidth() {
|
||||||
return w;
|
return w;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getPageHeight() {
|
public int getPageHeight() {
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cycle through horizontal modes
|
* Cycle through horizontal modes
|
||||||
*
|
*
|
||||||
* @return Next available horizontal mode (vertical resolution unchanged)
|
* @return Next available horizontal mode (vertical resolution unchanged)
|
||||||
*/
|
*/
|
||||||
public TextMode nextHorizMode() {
|
public TextMode nextHorizMode() {
|
||||||
return findMode((hIndex + 1) % Constants.HREZ.length, vIndex);
|
return findMode((hIndex + 1) % Constants.HREZ.length, vIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cycle through modes
|
* Cycle through modes
|
||||||
*
|
*
|
||||||
* @return Next available mode
|
* @return Next available mode
|
||||||
*/
|
*/
|
||||||
public TextMode nextMode() {
|
public TextMode nextMode() {
|
||||||
int m = vIndex + hIndex * Constants.VREZ.length;
|
int m = vIndex + hIndex * Constants.VREZ.length;
|
||||||
return Constants.getMode(m + 1);
|
return Constants.getMode(m + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cycle through vertical modes
|
* Cycle through vertical modes
|
||||||
*
|
*
|
||||||
* @return Next available vertical mode (horizontal resolution unchanged)
|
* @return Next available vertical mode (horizontal resolution unchanged)
|
||||||
*/
|
*/
|
||||||
public TextMode nextVertMode() {
|
public TextMode nextVertMode() {
|
||||||
return findMode((hIndex + 1) % Constants.HREZ.length, vIndex);
|
return findMode((hIndex + 1) % Constants.HREZ.length, vIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cycle through horizontal modes
|
* Cycle through horizontal modes
|
||||||
*
|
*
|
||||||
* @return Previous available horizontal mode (vertical resolution unchanged)
|
* @return Previous available horizontal mode (vertical resolution unchanged)
|
||||||
*/
|
*/
|
||||||
public TextMode prevHorizMode() {
|
public TextMode prevHorizMode() {
|
||||||
return findMode((hIndex - 1) % Constants.HREZ.length, vIndex);
|
return findMode((hIndex - 1) % Constants.HREZ.length, vIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cycle through modes
|
* Cycle through modes
|
||||||
*
|
*
|
||||||
* @return Previous available mode
|
* @return Previous available mode
|
||||||
*/
|
*/
|
||||||
public TextMode prevMode() {
|
public TextMode prevMode() {
|
||||||
int m = vIndex + hIndex * Constants.VREZ.length;
|
int m = vIndex + hIndex * Constants.VREZ.length;
|
||||||
return Constants.getMode(m);
|
return Constants.getMode(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cycle through vertical modes
|
* Cycle through vertical modes
|
||||||
*
|
*
|
||||||
* @return Previous available vertical mode (horizontal resolution unchanged)
|
* @return Previous available vertical mode (horizontal resolution unchanged)
|
||||||
*/
|
*/
|
||||||
public TextMode prevVertMode() {
|
public TextMode prevVertMode() {
|
||||||
return findMode((hIndex - 1) % Constants.HREZ.length, vIndex);
|
return findMode((hIndex - 1) % Constants.HREZ.length, vIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,73 +4,66 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public enum GridDirection {
|
public enum GridDirection {
|
||||||
|
EAST(0, 1, 0, 4), NORTH(90, 0, -1, 1), WEST(180, -1, 0, 8), SOUTH(270, 0, 1, 2), //
|
||||||
|
NORTHEAST(45, 1, -1, 5), NORTHWEST(135, -1, -1, 9), SOUTHWEST(225, -1, 1, 10), SOUTHEAST(315, 1, 1, 6), //
|
||||||
|
CENTER(0, 0, 0, 0);
|
||||||
|
|
||||||
EAST(0, 1, 0, 4),
|
/**
|
||||||
NORTH(90, 0, -1, 1),
|
* The four cardinal directions: {@link #NORTH}, {@link #SOUTH}, {@link #EAST},
|
||||||
WEST(180, -1, 0, 8),
|
* {@link #WEST}.
|
||||||
SOUTH(270, 0, 1, 2),
|
*/
|
||||||
NORTHEAST(45, 1, -1, 5),
|
public static final List<GridDirection> FOUR_DIRECTIONS = Arrays.asList(EAST, NORTH, WEST, SOUTH);
|
||||||
NORTHWEST(135, -1, -1, 9),
|
/**
|
||||||
SOUTHWEST(225, -1, 1, 10),
|
* The eight cardinal and intercardinal directions: {@link #NORTH},
|
||||||
SOUTHEAST(315, 1, 1, 6),
|
* {@link #SOUTH}, {@link #EAST}, {@link #WEST}, {@link #NORTHWEST},
|
||||||
CENTER(0, 0, 0, 0);
|
* {@link #NORTHEAST}, {@link #SOUTHWEST}, {@link #SOUTHEAST}.
|
||||||
|
*/
|
||||||
|
public static final List<GridDirection> EIGHT_DIRECTIONS = Arrays.asList(EAST, NORTHEAST, NORTH, NORTHWEST, WEST,
|
||||||
|
SOUTHWEST, SOUTH, SOUTHEAST);
|
||||||
|
/**
|
||||||
|
* The eight cardinal and intercardinal directions ({@link #EIGHT_DIRECTIONS}),
|
||||||
|
* plus {@link #CENTER}.
|
||||||
|
*/
|
||||||
|
public static final List<GridDirection> NINE_DIRECTIONS = Arrays.asList(EAST, NORTHEAST, NORTH, NORTHWEST, WEST,
|
||||||
|
SOUTHWEST, SOUTH, SOUTHEAST, CENTER);
|
||||||
|
|
||||||
/**
|
private final double degrees;
|
||||||
* The four cardinal directions: {@link #NORTH}, {@link #SOUTH}, {@link #EAST},
|
private final int dx;
|
||||||
* {@link #WEST}.
|
private final int dy;
|
||||||
*/
|
private final int mask;
|
||||||
public static final List<GridDirection> FOUR_DIRECTIONS = Arrays.asList(EAST, NORTH, WEST, SOUTH);
|
|
||||||
/**
|
|
||||||
* The eight cardinal and intercardinal directions: {@link #NORTH},
|
|
||||||
* {@link #SOUTH}, {@link #EAST}, {@link #WEST}, {@link #NORTHWEST},
|
|
||||||
* {@link #NORTHEAST}, {@link #SOUTHWEST}, {@link #SOUTHEAST}.
|
|
||||||
*/
|
|
||||||
public static final List<GridDirection> EIGHT_DIRECTIONS = Arrays.asList(EAST, NORTHEAST, NORTH, NORTHWEST, WEST,
|
|
||||||
SOUTHWEST, SOUTH, SOUTHEAST);
|
|
||||||
/**
|
|
||||||
* The eight cardinal and intercardinal directions ({@link #EIGHT_DIRECTIONS}),
|
|
||||||
* plus {@link #CENTER}.
|
|
||||||
*/
|
|
||||||
public static final List<GridDirection> NINE_DIRECTIONS = Arrays.asList(EAST, NORTHEAST, NORTH, NORTHWEST, WEST,
|
|
||||||
SOUTHWEST, SOUTH, SOUTHEAST, CENTER);
|
|
||||||
|
|
||||||
private final double degrees;
|
GridDirection(double degrees, int dx, int dy, int mask) {
|
||||||
private final int dx;
|
this.degrees = degrees;
|
||||||
private final int dy;
|
this.dx = dx;
|
||||||
private final int mask;
|
this.dy = dy;
|
||||||
|
this.mask = mask;
|
||||||
|
}
|
||||||
|
|
||||||
GridDirection(double degrees, int dx, int dy, int mask) {
|
/**
|
||||||
this.degrees = degrees;
|
* @return The angle of this direction, with 0° facing due {@link #EAST} and 90°
|
||||||
this.dx = dx;
|
* being {@link #NORTH}.
|
||||||
this.dy = dy;
|
*/
|
||||||
this.mask = mask;
|
public double getDegrees() {
|
||||||
}
|
return degrees;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The angle of this direction, with 0° facing due {@link #EAST} and 90°
|
* @return The change to your X-coordinate if you were to move one step in this
|
||||||
* being {@link #NORTH}.
|
* direction
|
||||||
*/
|
*/
|
||||||
public double getDegrees() {
|
public int getDx() {
|
||||||
return degrees;
|
return dx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The change to your X-coordinate if you were to move one step in this
|
* @return The change to your Y-coordinate if you were to move one step in this
|
||||||
* direction
|
* direction
|
||||||
*/
|
*/
|
||||||
public int getDx() {
|
public int getDy() {
|
||||||
return dx;
|
return dy;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public int getMask() {
|
||||||
* @return The change to your Y-coordinate if you were to move one step in this
|
return mask;
|
||||||
* direction
|
}
|
||||||
*/
|
|
||||||
public int getDy() {
|
|
||||||
return dy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMask() {
|
|
||||||
return mask;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import java.util.stream.Stream;
|
|||||||
* locations within the area.
|
* locations within the area.
|
||||||
* <p>
|
* <p>
|
||||||
* See {@link #location(int, int)} to covert (x,y)-coordinates to
|
* See {@link #location(int, int)} to covert (x,y)-coordinates to
|
||||||
* {@link Location}s.
|
* {@link ILocation}s.
|
||||||
* <p>
|
* <p>
|
||||||
* An {@link IArea} does not <em>store</em> anything, it just defines valid
|
* An {@link IArea} does not <em>store</em> anything, it just defines valid
|
||||||
* storage locations (e.g., for an {@link IGrid}), and the relationships between
|
* storage locations (e.g., for an {@link IGrid}), and the relationships between
|
||||||
@ -18,142 +18,144 @@ import java.util.stream.Stream;
|
|||||||
*
|
*
|
||||||
* @author Anya Helene Bagge, UiB
|
* @author Anya Helene Bagge, UiB
|
||||||
*/
|
*/
|
||||||
public interface IArea extends Iterable<Location> {
|
public interface IArea extends Iterable<ILocation> {
|
||||||
/**
|
/**
|
||||||
* Check if a (x,y) is inside the area.
|
* Check if a (x,y) is inside the area.
|
||||||
*
|
*
|
||||||
* @param x X-coordinate
|
* @param x
|
||||||
* @param y Y-coordinate
|
* X-coordinate
|
||||||
* @return True if the (x,y) position lies within the area
|
* @param y
|
||||||
*/
|
* Y-coordinate
|
||||||
boolean contains(int x, int y);
|
* @return True if the (x,y) position lies within the area
|
||||||
|
*/
|
||||||
|
boolean contains(int x, int y);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a position is inside the area.
|
* Check if a position is inside the area.
|
||||||
*
|
*
|
||||||
* @param pos A position
|
* @param pos
|
||||||
* @return True if the position lies within the area
|
* A position
|
||||||
*/
|
* @return True if the position lies within the area
|
||||||
boolean contains(IPosition pos);
|
*/
|
||||||
|
boolean contains(IPosition pos);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean equals(Object other);
|
boolean equals(Object other);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a 1D coordinate to a location
|
* Convert a 1D coordinate to a location
|
||||||
* <p>
|
* <p>
|
||||||
* Returns a location <code>l = fromIndex(i)</code> such that
|
* Returns a location <code>l = fromIndex(i)</code> such that
|
||||||
* <code>toIndex(l.getX(), l.getY()) == i</code>.
|
* <code>toIndex(l.getX(), l.getY()) == i</code>.
|
||||||
*
|
*
|
||||||
* @param i
|
* @param i
|
||||||
* @return A location
|
* @return A location
|
||||||
*/
|
*/
|
||||||
Location fromIndex(int i);
|
ILocation fromIndex(int i);
|
||||||
|
|
||||||
/**
|
/** @return Height of the area */
|
||||||
* @return Height of the area
|
int getHeight();
|
||||||
*/
|
|
||||||
int getHeight();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the number of legal positions in the area
|
* Returns the number of legal positions in the area
|
||||||
*
|
*
|
||||||
* @return Same as getWidth()*getHeight()
|
* @return Same as getWidth()*getHeight()
|
||||||
*/
|
*/
|
||||||
int getSize();
|
int getSize();
|
||||||
|
|
||||||
/**
|
/** @return Width of the area */
|
||||||
* @return Width of the area
|
int getWidth();
|
||||||
*/
|
|
||||||
int getWidth();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
int hashCode();
|
int hashCode();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a location object corresponding to (x,y)
|
* Get a location object corresponding to (x,y)
|
||||||
*
|
*
|
||||||
* @param x X-coordinate
|
* @param x
|
||||||
* @param y Y-coordinate
|
* X-coordinate
|
||||||
* @return The location object associated with (x,y)
|
* @param y
|
||||||
* @throws IndexOutOfBoundsException if {@link #contains(int, int)} returns false for (x,y)
|
* Y-coordinate
|
||||||
*/
|
* @return The location object associated with (x,y)
|
||||||
Location location(int x, int y);
|
* @throws IndexOutOfBoundsException
|
||||||
|
* if {@link #contains(int, int)} returns false for (x,y)
|
||||||
|
*/
|
||||||
|
ILocation location(int x, int y);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all locations in area
|
* Get all locations in area
|
||||||
* <p>
|
* <p>
|
||||||
* Since IArea is @{@link Iterable}, you can also use directly in a for-loop to
|
* Since IArea is @{@link Iterable}, you can also use directly in a for-loop to
|
||||||
* iterate over the locations.
|
* iterate over the locations.
|
||||||
* <p>
|
* <p>
|
||||||
* All locations in the list are guaranteed to be valid according to
|
* All locations in the list are guaranteed to be valid according to
|
||||||
* {@link #isValid(Location)}. The returned list cannot be modified.
|
* {@link #isValid(ILocation)}. The returned list cannot be modified.
|
||||||
*
|
*
|
||||||
* @return An unmodifiable list with all the locations in the area
|
* @return An unmodifiable list with all the locations in the area
|
||||||
*/
|
*/
|
||||||
List<Location> locations();
|
List<ILocation> locations();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an object for iterating over all the neighbours of the given position,
|
* Return an object for iterating over all the neighbours of the given position,
|
||||||
* suitable for use in a new-style for-loop.
|
* suitable for use in a new-style for-loop.
|
||||||
* <p>
|
* <p>
|
||||||
* The iterator will yield up to eight positions (less if the given position is
|
* The iterator will yield up to eight positions (less if the given position is
|
||||||
* at the edge of the area, and the coordinates are not wrapped). E.g., for a
|
* at the edge of the area, and the coordinates are not wrapped). E.g., for a
|
||||||
* 1x1 area, the iterator will yield nothing (if the area is not wrapped), or
|
* 1x1 area, the iterator will yield nothing (if the area is not wrapped), or
|
||||||
* the same position two or eight times (if the area is wrapped horizontally,
|
* the same position two or eight times (if the area is wrapped horizontally,
|
||||||
* vertically or both).
|
* vertically or both).
|
||||||
*
|
*
|
||||||
* @param pos A position in the area
|
* @param pos
|
||||||
* @return An iterable over positions, with {@link #contains(Location)} being
|
* A position in the area
|
||||||
* true for each position.
|
* @return An iterable over positions, with {@link #contains(ILocation)} being
|
||||||
* @throws IndexOutOfBoundsException if !contains(pos)
|
* true for each position.
|
||||||
* @see #wrapsHorizontally(), {@link #wrapsVertically()}
|
* @see #wrapsHorizontally(), {@link #wrapsVertically()}
|
||||||
*/
|
* @throws IndexOutOfBoundsException
|
||||||
Iterable<Location> neighboursOf(Location pos);
|
* if !contains(pos)
|
||||||
|
*/
|
||||||
|
Iterable<ILocation> neighboursOf(ILocation pos);
|
||||||
|
|
||||||
/**
|
/** @return A (possibly) parallel {@link Stream} of all locations in the area */
|
||||||
* @return A (possibly) parallel {@link Stream} of all locations in the area
|
Stream<ILocation> parallelStream();
|
||||||
*/
|
|
||||||
Stream<Location> parallelStream();
|
|
||||||
|
|
||||||
/**
|
/** @return A {@link Stream} of all locations in the area */
|
||||||
* @return A {@link Stream} of all locations in the area
|
Stream<ILocation> stream();
|
||||||
*/
|
|
||||||
Stream<Location> stream();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a 2D coordinate to 1D
|
* Convert a 2D coordinate to 1D
|
||||||
*
|
*
|
||||||
* @param x X-coordinate
|
* @param x
|
||||||
* @param y Y-coordinate
|
* X-coordinate
|
||||||
* @return x + y*getWidth()
|
* @param y
|
||||||
*/
|
* Y-coordinate
|
||||||
int toIndex(int x, int y);
|
* @return x + y*getWidth()
|
||||||
|
*/
|
||||||
|
int toIndex(int x, int y);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
String toString();
|
String toString();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the area wraps horizontally, then x will be the same as x+(k*getWidth())
|
* If the area wraps horizontally, then x will be the same as x+(k*getWidth())
|
||||||
* for any k – i.e., it will be as if the 2D area is projected on a cylinder or
|
* for any k – i.e., it will be as if the 2D area is projected on a cylinder or
|
||||||
* torus in 3D-space.
|
* torus in 3D-space.
|
||||||
* <p>
|
* <p>
|
||||||
* With no wrapping, accessing positions outside (0,0)–(getWidth(),getHeight())
|
* With no wrapping, accessing positions outside (0,0)–(getWidth(),getHeight())
|
||||||
* is illegal.
|
* is illegal.
|
||||||
*
|
*
|
||||||
* @return True if the area wraps around horizontally
|
* @return True if the area wraps around horizontally
|
||||||
*/
|
*/
|
||||||
boolean wrapsHorizontally();
|
boolean wrapsHorizontally();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the area wraps vertically, then y will be the same as y+(k*getHeight())
|
* If the area wraps vertically, then y will be the same as y+(k*getHeight())
|
||||||
* for any k – i.e., it will be as if the 2D area is projected on a cylinder or
|
* for any k – i.e., it will be as if the 2D area is projected on a cylinder or
|
||||||
* torus in 3D-space.
|
* torus in 3D-space.
|
||||||
* <p>
|
* <p>
|
||||||
* With no wrapping, accessing positions outside (0,0)–(getWidth(),getHeight())
|
* With no wrapping, accessing positions outside (0,0)–(getWidth(),getHeight())
|
||||||
* is illegal.
|
* is illegal.
|
||||||
*
|
*
|
||||||
* @return True if the area wraps around vertically
|
* @return True if the area wraps around vertically
|
||||||
*/
|
*/
|
||||||
boolean wrapsVertically();
|
boolean wrapsVertically();
|
||||||
}
|
}
|
||||||
|
@ -5,194 +5,209 @@ import java.util.stream.Stream;
|
|||||||
|
|
||||||
public interface IGrid<T> extends Iterable<T> {
|
public interface IGrid<T> extends Iterable<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make a copy
|
* Make a copy
|
||||||
*
|
*
|
||||||
* @return A fresh copy of the grid, with the same elements
|
* @return A fresh copy of the grid, with the same elements
|
||||||
*/
|
*/
|
||||||
IGrid<T> copy();
|
IGrid<T> copy();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a parallel {@link Stream} with all the elements in this grid.
|
* Create a parallel {@link Stream} with all the elements in this grid.
|
||||||
*
|
*
|
||||||
* @return A stream
|
* @return A stream
|
||||||
* @see {@link java.util.Collection#parallelStream()}
|
* @see {@link java.util.Collection#parallelStream()}
|
||||||
*/
|
*/
|
||||||
Stream<T> elementParallelStream();
|
Stream<T> elementParallelStream();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a {@link Stream} with all the elements in this grid.
|
* Create a {@link Stream} with all the elements in this grid.
|
||||||
*
|
*
|
||||||
* @return A stream
|
* @return A stream
|
||||||
*/
|
*/
|
||||||
Stream<T> elementStream();
|
Stream<T> elementStream();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialise the contents of all cells using an initialiser function.
|
* Initialise the contents of all cells using an initialiser function.
|
||||||
* <p>
|
*
|
||||||
* The function will be called with the (x,y) position of an element, and is
|
* The function will be called with the (x,y) position of an element, and is
|
||||||
* expected to return the element to place at that position. For example:
|
* expected to return the element to place at that position. For example:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* // fill all cells with the position as a string (e.g., "(2,2)")
|
* // fill all cells with the position as a string (e.g., "(2,2)")
|
||||||
* grid.setAll((x, y) -> String.format("(%d,%d)", x, y));
|
* grid.setAll((x, y) -> String.format("(%d,%d)", x, y));
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param initialiser The initialiser function
|
* @param initialiser
|
||||||
*/
|
* The initialiser function
|
||||||
void fill(Function<Location, T> initialiser);
|
*/
|
||||||
|
void fill(Function<ILocation, T> initialiser);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the contents of all cells to <code>element</code>
|
* Set the contents of all cells to <code>element</code>
|
||||||
* <p>
|
*
|
||||||
* For example:
|
* For example:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* // clear the grid
|
* // clear the grid
|
||||||
* grid.setAll(null);
|
* grid.setAll(null);
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param element
|
* @param element
|
||||||
*/
|
*/
|
||||||
void fill(T element);
|
void fill(T element);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the contents of the cell in the given x,y location.
|
* Get the contents of the cell in the given x,y location.
|
||||||
* <p>
|
*
|
||||||
* y must be greater than or equal to 0 and less than getHeight(). x must be
|
* y must be greater than or equal to 0 and less than getHeight(). x must be
|
||||||
* greater than or equal to 0 and less than getWidth().
|
* greater than or equal to 0 and less than getWidth().
|
||||||
*
|
*
|
||||||
* @param pos The (x,y) position of the grid cell to get the contents of.
|
* @param pos
|
||||||
* @throws IndexOutOfBoundsException if !isValid(pos)
|
* The (x,y) position of the grid cell to get the contents of.
|
||||||
*/
|
* @throws IndexOutOfBoundsException
|
||||||
T get(Location pos);
|
* if !isValid(pos)
|
||||||
|
*/
|
||||||
|
T get(ILocation pos);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the contents of the cell in the given x,y location.
|
* Get the contents of the cell in the given x,y location.
|
||||||
* <p>
|
*
|
||||||
* y must be greater than or equal to 0 and less than getHeight(). x must be
|
* y must be greater than or equal to 0 and less than getHeight(). x must be
|
||||||
* greater than or equal to 0 and less than getWidth().
|
* greater than or equal to 0 and less than getWidth().
|
||||||
*
|
*
|
||||||
* @param x The column of the cell to get the contents of.
|
* @param x
|
||||||
* @param y The row of the cell to get contents of.
|
* The column of the cell to get the contents of.
|
||||||
* @throws IndexOutOfBoundsException if !isValid(x,y)
|
* @param y
|
||||||
*/
|
* The row of the cell to get contents of.
|
||||||
T get(int x, int y);
|
* @throws IndexOutOfBoundsException
|
||||||
|
* if !isValid(x,y)
|
||||||
|
*/
|
||||||
|
T get(int x, int y);
|
||||||
|
|
||||||
IArea getArea();
|
IArea getArea();
|
||||||
|
|
||||||
/**
|
/** @return The height of the grid. */
|
||||||
* @return The height of the grid.
|
int getHeight();
|
||||||
*/
|
|
||||||
int getHeight();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the contents of the cell in the given x,y location.
|
* Get the contents of the cell in the given x,y location.
|
||||||
* <p>
|
*
|
||||||
* y must be greater than or equal to 0 and less than getHeight(). x must be
|
* y must be greater than or equal to 0 and less than getHeight(). x must be
|
||||||
* greater than or equal to 0 and less than getWidth().
|
* greater than or equal to 0 and less than getWidth().
|
||||||
*
|
*
|
||||||
* @param pos The (x,y) position of the grid cell to get the contents of.
|
* @param pos
|
||||||
* @param defaultResult A default value to be substituted if the (x,y) is out of bounds or
|
* The (x,y) position of the grid cell to get the contents of.
|
||||||
* contents == null.
|
* @param defaultResult
|
||||||
*/
|
* A default value to be substituted if the (x,y) is out of bounds or
|
||||||
T getOrDefault(Location pos, T defaultResult);
|
* contents == null.
|
||||||
|
*/
|
||||||
|
T getOrDefault(ILocation pos, T defaultResult);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the contents of the cell in the given x,y location.
|
* Get the contents of the cell in the given x,y location.
|
||||||
* <p>
|
*
|
||||||
* y must be greater than or equal to 0 and less than getHeight(). x must be
|
* y must be greater than or equal to 0 and less than getHeight(). x must be
|
||||||
* greater than or equal to 0 and less than getWidth().
|
* greater than or equal to 0 and less than getWidth().
|
||||||
*
|
*
|
||||||
* @param x The column of the cell to get the contents of.
|
* @param x
|
||||||
* @param y The row of the cell to get contents of.
|
* The column of the cell to get the contents of.
|
||||||
* @param defaultResult A default value to be substituted if the (x,y) is out of bounds or
|
* @param y
|
||||||
* contents == null.
|
* The row of the cell to get contents of.
|
||||||
*/
|
* @param defaultResult
|
||||||
T getOrDefault(int x, int y, T 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. */
|
||||||
* @return The width of the grid.
|
int getWidth();
|
||||||
*/
|
|
||||||
int getWidth();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if coordinates are valid.
|
* Check if coordinates are valid.
|
||||||
* <p>
|
*
|
||||||
* Valid coordinates are 0 <= pos.getX() < getWidth(), 0 <= pos.getY() <
|
* Valid coordinates are 0 <= pos.getX() < getWidth(), 0 <= pos.getY() <
|
||||||
* getHeight().
|
* getHeight().
|
||||||
*
|
*
|
||||||
* @param pos A position
|
* @param pos
|
||||||
* @return true if the position is within the grid
|
* A position
|
||||||
*/
|
* @return true if the position is within the grid
|
||||||
boolean isValid(Location pos);
|
*/
|
||||||
|
boolean isValid(ILocation pos);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if coordinates are valid.
|
* Check if coordinates are valid.
|
||||||
* <p>
|
*
|
||||||
* Valid coordinates are 0 <= x < getWidth(), 0 <= y < getHeight().
|
* Valid coordinates are 0 <= x < getWidth(), 0 <= y < getHeight().
|
||||||
*
|
*
|
||||||
* @param x an x coordinate
|
* @param x
|
||||||
* @param y an y coordinate
|
* an x coordinate
|
||||||
* @return true if the (x,y) position is within the grid
|
* @param y
|
||||||
*/
|
* an y coordinate
|
||||||
boolean isValid(int x, int y);
|
* @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.
|
* Create a parallel {@link Stream} with all the locations in this grid.
|
||||||
* <p>
|
* <p>
|
||||||
* All locations obtained through the stream are guaranteed to be valid
|
* All locations obtained through the stream are guaranteed to be valid
|
||||||
* according to {@link #isValid(Location)}.
|
* according to {@link #isValid(ILocation)}.
|
||||||
*
|
*
|
||||||
* @return A stream
|
* @return A stream
|
||||||
* @see {@link java.util.Collection#parallelStream()}
|
* @see {@link java.util.Collection#parallelStream()}
|
||||||
*/
|
*/
|
||||||
Stream<Location> locationParallelStream();
|
Stream<ILocation> locationParallelStream();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iterate over all grid locations
|
* Iterate over all grid locations
|
||||||
* <p>
|
* <p>
|
||||||
* See also {@link #iterator()} – using the grid directly in a for-loop will
|
* See also {@link #iterator()} – using the grid directly in a for-loop will
|
||||||
* iterate over the elements.
|
* iterate over the elements.
|
||||||
* <p>
|
* <p>
|
||||||
* All locations obtained through the iterator are guaranteed to be valid
|
* All locations obtained through the iterator are guaranteed to be valid
|
||||||
* according to {@link #isValid(Location)}.
|
* according to {@link #isValid(ILocation)}.
|
||||||
*
|
*
|
||||||
* @return An iterable for iterating over all the locations in the grid
|
* @return An iterable for iterating over all the locations in the grid
|
||||||
*/
|
*/
|
||||||
Iterable<Location> locations();
|
Iterable<ILocation> locations();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a {@link Stream} with all the locations in this grid.
|
* Create a {@link Stream} with all the locations in this grid.
|
||||||
* <p>
|
* <p>
|
||||||
* All locations obtained through the stream are guaranteed to be valid
|
* All locations obtained through the stream are guaranteed to be valid
|
||||||
* according to {@link #isValid(Location)}.
|
* according to {@link #isValid(ILocation)}.
|
||||||
*
|
*
|
||||||
* @return A stream
|
* @return A stream
|
||||||
*/
|
*/
|
||||||
Stream<Location> locationStream();
|
Stream<ILocation> locationStream();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the contents of the cell in the given x,y location.
|
* Set the contents of the cell in the given x,y location.
|
||||||
* <p>
|
*
|
||||||
* y must be greater than or equal to 0 and less than getHeight(). x must be
|
* y must be greater than or equal to 0 and less than getHeight(). x must be
|
||||||
* greater than or equal to 0 and less than getWidth().
|
* greater than or equal to 0 and less than getWidth().
|
||||||
*
|
*
|
||||||
* @param pos The (x,y) position of the grid cell to get the contents of.
|
* @param pos
|
||||||
* @param element The contents the cell is to have.
|
* The (x,y) position of the grid cell to get the contents of.
|
||||||
* @throws IndexOutOfBoundsException if !isValid(pos)
|
* @param element
|
||||||
*/
|
* The contents the cell is to have.
|
||||||
void set(Location pos, T element);
|
* @throws IndexOutOfBoundsException
|
||||||
|
* if !isValid(pos)
|
||||||
|
*/
|
||||||
|
void set(ILocation pos, T element);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the contents of the cell in the given x,y location.
|
* Set the contents of the cell in the given x,y location.
|
||||||
* <p>
|
*
|
||||||
* y must be greater than or equal to 0 and less than getHeight(). x must be
|
* y must be greater than or equal to 0 and less than getHeight(). x must be
|
||||||
* greater than or equal to 0 and less than getWidth().
|
* greater than or equal to 0 and less than getWidth().
|
||||||
*
|
*
|
||||||
* @param element The contents the cell is to have.
|
* @param element
|
||||||
* @throws IndexOutOfBoundsException if !isValid(x,y)
|
* The contents the cell is to have.
|
||||||
*/
|
* @throws IndexOutOfBoundsException
|
||||||
void set(int x, int y, T element);
|
* if !isValid(x,y)
|
||||||
|
*/
|
||||||
|
void set(int x, int y, T element);
|
||||||
|
|
||||||
}
|
}
|
107
src/main/java/inf101/v18/grid/ILocation.java
Normal file
107
src/main/java/inf101/v18/grid/ILocation.java
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package inf101.v18.grid;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a location within an {@link IArea}.
|
||||||
|
* <p>
|
||||||
|
* <em>Immutable:</em> Locations are immutable; i.e., a particular
|
||||||
|
* <code>ILocation</code> will always have the same x- and y-coordinates and
|
||||||
|
* cannot be changed. Methods that find neighbours (such as
|
||||||
|
* {@link #go(GridDirection)}) will return another <code>ILocation</code>.
|
||||||
|
* <p>
|
||||||
|
* <em>Area:</em> All {@link ILocation}s are tied to an {@link IArea}, thus they
|
||||||
|
* will know whether they are on the edge of the area (and not all neighbouring
|
||||||
|
* grid coordinates will be valid locations within the area) and other
|
||||||
|
* coordinate system properties that are determined by the {@link IArea} (e.g.,
|
||||||
|
* if the coordinate system wraps around like on the surface of a donut).
|
||||||
|
* <p>
|
||||||
|
* <em>Unique objects:</em> All {@link ILocation} in an {@link IArea} are
|
||||||
|
* unique. This means that <code>area.location(x,y) == area.location(x,y)</code>
|
||||||
|
* for all <em>x</em> and <em>y</em>, and that:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* // equality is reference equality for locations in the same area
|
||||||
|
* if (l1.getArea() == l2.getArea())
|
||||||
|
* assertEquals(l1.equals(l2), (l1 == l2));
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Anya Helene Bagge, UiB
|
||||||
|
*/
|
||||||
|
public interface ILocation extends IPosition {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterate over neighbours in eight directions.
|
||||||
|
* <p>
|
||||||
|
* (The iterator may yield fewer than eight locations if the current location is
|
||||||
|
* at the edge of its containing area.
|
||||||
|
*
|
||||||
|
* @return The neighbours in the eight cardinal and intercardinal directions
|
||||||
|
* ({@link GridDirection#NORTH}, @link GridDirection#SOUTH}, @link
|
||||||
|
* GridDirection#EAST}, @link GridDirection#WEST},
|
||||||
|
* {@link GridDirection#NORTHEAST}, @link
|
||||||
|
* GridDirection#NORTHWEST}, @link GridDirection#SOUTHEAST}, @link
|
||||||
|
* GridDirection#SOUTHWEST}, )
|
||||||
|
*/
|
||||||
|
Collection<ILocation> allNeighbours();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether you can go towards direction dir without going outside the
|
||||||
|
* area bounds
|
||||||
|
*
|
||||||
|
* @param dir
|
||||||
|
* @return True if go(dir) will succeed
|
||||||
|
*/
|
||||||
|
boolean canGo(GridDirection dir);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterate over north/south/east/west neighbours.
|
||||||
|
* <p>
|
||||||
|
* (The iterator may yield fewer than four locations if the current location is
|
||||||
|
* at the edge of its containing area.
|
||||||
|
*
|
||||||
|
* @return The neighbours in the four cardinal directions
|
||||||
|
* ({@link GridDirection#NORTH}, @link GridDirection#SOUTH}, @link
|
||||||
|
* GridDirection#EAST}, @link GridDirection#WEST}
|
||||||
|
*/
|
||||||
|
Collection<ILocation> cardinalNeighbours();
|
||||||
|
|
||||||
|
IArea getArea();
|
||||||
|
|
||||||
|
int getIndex();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the next location in direction dir.
|
||||||
|
* <p>
|
||||||
|
* This <em>does not</em> change the location object; rather it returns the
|
||||||
|
* ILocation you would end up at if you go in the given direction from this
|
||||||
|
* ILocation.
|
||||||
|
*
|
||||||
|
* @param dir
|
||||||
|
* A direction
|
||||||
|
* @return A neighbouring location
|
||||||
|
* @throws IndexOutOfBoundsException
|
||||||
|
* if !canGo(dir)
|
||||||
|
*/
|
||||||
|
ILocation go(GridDirection dir);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the grid cells between this location (exclusive) and another location
|
||||||
|
* (inclusive).
|
||||||
|
*
|
||||||
|
* This is a list of length {@link #gridDistanceTo(other)}, containing
|
||||||
|
* the cells that a chess queen would visit when moving to <code>other</code>.
|
||||||
|
* <p>
|
||||||
|
* Computes the maximum of the horizontal and the vertical distance. For
|
||||||
|
* example, to go from (0,0) to (3,5), you could go three steps SOUTHEAST and
|
||||||
|
* two steps SOUTH, so the {@link #gridDistanceTo(IPosition)} is 5.
|
||||||
|
*
|
||||||
|
* @param other
|
||||||
|
* @return Number of horizontal/vertical/diagonal (<em>queen</em>-like) steps to
|
||||||
|
* other
|
||||||
|
*/
|
||||||
|
List<ILocation> gridLineTo(ILocation other);
|
||||||
|
|
||||||
|
}
|
@ -5,172 +5,210 @@ import java.util.function.Predicate;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public interface IMultiGrid<T> extends IGrid<List<T>> {
|
public interface IMultiGrid<T> extends IGrid<List<T>> {
|
||||||
/**
|
/**
|
||||||
* Add to the cell at the given location.
|
* Add to the cell at the given location.
|
||||||
*
|
*
|
||||||
* @param loc The (x,y) position of the grid cell to get the contents of.
|
* @param loc
|
||||||
* @param element An element to be added to the cell.
|
* The (x,y) position of the grid cell to get the contents of.
|
||||||
* @throws IndexOutOfBoundsException if !isValid(loc)
|
* @param element
|
||||||
*/
|
* An element to be added to the cell.
|
||||||
default void add(Location loc, T element) {
|
* @throws IndexOutOfBoundsException
|
||||||
get(loc).add(element);
|
* if !isValid(loc)
|
||||||
}
|
*/
|
||||||
|
default void add(ILocation loc, T element) {
|
||||||
|
get(loc).add(element);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add to the cell at the given x,y location.
|
* Add to the cell at the given x,y location.
|
||||||
* <p>
|
*
|
||||||
* y must be greater than or equal to 0 and less than getHeight(). x must be
|
* y must be greater than or equal to 0 and less than getHeight(). x must be
|
||||||
* greater than or equal to 0 and less than getWidth().
|
* greater than or equal to 0 and less than getWidth().
|
||||||
*
|
*
|
||||||
* @param pos The (x,y) position of the grid cell to get the contents of.
|
* @param pos
|
||||||
* @param element An element to be added to the cell
|
* The (x,y) position of the grid cell to get the contents of.
|
||||||
* @throws IndexOutOfBoundsException if !isValid(x,y)
|
* @param element
|
||||||
*/
|
* An element to be added to the cell
|
||||||
default void add(int x, int y, T element) {
|
* @throws IndexOutOfBoundsException
|
||||||
get(x, y).add(element);
|
* if !isValid(x,y)
|
||||||
}
|
*/
|
||||||
|
default void add(int x, int y, T element) {
|
||||||
|
get(x, y).add(element);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a cell contains an element.
|
* Check if a cell contains an element.
|
||||||
*
|
*
|
||||||
* @param loc The (x,y) position of the grid cell
|
*
|
||||||
* @param predicate Search predicate.
|
* @param loc
|
||||||
* @return true if an element matching the predicate was found
|
* The (x,y) position of the grid cell
|
||||||
* @throws IndexOutOfBoundsException if !isValid(loc)
|
* @param predicate
|
||||||
*/
|
* Search predicate.
|
||||||
default boolean contains(Location loc, Predicate<T> predicate) {
|
* @return true if an element matching the predicate was found
|
||||||
for (T t : get(loc)) {
|
* @throws IndexOutOfBoundsException
|
||||||
if (predicate.test(t)) {
|
* if !isValid(loc)
|
||||||
return true;
|
*/
|
||||||
}
|
default boolean contains(ILocation loc, Predicate<T> predicate) {
|
||||||
}
|
for (T t : get(loc)) {
|
||||||
return false;
|
if (predicate.test(t))
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a cell contains an element.
|
* Check if a cell contains an element.
|
||||||
*
|
*
|
||||||
* @param loc The (x,y) position of the grid cell
|
*
|
||||||
* @param element An element to search for.
|
* @param loc
|
||||||
* @return true if element is at the given location
|
* The (x,y) position of the grid cell
|
||||||
* @throws IndexOutOfBoundsException if !isValid(loc)
|
* @param element
|
||||||
*/
|
* An element to search for.
|
||||||
default boolean contains(Location loc, T element) {
|
* @return true if element is at the given location
|
||||||
return get(loc).contains(element);
|
* @throws IndexOutOfBoundsException
|
||||||
}
|
* if !isValid(loc)
|
||||||
|
*/
|
||||||
|
default boolean contains(ILocation loc, T element) {
|
||||||
|
return get(loc).contains(element);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a cell contains an element.
|
* Check if a cell contains an element.
|
||||||
* <p>
|
*
|
||||||
* y must be greater than or equal to 0 and less than getHeight(). x must be
|
* y must be greater than or equal to 0 and less than getHeight(). x must be
|
||||||
* greater than or equal to 0 and less than getWidth().
|
* greater than or equal to 0 and less than getWidth().
|
||||||
*
|
*
|
||||||
* @param pos The (x,y) position of the grid cell to get the contents of.
|
* @param pos
|
||||||
* @param predicate Search predicate.
|
* The (x,y) position of the grid cell to get the contents of.
|
||||||
* @return true if an element matching the predicate was found
|
* @param predicate
|
||||||
* @throws IndexOutOfBoundsException if !isValid(x,y)
|
* Search predicate.
|
||||||
*/
|
* @return true if an element matching the predicate was found
|
||||||
default boolean contains(int x, int y, Predicate<T> predicate) {
|
* @throws IndexOutOfBoundsException
|
||||||
return contains(this.getArea().location(x, y), predicate);
|
* if !isValid(x,y)
|
||||||
}
|
*/
|
||||||
|
default boolean contains(int x, int y, Predicate<T> predicate) {
|
||||||
|
return contains(this.getArea().location(x, y), predicate);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a cell contains an element.
|
* Check if a cell contains an element.
|
||||||
* <p>
|
*
|
||||||
* y must be greater than or equal to 0 and less than getHeight(). x must be
|
* y must be greater than or equal to 0 and less than getHeight(). x must be
|
||||||
* greater than or equal to 0 and less than getWidth().
|
* greater than or equal to 0 and less than getWidth().
|
||||||
*
|
*
|
||||||
* @param pos The (x,y) position of the grid cell to get the contents of.
|
* @param pos
|
||||||
* @param element An element to search for.
|
* The (x,y) position of the grid cell to get the contents of.
|
||||||
* @return true if element is at the given location
|
* @param element
|
||||||
* @throws IndexOutOfBoundsException if !isValid(x,y)
|
* An element to search for.
|
||||||
*/
|
* @return true if element is at the given location
|
||||||
default boolean contains(int x, int y, T element) {
|
* @throws IndexOutOfBoundsException
|
||||||
return get(x, y).contains(element);
|
* 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
|
* Get all elements in a cell that match the predicate
|
||||||
*
|
*
|
||||||
* @param loc The (x,y) position of the grid cell
|
*
|
||||||
* @param predicate Search predicate.
|
* @param loc
|
||||||
* @return true if an element matching the predicate was found
|
* The (x,y) position of the grid cell
|
||||||
* @throws IndexOutOfBoundsException if !isValid(loc)
|
* @param predicate
|
||||||
*/
|
* Search predicate.
|
||||||
default List<T> get(Location loc, Predicate<T> predicate) {
|
* @return true if an element matching the predicate was found
|
||||||
return get(loc).stream().filter(predicate).collect(Collectors.toList());
|
* @throws IndexOutOfBoundsException
|
||||||
}
|
* if !isValid(loc)
|
||||||
|
*/
|
||||||
|
default List<T> get(ILocation loc, Predicate<T> predicate) {
|
||||||
|
return get(loc).stream().filter(predicate).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a cell contains an element.
|
* Check if a cell contains an element.
|
||||||
* <p>
|
*
|
||||||
* y must be greater than or equal to 0 and less than getHeight(). x must be
|
* y must be greater than or equal to 0 and less than getHeight(). x must be
|
||||||
* greater than or equal to 0 and less than getWidth().
|
* greater than or equal to 0 and less than getWidth().
|
||||||
*
|
*
|
||||||
* @param pos The (x,y) position of the grid cell to get the contents of.
|
* @param pos
|
||||||
* @param predicate Search predicate.
|
* The (x,y) position of the grid cell to get the contents of.
|
||||||
* @return true if an element matching the predicate was found
|
* @param predicate
|
||||||
* @throws IndexOutOfBoundsException if !isValid(x,y)
|
* Search predicate.
|
||||||
*/
|
* @return true if an element matching the predicate was found
|
||||||
default List<T> get(int x, int y, Predicate<T> predicate) {
|
* @throws IndexOutOfBoundsException
|
||||||
return get(this.getArea().location(x, y), predicate);
|
* if !isValid(x,y)
|
||||||
}
|
*/
|
||||||
|
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.
|
* Remove an element from the cell at the given location.
|
||||||
*
|
*
|
||||||
* @param loc The location of the grid cell
|
* @param loc
|
||||||
* @param predicate Predicate which should be true for elements to be removed
|
* The location of the grid cell
|
||||||
* @return Number of elements removed
|
* @param predicate
|
||||||
* @throws IndexOutOfBoundsException if !isValid(loc)
|
* Predicate which should be true for elements to be removed
|
||||||
*/
|
* @return Number of elements removed
|
||||||
default int remove(Location loc, Predicate<T> predicate) {
|
* @throws IndexOutOfBoundsException
|
||||||
List<T> list = get(loc);
|
* if !isValid(loc)
|
||||||
int s = list.size();
|
*/
|
||||||
get(loc).removeIf(predicate);
|
default int remove(ILocation loc, Predicate<T> predicate) {
|
||||||
return s - list.size();
|
List<T> list = get(loc);
|
||||||
}
|
int s = list.size();
|
||||||
|
get(loc).removeIf(predicate);
|
||||||
|
return s - list.size();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove an element from the cell at the given location.
|
* Remove an element from the cell at the given location.
|
||||||
*
|
*
|
||||||
* @param loc The location of the grid cell
|
* @param loc
|
||||||
* @param element An element to be removed from the cell.
|
* The location of the grid cell
|
||||||
* @return Number of elements removed
|
* @param element
|
||||||
* @throws IndexOutOfBoundsException if !isValid(loc)
|
* An element to be removed from the cell.
|
||||||
*/
|
* @return Number of elements removed
|
||||||
default int remove(Location loc, T element) {
|
* @throws IndexOutOfBoundsException
|
||||||
return get(loc).remove(element) ? 1 : 0;
|
* if !isValid(loc)
|
||||||
}
|
*/
|
||||||
|
default int remove(ILocation loc, T element) {
|
||||||
|
return get(loc).remove(element) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove an element from the cell at the given x,y location.
|
* Remove an element from the cell at the given x,y location.
|
||||||
* <p>
|
*
|
||||||
* y must be greater than or equal to 0 and less than getHeight(). x must be
|
* y must be greater than or equal to 0 and less than getHeight(). x must be
|
||||||
* greater than or equal to 0 and less than getWidth().
|
* greater than or equal to 0 and less than getWidth().
|
||||||
*
|
*
|
||||||
* @param pos The (x,y) position of the grid cell
|
* @param pos
|
||||||
* @param predicate Predicate which should be true for elements to be removed
|
* The (x,y) position of the grid cell
|
||||||
* @return Number of elements removed
|
* @param predicate
|
||||||
* @throws IndexOutOfBoundsException if !isValid(x,y)
|
* Predicate which should be true for elements to be removed
|
||||||
*/
|
* @return Number of elements removed
|
||||||
default int remove(int x, int y, Predicate<T> predicate) {
|
* @throws IndexOutOfBoundsException
|
||||||
return remove(getArea().location(x, y), predicate);
|
* if !isValid(x,y)
|
||||||
}
|
*/
|
||||||
|
default int remove(int x, int y, Predicate<T> predicate) {
|
||||||
|
return remove(getArea().location(x, y), predicate);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove an element from the cell at the given x,y location.
|
* Remove an element from the cell at the given x,y location.
|
||||||
* <p>
|
*
|
||||||
* y must be greater than or equal to 0 and less than getHeight(). x must be
|
* y must be greater than or equal to 0 and less than getHeight(). x must be
|
||||||
* greater than or equal to 0 and less than getWidth().
|
* greater than or equal to 0 and less than getWidth().
|
||||||
*
|
*
|
||||||
* @param pos The (x,y) position of the grid cell
|
* @param pos
|
||||||
* @param element An element to be removed from the cell
|
* The (x,y) position of the grid cell
|
||||||
* @return Number of elements removed
|
* @param element
|
||||||
* @throws IndexOutOfBoundsException if !isValid(x,y)
|
* An element to be removed from the cell
|
||||||
*/
|
* @return Number of elements removed
|
||||||
default int remove(int x, int y, T element) {
|
* @throws IndexOutOfBoundsException
|
||||||
return get(x, y).remove(element) ? 1 : 0;
|
* if !isValid(x,y)
|
||||||
}
|
*/
|
||||||
|
default int remove(int x, int y, T element) {
|
||||||
|
return get(x, y).remove(element) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -2,83 +2,82 @@ package inf101.v18.grid;
|
|||||||
|
|
||||||
public interface IPosition {
|
public interface IPosition {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param obj Another object
|
* @param obj
|
||||||
* @return true if obj is also an IPosition, and the x and y coordinates are
|
* Another object
|
||||||
* equal
|
* @return true if obj is also an IPosition, and the x and y coordinates are
|
||||||
*/
|
* equal
|
||||||
@Override
|
*/
|
||||||
boolean equals(Object obj);
|
@Override
|
||||||
|
boolean equals(Object obj);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the Euclidian distance between the midpoint of this position and another
|
* Find the Euclidian distance between the midpoint of this position and another
|
||||||
* position.
|
* position.
|
||||||
* <p>
|
*
|
||||||
* The distance is computed with the Pythagorean formula, with the assumption
|
* The distance is computed with the Pythagorean formula, with the assumption
|
||||||
* that the grid cells are square shaped with <em>width</em> = <em>height</em> =
|
* that the grid cells are square shaped with <em>width</em> = <em>height</em> =
|
||||||
* 1. For example, the distance from (0,0) to (3,5) is √(3²+5²) = 5.83.
|
* 1. For example, the distance from (0,0) to (3,5) is √(3²+5²) = 5.83.
|
||||||
*
|
*
|
||||||
* @param other
|
* @param other
|
||||||
* @return Euclidian distance between this and other's midpoints
|
* @return Euclidian distance between this and other's midpoints
|
||||||
*/
|
*/
|
||||||
double geometricDistanceTo(IPosition other);
|
double geometricDistanceTo(IPosition other);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the x-coordinate
|
* Gets the x-coordinate
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
int getX();
|
int getX();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the y-coordinate
|
* Gets the y-coordinate
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
int getY();
|
int getY();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the distance in grid cells to another location.
|
* Find the distance in grid cells to another location.
|
||||||
* <p>
|
*
|
||||||
* This is different from {@link #stepDistanceTo(IPosition)} in that diagonal
|
* This is different from {@link #stepDistanceTo(IPosition)} in that diagonal
|
||||||
* steps are allowed, and is the same as the number of steps a queen would take
|
* steps are allowed, and is the same as the number of steps a queen would take
|
||||||
* on a chess board.
|
* on a chess board.
|
||||||
* <p>
|
* <p>
|
||||||
* Computes the maximum of the horizontal and the vertical distance. For
|
* Computes the maximum of the horizontal and the vertical distance. For
|
||||||
* example, to go from (0,0) to (3,5), you could go three steps SOUTHEAST and
|
* example, to go from (0,0) to (3,5), you could go three steps SOUTHEAST and
|
||||||
* two steps SOUTH, so the {@link #gridDistanceTo(IPosition)} is 5.
|
* two steps SOUTH, so the {@link #gridDistanceTo(IPosition)} is 5.
|
||||||
*
|
*
|
||||||
* @param other
|
* @param other
|
||||||
* @return Number of horizontal/vertical/diagonal (<em>queen</em>-like) steps to
|
* @return Number of horizontal/vertical/diagonal (<em>queen</em>-like) steps to
|
||||||
* other
|
* other
|
||||||
*/
|
*/
|
||||||
int gridDistanceTo(IPosition other);
|
int gridDistanceTo(IPosition other);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
int hashCode();
|
int hashCode();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the number of non-diagonal steps needed to go from this location the
|
* Find the number of non-diagonal steps needed to go from this location the
|
||||||
* other location.
|
* other location.
|
||||||
* <p>
|
*
|
||||||
* This is different from {@link #gridDistanceTo(IPosition)} in that only
|
* This is different from {@link #gridDistanceTo(IPosition)} in that only
|
||||||
* non-diagonal steps are allowed, and is the same as the number of steps a rook
|
* non-diagonal steps are allowed, and is the same as the number of steps a rook
|
||||||
* would take on a chess board.
|
* would take on a chess board.
|
||||||
* <p>
|
* <p>
|
||||||
* Computes the distance to another location as the sum of the absolute
|
* Computes the distance to another location as the sum of the absolute
|
||||||
* difference between the x- and y-coordinates. For example, to go from (0,0) to
|
* difference between the x- and y-coordinates. For example, to go from (0,0) to
|
||||||
* (3,5), you would need to go three steps EAST and five steps SOUTH, so the
|
* (3,5), you would need to go three steps EAST and five steps SOUTH, so the
|
||||||
* {@link #stepDistanceTo(IPosition)} is 8.
|
* {@link #stepDistanceTo(IPosition)} is 8.
|
||||||
*
|
*
|
||||||
* @param other
|
* @param other
|
||||||
* @return Number of horizontal/vertical (<em>rook</em>-like) steps to other
|
* @return Number of horizontal/vertical (<em>rook</em>-like) steps to other
|
||||||
*/
|
*/
|
||||||
int stepDistanceTo(IPosition other);
|
int stepDistanceTo(IPosition other);
|
||||||
|
|
||||||
/**
|
/** @return Position as a string, "(x,y)" */
|
||||||
* @return Position as a string, "(x,y)"
|
@Override
|
||||||
*/
|
String toString();
|
||||||
@Override
|
|
||||||
String toString();
|
|
||||||
|
|
||||||
}
|
}
|
@ -1,104 +0,0 @@
|
|||||||
package inf101.v18.grid;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a location within an {@link IArea}.
|
|
||||||
* <p>
|
|
||||||
* <em>Immutable:</em> Locations are immutable; i.e., a particular
|
|
||||||
* <code>ILocation</code> will always have the same x- and y-coordinates and
|
|
||||||
* cannot be changed. Methods that find neighbours (such as
|
|
||||||
* {@link #go(GridDirection)}) will return another <code>ILocation</code>.
|
|
||||||
* <p>
|
|
||||||
* <em>Area:</em> All {@link Location}s are tied to an {@link IArea}, thus they
|
|
||||||
* will know whether they are on the edge of the area (and not all neighbouring
|
|
||||||
* grid coordinates will be valid locations within the area) and other
|
|
||||||
* coordinate system properties that are determined by the {@link IArea} (e.g.,
|
|
||||||
* if the coordinate system wraps around like on the surface of a donut).
|
|
||||||
* <p>
|
|
||||||
* <em>Unique objects:</em> All {@link Location} in an {@link IArea} are
|
|
||||||
* unique. This means that <code>area.location(x,y) == area.location(x,y)</code>
|
|
||||||
* for all <em>x</em> and <em>y</em>, and that:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* // equality is reference equality for locations in the same area
|
|
||||||
* if (l1.getArea() == l2.getArea())
|
|
||||||
* assertEquals(l1.equals(l2), (l1 == l2));
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @author Anya Helene Bagge, UiB
|
|
||||||
*/
|
|
||||||
public interface Location extends IPosition {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Iterate over neighbours in eight directions.
|
|
||||||
* <p>
|
|
||||||
* (The iterator may yield fewer than eight locations if the current location is
|
|
||||||
* at the edge of its containing area.
|
|
||||||
*
|
|
||||||
* @return The neighbours in the eight cardinal and intercardinal directions
|
|
||||||
* ({@link GridDirection#NORTH}, @link GridDirection#SOUTH}, @link
|
|
||||||
* GridDirection#EAST}, @link GridDirection#WEST},
|
|
||||||
* {@link GridDirection#NORTHEAST}, @link
|
|
||||||
* GridDirection#NORTHWEST}, @link GridDirection#SOUTHEAST}, @link
|
|
||||||
* GridDirection#SOUTHWEST}, )
|
|
||||||
*/
|
|
||||||
Collection<Location> allNeighbours();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether you can go towards direction dir without going outside the
|
|
||||||
* area bounds
|
|
||||||
*
|
|
||||||
* @param dir
|
|
||||||
* @return True if go(dir) will succeed
|
|
||||||
*/
|
|
||||||
boolean canGo(GridDirection dir);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Iterate over north/south/east/west neighbours.
|
|
||||||
* <p>
|
|
||||||
* (The iterator may yield fewer than four locations if the current location is
|
|
||||||
* at the edge of its containing area.
|
|
||||||
*
|
|
||||||
* @return The neighbours in the four cardinal directions
|
|
||||||
* ({@link GridDirection#NORTH}, @link GridDirection#SOUTH}, @link
|
|
||||||
* GridDirection#EAST}, @link GridDirection#WEST}
|
|
||||||
*/
|
|
||||||
Collection<Location> cardinalNeighbours();
|
|
||||||
|
|
||||||
IArea getArea();
|
|
||||||
|
|
||||||
int getIndex();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the next location in direction dir.
|
|
||||||
* <p>
|
|
||||||
* This <em>does not</em> change the location object; rather it returns the
|
|
||||||
* ILocation you would end up at if you go in the given direction from this
|
|
||||||
* ILocation.
|
|
||||||
*
|
|
||||||
* @param dir A direction
|
|
||||||
* @return A neighbouring location
|
|
||||||
* @throws IndexOutOfBoundsException if !canGo(dir)
|
|
||||||
*/
|
|
||||||
Location go(GridDirection dir);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the grid cells between this location (exclusive) and another location
|
|
||||||
* (inclusive).
|
|
||||||
* <p>
|
|
||||||
* This is a list of length {@link #gridDistanceTo(other)}, containing
|
|
||||||
* the cells that a chess queen would visit when moving to <code>other</code>.
|
|
||||||
* <p>
|
|
||||||
* Computes the maximum of the horizontal and the vertical distance. For
|
|
||||||
* example, to go from (0,0) to (3,5), you could go three steps SOUTHEAST and
|
|
||||||
* two steps SOUTH, so the {@link #gridDistanceTo(IPosition)} is 5.
|
|
||||||
*
|
|
||||||
* @param other
|
|
||||||
* @return Number of horizontal/vertical/diagonal (<em>queen</em>-like) steps to
|
|
||||||
* other
|
|
||||||
*/
|
|
||||||
List<Location> gridLineTo(Location other);
|
|
||||||
|
|
||||||
}
|
|
@ -5,12 +5,12 @@ import java.util.List;
|
|||||||
|
|
||||||
public class MultiGrid<T> extends MyGrid<List<T>> implements IMultiGrid<T> {
|
public class MultiGrid<T> extends MyGrid<List<T>> implements IMultiGrid<T> {
|
||||||
|
|
||||||
public MultiGrid(IArea area) {
|
public MultiGrid(IArea area) {
|
||||||
super(area, (l) -> new ArrayList<T>());
|
super(area, (l) -> new ArrayList<T>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultiGrid(int width, int height) {
|
public MultiGrid(int width, int height) {
|
||||||
super(width, height, (l) -> new ArrayList<T>());
|
super(width, height, (l) -> new ArrayList<T>());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,219 +6,216 @@ import java.util.List;
|
|||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/** A Grid contains a set of cells in a square 2D matrix. */
|
||||||
* A Grid contains a set of cells in a square 2D matrix.
|
|
||||||
*/
|
|
||||||
public class MyGrid<T> implements IGrid<T> {
|
public class MyGrid<T> implements IGrid<T> {
|
||||||
private final IArea area;
|
private final IArea area;
|
||||||
private final List<T> cells;
|
private final List<T> cells;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a grid with the given dimensions.
|
* Construct a grid with the given dimensions.
|
||||||
* <p>
|
*
|
||||||
* The initialiser function will be called with the (x,y) position of an
|
* The initialiser function will be called with the (x,y) position of an
|
||||||
* element, and is expected to return the element to place at that position. For
|
* element, and is expected to return the element to place at that position. For
|
||||||
* example:
|
* example:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* // fill all cells with the position as a string (e.g., "(2,2)")
|
* // fill all cells with the position as a string (e.g., "(2,2)")
|
||||||
* new MyGrid(10, 10, ((x, y) -> String.format("(%d,%d)", x, y));
|
* new MyGrid(10, 10, ((x, y) -> String.format("(%d,%d)", x, y));
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param area
|
* @param area
|
||||||
* @param initialiser The initialiser function
|
* @param initialiser
|
||||||
*/
|
* The initialiser function
|
||||||
public MyGrid(IArea area, Function<Location, T> initialiser) {
|
*/
|
||||||
if (area == null || initialiser == null) {
|
public MyGrid(IArea area, Function<ILocation, T> initialiser) {
|
||||||
throw new IllegalArgumentException();
|
if (area == null || initialiser == null) {
|
||||||
}
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
|
||||||
this.area = area;
|
this.area = area;
|
||||||
this.cells = new ArrayList<T>(area.getSize());
|
this.cells = new ArrayList<T>(area.getSize());
|
||||||
for (Location loc : area) {
|
for (ILocation loc : area) {
|
||||||
cells.add(initialiser.apply(loc));
|
cells.add(initialiser.apply(loc));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a grid with the given dimensions.
|
* Construct a grid with the given dimensions.
|
||||||
*
|
*
|
||||||
* @param area
|
* @param area
|
||||||
* @param initElement What the cells should initially hold (possibly null)
|
* @param initElement
|
||||||
*/
|
* What the cells should initially hold (possibly null)
|
||||||
public MyGrid(IArea area, T initElement) {
|
*/
|
||||||
if (area == null) {
|
public MyGrid(IArea area, T initElement) {
|
||||||
throw new IllegalArgumentException();
|
if (area == null) {
|
||||||
}
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
|
||||||
this.area = area;
|
this.area = area;
|
||||||
this.cells = new ArrayList<T>(area.getSize());
|
this.cells = new ArrayList<T>(area.getSize());
|
||||||
for (int i = 0; i < area.getSize(); ++i) {
|
for (int i = 0; i < area.getSize(); ++i) {
|
||||||
cells.add(initElement);
|
cells.add(initElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a grid with the given dimensions.
|
* Construct a grid with the given dimensions.
|
||||||
* <p>
|
*
|
||||||
* The initialiser function will be called with the (x,y) position of an
|
* The initialiser function will be called with the (x,y) position of an
|
||||||
* element, and is expected to return the element to place at that position. For
|
* element, and is expected to return the element to place at that position. For
|
||||||
* example:
|
* example:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* // fill all cells with the position as a string (e.g., "(2,2)")
|
* // fill all cells with the position as a string (e.g., "(2,2)")
|
||||||
* new MyGrid(10, 10, ((x, y) -> String.format("(%d,%d)", x, y));
|
* new MyGrid(10, 10, ((x, y) -> String.format("(%d,%d)", x, y));
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param width
|
* @param width
|
||||||
* @param height
|
* @param height
|
||||||
* @param initialiser The initialiser function
|
* @param initialiser
|
||||||
*/
|
* The initialiser function
|
||||||
public MyGrid(int width, int height, Function<Location, T> initialiser) {
|
*/
|
||||||
this(new RectArea(width, height), initialiser);
|
public MyGrid(int width, int height, Function<ILocation, T> initialiser) {
|
||||||
}
|
this(new RectArea(width, height), initialiser);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a grid with the given dimensions.
|
* Construct a grid with the given dimensions.
|
||||||
*
|
*
|
||||||
* @param width
|
* @param width
|
||||||
* @param height
|
* @param height
|
||||||
* @param initElement What the cells should initially hold (possibly null)
|
* @param initElement
|
||||||
*/
|
* What the cells should initially hold (possibly null)
|
||||||
public MyGrid(int width, int height, T initElement) {
|
*/
|
||||||
this(new RectArea(width, height), initElement);
|
public MyGrid(int width, int height, T initElement) {
|
||||||
}
|
this(new RectArea(width, height), initElement);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IGrid<T> copy() {
|
public IGrid<T> copy() {
|
||||||
return new MyGrid<>(getWidth(), getHeight(), (l) -> get(l));
|
return new MyGrid<>(getWidth(), getHeight(), (l) -> get(l));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<T> elementParallelStream() {
|
public Stream<T> elementParallelStream() {
|
||||||
return cells.parallelStream();
|
return cells.parallelStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<T> elementStream() {
|
public Stream<T> elementStream() {
|
||||||
return cells.stream();
|
return cells.stream();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fill(Function<Location, T> initialiser) {
|
public void fill(Function<ILocation, T> initialiser) {
|
||||||
if (initialiser == null) {
|
if (initialiser == null)
|
||||||
throw new NullPointerException();
|
throw new NullPointerException();
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < area.getSize(); i++) {
|
for (int i = 0; i < area.getSize(); i++) {
|
||||||
cells.set(i, initialiser.apply(area.fromIndex(i)));
|
cells.set(i, initialiser.apply(area.fromIndex(i)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fill(T element) {
|
public void fill(T element) {
|
||||||
for (int i = 0; i < area.getSize(); i++) {
|
for (int i = 0; i < area.getSize(); i++) {
|
||||||
cells.set(i, element);
|
cells.set(i, element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T get(Location loc) {
|
public T get(ILocation loc) {
|
||||||
if (loc.getArea() == area) {
|
if (loc.getArea() == area)
|
||||||
return cells.get(loc.getIndex());
|
return cells.get(loc.getIndex());
|
||||||
} else {
|
else
|
||||||
return cells.get(area.toIndex(loc.getX(), loc.getY()));
|
return cells.get(area.toIndex(loc.getX(), loc.getY()));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T get(int x, int y) {
|
public T get(int x, int y) {
|
||||||
return cells.get(area.toIndex(x, y));
|
return cells.get(area.toIndex(x, y));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IArea getArea() {
|
public IArea getArea() {
|
||||||
return area;
|
return area;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getHeight() {
|
public int getHeight() {
|
||||||
return area.getHeight();
|
return area.getHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T getOrDefault(Location loc, T defaultResult) {
|
public T getOrDefault(ILocation loc, T defaultResult) {
|
||||||
if (loc.getArea() == area) {
|
if (loc.getArea() == area) {
|
||||||
T r = cells.get(loc.getIndex());
|
T r = cells.get(loc.getIndex());
|
||||||
if (r != null) {
|
if (r != null)
|
||||||
return r;
|
return r;
|
||||||
} else {
|
else
|
||||||
return defaultResult;
|
return defaultResult;
|
||||||
}
|
} else {
|
||||||
} else {
|
return getOrDefault(loc.getX(), loc.getY(), defaultResult);
|
||||||
return getOrDefault(loc.getX(), loc.getY(), defaultResult);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T getOrDefault(int x, int y, T defaultResult) {
|
public T getOrDefault(int x, int y, T defaultResult) {
|
||||||
T r = null;
|
T r = null;
|
||||||
if (isValid(x, y)) {
|
if (isValid(x, y))
|
||||||
r = get(x, y);
|
r = get(x, y);
|
||||||
}
|
if (r != null)
|
||||||
if (r != null) {
|
return r;
|
||||||
return r;
|
else
|
||||||
} else {
|
return defaultResult;
|
||||||
return defaultResult;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getWidth() {
|
public int getWidth() {
|
||||||
return area.getWidth();
|
return area.getWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid(Location loc) {
|
public boolean isValid(ILocation loc) {
|
||||||
return loc.getArea() == area || area.contains(loc.getX(), loc.getY());
|
return loc.getArea() == area || area.contains(loc.getX(), loc.getY());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid(int x, int y) {
|
public boolean isValid(int x, int y) {
|
||||||
return area.contains(x, y);
|
return area.contains(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<T> iterator() {
|
public Iterator<T> iterator() {
|
||||||
return cells.iterator();
|
return cells.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<Location> locationParallelStream() {
|
public Stream<ILocation> locationParallelStream() {
|
||||||
return area.parallelStream();
|
return area.parallelStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterable<Location> locations() {
|
public Iterable<ILocation> locations() {
|
||||||
return area;
|
return area;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<Location> locationStream() {
|
public Stream<ILocation> locationStream() {
|
||||||
return area.stream();
|
return area.stream();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void set(Location loc, T element) {
|
public void set(ILocation loc, T element) {
|
||||||
if (loc.getArea() == area) {
|
if (loc.getArea() == area) {
|
||||||
cells.set(loc.getIndex(), element);
|
cells.set(loc.getIndex(), element);
|
||||||
} else {
|
} else {
|
||||||
set(loc.getX(), loc.getY(), element);
|
set(loc.getX(), loc.getY(), element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void set(int x, int y, T elem) {
|
public void set(int x, int y, T elem) {
|
||||||
cells.set(area.toIndex(x, y), elem);
|
cells.set(area.toIndex(x, y), elem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,21 +2,9 @@
|
|||||||
|
|
||||||
## Changes from the Cellular Automaton version
|
## Changes from the Cellular Automaton version
|
||||||
|
|
||||||
* If you look at `MyGrid.java`, you will notice that it no longer uses `IList<T>`/`MyList<T>` to hold the grid cells,
|
* If you look at `MyGrid.java`, you will notice that it no longer uses `IList<T>`/`MyList<T>` to hold the grid cells, and both `IList.java` and `MyList.java` have been removed. Instead we use standard built-in Java lists, with the `List<T>` interface (and `ArrayList<T>` as the class for new list objects).
|
||||||
and both `IList.java` and `MyList.java` have been removed. Instead we use standard built-in Java lists, with
|
* This saves us the work of maintaining and improving our own list, and makes it immediately compatible with useful standard Java library operations on lists.
|
||||||
the `List<T>` interface (and `ArrayList<T>` as the class for new list objects).
|
* In general, you should always use built-in APIs (such as the Java Collections API which provides lists and similar) when available. Not only does it save time, but they're likely to be much better tested (and probably better designed) than you can do yourself, so they're less likely to have, e.g., stupid bugs that only show up in corner cases. You may also get better performance.
|
||||||
* This saves us the work of maintaining and improving our own list, and makes it immediately compatible with useful
|
* You might want to do things yourself if you want to learn how stuff works; also, in some cases, there may not be a suitable API available – or the API doesn't really fully fit with what we want to do. While Java has one-dimensional lists and arrays, it doesn't really have built-in classes for grids, so we're making our own.
|
||||||
standard Java library operations on lists.
|
* An alternative to IGrid/MyGrid would be to use two-dimensional arrays – but these aren't *actually* two-dimensional arrays, but rather arrays-of-arrays, which makes them fit less well with the concept of a “grid” (the same goes for making a list of lists). In addition to being a bit inconvenient, they're also much less efficient in use – and they won't let us do useful things like “please give me the cell to the north of this cell” or “please give me all cells neighbouring this cell”.
|
||||||
* In general, you should always use built-in APIs (such as the Java Collections API which provides lists and
|
|
||||||
similar) when available. Not only does it save time, but they're likely to be much better tested (and probably
|
|
||||||
better designed) than you can do yourself, so they're less likely to have, e.g., stupid bugs that only show up in
|
|
||||||
corner cases. You may also get better performance.
|
|
||||||
* You might want to do things yourself if you want to learn how stuff works; also, in some cases, there may not be a
|
|
||||||
suitable API available – or the API doesn't really fully fit with what we want to do. While Java has
|
|
||||||
one-dimensional lists and arrays, it doesn't really have built-in classes for grids, so we're making our own.
|
|
||||||
* An alternative to IGrid/MyGrid would be to use two-dimensional arrays – but these aren't *actually*
|
|
||||||
two-dimensional arrays, but rather arrays-of-arrays, which makes them fit less well with the concept of a “grid” (
|
|
||||||
the same goes for making a list of lists). In addition to being a bit inconvenient, they're also much less
|
|
||||||
efficient in use – and they won't let us do useful things like “please give me the cell to the north of this cell”
|
|
||||||
or “please give me all cells neighbouring this cell”.
|
|
||||||
|
|
@ -8,338 +8,334 @@ import java.util.List;
|
|||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class RectArea implements IArea {
|
public class RectArea implements IArea {
|
||||||
/**
|
/** A class to represent an (x, y)-location on a grid. */
|
||||||
* A class to represent an (x, y)-location on a grid.
|
class Location implements ILocation {
|
||||||
*/
|
|
||||||
class Location implements inf101.v18.grid.Location {
|
|
||||||
|
|
||||||
/**
|
/** value of the x-coordinate */
|
||||||
* value of the x-coordinate
|
protected final int x;
|
||||||
*/
|
/** value of the y-coordinate */
|
||||||
protected final int x;
|
protected final int y;
|
||||||
/**
|
protected final int idx;
|
||||||
* value of the y-coordinate
|
protected final int edgeMask;
|
||||||
*/
|
|
||||||
protected final int y;
|
|
||||||
protected final int idx;
|
|
||||||
protected final int edgeMask;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main constructor. Initializes a new {@link #Location} objects with the
|
* Main constructor. Initializes a new {@link #Location} objects with the
|
||||||
* corresponding values of x and y.
|
* corresponding values of x and y.
|
||||||
*
|
*
|
||||||
* @param x X coordinate
|
* @param x
|
||||||
* @param y Y coordinate
|
* X coordinate
|
||||||
* @param idx 1-dimensional index
|
* @param y
|
||||||
* @param edgeMask mask with bits {@link RectArea#N}, {@link RectArea#S},
|
* Y coordinate
|
||||||
* {@link RectArea#E}, {@link RectArea#W} set if we're on the
|
* @param idx
|
||||||
* corresponding edge of the area
|
* 1-dimensional index
|
||||||
*/
|
* @param edgeMask
|
||||||
Location(int x, int y, int idx, int edgeMask) {
|
* mask with bits {@link RectArea#N}, {@link RectArea#S},
|
||||||
this.x = x;
|
* {@link RectArea#E}, {@link RectArea#W} set if we're on the
|
||||||
this.y = y;
|
* corresponding edge of the area
|
||||||
this.idx = idx;
|
*/
|
||||||
this.edgeMask = edgeMask;
|
Location(int x, int y, int idx, int edgeMask) {
|
||||||
}
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.idx = idx;
|
||||||
|
this.edgeMask = edgeMask;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<inf101.v18.grid.Location> allNeighbours() {
|
public Collection<ILocation> allNeighbours() {
|
||||||
Collection<inf101.v18.grid.Location> ns = new ArrayList<>(8);
|
Collection<ILocation> ns = new ArrayList<>(8);
|
||||||
for (GridDirection d : GridDirection.EIGHT_DIRECTIONS) {
|
for (GridDirection d : GridDirection.EIGHT_DIRECTIONS) {
|
||||||
if (canGo(d)) {
|
if (canGo(d))
|
||||||
ns.add(go(d));
|
ns.add(go(d));
|
||||||
}
|
}
|
||||||
}
|
return ns;
|
||||||
return ns;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canGo(GridDirection dir) {
|
public boolean canGo(GridDirection dir) {
|
||||||
return (edgeMask & dir.getMask()) == 0;
|
return (edgeMask & dir.getMask()) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<inf101.v18.grid.Location> cardinalNeighbours() {
|
public Collection<ILocation> cardinalNeighbours() {
|
||||||
Collection<inf101.v18.grid.Location> ns = new ArrayList<>(4);
|
Collection<ILocation> ns = new ArrayList<>(4);
|
||||||
for (GridDirection d : GridDirection.FOUR_DIRECTIONS) {
|
for (GridDirection d : GridDirection.FOUR_DIRECTIONS) {
|
||||||
if (canGo(d)) {
|
if (canGo(d))
|
||||||
ns.add(go(d));
|
ns.add(go(d));
|
||||||
}
|
}
|
||||||
}
|
return ns;
|
||||||
return ns;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (this == obj) {
|
if (this == obj) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (obj == null) {
|
if (obj == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!(obj instanceof IPosition)) {
|
if (!(obj instanceof IPosition)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
IPosition other = (IPosition) obj;
|
IPosition other = (IPosition) obj;
|
||||||
if (x != other.getX()) {
|
if (x != other.getX()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return y == other.getY();
|
return y == other.getY();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public double geometricDistanceTo(IPosition other) {
|
public double geometricDistanceTo(IPosition other) {
|
||||||
return Math.sqrt(Math.pow(this.x - other.getX(), 2) + Math.pow(this.y - other.getY(), 2));
|
return Math.sqrt(Math.pow(this.x - other.getX(), 2) + Math.pow(this.y - other.getY(), 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IArea getArea() {
|
public IArea getArea() {
|
||||||
return RectArea.this;
|
return RectArea.this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getIndex() {
|
public int getIndex() {
|
||||||
return idx;
|
return idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getX() {
|
public int getX() {
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getY() {
|
public int getY() {
|
||||||
return y;
|
return y;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public inf101.v18.grid.Location go(GridDirection dir) {
|
public ILocation go(GridDirection dir) {
|
||||||
return location(x + dir.getDx(), y + dir.getDy());
|
return location(x + dir.getDx(), y + dir.getDy());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int gridDistanceTo(IPosition other) {
|
public int gridDistanceTo(IPosition other) {
|
||||||
return Math.max(Math.abs(this.x - other.getX()), Math.abs(this.y - other.getY()));
|
return Math.max(Math.abs(this.x - other.getX()), Math.abs(this.y - other.getY()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<inf101.v18.grid.Location> gridLineTo(inf101.v18.grid.Location other) {
|
public List<ILocation> gridLineTo(ILocation other) {
|
||||||
if (!contains(other)) {
|
if (!contains(other))
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
}
|
int distX = other.getX() - x;
|
||||||
int distX = other.getX() - x;
|
int distY = other.getY() - y;
|
||||||
int distY = other.getY() - y;
|
int length = Math.max(Math.abs(distX), Math.abs(distY));
|
||||||
int length = Math.max(Math.abs(distX), Math.abs(distY));
|
List<ILocation> line = new ArrayList<>(length);
|
||||||
List<inf101.v18.grid.Location> line = new ArrayList<>(length);
|
if (length == 0)
|
||||||
if (length == 0) {
|
return line;
|
||||||
return line;
|
double dx = (double) distX / (double) length;
|
||||||
}
|
double dy = (double) distY / (double) length;
|
||||||
double dx = (double) distX / (double) length;
|
// System.out.printf("dx=%g, dy=%g, length=%d%n", dx, dy, length);
|
||||||
double dy = (double) distY / (double) length;
|
for (int i = 1; i <= length; i++) {
|
||||||
// System.out.printf("dx=%g, dy=%g, length=%d%n", dx, dy, length);
|
line.add(location(x + (int) Math.round(dx * i), y + (int) Math.round(dy * i)));
|
||||||
for (int i = 1; i <= length; i++) {
|
}
|
||||||
line.add(location(x + (int) Math.round(dx * i), y + (int) Math.round(dy * i)));
|
return line;
|
||||||
}
|
}
|
||||||
return line;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
final int prime = 31;
|
final int prime = 31;
|
||||||
int result = 1;
|
int result = 1;
|
||||||
result = prime * result + x;
|
result = prime * result + x;
|
||||||
result = prime * result + y;
|
result = prime * result + y;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int stepDistanceTo(IPosition other) {
|
public int stepDistanceTo(IPosition other) {
|
||||||
return Math.abs(this.x - other.getX()) + Math.abs(this.y - other.getY());
|
return Math.abs(this.x - other.getX()) + Math.abs(this.y - other.getY());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "(x=" + x + ",y=" + y + ")";
|
return "(x=" + x + ",y=" + y + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final int width;
|
protected final int width;
|
||||||
protected final int height;
|
protected final int height;
|
||||||
protected final int size;
|
protected final int size;
|
||||||
protected final List<inf101.v18.grid.Location> locs;
|
protected final List<ILocation> locs;
|
||||||
|
|
||||||
protected final boolean hWrap, vWrap;
|
protected final boolean hWrap, vWrap;
|
||||||
|
|
||||||
public RectArea(int width, int height) {
|
public RectArea(int width, int height) {
|
||||||
this(width, height, false, false);
|
this(width, height, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RectArea(int width, int height, boolean horizWrap, boolean vertWrap) {
|
private RectArea(int width, int height, boolean horizWrap, boolean vertWrap) {
|
||||||
if (width < 1 || height < 1) {
|
if (width < 1 || height < 1) {
|
||||||
throw new IllegalArgumentException("Width and height must be positive");
|
throw new IllegalArgumentException("Width and height must be positive");
|
||||||
}
|
}
|
||||||
this.hWrap = horizWrap;
|
this.hWrap = horizWrap;
|
||||||
this.vWrap = vertWrap;
|
this.vWrap = vertWrap;
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
this.size = width * height;
|
this.size = width * height;
|
||||||
List<inf101.v18.grid.Location> l = new ArrayList<>(size);
|
List<ILocation> l = new ArrayList<>(size);
|
||||||
for (int y = 0, i = 0; y < height; y++) {
|
for (int y = 0, i = 0; y < height; y++) {
|
||||||
// set N or S bits if we're on the northern or southern edge
|
// set N or S bits if we're on the northern or southern edge
|
||||||
int edge = (y == 0 ? GridDirection.NORTH.getMask() : 0)
|
int edge = (y == 0 ? GridDirection.NORTH.getMask() : 0)
|
||||||
| (y == height - 1 ? GridDirection.SOUTH.getMask() : 0);
|
| (y == height - 1 ? GridDirection.SOUTH.getMask() : 0);
|
||||||
for (int x = 0; x < width; x++, i++) {
|
for (int x = 0; x < width; x++, i++) {
|
||||||
// set W or E bits if we're on the western or eastern edge
|
// set W or E bits if we're on the western or eastern edge
|
||||||
int e = edge | (x == 0 ? GridDirection.WEST.getMask() : 0)
|
int e = edge | (x == 0 ? GridDirection.WEST.getMask() : 0)
|
||||||
| (x == width - 1 ? GridDirection.EAST.getMask() : 0);
|
| (x == width - 1 ? GridDirection.EAST.getMask() : 0);
|
||||||
l.add(new Location(x, y, i, e));
|
l.add(new Location(x, y, i, e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
locs = Collections.unmodifiableList(l);
|
locs = Collections.unmodifiableList(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param x X-coordinate
|
* @param x
|
||||||
* @return The same x, wrapped to wrapX(x)
|
* X-coordinate
|
||||||
* @throws IndexOutOfBoundsException if coordinate is out of range, and wrapping is not enabled
|
* @return The same x, wrapped to wrapX(x)
|
||||||
*/
|
* @throws IndexOutOfBoundsException
|
||||||
protected int checkX(int x) {
|
* if coordinate is out of range, and wrapping is not enabled
|
||||||
x = wrapX(x);
|
*/
|
||||||
if (x < 0 || x >= width) {
|
protected int checkX(int x) {
|
||||||
throw new IndexOutOfBoundsException("x=" + x);
|
x = wrapX(x);
|
||||||
}
|
if (x < 0 || x >= width) {
|
||||||
|
throw new IndexOutOfBoundsException("x=" + x);
|
||||||
|
}
|
||||||
|
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param y Y-coordinate
|
* @param y
|
||||||
* @return The same y, wrapped to wrapY(y)
|
* Y-coordinate
|
||||||
* @throws IndexOutOfBoundsException if coordinate is out of range, and wrapping is not enabled
|
* @return The same y, wrapped to wrapY(y)
|
||||||
*/
|
* @throws IndexOutOfBoundsException
|
||||||
protected int checkY(int y) {
|
* if coordinate is out of range, and wrapping is not enabled
|
||||||
y = wrapY(y);
|
*/
|
||||||
if (y < 0 || y >= height) {
|
protected int checkY(int y) {
|
||||||
throw new IndexOutOfBoundsException("y=" + y);
|
y = wrapY(y);
|
||||||
}
|
if (y < 0 || y >= height) {
|
||||||
return y;
|
throw new IndexOutOfBoundsException("y=" + y);
|
||||||
}
|
}
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean contains(int x, int y) {
|
public boolean contains(int x, int y) {
|
||||||
x = wrapX(x);
|
x = wrapX(x);
|
||||||
y = wrapY(y);
|
y = wrapY(y);
|
||||||
return x >= 0 && x < width && y >= 0 && y < height;
|
return x >= 0 && x < width && y >= 0 && y < height;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean contains(IPosition pos) {
|
public boolean contains(IPosition pos) {
|
||||||
return (pos instanceof inf101.v18.grid.Location && ((inf101.v18.grid.Location) pos).getArea() == this) || contains(pos.getX(), pos.getY());
|
return (pos instanceof ILocation && ((ILocation) pos).getArea() == this) || contains(pos.getX(), pos.getY());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public inf101.v18.grid.Location fromIndex(int i) {
|
public ILocation fromIndex(int i) {
|
||||||
if (i >= 0 && i < size) {
|
if (i >= 0 && i < size)
|
||||||
return locs.get(i);
|
return locs.get(i);
|
||||||
} else {
|
else
|
||||||
throw new IndexOutOfBoundsException("" + i);
|
throw new IndexOutOfBoundsException("" + i);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getHeight() {
|
public int getHeight() {
|
||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getSize() {
|
public int getSize() {
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getWidth() {
|
public int getWidth() {
|
||||||
return width;
|
return width;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<inf101.v18.grid.Location> iterator() {
|
public Iterator<ILocation> iterator() {
|
||||||
return locs.iterator();
|
return locs.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public inf101.v18.grid.Location location(int x, int y) {
|
public ILocation location(int x, int y) {
|
||||||
if (x < 0 || x >= width || y < 0 || y >= height) {
|
if (x < 0 || x >= width || y < 0 || y >= height)
|
||||||
throw new IndexOutOfBoundsException("(" + x + "," + y + ")");
|
throw new IndexOutOfBoundsException("(" + x + "," + y + ")");
|
||||||
}
|
int i = x + y * width;
|
||||||
int i = x + y * width;
|
return locs.get(i);
|
||||||
return locs.get(i);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<inf101.v18.grid.Location> locations() {
|
public List<ILocation> locations() {
|
||||||
return locs; // (OK since locs has been through Collections.unmodifiableList())
|
return locs; // (OK since locs has been through Collections.unmodifiableList())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterable<inf101.v18.grid.Location> neighboursOf(inf101.v18.grid.Location pos) {
|
public Iterable<ILocation> neighboursOf(ILocation pos) {
|
||||||
return pos.allNeighbours();
|
return pos.allNeighbours();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<inf101.v18.grid.Location> parallelStream() {
|
public Stream<ILocation> parallelStream() {
|
||||||
return locs.parallelStream();
|
return locs.parallelStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<inf101.v18.grid.Location> stream() {
|
public Stream<ILocation> stream() {
|
||||||
return locs.stream();
|
return locs.stream();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int toIndex(int x, int y) {
|
public int toIndex(int x, int y) {
|
||||||
x = checkX(x);
|
x = checkX(x);
|
||||||
y = checkY(y);
|
y = checkY(y);
|
||||||
return y * width + x;
|
return y * width + x;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "RectArea [width=" + width + ", height=" + height + ", hWrap=" + hWrap + ", vWrap=" + vWrap + "]";
|
return "RectArea [width=" + width + ", height=" + height + ", hWrap=" + hWrap + ", vWrap=" + vWrap + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean wrapsHorizontally() {
|
public boolean wrapsHorizontally() {
|
||||||
return hWrap;
|
return hWrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean wrapsVertically() {
|
public boolean wrapsVertically() {
|
||||||
return vWrap;
|
return vWrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected int wrapX(int x) {
|
protected int wrapX(int x) {
|
||||||
if (hWrap) {
|
if (hWrap) {
|
||||||
if (x < 0) {
|
if (x < 0) {
|
||||||
return getWidth() + x % getWidth();
|
return getWidth() + x % getWidth();
|
||||||
} else {
|
} else {
|
||||||
return x % getWidth();
|
return x % getWidth();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected int wrapY(int y) {
|
protected int wrapY(int y) {
|
||||||
if (hWrap) {
|
if (hWrap) {
|
||||||
if (y < 0) {
|
if (y < 0) {
|
||||||
return getHeight() + y % getHeight();
|
return getHeight() + y % getHeight();
|
||||||
} else {
|
} else {
|
||||||
return y % getHeight();
|
return y % getHeight();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return y;
|
return y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,29 +4,29 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class AppInfo {
|
public class AppInfo {
|
||||||
/**
|
/**
|
||||||
* Your application name.
|
* Your application name.
|
||||||
*/
|
*/
|
||||||
public static final String APP_NAME = "Not Really Rogue";
|
public static final String APP_NAME = "Not Really Rogue";
|
||||||
/**
|
/**
|
||||||
* Your name.
|
* Your name.
|
||||||
*/
|
*/
|
||||||
public static final String APP_DEVELOPER = "Kristian Knarvik (kkn015)";
|
public static final String APP_DEVELOPER = "Kristian Knarvik (kkn015)";
|
||||||
/**
|
/**
|
||||||
* A short description.
|
* A short description.
|
||||||
*/
|
*/
|
||||||
public static final String APP_DESCRIPTION = "Implementasjon av inf101.v18.sem1";
|
public static final String APP_DESCRIPTION = "Implementasjon av inf101.v18.sem1";
|
||||||
/**
|
/**
|
||||||
* List of extra credits (e.g. for media sources)
|
* List of extra credits (e.g. for media sources)
|
||||||
*/
|
*/
|
||||||
public static final List<String> APP_EXTRA_CREDITS = Arrays.asList(
|
public static final List<String> APP_EXTRA_CREDITS = Arrays.asList(
|
||||||
"Sounds by Mike Koenig, Stephan Schutze and Mark DiAngelo",
|
"Sounds by Mike Koenig, Stephan Schutze and Mark DiAngelo",
|
||||||
"Thanks to Stian Johannesen Husum and Henning Berge for exchanging ideas"
|
"Thanks to Stian Johannesen Husum and Henning Berge for exchanging ideas"
|
||||||
);
|
);
|
||||||
/**
|
/**
|
||||||
* Help text. Could be used for an in-game help page, perhaps.
|
* Help text. Could be used for an in-game help page, perhaps.
|
||||||
* <p>
|
* <p>
|
||||||
* Use <code>\n</code> for new lines, <code>\f<code> between pages (if multi-page).
|
* Use <code>\n</code> for new lines, <code>\f<code> between pages (if multi-page).
|
||||||
*/
|
*/
|
||||||
public static final String APP_HELP = "";
|
public static final String APP_HELP = "";
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,7 @@ package inf101.v18.rogue101;
|
|||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
|
|
||||||
public class Launcher {
|
public class Launcher {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
Application.launch(Main.class, args);
|
Application.launch(Main.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
package inf101.v18.rogue101;
|
package inf101.v18.rogue101;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
|
||||||
import inf101.v18.gfx.Screen;
|
import inf101.v18.gfx.Screen;
|
||||||
import inf101.v18.gfx.gfxmode.ITurtle;
|
import inf101.v18.gfx.gfxmode.ITurtle;
|
||||||
import inf101.v18.gfx.textmode.Printer;
|
import inf101.v18.gfx.textmode.Printer;
|
||||||
import inf101.v18.gfx.textmode.TextMode;
|
import inf101.v18.gfx.textmode.TextMode;
|
||||||
import inf101.v18.rogue101.game.RogueGame;
|
import inf101.v18.rogue101.game.Game;
|
||||||
import javafx.animation.KeyFrame;
|
import javafx.animation.KeyFrame;
|
||||||
import javafx.animation.Timeline;
|
import javafx.animation.Timeline;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
@ -14,118 +17,112 @@ import javafx.scene.input.KeyEvent;
|
|||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
|
|
||||||
public class Main extends Application {
|
public class Main extends Application {
|
||||||
|
// you might want to tune these options
|
||||||
|
public static final boolean MAIN_USE_BACKGROUND_GRID = false;
|
||||||
|
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;
|
||||||
|
|
||||||
// you might want to tune these options
|
public static final boolean DEBUG_TIME = false;
|
||||||
public static final boolean MAIN_USE_BACKGROUND_GRID = false;
|
|
||||||
public static final boolean MAP_AUTO_SCALE_ITEM_DRAW = true;
|
|
||||||
public static final boolean MAP_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_STATUS = 21;
|
||||||
public static final int LINE_MSG1 = 22;
|
public static final int LINE_MSG1 = 22;
|
||||||
public static final int LINE_MSG2 = 23;
|
public static final int LINE_MSG2 = 23;
|
||||||
public static final int LINE_MSG3 = 24;
|
public static final int LINE_MSG3 = 24;
|
||||||
public static final int LINE_DEBUG = 25;
|
public static final int LINE_DEBUG = 25;
|
||||||
public static final int COLUMN_MAP_END = 40;
|
public static final int COLUMN_MAP_END = 40;
|
||||||
public static final int COLUMN_RIGHT_SIDE_START = 43;
|
public static final int COLUMN_RIGHTSIDE_START = 43;
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
launch(args);
|
launch(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Screen screen;
|
private Screen screen;
|
||||||
private ITurtle painter;
|
private ITurtle painter;
|
||||||
private Printer printer;
|
private Printer printer;
|
||||||
|
|
||||||
private RogueGame game;
|
private Game game;
|
||||||
|
|
||||||
private boolean grid = MAIN_USE_BACKGROUND_GRID;
|
private boolean grid = MAIN_USE_BACKGROUND_GRID;
|
||||||
private boolean autoNextTurn = false;
|
private boolean autoNextTurn = false;
|
||||||
private Timeline bigStepTimeline;
|
private Timeline bigStepTimeline;
|
||||||
private Timeline smallStepTimeline;
|
private Timeline smallStepTimeline;
|
||||||
|
|
||||||
private void setup() {
|
private void setup() {
|
||||||
//
|
//
|
||||||
game = new RogueGame(screen, painter, printer);
|
game = new Game(screen, painter, printer);
|
||||||
game.draw();
|
game.draw();
|
||||||
|
|
||||||
//
|
//
|
||||||
bigStepTimeline = new Timeline();
|
bigStepTimeline = new Timeline();
|
||||||
bigStepTimeline.setCycleCount(Timeline.INDEFINITE);
|
bigStepTimeline.setCycleCount(Timeline.INDEFINITE);
|
||||||
KeyFrame kf = new KeyFrame(Duration.millis(1000), (ActionEvent event) -> {
|
KeyFrame kf = new KeyFrame(Duration.millis(1000), (ActionEvent event) -> {
|
||||||
if (autoNextTurn) {
|
if (autoNextTurn) {
|
||||||
doTurn();
|
doTurn();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
bigStepTimeline.getKeyFrames().add(kf);
|
bigStepTimeline.getKeyFrames().add(kf);
|
||||||
// bigStepTimeline.playFromStart();
|
// bigStepTimeline.playFromStart();
|
||||||
|
|
||||||
//
|
//
|
||||||
smallStepTimeline = new Timeline();
|
smallStepTimeline = new Timeline();
|
||||||
smallStepTimeline.setCycleCount(1);
|
smallStepTimeline.setCycleCount(1);
|
||||||
kf = new KeyFrame(Duration.millis(1), (ActionEvent event) -> {
|
kf = new KeyFrame(Duration.millis(1), (ActionEvent event) -> {
|
||||||
doTurn();
|
doTurn();
|
||||||
});
|
});
|
||||||
smallStepTimeline.getKeyFrames().add(kf);
|
smallStepTimeline.getKeyFrames().add(kf);
|
||||||
|
|
||||||
// finally, start game
|
// finally, start game
|
||||||
doTurn();
|
doTurn();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage primaryStage) throws Exception {
|
public void start(Stage primaryStage) throws Exception {
|
||||||
screen = Screen.startPaintScene(primaryStage, Screen.CONFIG_PIXELS_STEP_SCALED); // Screen.CONFIG_SCREEN_FULLSCREEN_NO_HINT);
|
screen = Screen.startPaintScene(primaryStage, Screen.CONFIG_PIXELS_STEP_SCALED); // Screen.CONFIG_SCREEN_FULLSCREEN_NO_HINT);
|
||||||
|
|
||||||
printer = screen.createPrinter();
|
printer = screen.createPrinter();
|
||||||
painter = screen.createPainter();
|
painter = screen.createPainter();
|
||||||
printer.setTextMode(MAIN_TEXT_MODE, true);
|
printer.setTextMode(MAIN_TEXT_MODE, true);
|
||||||
|
|
||||||
// Font with emojis – need separate download
|
// Font with emojis – need separate download
|
||||||
printer.setFont(Printer.FONT_SYMBOLA);
|
printer.setFont(Printer.FONT_SYMBOLA);
|
||||||
|
|
||||||
if (grid) {
|
if (grid)
|
||||||
printer.drawCharCells();
|
printer.drawCharCells();
|
||||||
}
|
printer.setAutoScroll(false);
|
||||||
printer.setAutoScroll(false);
|
screen.setKeyPressedHandler((KeyEvent event) -> {
|
||||||
screen.setKeyPressedHandler((KeyEvent event) -> {
|
KeyCode code = event.getCode();
|
||||||
KeyCode code = event.getCode();
|
if (event.isShortcutDown()) {
|
||||||
if (event.isShortcutDown()) {
|
if (code == KeyCode.Q) {
|
||||||
if (code == KeyCode.Q) {
|
System.exit(0);
|
||||||
System.exit(0);
|
} else if (code == KeyCode.R) {
|
||||||
} else if (code == KeyCode.R) {
|
printer.cycleMode(true);
|
||||||
printer.cycleMode(true);
|
if (grid)
|
||||||
if (grid) {
|
printer.drawCharCells();
|
||||||
printer.drawCharCells();
|
game.draw();
|
||||||
}
|
printer.redrawDirty();
|
||||||
game.draw();
|
return true;
|
||||||
printer.redrawDirty();
|
} else if (code == KeyCode.A) {
|
||||||
return true;
|
screen.cycleAspect();
|
||||||
} else if (code == KeyCode.A) {
|
if (grid)
|
||||||
screen.cycleAspect();
|
printer.drawCharCells();
|
||||||
if (grid) {
|
return true;
|
||||||
printer.drawCharCells();
|
} else if (code == KeyCode.G) {
|
||||||
}
|
grid = !grid;
|
||||||
return true;
|
if (grid)
|
||||||
} else if (code == KeyCode.G) {
|
printer.drawCharCells();
|
||||||
grid = !grid;
|
else
|
||||||
if (grid) {
|
screen.clearBackground();
|
||||||
printer.drawCharCells();
|
return true;
|
||||||
} else {
|
} else if (code == KeyCode.F) {
|
||||||
screen.clearBackground();
|
screen.setFullScreen(!screen.isFullScreen());
|
||||||
}
|
return true;
|
||||||
return true;
|
} else if (code == KeyCode.L) {
|
||||||
} else if (code == KeyCode.F) {
|
printer.redrawTextPage();
|
||||||
screen.setFullScreen(!screen.isFullScreen());
|
return true;
|
||||||
return true;
|
}
|
||||||
} else if (code == KeyCode.L) {
|
|
||||||
printer.redrawTextPage();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
/*} else if (code == KeyCode.ENTER) {
|
/*} else if (code == KeyCode.ENTER) {
|
||||||
try {
|
try {
|
||||||
doTurn();
|
doTurn();
|
||||||
@ -134,83 +131,81 @@ public class Main extends Application {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
return true;*/ // This interferes with other code
|
return true;*/ // This interferes with other code
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
if (game.keyPressed(code)) {
|
if (game.keyPressed(code)) {
|
||||||
game.draw();
|
game.draw();
|
||||||
} else {
|
} else {
|
||||||
doTurn();
|
doTurn();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
try {
|
try {
|
||||||
StringWriter sw = new StringWriter();
|
StringWriter sw = new StringWriter();
|
||||||
PrintWriter writer = new PrintWriter(sw);
|
PrintWriter writer = new PrintWriter(sw);
|
||||||
e.printStackTrace(writer);
|
e.printStackTrace(writer);
|
||||||
writer.close();
|
writer.close();
|
||||||
String trace = sw.toString().split("\n")[0];
|
String trace = sw.toString().split("\n")[0];
|
||||||
game.displayDebug("Exception: " + trace);
|
game.displayDebug("Exception: " + trace);
|
||||||
} catch (Exception e2) {
|
} catch (Exception e2) {
|
||||||
System.err.println("Also got this exception trying to display the previous one");
|
System.err.println("Also got this exception trying to display the previous one");
|
||||||
e2.printStackTrace();
|
e2.printStackTrace();
|
||||||
game.displayDebug("Exception: " + e.getMessage());
|
game.displayDebug("Exception: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
printer.redrawDirty();
|
printer.redrawDirty();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
/*
|
/*
|
||||||
* screen.setKeyTypedHandler((KeyEvent event) -> { if (event.getCharacter() !=
|
* screen.setKeyTypedHandler((KeyEvent event) -> { if (event.getCharacter() !=
|
||||||
* KeyEvent.CHAR_UNDEFINED) { printer.print(event.getCharacter()); return true;
|
* KeyEvent.CHAR_UNDEFINED) { printer.print(event.getCharacter()); return true;
|
||||||
* } return false; });
|
* } return false; });
|
||||||
*/
|
*/
|
||||||
setup();
|
setup();
|
||||||
|
|
||||||
primaryStage.show();
|
primaryStage.show();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void doTurn() {
|
public void doTurn() {
|
||||||
long t = System.currentTimeMillis();
|
long t = System.currentTimeMillis();
|
||||||
boolean waitForPlayer = game.doTurn();
|
boolean waitForPlayer = game.doTurn();
|
||||||
if (DEBUG_TIME) {
|
if (DEBUG_TIME)
|
||||||
System.out.println("doTurn() took " + (System.currentTimeMillis() - t) + "ms");
|
System.out.println("doTurn() took " + (System.currentTimeMillis() - t) + "ms");
|
||||||
}
|
long t2 = System.currentTimeMillis();
|
||||||
long t2 = System.currentTimeMillis();
|
game.draw();
|
||||||
game.draw();
|
printer.redrawDirty();
|
||||||
printer.redrawDirty();
|
if (DEBUG_TIME) {
|
||||||
if (DEBUG_TIME) {
|
System.out.println("draw() took " + (System.currentTimeMillis() - t2) + "ms");
|
||||||
System.out.println("draw() took " + (System.currentTimeMillis() - t2) + "ms");
|
System.out.println("doTurn()+draw() took " + (System.currentTimeMillis() - t) + "ms");
|
||||||
System.out.println("doTurn()+draw() took " + (System.currentTimeMillis() - t) + "ms");
|
System.out.println("waiting for player? " + waitForPlayer);
|
||||||
System.out.println("waiting for player? " + waitForPlayer);
|
}
|
||||||
}
|
if (!waitForPlayer)
|
||||||
if (!waitForPlayer) {
|
smallStepTimeline.playFromStart(); // this will kickstart a new turn in a few milliseconds
|
||||||
smallStepTimeline.playFromStart(); // this will kickstart a new turn in a few milliseconds
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String BUILTIN_MAP = "40 20\n" //
|
public static String BUILTIN_MAP = "40 20\n" //
|
||||||
+ "########################################\n" //
|
+ "########################################\n" //
|
||||||
+ "#...... ..C.R ......R.R......... ..R...#\n" //
|
+ "#...... ..C.R ......R.R......... ..R...#\n" //
|
||||||
+ "#.R@R...... ..........RC..R...... ... .#\n" //
|
+ "#.R@R...... ..........RC..R...... ... .#\n" //
|
||||||
+ "#.......... ..R......R.R..R........R...#\n" //
|
+ "#.......... ..R......R.R..R........R...#\n" //
|
||||||
+ "#R. R......... R..R.........R......R.RR#\n" //
|
+ "#R. R......... R..R.........R......R.RR#\n" //
|
||||||
+ "#... ..R........R......R. R........R.RR#\n" //
|
+ "#... ..R........R......R. R........R.RR#\n" //
|
||||||
+ "###############################....R..R#\n" //
|
+ "###############################....R..R#\n" //
|
||||||
+ "#. ...R..C. ..R.R..........C.RC....... #\n" //
|
+ "#. ...R..C. ..R.R..........C.RC....... #\n" //
|
||||||
+ "#..C.....R..... ........RR R..R.....R..#\n" //
|
+ "#..C.....R..... ........RR R..R.....R..#\n" //
|
||||||
+ "#...R..R.R..............R .R..R........#\n" //
|
+ "#...R..R.R..............R .R..R........#\n" //
|
||||||
+ "#.R.....R........RRR.......R.. .C....R.#\n" //
|
+ "#.R.....R........RRR.......R.. .C....R.#\n" //
|
||||||
+ "#.C.. ..R. .....R.RC..C....R...R..C. .#\n" //
|
+ "#.C.. ..R. .....R.RC..C....R...R..C. .#\n" //
|
||||||
+ "#. R..............R R..R........C.....R#\n" //
|
+ "#. R..............R R..R........C.....R#\n" //
|
||||||
+ "#........###############################\n" //
|
+ "#........###############################\n" //
|
||||||
+ "# R.........R...C....R.....R...R.......#\n" //
|
+ "# R.........R...C....R.....R...R.......#\n" //
|
||||||
+ "# R......... R..R........R......R.RR..##\n" //
|
+ "# R......... R..R........R......R.RR..##\n" //
|
||||||
+ "#. ..R........R.....R. ....C...R.RR...#\n" //
|
+ "#. ..R........R.....R. ....C...R.RR...#\n" //
|
||||||
+ "#....RC..R........R......R.RC......R...#\n" //
|
+ "#....RC..R........R......R.RC......R...#\n" //
|
||||||
+ "#.C.... ..... ......... .R..R....R...R.#\n" //
|
+ "#.C.... ..... ......... .R..R....R...R.#\n" //
|
||||||
+ "########################################\n" //
|
+ "########################################\n" //
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,29 @@
|
|||||||
package inf101.v18.rogue101.enemies;
|
package inf101.v18.rogue101.enemies;
|
||||||
|
|
||||||
|
import inf101.v18.grid.ILocation;
|
||||||
import inf101.v18.rogue101.Main;
|
import inf101.v18.rogue101.Main;
|
||||||
import inf101.v18.rogue101.game.Game;
|
import inf101.v18.rogue101.game.Game;
|
||||||
import inf101.v18.rogue101.game.RogueGame;
|
import inf101.v18.rogue101.game.IGame;
|
||||||
import inf101.v18.rogue101.items.weapon.BasicSword;
|
import inf101.v18.rogue101.items.Backpack;
|
||||||
import inf101.v18.rogue101.object.Item;
|
import inf101.v18.rogue101.items.Sword;
|
||||||
import inf101.v18.rogue101.state.AttackType;
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
import inf101.v18.util.NPCHelper;
|
import inf101.v18.rogue101.objects.INonPlayer;
|
||||||
|
import inf101.v18.rogue101.shared.NPC;
|
||||||
|
import inf101.v18.rogue101.states.Attack;
|
||||||
|
|
||||||
/**
|
public class Boss implements INonPlayer {
|
||||||
* A boss enemy that's harder than any other enemy
|
private int hp = getMaxHealth();
|
||||||
*/
|
private final Backpack backpack = new Backpack();
|
||||||
public class Boss extends Enemy {
|
private ILocation loc;
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiates a new boss character
|
|
||||||
*/
|
|
||||||
public Boss() {
|
public Boss() {
|
||||||
backpack.add(new BasicSword());
|
backpack.add(new Sword());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doTurn(Game game) {
|
public void doTurn(IGame game) {
|
||||||
currentLocation = game.getLocation();
|
loc = game.getLocation();
|
||||||
NPCHelper.tryAttack(game, 1, AttackType.MELEE);
|
NPC.tryAttack(game, 1, Attack.MELEE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -36,13 +36,23 @@ public class Boss extends Enemy {
|
|||||||
return 15;
|
return 15;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IItem getItem(Class<?> type) {
|
||||||
|
for (IItem item : backpack.getContent()) {
|
||||||
|
if (type.isInstance(item)) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCurrentHealth() {
|
public int getCurrentHealth() {
|
||||||
return hp;
|
return hp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefense() {
|
public int getDefence() {
|
||||||
return 40;
|
return 40;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,13 +87,21 @@ public class Boss extends Enemy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int handleDamage(Game game, Item source, int amount) {
|
public int handleDamage(IGame game, IItem source, int amount) {
|
||||||
super.handleDamage(game, source, amount);
|
hp -= amount;
|
||||||
if (hp < 0) {
|
if (hp < 0 && backpack.size() > 0) { //Will be useful in a dungeon with several bosses
|
||||||
((RogueGame) game).win();
|
boolean dropped = false;
|
||||||
|
for (IItem item : backpack.getContent()) {
|
||||||
|
if (game.dropAt(loc, item)) {
|
||||||
|
dropped = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dropped) {
|
||||||
|
game.displayMessage(getName() + " dropped something");
|
||||||
|
}
|
||||||
|
((Game)game).win();
|
||||||
}
|
}
|
||||||
game.getPrinter().printAt(Main.COLUMN_RIGHT_SIDE_START, 19, "Boss HP: " + NPCHelper.hpBar(this));
|
game.getPrinter().printAt(Main.COLUMN_RIGHTSIDE_START, 19, "Boss HP: " + NPC.hpBar(this));
|
||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
package inf101.v18.rogue101.enemies;
|
|
||||||
|
|
||||||
import inf101.v18.grid.Location;
|
|
||||||
import inf101.v18.rogue101.game.Game;
|
|
||||||
import inf101.v18.rogue101.items.container.Backpack;
|
|
||||||
import inf101.v18.rogue101.object.Item;
|
|
||||||
import inf101.v18.rogue101.object.NonPlayerCharacter;
|
|
||||||
import inf101.v18.rogue101.state.Sound;
|
|
||||||
|
|
||||||
public abstract class Enemy implements NonPlayerCharacter {
|
|
||||||
|
|
||||||
protected int hp = getMaxHealth();
|
|
||||||
protected final Backpack backpack = new Backpack();
|
|
||||||
protected Location currentLocation;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Item getItem(Class<?> type) {
|
|
||||||
for (Item item : backpack.getContent()) {
|
|
||||||
if (type.isInstance(item)) {
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int handleDamage(Game game, Item source, int amount) {
|
|
||||||
hp -= amount;
|
|
||||||
if (hp < 0) {
|
|
||||||
Sound.NPC_DEATH.play();
|
|
||||||
}
|
|
||||||
if (hp < 0 && backpack.size() > 0) { //Will be useful in a dungeon with several bosses
|
|
||||||
boolean dropped = false;
|
|
||||||
for (Item item : backpack.getContent()) {
|
|
||||||
if (game.dropAt(currentLocation, item)) {
|
|
||||||
dropped = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (dropped) {
|
|
||||||
game.displayMessage(getName() + " dropped something");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,127 +1,108 @@
|
|||||||
package inf101.v18.rogue101.enemies;
|
package inf101.v18.rogue101.enemies;
|
||||||
|
|
||||||
import inf101.v18.grid.GridDirection;
|
import inf101.v18.grid.GridDirection;
|
||||||
import inf101.v18.grid.Location;
|
import inf101.v18.rogue101.game.IGame;
|
||||||
import inf101.v18.rogue101.game.Game;
|
import inf101.v18.rogue101.items.*;
|
||||||
import inf101.v18.rogue101.items.buff.BuffItem;
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
import inf101.v18.rogue101.items.weapon.BasicBow;
|
import inf101.v18.rogue101.objects.INonPlayer;
|
||||||
import inf101.v18.rogue101.items.weapon.BasicSword;
|
import inf101.v18.rogue101.shared.NPC;
|
||||||
import inf101.v18.rogue101.items.weapon.FireStaff;
|
import inf101.v18.rogue101.states.Age;
|
||||||
import inf101.v18.rogue101.items.weapon.MagicWeapon;
|
import inf101.v18.rogue101.states.Attack;
|
||||||
import inf101.v18.rogue101.items.weapon.MeleeWeapon;
|
import inf101.v18.rogue101.states.Occupation;
|
||||||
import inf101.v18.rogue101.items.weapon.RangedWeapon;
|
import inf101.v18.rogue101.states.Personality;
|
||||||
import inf101.v18.rogue101.object.Item;
|
|
||||||
import inf101.v18.rogue101.state.AttackType;
|
|
||||||
import inf101.v18.rogue101.state.LifeStage;
|
|
||||||
import inf101.v18.rogue101.state.Occupation;
|
|
||||||
import inf101.v18.rogue101.state.Personality;
|
|
||||||
import inf101.v18.util.NPCHelper;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
/**
|
public class Girl implements INonPlayer {
|
||||||
* An enemy NPC
|
|
||||||
*/
|
|
||||||
public class Girl extends Enemy {
|
|
||||||
|
|
||||||
private final String name = randomName();
|
private final String name = randomName();
|
||||||
private final LifeStage lifeStage = LifeStage.getRandom();
|
private final Age age = Age.getRandom();
|
||||||
private final Personality personality = Personality.getRandom();
|
private final Personality personality = Personality.getRandom();
|
||||||
private final Occupation occupation = Occupation.getRandom();
|
private final Occupation occupation = Occupation.getRandom();
|
||||||
private int maxHp;
|
private int maxhp;
|
||||||
|
private int hp;
|
||||||
private int attack;
|
private int attack;
|
||||||
private int defence;
|
private int defence;
|
||||||
private int visibility;
|
private int visibility;
|
||||||
private int damage;
|
private int damage;
|
||||||
private List<GridDirection> damageDirections;
|
private final Backpack backpack = new Backpack();
|
||||||
private static final Random random = new Random();
|
private static final Random random = new Random();
|
||||||
private List<Class<?>> validItems;
|
private List<Class<?>> validItems;
|
||||||
private static final String[] nameList = {"Milk", "Salad", "Mikan", "Rima", "Suki", "Miki", "Hinata", "Haruna",
|
private static final String[] namelist = {"Milk", "Salad", "Mikan", "Rima", "Suki", "Miki", "Hinata", "Haruna", "Asuna"};
|
||||||
"Asuna"};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiates a new Girl enemy NPC
|
|
||||||
*/
|
|
||||||
public Girl() {
|
public Girl() {
|
||||||
setStats();
|
setStats();
|
||||||
setValidItems();
|
setValidItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setStats() {
|
private void setStats() {
|
||||||
// Set stats based on the NPC's age
|
switch (age) {
|
||||||
switch (lifeStage) {
|
|
||||||
case TODDLER:
|
case TODDLER:
|
||||||
maxHp = 30;
|
maxhp = 30;
|
||||||
attack = 10;
|
attack = 10;
|
||||||
defence = 50;
|
defence = 50;
|
||||||
damage = 1 + random.nextInt(5);
|
damage = 1 + random.nextInt(5);
|
||||||
visibility = 1;
|
visibility = 1;
|
||||||
break;
|
break;
|
||||||
case CHILD:
|
case CHILD:
|
||||||
maxHp = 50;
|
maxhp = 50;
|
||||||
attack = 20;
|
attack = 20;
|
||||||
defence = 40;
|
defence = 40;
|
||||||
damage = 2 + random.nextInt(5);
|
damage = 2 + random.nextInt(5);
|
||||||
visibility = 2;
|
visibility = 2;
|
||||||
break;
|
break;
|
||||||
case TEEN:
|
case TEEN:
|
||||||
maxHp = 70;
|
maxhp = 70;
|
||||||
attack = 25;
|
attack = 25;
|
||||||
defence = 30;
|
defence = 30;
|
||||||
damage = 3 + random.nextInt(5);
|
damage = 3 + random.nextInt(5);
|
||||||
visibility = 3;
|
visibility = 3;
|
||||||
break;
|
break;
|
||||||
case ADULT:
|
case ADULT:
|
||||||
maxHp = 100;
|
maxhp = 100;
|
||||||
attack = 30;
|
attack = 30;
|
||||||
defence = 20;
|
defence = 20;
|
||||||
damage = 4 + random.nextInt(5);
|
damage = 4 + random.nextInt(5);
|
||||||
visibility = 4;
|
visibility = 4;
|
||||||
break;
|
break;
|
||||||
case ELDER:
|
case ELDER:
|
||||||
maxHp = 70;
|
maxhp = 70;
|
||||||
attack = 15;
|
attack = 15;
|
||||||
defence = 35;
|
defence = 35;
|
||||||
damage = 3 + random.nextInt(5);
|
damage = 3 + random.nextInt(5);
|
||||||
visibility = 2;
|
visibility = 2;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Improve stats based on occupation
|
|
||||||
if (occupation == Occupation.KNIGHT) {
|
if (occupation == Occupation.KNIGHT) {
|
||||||
attack += 10; //Knights are quite powerful.
|
attack += 10; //Knights are quite powerful.
|
||||||
} else if (occupation == Occupation.MAGE) {
|
} else if (occupation == Occupation.MAGE) {
|
||||||
attack += 5; // Mages have lesser range than archers, but more damage.
|
attack += 5; // Mages have lesser range than bowsmen, but more damage.
|
||||||
}
|
}
|
||||||
// Add random stat improvements
|
maxhp += (int)(random.nextGaussian() * 10);
|
||||||
maxHp += (int) (random.nextGaussian() * 10);
|
hp = maxhp;
|
||||||
hp = maxHp;
|
attack += (int)(random.nextGaussian() * 5);
|
||||||
attack += (int) (random.nextGaussian() * 5);
|
defence += (int)(random.nextGaussian() * 5);
|
||||||
defence += (int) (random.nextGaussian() * 5);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Has a chance of giving a weapon to a girl.
|
* Has a chance of giving a weapon to a girl.
|
||||||
*
|
*
|
||||||
* @param lvl The current map level
|
* @param lvl The current map level
|
||||||
*/
|
*/
|
||||||
public void giveWeapon(int lvl) {
|
public void giveWeapon(int lvl) {
|
||||||
// Randomly grant a weapon based on occupation.
|
if (random.nextInt(5) < lvl) {
|
||||||
// The chance of being granted a weapon increases for each level
|
switch (occupation) {
|
||||||
if (random.nextInt(5) >= lvl) {
|
case KNIGHT:
|
||||||
return;
|
backpack.add(new Sword());
|
||||||
}
|
break;
|
||||||
switch (occupation) {
|
case MAGE:
|
||||||
case KNIGHT:
|
backpack.add(new Staff());
|
||||||
backpack.add(new BasicSword());
|
break;
|
||||||
break;
|
case BOWSMAN:
|
||||||
case MAGE:
|
backpack.add(new Bow());
|
||||||
backpack.add(new FireStaff());
|
}
|
||||||
break;
|
|
||||||
case ARCHER:
|
|
||||||
backpack.add(new BasicBow());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,70 +112,48 @@ public class Girl extends Enemy {
|
|||||||
private void setValidItems() {
|
private void setValidItems() {
|
||||||
validItems = new ArrayList<>();
|
validItems = new ArrayList<>();
|
||||||
switch (occupation) {
|
switch (occupation) {
|
||||||
case ARCHER:
|
case BOWSMAN:
|
||||||
validItems.add(RangedWeapon.class);
|
validItems.add(IRangedWeapon.class);
|
||||||
break;
|
break;
|
||||||
case MAGE:
|
case MAGE:
|
||||||
validItems.add(MagicWeapon.class);
|
validItems.add(IMagicWeapon.class);
|
||||||
break;
|
break;
|
||||||
case KNIGHT:
|
case KNIGHT:
|
||||||
validItems.add(MeleeWeapon.class);
|
validItems.add(IMeleeWeapon.class);
|
||||||
}
|
}
|
||||||
validItems.add(BuffItem.class);
|
validItems.add(IBuffItem.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Picks a "random" name
|
* Picks a "random" name
|
||||||
*
|
*
|
||||||
* @return A random name
|
* @return A random name
|
||||||
*/
|
*/
|
||||||
private String randomName() {
|
private String randomName() {
|
||||||
return nameList[random.nextInt(nameList.length)] + "-chan";
|
return namelist[random.nextInt(namelist.length)] + "-chan";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doTurn(Game game) {
|
public void doTurn(IGame game) {
|
||||||
currentLocation = game.getLocation();
|
|
||||||
List<GridDirection> damageDirections = this.damageDirections;
|
|
||||||
this.damageDirections = null;
|
|
||||||
|
|
||||||
// If this NPC has space, pick up any items on the ground
|
|
||||||
if (backpack.hasSpace()) {
|
if (backpack.hasSpace()) {
|
||||||
Item item = NPCHelper.pickUp(game, validItems);
|
IItem item = NPC.pickUp(game, validItems);
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
backpack.add(item);
|
backpack.add(item);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (NPCHelper.trackItem(game, validItems)) {
|
if (NPC.trackItem(game, validItems)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: If attacked on the last turn, flee from the attacker if not within sight range
|
if (personality == Personality.AFRAID && NPC.flee(game)) {
|
||||||
if ((personality == Personality.AFRAID || hp < 0.1 * maxHp)) {
|
return;
|
||||||
// Flee from the nearest enemy
|
|
||||||
if (NPCHelper.flee(game)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flee in the direction this NPC was damaged from. This allows the NPC to flee from ranged attacks outside
|
|
||||||
// visible range.
|
|
||||||
if (damageDirections != null) {
|
|
||||||
for (GridDirection direction : damageDirections) {
|
|
||||||
if (game.canGo(direction)) {
|
|
||||||
game.move(direction);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attack if necessary
|
|
||||||
if (willAttack(game) && attack(game)) {
|
if (willAttack(game) && attack(game)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move a random direction if possible
|
|
||||||
List<GridDirection> possibleMoves = game.getPossibleMoves();
|
List<GridDirection> possibleMoves = game.getPossibleMoves();
|
||||||
if (!possibleMoves.isEmpty()) {
|
if (!possibleMoves.isEmpty()) {
|
||||||
Collections.shuffle(possibleMoves);
|
Collections.shuffle(possibleMoves);
|
||||||
@ -205,22 +164,22 @@ public class Girl extends Enemy {
|
|||||||
/**
|
/**
|
||||||
* Tries to attack with an attack specified by the Girl's occupation
|
* Tries to attack with an attack specified by the Girl's occupation
|
||||||
*
|
*
|
||||||
* @param game An IGame object
|
* @param game An IGame object
|
||||||
* @return True if an attack occurred. False otherwise
|
* @return True if an attack occurred. False otherwise
|
||||||
*/
|
*/
|
||||||
private boolean attack(Game game) {
|
private boolean attack(IGame game) {
|
||||||
switch (occupation) {
|
switch (occupation) {
|
||||||
case KNIGHT:
|
case KNIGHT:
|
||||||
if (NPCHelper.tryAttack(game, 1, AttackType.MELEE)) {
|
if (NPC.tryAttack(game, 1, Attack.MELEE)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MAGE:
|
case MAGE:
|
||||||
if (NPCHelper.tryAttack(game, getVision() / 2 + getVision() % 2 == 0 ? 0 : 1, AttackType.MAGIC)) {
|
if (NPC.tryAttack(game, getVision() / 2 + getVision() % 2 == 0 ? 0 : 1, Attack.MAGIC)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case ARCHER:
|
case BOWSMAN:
|
||||||
if (NPCHelper.tryAttack(game, getVision(), AttackType.RANGED)) {
|
if (NPC.tryAttack(game, getVision(), Attack.RANGED)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -230,26 +189,23 @@ public class Girl extends Enemy {
|
|||||||
/**
|
/**
|
||||||
* Checks if the current girl will try to attack.
|
* Checks if the current girl will try to attack.
|
||||||
*
|
*
|
||||||
* @param game An IGame object
|
* @param game An IGame object
|
||||||
* @return True if the girl will attack. False otherwise
|
* @return True if the girl will attack. False otherwise
|
||||||
*/
|
*/
|
||||||
private boolean willAttack(Game game) {
|
private boolean willAttack(IGame game) {
|
||||||
boolean attack = false;
|
boolean attack = false;
|
||||||
switch (personality) {
|
switch (personality) {
|
||||||
case CALM:
|
case CALM:
|
||||||
// Attack if previously attacked
|
if (hp < maxhp) {
|
||||||
if (hp < maxHp) {
|
|
||||||
attack = true;
|
attack = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case AFRAID:
|
case AFRAID:
|
||||||
// Attack if cornered
|
|
||||||
if (game.getPossibleMoves().isEmpty()) {
|
if (game.getPossibleMoves().isEmpty()) {
|
||||||
attack = true;
|
attack = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case AGGRESSIVE:
|
case AGRESSIVE:
|
||||||
// Always attack
|
|
||||||
attack = true;
|
attack = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -272,13 +228,13 @@ public class Girl extends Enemy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefense() {
|
public int getDefence() {
|
||||||
return defence;
|
return defence;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getMaxHealth() {
|
public int getMaxHealth() {
|
||||||
return maxHp;
|
return maxhp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -307,12 +263,18 @@ public class Girl extends Enemy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int handleDamage(Game game, Item source, int amount) {
|
public IItem getItem(Class<?> type) {
|
||||||
super.handleDamage(game, source, amount);
|
for (IItem item : backpack.getContent()) {
|
||||||
Location location = game.getMap().getLocation(source);
|
if (type.isInstance(item)) {
|
||||||
Location location2 = game.getMap().getLocation(this);
|
return item;
|
||||||
damageDirections = game.locationDirection(location, location2);
|
}
|
||||||
return amount;
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int handleDamage(IGame game, IItem source, int amount) {
|
||||||
|
hp -= amount;
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,97 +1,145 @@
|
|||||||
package inf101.v18.rogue101.events;
|
package inf101.v18.rogue101.events;
|
||||||
|
|
||||||
import inf101.v18.rogue101.game.Game;
|
import inf101.v18.rogue101.game.IGame;
|
||||||
import inf101.v18.rogue101.object.Item;
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Example implementation of events – could be used to have more complex
|
* Example implementation of events – could be used to have more complex
|
||||||
* behaviour than just attack/defend/get damaged/pickup/drop.
|
* behaviour than just attack/defend/get damaged/pickup/drop.
|
||||||
*
|
*
|
||||||
* @param <T> Relevant extra data for this particular event
|
|
||||||
* @author anya
|
* @author anya
|
||||||
|
*
|
||||||
|
* @param <T>
|
||||||
|
* Relevant extra data for this particular event
|
||||||
*/
|
*/
|
||||||
public class GameEvent<T> implements IEvent<T> {
|
public class GameEvent<T> implements IEvent<T> {
|
||||||
|
public static final String ATTACK_FAILURE = "attackFailure";
|
||||||
|
public static final String ATTACK_SUCCESS = "attackSuccess";
|
||||||
|
public static final String DEFEND_FAILURE = "defendFailure";
|
||||||
|
public static final String DEFEND_SUCCESS = "defendSuccess";
|
||||||
|
public static final String EATEN = "eaten";
|
||||||
|
|
||||||
private final String name;
|
/**
|
||||||
private final Item source;
|
* Create and send events for an attack.
|
||||||
private final Item target;
|
* <p>
|
||||||
private T value;
|
* 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 Game game;
|
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 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, Game game, Item source, Item target, T value) {
|
|
||||||
this.name = name;
|
|
||||||
this.game = game;
|
|
||||||
this.source = source;
|
|
||||||
this.target = target;
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new game event
|
* Create a new game event
|
||||||
*
|
*
|
||||||
* @param name The name is used when checking which event this is / determine its
|
* @param name
|
||||||
* “meaning”
|
* The name is used when checking which event this is / determine its
|
||||||
* @param source The item that caused the event, or <code>null</code> if
|
* “meaning”
|
||||||
* unknown/not relevant
|
* @param game
|
||||||
*/
|
* The game, or <code>null</code> if unknown/not relevant
|
||||||
public GameEvent(String name, Item source) {
|
* @param source
|
||||||
this(name, null, source, null, null);
|
* 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, 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
|
* Create a new game event
|
||||||
*
|
*
|
||||||
* @param name The name is used when checking which event this is / determine its
|
* @param name
|
||||||
* “meaning”
|
* The name is used when checking which event this is / determine its
|
||||||
* @param source The item that caused the event, or <code>null</code> if
|
* “meaning”
|
||||||
* unknown/not relevant
|
* @param source
|
||||||
* @param value Arbitrary extra data
|
* The item that caused the event, or <code>null</code> if
|
||||||
*/
|
* unknown/not relevant
|
||||||
public GameEvent(String name, Item source, T value) {
|
*/
|
||||||
this(name, null, source, null, value);
|
public GameEvent(String name, IItem source) {
|
||||||
}
|
this(name, null, source, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public T getData() {
|
* Create a new game event
|
||||||
return value;
|
*
|
||||||
}
|
* @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
|
@Override
|
||||||
public String getEventName() {
|
public T getData() {
|
||||||
return name;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Game getGame() {
|
public String getEventName() {
|
||||||
return game;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Item getSource() {
|
public IGame getGame() {
|
||||||
return source;
|
return game;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Item getTarget() {
|
public IItem getSource() {
|
||||||
return target;
|
return source;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setData(T value) {
|
public IItem getTarget() {
|
||||||
this.value = value;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setData(T value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package inf101.v18.rogue101.events;
|
package inf101.v18.rogue101.events;
|
||||||
|
|
||||||
import inf101.v18.rogue101.game.Game;
|
import inf101.v18.rogue101.game.IGame;
|
||||||
import inf101.v18.rogue101.object.Item;
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An “event” is something that happens in the game, typically due to an actor
|
* An “event” is something that happens in the game, typically due to an actor
|
||||||
@ -20,50 +20,53 @@ import inf101.v18.rogue101.object.Item;
|
|||||||
* <p>
|
* <p>
|
||||||
* This system is fairly simplistic, and you're not expected to make use of it.
|
* This system is fairly simplistic, and you're not expected to make use of it.
|
||||||
*
|
*
|
||||||
* @param <T> Type of the extra data
|
|
||||||
* @author anya
|
* @author anya
|
||||||
|
*
|
||||||
|
* @param <T>
|
||||||
|
* Type of the extra data
|
||||||
*/
|
*/
|
||||||
public interface IEvent<T> {
|
public interface IEvent<T> {
|
||||||
/**
|
/**
|
||||||
* @return Extra data stored in this event
|
* @return Extra data stored in this event
|
||||||
*/
|
*/
|
||||||
T getData();
|
T getData();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The name of this event
|
* @return The name of this event
|
||||||
*/
|
*/
|
||||||
String getEventName();
|
String getEventName();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Not all events need to be connected to the game, but you can use this if you
|
* 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).
|
* need it (e.g., for showing a message, or adding something to the map).
|
||||||
* <p>
|
* <p>
|
||||||
* The result might be null.
|
* The result might be null.
|
||||||
*
|
*
|
||||||
* @return The game associated with this event, or null.
|
* @return The game associated with this event, or null.
|
||||||
*/
|
*/
|
||||||
Game getGame();
|
IGame getGame();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The source is the item that “caused” the event
|
* The source is the item that “caused” the event
|
||||||
* <p>
|
* <p>
|
||||||
* Could be null if the source is unknown or not relevant.
|
* Could be null if the source is unknown or not relevant.
|
||||||
*
|
*
|
||||||
* @return The source of this event
|
* @return The source of this event
|
||||||
*/
|
*/
|
||||||
Item getSource();
|
IItem getSource();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The target is the item that is affected by the event
|
* The target is the item that is affected by the event
|
||||||
* <p>
|
* <p>
|
||||||
* Could be null if the target is unknown or not relevant.
|
* Could be null if the target is unknown or not relevant.
|
||||||
*
|
*
|
||||||
* @return The target of this event, or null
|
* @return The target of this event, or null
|
||||||
*/
|
*/
|
||||||
Item getTarget();
|
IItem getTarget();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param value Extra data to store in this event
|
* @param value
|
||||||
*/
|
* Extra data to store in this event
|
||||||
void setData(T value);
|
*/
|
||||||
|
void setData(T value);
|
||||||
}
|
}
|
||||||
|
@ -1,82 +1,82 @@
|
|||||||
package inf101.v18.rogue101.examples;
|
package inf101.v18.rogue101.examples;
|
||||||
|
|
||||||
import inf101.v18.gfx.gfxmode.ITurtle;
|
import inf101.v18.gfx.gfxmode.ITurtle;
|
||||||
import inf101.v18.rogue101.game.Game;
|
import inf101.v18.rogue101.game.IGame;
|
||||||
import inf101.v18.rogue101.object.Item;
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
public class Carrot implements Item {
|
public class Carrot implements IItem {
|
||||||
private int hp = getMaxHealth();
|
private int hp = getMaxHealth();
|
||||||
|
|
||||||
public void doTurn() {
|
public void doTurn() {
|
||||||
hp = Math.min(hp + 1, getMaxHealth());
|
hp = Math.min(hp + 1, getMaxHealth());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean draw(ITurtle painter, double w, double h) {
|
public boolean draw(ITurtle painter, double w, double h) {
|
||||||
painter.save();
|
painter.save();
|
||||||
painter.turn(75);
|
painter.turn(75);
|
||||||
double size = ((double) hp + getMaxHealth()) / (2.0 * getMaxHealth());
|
double size = ((double) hp + getMaxHealth()) / (2.0 * getMaxHealth());
|
||||||
double carrotLength = size * h * .6;
|
double carrotLength = size * h * .6;
|
||||||
double carrotWidth = size * h * .4;
|
double carrotWidth = size * h * .4;
|
||||||
painter.jump(-carrotLength / 6);
|
painter.jump(-carrotLength / 6);
|
||||||
painter.shape().ellipse().width(carrotLength).height(carrotWidth).fillPaint(Color.ORANGE).fill();
|
painter.shape().ellipse().width(carrotLength).height(carrotWidth).fillPaint(Color.ORANGE).fill();
|
||||||
painter.jump(carrotLength / 2);
|
painter.jump(carrotLength / 2);
|
||||||
painter.setInk(Color.FORESTGREEN);
|
painter.setInk(Color.FORESTGREEN);
|
||||||
for (int i = -1; i < 2; i++) {
|
for (int i = -1; i < 2; i++) {
|
||||||
painter.save();
|
painter.save();
|
||||||
painter.turn(20 * i);
|
painter.turn(20 * i);
|
||||||
painter.draw(carrotLength / 3);
|
painter.draw(carrotLength / 3);
|
||||||
painter.restore();
|
painter.restore();
|
||||||
}
|
}
|
||||||
painter.restore();
|
painter.restore();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCurrentHealth() {
|
public int getCurrentHealth() {
|
||||||
return hp;
|
return hp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefense() {
|
public int getDefence() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public double getHealthStatus() {
|
public double getHealthStatus() {
|
||||||
return getCurrentHealth() / getMaxHealth();
|
return getCurrentHealth() / getMaxHealth();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getMaxHealth() {
|
public int getMaxHealth() {
|
||||||
return 23;
|
return 23;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "carrot";
|
return "carrot";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getSize() {
|
public int getSize() {
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getSymbol() {
|
public String getSymbol() {
|
||||||
return "C";
|
return "C";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int handleDamage(Game game, Item source, int amount) {
|
public int handleDamage(IGame game, IItem source, int amount) {
|
||||||
hp -= amount;
|
hp -= amount;
|
||||||
|
|
||||||
if (hp < 0) {
|
if (hp < 0) {
|
||||||
// we're all eaten!
|
// we're all eaten!
|
||||||
hp = 0;
|
hp = 0;
|
||||||
}
|
}
|
||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
51
src/main/java/inf101/v18/rogue101/examples/ExampleItem.java
Normal file
51
src/main/java/inf101/v18/rogue101/examples/ExampleItem.java
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package inf101.v18.rogue101.examples;
|
||||||
|
|
||||||
|
import inf101.v18.gfx.gfxmode.ITurtle;
|
||||||
|
import inf101.v18.rogue101.game.IGame;
|
||||||
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
|
|
||||||
|
public class ExampleItem implements IItem {
|
||||||
|
private int hp = getMaxHealth();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean draw(ITurtle painter, double w, double h) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentHealth() {
|
||||||
|
return hp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDefence() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxHealth() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "strange model of an item";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSymbol() {
|
||||||
|
return "X";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int handleDamage(IGame game, IItem source, int amount) {
|
||||||
|
hp -= amount;
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,123 +1,122 @@
|
|||||||
package inf101.v18.rogue101.examples;
|
package inf101.v18.rogue101.examples;
|
||||||
|
|
||||||
import inf101.v18.gfx.gfxmode.ITurtle;
|
|
||||||
import inf101.v18.grid.GridDirection;
|
|
||||||
import inf101.v18.rogue101.game.Game;
|
|
||||||
import inf101.v18.rogue101.object.Item;
|
|
||||||
import inf101.v18.rogue101.object.NonPlayerCharacter;
|
|
||||||
import inf101.v18.rogue101.state.AttackType;
|
|
||||||
import inf101.v18.util.NPCHelper;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class Rabbit implements NonPlayerCharacter {
|
import inf101.v18.gfx.gfxmode.ITurtle;
|
||||||
private int food = 0;
|
import inf101.v18.grid.GridDirection;
|
||||||
private int hp = getMaxHealth();
|
import inf101.v18.rogue101.game.IGame;
|
||||||
private static final List<Class<?>> validItems = new ArrayList<>();
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
|
import inf101.v18.rogue101.objects.INonPlayer;
|
||||||
|
import inf101.v18.rogue101.shared.NPC;
|
||||||
|
import inf101.v18.rogue101.states.Attack;
|
||||||
|
|
||||||
static {
|
public class Rabbit implements INonPlayer {
|
||||||
validItems.add(Carrot.class);
|
private int food = 0;
|
||||||
}
|
private int hp = getMaxHealth();
|
||||||
|
private static final List<Class<?>> validItems = new ArrayList<>();
|
||||||
|
static {
|
||||||
|
validItems.add(Carrot.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doTurn(Game game) {
|
public void doTurn(IGame game) {
|
||||||
if (food == 0) {
|
if (food == 0) {
|
||||||
hp--;
|
hp--;
|
||||||
} else {
|
} else {
|
||||||
food--;
|
food--;
|
||||||
}
|
}
|
||||||
if (hp < 1) {
|
if (hp < 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NPCHelper.tryAttack(game, 1, AttackType.MELEE)) {
|
if (NPC.tryAttack(game, 1, Attack.MELEE)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Item item : game.getLocalItems()) {
|
for (IItem item : game.getLocalItems()) {
|
||||||
if (item instanceof Carrot) {
|
if (item instanceof Carrot) {
|
||||||
System.out.println("found carrot!");
|
System.out.println("found carrot!");
|
||||||
int eaten = item.handleDamage(game, this, getDamage());
|
int eaten = item.handleDamage(game, this, getDamage());
|
||||||
if (eaten > 0) {
|
if (eaten > 0) {
|
||||||
System.out.println("ate carrot worth " + eaten + "!");
|
System.out.println("ate carrot worth " + eaten + "!");
|
||||||
food += 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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NPCHelper.trackItem(game, validItems)) {
|
if (NPC.trackItem(game, validItems)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<GridDirection> possibleMoves = game.getPossibleMoves();
|
List<GridDirection> possibleMoves = game.getPossibleMoves();
|
||||||
if (!possibleMoves.isEmpty()) {
|
if (!possibleMoves.isEmpty()) {
|
||||||
Collections.shuffle(possibleMoves);
|
Collections.shuffle(possibleMoves);
|
||||||
game.move(possibleMoves.get(0));
|
game.move(possibleMoves.get(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean draw(ITurtle painter, double w, double h) {
|
public boolean draw(ITurtle painter, double w, double h) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getAttack() {
|
public int getAttack() {
|
||||||
return 10;
|
return 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCurrentHealth() {
|
public int getCurrentHealth() {
|
||||||
return hp;
|
return hp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDamage() {
|
public int getDamage() {
|
||||||
return 5;
|
return 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefense() {
|
public int getDefence() {
|
||||||
return 10;
|
return 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getMaxHealth() {
|
public int getMaxHealth() {
|
||||||
return 30;
|
return 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "Rabbit";
|
return "Rabbit";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getSize() {
|
public int getSize() {
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getVision() {
|
public int getVision() {
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Item getItem(Class<?> type) {
|
public IItem getItem(Class<?> type) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getSymbol() {
|
public String getSymbol() {
|
||||||
return hp > 0 ? "R" : "¤";
|
return hp > 0 ? "R" : "¤";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int handleDamage(Game game, Item source, int amount) {
|
public int handleDamage(IGame game, IItem source, int amount) {
|
||||||
hp -= amount;
|
hp -= amount;
|
||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
340
src/main/java/inf101/v18/rogue101/game/IGame.java
Normal file
340
src/main/java/inf101/v18/rogue101/game/IGame.java
Normal file
@ -0,0 +1,340 @@
|
|||||||
|
package inf101.v18.rogue101.game;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import inf101.v18.gfx.gfxmode.ITurtle;
|
||||||
|
import inf101.v18.gfx.textmode.Printer;
|
||||||
|
import inf101.v18.grid.GridDirection;
|
||||||
|
import inf101.v18.grid.ILocation;
|
||||||
|
import inf101.v18.rogue101.map.IMapView;
|
||||||
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
|
import inf101.v18.rogue101.objects.IActor;
|
||||||
|
import inf101.v18.rogue101.objects.INonPlayer;
|
||||||
|
import inf101.v18.rogue101.objects.IPlayer;
|
||||||
|
import inf101.v18.rogue101.states.Attack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Game interface
|
||||||
|
* <p>
|
||||||
|
* The game has a map and a current {@link IActor} (the player or non-player
|
||||||
|
* whose turn it currently is). The game also knows the current location of the
|
||||||
|
* actor. Most methods that deal with the map will use this current location –
|
||||||
|
* they are meant to be used by the current actor for exploring or interacting
|
||||||
|
* with its surroundings.
|
||||||
|
* <p>
|
||||||
|
* In other words, you should avoid calling most of these methods if you're not
|
||||||
|
* the current actor. You know you're the current actor when you're inside your
|
||||||
|
* {@link IPlayer#keyPressed()} or {@link INonPlayer#doTurn()} method.
|
||||||
|
*
|
||||||
|
* @author anya
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface IGame {
|
||||||
|
/**
|
||||||
|
* Add an item to the current location
|
||||||
|
* <p>
|
||||||
|
* If the item is an actor, it won't move until the next turn.
|
||||||
|
*
|
||||||
|
* @param item
|
||||||
|
*/
|
||||||
|
void addItem(IItem item);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new item (identified by symbol) to the current location
|
||||||
|
* <p>
|
||||||
|
* If the item is an actor, it won't move until the next turn.
|
||||||
|
*
|
||||||
|
* @param sym
|
||||||
|
*/
|
||||||
|
void addItem(String sym);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform an attack on the target.
|
||||||
|
* <p>
|
||||||
|
* Will use your {@link IActor#getAttack()} against the target's
|
||||||
|
* {@link IItem#getDefence()}, and then possiblyu find the damage you're dealing
|
||||||
|
* with {@link IActor#getDamage()} and inform the target using
|
||||||
|
* {@link IItem#handleDamage(IGame, IItem, int)}
|
||||||
|
*
|
||||||
|
* @param dir
|
||||||
|
* Direction
|
||||||
|
* @param target
|
||||||
|
* A target item, which should be in the neighbouring square in the
|
||||||
|
* given direction
|
||||||
|
* @return Your new location if the attack resulted in you moving
|
||||||
|
*/
|
||||||
|
ILocation attack(GridDirection dir, IItem target);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dir
|
||||||
|
* @return True if it's possible to move in the given direction
|
||||||
|
*/
|
||||||
|
boolean canGo(GridDirection dir);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new item based on a text symbol
|
||||||
|
* <p>
|
||||||
|
* The item won't be added to the map unless you call {@link #addItem(IItem)}.
|
||||||
|
*
|
||||||
|
* @param symbol
|
||||||
|
* @return The new item
|
||||||
|
*/
|
||||||
|
IItem createItem(String symbol);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a message in the debug area on the screen (bottom line)
|
||||||
|
*
|
||||||
|
* @param s
|
||||||
|
* A message
|
||||||
|
*/
|
||||||
|
void displayDebug(String s);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a message in the message area on the screen (below the map and the
|
||||||
|
* status line)
|
||||||
|
*
|
||||||
|
* @param s
|
||||||
|
* A message
|
||||||
|
*/
|
||||||
|
void displayMessage(String s);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a status message in the status area on the screen (right below the
|
||||||
|
* map)
|
||||||
|
*
|
||||||
|
* @param s
|
||||||
|
* A message
|
||||||
|
*/
|
||||||
|
void displayStatus(String s);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a message in the message area on the screen (below the map and the
|
||||||
|
* status line)
|
||||||
|
*
|
||||||
|
* @param s
|
||||||
|
* A message
|
||||||
|
* @see String#format(String, Object...)
|
||||||
|
*/
|
||||||
|
void formatDebug(String s, Object... args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a formatted message in the message area on the screen (below the map
|
||||||
|
* and the status line)
|
||||||
|
*
|
||||||
|
* @param s
|
||||||
|
* A message
|
||||||
|
* @see String#format(String, Object...)
|
||||||
|
*/
|
||||||
|
void formatMessage(String s, Object... args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a formatted status message in the status area on the screen (right
|
||||||
|
* below the map)
|
||||||
|
*
|
||||||
|
* @param s
|
||||||
|
* A message
|
||||||
|
* @see String#format(String, Object...)
|
||||||
|
*/
|
||||||
|
void formatStatus(String s, Object... args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pick up an item
|
||||||
|
* <p>
|
||||||
|
* This should be used by IActors who want to pick up an item and carry it. The
|
||||||
|
* item will be returned if picking it succeeded (the actor <em>might</em> also
|
||||||
|
* make a mistake and pick up the wrong item!).
|
||||||
|
*
|
||||||
|
* @param item
|
||||||
|
* An item, should be in the current map location
|
||||||
|
* @return The item that was picked up (normally <code>item</code>), or
|
||||||
|
* <code>null</code> if it failed
|
||||||
|
*/
|
||||||
|
IItem pickUp(IItem item);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop an item
|
||||||
|
* <p>
|
||||||
|
* This should be used by IActors who are carrying an item and want to put it on
|
||||||
|
* the ground. Check the return value to see if it succeeded.
|
||||||
|
*
|
||||||
|
* @param item
|
||||||
|
* An item, should be carried by the current actor and not already be
|
||||||
|
* on the map
|
||||||
|
* @return True if the item was placed on the map, false means you're still
|
||||||
|
* holding it
|
||||||
|
*/
|
||||||
|
boolean drop(IItem item);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the same as drop, but for a specified location.
|
||||||
|
*
|
||||||
|
* @param loc The location to drop the location
|
||||||
|
* @param item The item to drop
|
||||||
|
* @return True if the item was dropped. False otherwise
|
||||||
|
*/
|
||||||
|
boolean dropAt(ILocation loc, IItem item);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the unused graphics area (you can fill it with whatever you want!)
|
||||||
|
*/
|
||||||
|
void clearFreeGraphicsArea();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the unused text area (you can fill it with whatever you want!)
|
||||||
|
*/
|
||||||
|
void clearFreeTextArea();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the bounds of the free graphics area.
|
||||||
|
* <p>
|
||||||
|
* You can fill this with whatever you want, using {@link #getPainter()} and
|
||||||
|
* {@link #clearFreeGraphicsArea()}.
|
||||||
|
*
|
||||||
|
* @return Array of coordinates; ([0],[1]) are the top-left corner, and
|
||||||
|
* ([2],[3]) are the bottom-right corner (exclusive).
|
||||||
|
*/
|
||||||
|
double[] getFreeGraphicsAreaBounds();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the bounds of the free text area.
|
||||||
|
* <p>
|
||||||
|
* You can fill this with whatever you want, using {@link #getPrinter()} and
|
||||||
|
* {@link #clearFreeTextArea()}.
|
||||||
|
* <p>
|
||||||
|
* You'll probably want to use something like:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* int[] bounds = getFreeTextArea();
|
||||||
|
* int x = bounds[0];
|
||||||
|
* int y = bounds[1];
|
||||||
|
* game.getPrinter().printAt(x, y++, "Hello");
|
||||||
|
* game.getPrinter().printAt(x, y++, "Do you have any carrot cake?", Color.ORANGE);
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @return Array of column/line numbers; ([0],[1]) is the top-left corner, and
|
||||||
|
* ([2],[3]) is the bottom-right corner (inclusive).
|
||||||
|
*/
|
||||||
|
int[] getFreeTextAreaBounds();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link #getFreeGraphicsAreaBounds()}, {@link #clearFreeGraphicsArea()}.
|
||||||
|
*
|
||||||
|
* @return A Turtle, for painting graphics
|
||||||
|
*/
|
||||||
|
ITurtle getPainter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link #getFreeTextAreaBounds()}, {@link #clearFreeTextArea()}.
|
||||||
|
*
|
||||||
|
* @return A printer, for printing text
|
||||||
|
*/
|
||||||
|
Printer getPrinter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The height of the map
|
||||||
|
*/
|
||||||
|
int getHeight();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return A list of the non-actor items at the current map location
|
||||||
|
*/
|
||||||
|
List<IItem> getLocalItems();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current actor's location.
|
||||||
|
* <p>
|
||||||
|
* You should only call this from an IActor that is currently doing its move.
|
||||||
|
*
|
||||||
|
* @return Location of the current actor
|
||||||
|
*/
|
||||||
|
ILocation getLocation();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current actor
|
||||||
|
* <p>
|
||||||
|
* You can check if it's your move by doing game.getActor()==this.
|
||||||
|
*
|
||||||
|
* @return The current actor (i.e., the (IPlayer/INonPlayer) player who's turn it currently is)
|
||||||
|
*/
|
||||||
|
IActor getActor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the neighbouring map location in direction <code>dir</code>
|
||||||
|
* <p>
|
||||||
|
* Same as <code>getLocation().go(dir)</code>
|
||||||
|
*
|
||||||
|
* @param dir
|
||||||
|
* A direction
|
||||||
|
* @return A location, or <code>null</code> if the location would be outside the
|
||||||
|
* map
|
||||||
|
*/
|
||||||
|
ILocation getLocation(GridDirection dir);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The map
|
||||||
|
*/
|
||||||
|
IMapView getMap();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return A list of directions we can move in, for use with
|
||||||
|
* {@link #move(GridDirection)}
|
||||||
|
*/
|
||||||
|
List<GridDirection> getPossibleMoves();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of all locations that are visible from the current location.
|
||||||
|
* <p>
|
||||||
|
* The location list is sorted so that nearby locations come earlier in the
|
||||||
|
* list. E.g., if <code>l = getVisible()<code> and <code>i < j</code>then
|
||||||
|
* <code>getLocation().gridDistanceTo(l.get(i)) < getLocation().gridDistanceTo(l.get(j))</code>
|
||||||
|
*
|
||||||
|
* @return A list of grid cells visible from the {@link #getLocation()}
|
||||||
|
*/
|
||||||
|
List<ILocation> getVisible();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Width of the map
|
||||||
|
*/
|
||||||
|
int getWidth();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the current actor in the given direction.
|
||||||
|
* <p>
|
||||||
|
* The new location will be returned.
|
||||||
|
*
|
||||||
|
* @param dir
|
||||||
|
* @return A new location
|
||||||
|
* @throws IllegalMoveException
|
||||||
|
* if moving in that direction is illegal
|
||||||
|
*/
|
||||||
|
ILocation move(GridDirection dir);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a ranged attack on the target.
|
||||||
|
* <p>
|
||||||
|
* Rules for this are up to you!
|
||||||
|
*
|
||||||
|
* @param dir
|
||||||
|
* Direction
|
||||||
|
* @param target
|
||||||
|
* A target item, which should in some square in the given direction
|
||||||
|
* @return Your new location if the attack resulted in you moving (unlikely)
|
||||||
|
*/
|
||||||
|
ILocation rangedAttack(GridDirection dir, IItem target, Attack type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return A random generator
|
||||||
|
*/
|
||||||
|
Random getRandom();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of the best directions to go from current to neighbour.
|
||||||
|
* If the target is positioned diagonally from the start, the direction requiring the most steps is chosen.
|
||||||
|
*
|
||||||
|
* @param current The location to go from
|
||||||
|
* @param neighbour The location to go to
|
||||||
|
* @return A direction
|
||||||
|
*/
|
||||||
|
List<GridDirection> locationDirection(ILocation current, ILocation neighbour);
|
||||||
|
}
|
@ -5,15 +5,16 @@ package inf101.v18.rogue101.game;
|
|||||||
* for example to a position outside the map.
|
* for example to a position outside the map.
|
||||||
*
|
*
|
||||||
* @author larsjaffke
|
* @author larsjaffke
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public class IllegalMoveException extends RuntimeException {
|
public class IllegalMoveException extends RuntimeException {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private static final long serialVersionUID = 7641529271996915740L;
|
private static final long serialVersionUID = 7641529271996915740L;
|
||||||
|
|
||||||
public IllegalMoveException(String message) {
|
public IllegalMoveException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,852 +0,0 @@
|
|||||||
package inf101.v18.rogue101.game;
|
|
||||||
|
|
||||||
import inf101.v18.gfx.Screen;
|
|
||||||
import inf101.v18.gfx.gfxmode.ITurtle;
|
|
||||||
import inf101.v18.gfx.gfxmode.TurtlePainter;
|
|
||||||
import inf101.v18.gfx.textmode.Printer;
|
|
||||||
import inf101.v18.grid.GridDirection;
|
|
||||||
import inf101.v18.grid.IGrid;
|
|
||||||
import inf101.v18.grid.Location;
|
|
||||||
import inf101.v18.rogue101.Main;
|
|
||||||
import inf101.v18.rogue101.enemies.Boss;
|
|
||||||
import inf101.v18.rogue101.enemies.Girl;
|
|
||||||
import inf101.v18.rogue101.examples.Carrot;
|
|
||||||
import inf101.v18.rogue101.examples.Rabbit;
|
|
||||||
import inf101.v18.rogue101.items.buff.Binoculars;
|
|
||||||
import inf101.v18.rogue101.items.buff.BuffItem;
|
|
||||||
import inf101.v18.rogue101.items.buff.Shield;
|
|
||||||
import inf101.v18.rogue101.items.consumable.HealthPotion;
|
|
||||||
import inf101.v18.rogue101.items.consumable.Manga;
|
|
||||||
import inf101.v18.rogue101.items.container.Chest;
|
|
||||||
import inf101.v18.rogue101.items.container.Static;
|
|
||||||
import inf101.v18.rogue101.items.weapon.BasicBow;
|
|
||||||
import inf101.v18.rogue101.items.weapon.BasicSword;
|
|
||||||
import inf101.v18.rogue101.items.weapon.FireStaff;
|
|
||||||
import inf101.v18.rogue101.items.weapon.MagicWeapon;
|
|
||||||
import inf101.v18.rogue101.items.weapon.MeleeWeapon;
|
|
||||||
import inf101.v18.rogue101.items.weapon.RangedWeapon;
|
|
||||||
import inf101.v18.rogue101.items.weapon.Weapon;
|
|
||||||
import inf101.v18.rogue101.map.AGameMap;
|
|
||||||
import inf101.v18.rogue101.map.GameMap;
|
|
||||||
import inf101.v18.rogue101.map.MapReader;
|
|
||||||
import inf101.v18.rogue101.map.MapView;
|
|
||||||
import inf101.v18.rogue101.object.Actor;
|
|
||||||
import inf101.v18.rogue101.object.Dust;
|
|
||||||
import inf101.v18.rogue101.object.FakeWall;
|
|
||||||
import inf101.v18.rogue101.object.Item;
|
|
||||||
import inf101.v18.rogue101.object.NonPlayerCharacter;
|
|
||||||
import inf101.v18.rogue101.object.Player;
|
|
||||||
import inf101.v18.rogue101.object.PlayerCharacter;
|
|
||||||
import inf101.v18.rogue101.object.StairsDown;
|
|
||||||
import inf101.v18.rogue101.object.StairsUp;
|
|
||||||
import inf101.v18.rogue101.object.Wall;
|
|
||||||
import inf101.v18.rogue101.state.AttackType;
|
|
||||||
import inf101.v18.rogue101.state.Sound;
|
|
||||||
import inf101.v18.util.NPCHelper;
|
|
||||||
import javafx.scene.canvas.GraphicsContext;
|
|
||||||
import javafx.scene.input.KeyCode;
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
public class RogueGame implements Game {
|
|
||||||
/**
|
|
||||||
* All the IActors that have things left to do this turn
|
|
||||||
*/
|
|
||||||
private List<Actor> actors = Collections.synchronizedList(new ArrayList<>());
|
|
||||||
/**
|
|
||||||
* For fancy solution to factory problem
|
|
||||||
*/
|
|
||||||
private final Map<String, Supplier<Item>> itemFactories = new HashMap<>();
|
|
||||||
/**
|
|
||||||
* Useful random generator
|
|
||||||
*/
|
|
||||||
private final Random random = new Random();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves the last three messages
|
|
||||||
*/
|
|
||||||
private final List<String> lastMessages = new ArrayList<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The game map. {@link GameMap} gives us a few more details than
|
|
||||||
* {@link MapView} (write access to item lists); the game needs this but
|
|
||||||
* individual items don't.
|
|
||||||
*/
|
|
||||||
private GameMap map;
|
|
||||||
private final List<GameMap> maps = new ArrayList<>();
|
|
||||||
private int currentLVL = 0;
|
|
||||||
private Actor currentActor;
|
|
||||||
private Location currentLocation;
|
|
||||||
private int movePoints = 0;
|
|
||||||
private final ITurtle painter;
|
|
||||||
private final Printer printer;
|
|
||||||
private int numberOfPlayers = 0;
|
|
||||||
private boolean won = false;
|
|
||||||
private List<Location> visible = null;
|
|
||||||
|
|
||||||
public RogueGame(Screen screen, ITurtle painter, Printer printer) {
|
|
||||||
this.painter = painter;
|
|
||||||
this.printer = printer;
|
|
||||||
|
|
||||||
addFactory();
|
|
||||||
|
|
||||||
// NOTE: in a more realistic situation, we will have multiple levels (one map
|
|
||||||
// per level), and (at least for a Roguelike game) the levels should be
|
|
||||||
// generated
|
|
||||||
//
|
|
||||||
// inputGrid will be filled with single-character strings indicating what (if
|
|
||||||
// anything)
|
|
||||||
// should be placed at that map square
|
|
||||||
loadMap("level1.txt", 1);
|
|
||||||
loadMap("level2.txt", 2);
|
|
||||||
loadMap("level3.txt", 3);
|
|
||||||
loadMap("level4.txt", 4);
|
|
||||||
loadMap("level5.txt", 5);
|
|
||||||
map = maps.get(0);
|
|
||||||
map.add(map.getLocation(3, 3), new Player()); //We must be sure there is never more than one player
|
|
||||||
|
|
||||||
// Prints some helpful information.
|
|
||||||
String[] info = {"Controls:", "WASD or arrow keys for movement", "E to pick up an item", "Q to drop an item", "1-0 to choose an item (10=0)", "N to change name", "ENTER to confirm", "R to use a ranged attack", "F to use a magic attack", "C to use a consumable"};
|
|
||||||
for (int i = 0; i < info.length; i++) {
|
|
||||||
this.printer.printAt(Main.COLUMN_RIGHT_SIDE_START, 1 + i, info[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public RogueGame(String mapString) {
|
|
||||||
printer = new Printer(1280, 720);
|
|
||||||
painter = new TurtlePainter(1280, 720);
|
|
||||||
|
|
||||||
addFactory();
|
|
||||||
|
|
||||||
IGrid<String> inputGrid = MapReader.readString(mapString);
|
|
||||||
this.map = new AGameMap(inputGrid.getArea());
|
|
||||||
for (Location loc : inputGrid.locations()) {
|
|
||||||
Item item = createItem(inputGrid.get(loc));
|
|
||||||
if (item != null) {
|
|
||||||
map.add(loc, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addItem(Item item) {
|
|
||||||
map.add(currentLocation, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addItem(String sym) {
|
|
||||||
Item item = createItem(sym);
|
|
||||||
if (item != null) {
|
|
||||||
map.add(currentLocation, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Goes up one floor if possible.
|
|
||||||
*/
|
|
||||||
public void goUp() {
|
|
||||||
if (currentLVL + 1 < maps.size()) {
|
|
||||||
map = maps.get(++currentLVL);
|
|
||||||
if (!map.has(currentLocation, currentActor)) {
|
|
||||||
map.add(currentLocation, currentActor);
|
|
||||||
}
|
|
||||||
actors = new ArrayList<>();
|
|
||||||
displayMessage("You went up the stairs");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Goes down one floor if possible.
|
|
||||||
*/
|
|
||||||
public void goDown() {
|
|
||||||
if (currentLVL > 0) {
|
|
||||||
map = maps.get(--currentLVL);
|
|
||||||
if (!map.has(currentLocation, currentActor)) {
|
|
||||||
map.add(currentLocation, currentActor);
|
|
||||||
}
|
|
||||||
actors = new ArrayList<>();
|
|
||||||
displayMessage("You went down the stairs");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the attack of an IActor based on equipped items.
|
|
||||||
*
|
|
||||||
* @return The attack
|
|
||||||
*/
|
|
||||||
private int getAttack(AttackType type) {
|
|
||||||
int attack = currentActor.getAttack() + random.nextInt(20) + 1;
|
|
||||||
Weapon weapon = NPCHelper.getWeapon(type, currentActor);
|
|
||||||
if (weapon != null) {
|
|
||||||
attack += weapon.getWeaponAttack();
|
|
||||||
}
|
|
||||||
return attack;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the defence of the current target.
|
|
||||||
*
|
|
||||||
* @param target The target to evaluate
|
|
||||||
* @return The defence of the target
|
|
||||||
*/
|
|
||||||
private int getDefence(Item target) {
|
|
||||||
int defence = target.getDefense() + 10;
|
|
||||||
Actor actor = (Actor) target;
|
|
||||||
BuffItem item = (BuffItem) actor.getItem(BuffItem.class);
|
|
||||||
if (item != null) {
|
|
||||||
defence += item.getBuffDefene();
|
|
||||||
}
|
|
||||||
return defence;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the damage done against the current target.
|
|
||||||
*
|
|
||||||
* @param target The target to evaluate.
|
|
||||||
* @return The damage done to the target.
|
|
||||||
*/
|
|
||||||
private int getDamage(Item target, AttackType type) {
|
|
||||||
int damage = currentActor.getDamage();
|
|
||||||
Weapon weapon = NPCHelper.getWeapon(type, currentActor);
|
|
||||||
if (weapon != null) {
|
|
||||||
damage += weapon.getWeaponDamage();
|
|
||||||
}
|
|
||||||
BuffItem buff = (BuffItem) currentActor.getItem(BuffItem.class);
|
|
||||||
if (buff != null) {
|
|
||||||
damage += buff.getBuffDamage();
|
|
||||||
}
|
|
||||||
BuffItem item = (BuffItem) ((Actor) target).getItem(BuffItem.class);
|
|
||||||
if (item != null) {
|
|
||||||
damage -= item.getBuffDamageReduction();
|
|
||||||
}
|
|
||||||
return damage;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Location attack(GridDirection dir, Item target) {
|
|
||||||
Location loc = currentLocation.go(dir);
|
|
||||||
if (!map.has(loc, target)) {
|
|
||||||
throw new IllegalMoveException("Target isn't there!");
|
|
||||||
}
|
|
||||||
Weapon weapon = (Weapon) currentActor.getItem(MeleeWeapon.class);
|
|
||||||
if (weapon != null) {
|
|
||||||
weapon.getSound().play();
|
|
||||||
} else {
|
|
||||||
Sound.MELEE_NO_WEAPON.play();
|
|
||||||
}
|
|
||||||
if (getAttack(AttackType.MELEE) >= getDefence(target)) {
|
|
||||||
int actualDamage = target.handleDamage(this, target, getDamage(target, AttackType.MELEE));
|
|
||||||
if (currentActor != null) {
|
|
||||||
formatMessage("%s hits %s for %d damage", currentActor.getName(), target.getName(), actualDamage);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
formatMessage("%s tried to hit %s, but missed", currentActor.getName(), target.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
map.clean(loc);
|
|
||||||
|
|
||||||
if (target.isDestroyed() && currentLocation != null) {
|
|
||||||
return move(dir);
|
|
||||||
} else {
|
|
||||||
movePoints--;
|
|
||||||
return currentLocation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Location rangedAttack(GridDirection dir, Item target, AttackType type) {
|
|
||||||
Location loc = currentLocation;
|
|
||||||
Weapon weapon = null;
|
|
||||||
switch (type) {
|
|
||||||
case MAGIC:
|
|
||||||
weapon = (Weapon) currentActor.getItem(MagicWeapon.class);
|
|
||||||
break;
|
|
||||||
case RANGED:
|
|
||||||
weapon = (Weapon) currentActor.getItem(RangedWeapon.class);
|
|
||||||
}
|
|
||||||
if (weapon != null) {
|
|
||||||
weapon.getSound().play();
|
|
||||||
} else {
|
|
||||||
Sound.RANGED_NO_WEAPON.play();
|
|
||||||
}
|
|
||||||
if (getAttack(type) >= getDefence(target)) {
|
|
||||||
int damage = getDamage(target, type) / loc.gridDistanceTo(map.getLocation(target));
|
|
||||||
int actualDamage = target.handleDamage(this, target, damage);
|
|
||||||
formatMessage("%s hits %s for %d damage", currentActor.getName(), target.getName(), actualDamage);
|
|
||||||
} else {
|
|
||||||
formatMessage("%s tried to hit %s, but missed", currentActor.getName(), target.getName());
|
|
||||||
}
|
|
||||||
map.clean(map.getLocation(target));
|
|
||||||
if (target.isDestroyed() && map.has(currentLocation.go(dir), target)) {
|
|
||||||
return move(dir);
|
|
||||||
} else {
|
|
||||||
return currentLocation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Begin a new game turn, or continue to the previous turn
|
|
||||||
*
|
|
||||||
* @return True if the game should wait for more user input
|
|
||||||
*/
|
|
||||||
public boolean doTurn() {
|
|
||||||
do {
|
|
||||||
if (actors.isEmpty()) {
|
|
||||||
// System.err.println("new turn!");
|
|
||||||
|
|
||||||
// no one in the queue, we're starting a new turn!
|
|
||||||
// first collect all the actors:
|
|
||||||
beginTurn();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*if (random.nextInt(100) < 20) {
|
|
||||||
ILocation loc = map.getLocation(random.nextInt(map.getWidth()), random.nextInt(map.getHeight()));
|
|
||||||
if (!map.hasActors(loc) && !map.hasItems(loc) && !map.hasWall(loc)) {
|
|
||||||
map.add(loc, new Carrot());
|
|
||||||
}
|
|
||||||
}*/ //We don't want this in the actual game.
|
|
||||||
|
|
||||||
// process actors one by one; for the IPlayer, we return and wait for keypresses
|
|
||||||
// Possible for INonPlayer, we could also return early (returning
|
|
||||||
// *false*), and then insert a little timer delay between each non-player move
|
|
||||||
// (the timer
|
|
||||||
// is already set up in Main)
|
|
||||||
if (numberOfPlayers == 0 && !won) {
|
|
||||||
kill();
|
|
||||||
}
|
|
||||||
while (!actors.isEmpty()) {
|
|
||||||
// get the next player or non-player in the queue
|
|
||||||
currentActor = actors.remove(0);
|
|
||||||
if (currentActor == null) {
|
|
||||||
return false; //TODO: Find out why a girl suddenly becomes null (only after map change, not caught in beginTurn, happens randomly)
|
|
||||||
}
|
|
||||||
// skip if it's dead
|
|
||||||
if (currentActor.isDestroyed()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
currentLocation = map.getLocation(currentActor);
|
|
||||||
if (currentLocation == null) {
|
|
||||||
displayDebug("doTurn(): Whoops! Actor has disappeared from the map: " + currentActor);
|
|
||||||
}
|
|
||||||
movePoints = 1; // everyone gets to do one thing
|
|
||||||
visible = null;
|
|
||||||
|
|
||||||
if (currentActor instanceof NonPlayerCharacter) {
|
|
||||||
// computer-controlled players do their stuff right away
|
|
||||||
((NonPlayerCharacter) currentActor).doTurn(this);
|
|
||||||
// remove any dead items from current location
|
|
||||||
map.clean(currentLocation);
|
|
||||||
return false;
|
|
||||||
} else if (currentActor instanceof PlayerCharacter) {
|
|
||||||
if (!currentActor.isDestroyed()) {
|
|
||||||
// For the human player, we need to wait for input, so we just return.
|
|
||||||
// Further keypresses will cause keyPressed() to be called, and once the human
|
|
||||||
// makes a move, it'll lose its movement point and doTurn() will be called again
|
|
||||||
//
|
|
||||||
// NOTE: currentActor and currentLocation are set to the IPlayer (above),
|
|
||||||
// so the game remembers who the player is whenever new keypresses occur. This
|
|
||||||
// is also how e.g., getLocalItems() work – the game always keeps track of
|
|
||||||
// whose turn it is.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
displayDebug("doTurn(): Hmm, this is a very strange actor: " + currentActor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while (numberOfPlayers > 0); // we can safely repeat if we have players, since we'll return (and break out of
|
|
||||||
// the loop) once we hit the player
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Player is dead. It needs to be properly killed off.
|
|
||||||
*/
|
|
||||||
private void kill() {
|
|
||||||
map.remove(currentLocation, currentActor);
|
|
||||||
currentActor = null;
|
|
||||||
currentLocation = null;
|
|
||||||
actors = new ArrayList<>();
|
|
||||||
loadMap("gameover.txt");
|
|
||||||
Sound.GAME_OVER.play();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void win() { //Trigger when the boss dies
|
|
||||||
map.remove(currentLocation, currentActor);
|
|
||||||
currentActor = null;
|
|
||||||
currentLocation = null;
|
|
||||||
actors = new ArrayList<>();
|
|
||||||
loadMap("victory.txt");
|
|
||||||
map.draw(painter, printer);
|
|
||||||
Sound.WIN.play();
|
|
||||||
won = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads a map with the desired name
|
|
||||||
*
|
|
||||||
* @param mapName Name of map, including extension.
|
|
||||||
*/
|
|
||||||
private void loadMap(String mapName) {
|
|
||||||
IGrid<String> inputGrid = MapReader.readFile("maps/" + mapName);
|
|
||||||
if (inputGrid == null) {
|
|
||||||
System.err.println("Map not found – falling back to builtin map");
|
|
||||||
inputGrid = MapReader.readString(Main.BUILTIN_MAP);
|
|
||||||
}
|
|
||||||
GameMap map = new AGameMap(inputGrid.getArea());
|
|
||||||
for (Location loc : inputGrid.locations()) {
|
|
||||||
Item item = createItem(inputGrid.get(loc));
|
|
||||||
if (item != null) {
|
|
||||||
map.add(loc, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.map = map;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadMap(String mapName, int lvl) {
|
|
||||||
IGrid<String> inputGrid = MapReader.readFile("maps/" + mapName);
|
|
||||||
if (inputGrid == null) {
|
|
||||||
System.err.println("Map not found – falling back to builtin map");
|
|
||||||
inputGrid = MapReader.readString(Main.BUILTIN_MAP);
|
|
||||||
}
|
|
||||||
GameMap map = new AGameMap(inputGrid.getArea());
|
|
||||||
for (Location loc : inputGrid.locations()) {
|
|
||||||
Item item = createItem(inputGrid.get(loc));
|
|
||||||
if (item instanceof Chest) {
|
|
||||||
((Chest) item).fill(lvl);
|
|
||||||
} else if (item instanceof Girl) {
|
|
||||||
((Girl) item).giveWeapon(lvl);
|
|
||||||
}
|
|
||||||
if (item != null) {
|
|
||||||
map.add(loc, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
maps.add(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Go through the map and collect all the actors.
|
|
||||||
*/
|
|
||||||
private void beginTurn() {
|
|
||||||
numberOfPlayers = 0;
|
|
||||||
// this extra fancy iteration over each map location runs *in parallel* on
|
|
||||||
// multicore systems!
|
|
||||||
// that makes some things more tricky, hence the "synchronized" block and
|
|
||||||
// "Collections.synchronizedList()" in the initialization of "actors".
|
|
||||||
// NOTE: If you want to modify this yourself, it might be a good idea to replace
|
|
||||||
// "parallelStream()" by "stream()", because weird things can happen when many
|
|
||||||
// things happen
|
|
||||||
// at the same time! (or do INF214 or DAT103 to learn about locks and threading)
|
|
||||||
map.getArea().parallelStream().forEach((loc) -> { // will do this for each location in map
|
|
||||||
List<Item> list = map.getAllModifiable(loc); // all items at loc
|
|
||||||
Iterator<Item> li = list.iterator(); // manual iterator lets us remove() items
|
|
||||||
while (li.hasNext()) { // this is what "for(IItem item : list)" looks like on the inside
|
|
||||||
Item item = li.next();
|
|
||||||
if (item.getCurrentHealth() < 0) {
|
|
||||||
// normally, we expect these things to be removed when they are destroyed, so
|
|
||||||
// this shouldn't happen
|
|
||||||
synchronized (this) {
|
|
||||||
formatDebug("beginTurn(): found and removed leftover destroyed item %s '%s' at %s%n",
|
|
||||||
item.getName(), item.getSymbol(), loc);
|
|
||||||
}
|
|
||||||
li.remove();
|
|
||||||
map.remove(loc, item); // need to do this too, to update item map
|
|
||||||
} else if (item instanceof PlayerCharacter) {
|
|
||||||
actors.add(0, (Actor) item); // we let the human player go first
|
|
||||||
synchronized (this) {
|
|
||||||
numberOfPlayers++;
|
|
||||||
}
|
|
||||||
} else if (item instanceof Actor) {
|
|
||||||
actors.add((Actor) item); // add other actors to the end of the list
|
|
||||||
} else if (item instanceof Carrot) {
|
|
||||||
((Carrot) item).doTurn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canGo(GridDirection dir) {
|
|
||||||
return map.canGo(currentLocation, dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addFactory() {
|
|
||||||
itemFactories.put("#", Wall::new);
|
|
||||||
itemFactories.put("@", Player::new);
|
|
||||||
itemFactories.put("C", Carrot::new);
|
|
||||||
itemFactories.put("R", Rabbit::new);
|
|
||||||
itemFactories.put("M", Manga::new);
|
|
||||||
itemFactories.put("G", Girl::new);
|
|
||||||
itemFactories.put(".", Dust::new);
|
|
||||||
itemFactories.put("S", BasicSword::new);
|
|
||||||
itemFactories.put("c", Chest::new);
|
|
||||||
itemFactories.put("B", Boss::new);
|
|
||||||
itemFactories.put("s", FireStaff::new);
|
|
||||||
itemFactories.put("b", BasicBow::new);
|
|
||||||
itemFactories.put("H", HealthPotion::new);
|
|
||||||
itemFactories.put("=", FakeWall::new);
|
|
||||||
itemFactories.put("<", StairsUp::new);
|
|
||||||
itemFactories.put(">", StairsDown::new);
|
|
||||||
itemFactories.put("m", Binoculars::new);
|
|
||||||
itemFactories.put("f", Shield::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Item createItem(String symbol) {
|
|
||||||
if (" ".equals(symbol)) {
|
|
||||||
return null;
|
|
||||||
}// alternative/advanced method
|
|
||||||
Supplier<Item> factory = itemFactories.get(symbol);
|
|
||||||
if (factory != null) {
|
|
||||||
return factory.get();
|
|
||||||
} else {
|
|
||||||
System.err.println("createItem: Don't know how to create a '" + symbol + "'");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void displayDebug(String s) {
|
|
||||||
printer.clearLine(Main.LINE_DEBUG);
|
|
||||||
printer.printAt(1, Main.LINE_DEBUG, s, Color.DARKRED);
|
|
||||||
System.err.println(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void displayMessage(String s) {
|
|
||||||
if (lastMessages.size() >= 3) {
|
|
||||||
lastMessages.remove(2);
|
|
||||||
}
|
|
||||||
lastMessages.add(0, s);
|
|
||||||
printer.clearLine(Main.LINE_MSG1);
|
|
||||||
printer.clearLine(Main.LINE_MSG2);
|
|
||||||
printer.clearLine(Main.LINE_MSG3);
|
|
||||||
|
|
||||||
int maxLen = 80; //The maximum length of a message to not overflow.
|
|
||||||
boolean secondLineWritten = false;
|
|
||||||
boolean thirdLineWritten = false;
|
|
||||||
|
|
||||||
//Makes long messages overflow to the next line.
|
|
||||||
if (lastMessages.size() > 0) {
|
|
||||||
String message = lastMessages.get(0);
|
|
||||||
if (message.length() > 2 * maxLen) {
|
|
||||||
printer.printAt(1, Main.LINE_MSG1, message.substring(0, maxLen));
|
|
||||||
printer.printAt(1, Main.LINE_MSG2, message.substring(maxLen, 2 * maxLen));
|
|
||||||
printer.printAt(1, Main.LINE_MSG3, message.substring(2 * maxLen));
|
|
||||||
secondLineWritten = thirdLineWritten = true;
|
|
||||||
} else if (message.length() > maxLen) {
|
|
||||||
printer.printAt(1, Main.LINE_MSG1, message.substring(0, maxLen));
|
|
||||||
printer.printAt(1, Main.LINE_MSG2, message.substring(maxLen));
|
|
||||||
secondLineWritten = true;
|
|
||||||
} else {
|
|
||||||
printer.printAt(1, Main.LINE_MSG1, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lastMessages.size() > 1 && !secondLineWritten) {
|
|
||||||
String message = lastMessages.get(1);
|
|
||||||
if (message.length() > maxLen) {
|
|
||||||
printer.printAt(1, Main.LINE_MSG2, message.substring(0, maxLen));
|
|
||||||
printer.printAt(1, Main.LINE_MSG3, message.substring(maxLen));
|
|
||||||
thirdLineWritten = true;
|
|
||||||
} else {
|
|
||||||
printer.printAt(1, Main.LINE_MSG2, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lastMessages.size() > 2 && !thirdLineWritten) {
|
|
||||||
printer.printAt(1, Main.LINE_MSG3, lastMessages.get(2));
|
|
||||||
}
|
|
||||||
//System.out.println("Message: «" + s + "»");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void displayStatus(String s) {
|
|
||||||
printer.clearLine(Main.LINE_STATUS);
|
|
||||||
printer.printAt(1, Main.LINE_STATUS, s);
|
|
||||||
//System.out.println("Status: «" + s + "»");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void draw() {
|
|
||||||
if (numberOfPlayers == 0) {
|
|
||||||
map.draw(painter, printer);
|
|
||||||
} else {
|
|
||||||
((AGameMap) map).drawVisible(painter, printer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean drop(Item item) {
|
|
||||||
if (item != null) {
|
|
||||||
map.add(currentLocation, item);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean dropAt(Location loc, Item item) {
|
|
||||||
if (item != null) {
|
|
||||||
map.add(loc, item);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void formatDebug(String s, Object... args) {
|
|
||||||
displayDebug(String.format(s, args));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void formatMessage(String s, Object... args) {
|
|
||||||
displayMessage(String.format(s, args));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void formatStatus(String s, Object... args) {
|
|
||||||
displayStatus(String.format(s, args));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getHeight() {
|
|
||||||
return map.getHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Item> getLocalItems() {
|
|
||||||
return map.getItems(currentLocation);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Location getLocation() {
|
|
||||||
return currentLocation;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Location getLocation(GridDirection dir) {
|
|
||||||
if (currentLocation.canGo(dir)) {
|
|
||||||
return currentLocation.go(dir);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the game map. {@link GameMap} gives us a few more details than
|
|
||||||
* {@link MapView} (write access to item lists); the game needs this but
|
|
||||||
* individual items don't.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public MapView getMap() {
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<GridDirection> getPossibleMoves() {
|
|
||||||
List<GridDirection> moves = new ArrayList<>();
|
|
||||||
for (GridDirection dir : GridDirection.FOUR_DIRECTIONS) {
|
|
||||||
if (canGo(dir)) {
|
|
||||||
moves.add(dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return moves;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Location> getVisible() {
|
|
||||||
if (this.visible != null) {
|
|
||||||
return this.visible;
|
|
||||||
}
|
|
||||||
List<Location> neighbours = this.map.getNeighbourhood(this.currentLocation, this.currentActor.getVision());
|
|
||||||
List<Location> invalid = new ArrayList<>();
|
|
||||||
for (Location neighbour : neighbours) {
|
|
||||||
for (Location tile : this.currentLocation.gridLineTo(neighbour)) {
|
|
||||||
if (this.map.hasWall(tile)) {
|
|
||||||
invalid.add(neighbour);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
neighbours.removeAll(invalid);
|
|
||||||
this.visible = neighbours;
|
|
||||||
return neighbours;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getWidth() {
|
|
||||||
return this.map.getWidth();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean keyPressed(KeyCode code) {
|
|
||||||
// only an IPlayer/human can handle keypresses, and only if it's the human's
|
|
||||||
// turn
|
|
||||||
return !(currentActor instanceof PlayerCharacter) || !((PlayerCharacter) currentActor).keyPressed(this, code);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Location move(GridDirection dir) {
|
|
||||||
if (movePoints < 1) {
|
|
||||||
throw new IllegalMoveException("You're out of moves!");
|
|
||||||
}
|
|
||||||
Location newLoc = map.go(currentLocation, dir);
|
|
||||||
map.remove(currentLocation, currentActor);
|
|
||||||
map.add(newLoc, currentActor);
|
|
||||||
currentLocation = newLoc;
|
|
||||||
movePoints--;
|
|
||||||
return currentLocation;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Item pickUp(Item item) {
|
|
||||||
if (item != null && map.has(currentLocation, item) && !(item instanceof Static)) {
|
|
||||||
if (item instanceof Actor) {
|
|
||||||
if (item.getCurrentHealth() / item.getMaxHealth() < 3) {
|
|
||||||
map.remove(currentLocation, item);
|
|
||||||
return item;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else if (currentActor.getAttack() > item.getDefense()) {
|
|
||||||
map.remove(currentLocation, item);
|
|
||||||
return item;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ITurtle getPainter() {
|
|
||||||
return painter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Printer getPrinter() {
|
|
||||||
return printer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int[] getFreeTextAreaBounds() {
|
|
||||||
int[] area = new int[4];
|
|
||||||
area[0] = getWidth() + 1;
|
|
||||||
area[1] = 1;
|
|
||||||
area[2] = printer.getLineWidth();
|
|
||||||
area[3] = printer.getPageHeight() - 5;
|
|
||||||
return area;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clearFreeTextArea() {
|
|
||||||
printer.clearRegion(getWidth() + 1, 1, printer.getLineWidth() - getWidth(), printer.getPageHeight() - 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clearFreeGraphicsArea() {
|
|
||||||
painter.as(GraphicsContext.class).clearRect(getWidth() * printer.getCharWidth(), 0,
|
|
||||||
painter.getWidth() - getWidth() * printer.getCharWidth(),
|
|
||||||
(printer.getPageHeight() - 5) * printer.getCharHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double[] getFreeGraphicsAreaBounds() {
|
|
||||||
double[] area = new double[4];
|
|
||||||
area[0] = getWidth() * printer.getCharWidth();
|
|
||||||
area[1] = 0;
|
|
||||||
area[2] = painter.getWidth();
|
|
||||||
area[3] = getHeight() * printer.getCharHeight();
|
|
||||||
return area;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Actor getActor() {
|
|
||||||
return currentActor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Location setCurrent(Actor actor) {
|
|
||||||
currentLocation = map.getLocation(actor);
|
|
||||||
if (currentLocation != null) {
|
|
||||||
currentActor = actor;
|
|
||||||
movePoints = 1;
|
|
||||||
}
|
|
||||||
return currentLocation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Actor setCurrent(Location loc) {
|
|
||||||
List<Actor> list = map.getActors(loc);
|
|
||||||
if (!list.isEmpty()) {
|
|
||||||
currentActor = list.get(0);
|
|
||||||
currentLocation = loc;
|
|
||||||
movePoints = 1;
|
|
||||||
}
|
|
||||||
return currentActor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Actor setCurrent(int x, int y) {
|
|
||||||
return setCurrent(map.getLocation(x, y));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Random getRandom() {
|
|
||||||
return random;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<GridDirection> locationDirection(Location start, Location target) {
|
|
||||||
int targetX = target.getX(), targetY = target.getY();
|
|
||||||
int startX = start.getX(), startY = start.getY();
|
|
||||||
List<GridDirection> dirs = new ArrayList<>();
|
|
||||||
boolean yDifferenceIsLarger = Math.abs(targetX - startX) < Math.abs(targetY - startY);
|
|
||||||
if (targetX > startX && targetY > startY) {
|
|
||||||
if (yDifferenceIsLarger) {
|
|
||||||
dirs.add(GridDirection.SOUTH);
|
|
||||||
dirs.add(GridDirection.EAST);
|
|
||||||
} else {
|
|
||||||
dirs.add(GridDirection.EAST);
|
|
||||||
dirs.add(GridDirection.SOUTH);
|
|
||||||
}
|
|
||||||
} else if (targetX > startX && targetY < startY) {
|
|
||||||
if (yDifferenceIsLarger) {
|
|
||||||
dirs.add(GridDirection.NORTH);
|
|
||||||
dirs.add(GridDirection.EAST);
|
|
||||||
} else {
|
|
||||||
dirs.add(GridDirection.EAST);
|
|
||||||
dirs.add(GridDirection.NORTH);
|
|
||||||
}
|
|
||||||
} else if (targetX < startX && targetY > startY) {
|
|
||||||
if (yDifferenceIsLarger) {
|
|
||||||
dirs.add(GridDirection.SOUTH);
|
|
||||||
dirs.add(GridDirection.WEST);
|
|
||||||
} else {
|
|
||||||
dirs.add(GridDirection.WEST);
|
|
||||||
dirs.add(GridDirection.SOUTH);
|
|
||||||
}
|
|
||||||
} else if (targetX < startX && targetY < startY) {
|
|
||||||
if (yDifferenceIsLarger) {
|
|
||||||
dirs.add(GridDirection.NORTH);
|
|
||||||
dirs.add(GridDirection.WEST);
|
|
||||||
} else {
|
|
||||||
dirs.add(GridDirection.WEST);
|
|
||||||
dirs.add(GridDirection.NORTH);
|
|
||||||
}
|
|
||||||
} else if (targetX > startX) {
|
|
||||||
dirs.add(GridDirection.EAST);
|
|
||||||
} else if (targetX < startX) {
|
|
||||||
dirs.add(GridDirection.WEST);
|
|
||||||
} else if (targetY > startY) {
|
|
||||||
dirs.add(GridDirection.SOUTH);
|
|
||||||
} else if (targetY < startY) {
|
|
||||||
dirs.add(GridDirection.NORTH);
|
|
||||||
}
|
|
||||||
return dirs;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +1,17 @@
|
|||||||
package inf101.v18.rogue101.items.container;
|
package inf101.v18.rogue101.items;
|
||||||
|
|
||||||
import inf101.v18.rogue101.game.Game;
|
import inf101.v18.rogue101.game.IGame;
|
||||||
import inf101.v18.rogue101.object.Item;
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class Backpack implements Container {
|
public class Backpack implements IContainer {
|
||||||
/**
|
/**
|
||||||
* A list containing everything in the backpack.
|
* A list containing everything in the backpack.
|
||||||
*/
|
*/
|
||||||
private final List<Item> content = new ArrayList<>();
|
private final List<IItem> content = new ArrayList<>();
|
||||||
/**
|
/**
|
||||||
* The maximum amount of items allowed in a single backpack.
|
* The maximum amount of items allowed in a single backpack.
|
||||||
*/
|
*/
|
||||||
@ -23,7 +23,7 @@ public class Backpack implements Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefense() {
|
public int getDefence() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ public class Backpack implements Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int handleDamage(Game game, Item source, int amount) {
|
public int handleDamage(IGame game, IItem source, int amount) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ public class Backpack implements Container {
|
|||||||
/**
|
/**
|
||||||
* Checks if the Backpack is empty
|
* Checks if the Backpack is empty
|
||||||
*
|
*
|
||||||
* @return True if empty. False otherwise
|
* @return True if empty. False otherwise
|
||||||
*/
|
*/
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
return content.isEmpty();
|
return content.isEmpty();
|
||||||
@ -73,10 +73,10 @@ public class Backpack implements Container {
|
|||||||
/**
|
/**
|
||||||
* Tries to add an item to the Backpack
|
* Tries to add an item to the Backpack
|
||||||
*
|
*
|
||||||
* @param item The item to add
|
* @param item The item to add
|
||||||
* @return True if the item was added. False if the backpack is full
|
* @return True if the item was added. False if the backpack is full
|
||||||
*/
|
*/
|
||||||
public boolean add(Item item) {
|
public boolean add(IItem item) {
|
||||||
if (size() < MAX_SIZE) {
|
if (size() < MAX_SIZE) {
|
||||||
content.add(item);
|
content.add(item);
|
||||||
return true;
|
return true;
|
||||||
@ -93,9 +93,9 @@ public class Backpack implements Container {
|
|||||||
/**
|
/**
|
||||||
* Removes item from the backpack.
|
* Removes item from the backpack.
|
||||||
*
|
*
|
||||||
* @param item The item to remove
|
* @param item The item to remove
|
||||||
*/
|
*/
|
||||||
public void remove(Item item) {
|
public void remove(IItem item) {
|
||||||
content.remove(item);
|
content.remove(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,14 +103,14 @@ public class Backpack implements Container {
|
|||||||
* Gets a T at index i,
|
* Gets a T at index i,
|
||||||
*
|
*
|
||||||
* @param i The index of an element
|
* @param i The index of an element
|
||||||
* @return An object of type T
|
* @return An object of type T
|
||||||
*/
|
*/
|
||||||
public Item get(int i) {
|
public IItem get(int i) {
|
||||||
return content.get(i);
|
return content.get(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean addItem(Item item) {
|
public boolean addItem(IItem item) {
|
||||||
if (content.size() < MAX_SIZE) {
|
if (content.size() < MAX_SIZE) {
|
||||||
content.add(item);
|
content.add(item);
|
||||||
return true;
|
return true;
|
||||||
@ -122,9 +122,9 @@ public class Backpack implements Container {
|
|||||||
/**
|
/**
|
||||||
* Gets the content List for direct manipulation.
|
* Gets the content List for direct manipulation.
|
||||||
*
|
*
|
||||||
* @return A list of T
|
* @return A list of T
|
||||||
*/
|
*/
|
||||||
public List<Item> getContent() {
|
public List<IItem> getContent() {
|
||||||
return Collections.unmodifiableList(content);
|
return Collections.unmodifiableList(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,8 +134,8 @@ public class Backpack implements Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Item getFirst(Class<?> clazz) {
|
public IItem getFirst(Class<?> clazz) {
|
||||||
for (Item item : content) {
|
for (IItem item : content) {
|
||||||
if (clazz.isInstance(item)) {
|
if (clazz.isInstance(item)) {
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
@ -1,9 +1,9 @@
|
|||||||
package inf101.v18.rogue101.items.buff;
|
package inf101.v18.rogue101.items;
|
||||||
|
|
||||||
import inf101.v18.rogue101.game.Game;
|
import inf101.v18.rogue101.game.IGame;
|
||||||
import inf101.v18.rogue101.object.Item;
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
|
|
||||||
public class Binoculars implements BuffItem {
|
public class Binoculars implements IBuffItem {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getBuffVisibility() {
|
public int getBuffVisibility() {
|
||||||
@ -16,7 +16,7 @@ public class Binoculars implements BuffItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefense() {
|
public int getDefence() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ public class Binoculars implements BuffItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int handleDamage(Game game, Item source, int amount) {
|
public int handleDamage(IGame game, IItem source, int amount) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,11 +1,11 @@
|
|||||||
package inf101.v18.rogue101.items.weapon;
|
package inf101.v18.rogue101.items;
|
||||||
|
|
||||||
import inf101.v18.rogue101.game.Game;
|
import inf101.v18.rogue101.game.IGame;
|
||||||
import inf101.v18.rogue101.object.Item;
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
public class BasicBow implements RangedWeapon {
|
public class Bow implements IRangedWeapon {
|
||||||
private static final Random random = new Random();
|
private static final Random random = new Random();
|
||||||
private final int damage = 3 + random.nextInt(20);
|
private final int damage = 3 + random.nextInt(20);
|
||||||
private final int hp = getMaxHealth();
|
private final int hp = getMaxHealth();
|
||||||
@ -26,7 +26,7 @@ public class BasicBow implements RangedWeapon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefense() {
|
public int getDefence() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ public class BasicBow implements RangedWeapon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int handleDamage(Game game, Item source, int amount) {
|
public int handleDamage(IGame game, IItem source, int amount) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,22 +1,15 @@
|
|||||||
package inf101.v18.rogue101.items.container;
|
package inf101.v18.rogue101.items;
|
||||||
|
|
||||||
import inf101.v18.rogue101.game.Game;
|
import inf101.v18.rogue101.game.IGame;
|
||||||
import inf101.v18.rogue101.items.buff.Binoculars;
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
import inf101.v18.rogue101.items.buff.Shield;
|
|
||||||
import inf101.v18.rogue101.items.consumable.HealthPotion;
|
|
||||||
import inf101.v18.rogue101.items.consumable.Manga;
|
|
||||||
import inf101.v18.rogue101.items.weapon.BasicBow;
|
|
||||||
import inf101.v18.rogue101.items.weapon.BasicSword;
|
|
||||||
import inf101.v18.rogue101.items.weapon.FireStaff;
|
|
||||||
import inf101.v18.rogue101.object.Item;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
public class Chest implements Container, Static {
|
public class Chest implements IContainer, IStatic {
|
||||||
private final List<Item> container;
|
private final List<IItem> container;
|
||||||
private final int MAX_SIZE = 10;
|
private final int MAX_SIZE = 10;
|
||||||
|
|
||||||
public Chest() {
|
public Chest() {
|
||||||
@ -26,15 +19,15 @@ public class Chest implements Container, Static {
|
|||||||
/**
|
/**
|
||||||
* Randomly fills chest with random items based on dungeon level.
|
* Randomly fills chest with random items based on dungeon level.
|
||||||
*
|
*
|
||||||
* @param lvl The current dungeon level
|
* @param lvl The current dungeon level
|
||||||
*/
|
*/
|
||||||
public void fill(int lvl) {
|
public void fill (int lvl) {
|
||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
int itemChance = 3;
|
int itemChance = 3;
|
||||||
List<Item> items = new ArrayList<>();
|
List<IItem> items = new ArrayList<>();
|
||||||
items.add(new FireStaff());
|
items.add(new Staff());
|
||||||
items.add(new BasicSword());
|
items.add(new Sword());
|
||||||
items.add(new BasicBow());
|
items.add(new Bow());
|
||||||
items.add(new Binoculars());
|
items.add(new Binoculars());
|
||||||
items.add(new Shield());
|
items.add(new Shield());
|
||||||
items.add(new Manga());
|
items.add(new Manga());
|
||||||
@ -56,12 +49,12 @@ public class Chest implements Container, Static {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Item get(int i) {
|
public IItem get(int i) {
|
||||||
return container.get(i);
|
return container.get(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Item> getContent() {
|
public List<IItem> getContent() {
|
||||||
return Collections.unmodifiableList(container);
|
return Collections.unmodifiableList(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,8 +64,8 @@ public class Chest implements Container, Static {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Item getFirst(Class<?> clazz) {
|
public IItem getFirst(Class<?> clazz) {
|
||||||
for (Item item : container) {
|
for (IItem item : container) {
|
||||||
if (clazz.isInstance(item)) {
|
if (clazz.isInstance(item)) {
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@ -110,12 +103,12 @@ public class Chest implements Container, Static {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int handleDamage(Game game, Item source, int amount) {
|
public int handleDamage(IGame game, IItem source, int amount) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean addItem(Item item) {
|
public boolean addItem(IItem item) {
|
||||||
if (container.size() < MAX_SIZE) {
|
if (container.size() < MAX_SIZE) {
|
||||||
container.add(item);
|
container.add(item);
|
||||||
return true;
|
return true;
|
@ -1,13 +1,9 @@
|
|||||||
package inf101.v18.rogue101.items.consumable;
|
package inf101.v18.rogue101.items;
|
||||||
|
|
||||||
import inf101.v18.rogue101.game.Game;
|
import inf101.v18.rogue101.game.IGame;
|
||||||
import inf101.v18.rogue101.object.Item;
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
|
|
||||||
/**
|
|
||||||
* A consumable that restores 100 health points
|
|
||||||
*/
|
|
||||||
public class HealthPotion implements Consumable {
|
|
||||||
|
|
||||||
|
public class HealthPotion implements IConsumable {
|
||||||
@Override
|
@Override
|
||||||
public int hpIncrease() {
|
public int hpIncrease() {
|
||||||
return 100;
|
return 100;
|
||||||
@ -19,7 +15,7 @@ public class HealthPotion implements Consumable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int defenseIncrease() {
|
public int defenceIncrease() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +25,7 @@ public class HealthPotion implements Consumable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefense() {
|
public int getDefence() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,8 +50,7 @@ public class HealthPotion implements Consumable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int handleDamage(Game game, Item source, int amount) {
|
public int handleDamage(IGame game, IItem source, int amount) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,16 +1,12 @@
|
|||||||
package inf101.v18.rogue101.items.buff;
|
package inf101.v18.rogue101.items;
|
||||||
|
|
||||||
import inf101.v18.rogue101.object.Item;
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
|
|
||||||
/**
|
|
||||||
* An item that increases one or more of a player's stats
|
|
||||||
*/
|
|
||||||
public interface BuffItem extends Item {
|
|
||||||
|
|
||||||
|
public interface IBuffItem extends IItem {
|
||||||
/**
|
/**
|
||||||
* Retrieve damage increase done by the buff item.
|
* Retrieve damage increase done by the buff item.
|
||||||
*
|
*
|
||||||
* @return An int, May be 0
|
* @return An int, May be 0
|
||||||
*/
|
*/
|
||||||
default int getBuffDamage() {
|
default int getBuffDamage() {
|
||||||
return 0;
|
return 0;
|
||||||
@ -19,16 +15,16 @@ public interface BuffItem extends Item {
|
|||||||
/**
|
/**
|
||||||
* Retrieve defence increase done by the buff item.
|
* Retrieve defence increase done by the buff item.
|
||||||
*
|
*
|
||||||
* @return An int, May be 0
|
* @return An int, May be 0
|
||||||
*/
|
*/
|
||||||
default int getBuffDefene() {
|
default int getBuffDefence() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve defence increase done by the buff item.
|
* Retrieve defence increase done by the buff item.
|
||||||
*
|
*
|
||||||
* @return An int, May be 0
|
* @return An int, May be 0
|
||||||
*/
|
*/
|
||||||
default int getBuffDamageReduction() {
|
default int getBuffDamageReduction() {
|
||||||
return 0;
|
return 0;
|
||||||
@ -37,10 +33,9 @@ public interface BuffItem extends Item {
|
|||||||
/**
|
/**
|
||||||
* Retrieve visibility increase done by the buff item.
|
* Retrieve visibility increase done by the buff item.
|
||||||
*
|
*
|
||||||
* @return An int, May be 0
|
* @return An int, May be 0
|
||||||
*/
|
*/
|
||||||
default int getBuffVisibility() {
|
default int getBuffVisibility() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
9
src/main/java/inf101/v18/rogue101/items/IConsumable.java
Normal file
9
src/main/java/inf101/v18/rogue101/items/IConsumable.java
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package inf101.v18.rogue101.items;
|
||||||
|
|
||||||
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
|
|
||||||
|
public interface IConsumable extends IItem {
|
||||||
|
int hpIncrease();
|
||||||
|
int attackIncrease();
|
||||||
|
int defenceIncrease();
|
||||||
|
}
|
@ -1,10 +1,10 @@
|
|||||||
package inf101.v18.rogue101.items.container;
|
package inf101.v18.rogue101.items;
|
||||||
|
|
||||||
import inf101.v18.rogue101.object.Item;
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public interface Container extends Item {
|
public interface IContainer extends IItem {
|
||||||
/**
|
/**
|
||||||
* Retrieves an item from a container in index i
|
* Retrieves an item from a container in index i
|
||||||
*
|
*
|
||||||
@ -12,22 +12,22 @@ public interface Container extends Item {
|
|||||||
* @return An IItem
|
* @return An IItem
|
||||||
* @throws IndexOutOfBoundsException If the index is out of range.
|
* @throws IndexOutOfBoundsException If the index is out of range.
|
||||||
*/
|
*/
|
||||||
Item get(int i);
|
IItem get(int i);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an item to a container.
|
* Adds an item to a container.
|
||||||
*
|
*
|
||||||
* @param item The item to add to the container
|
|
||||||
* @return True if the container was not full
|
* @return True if the container was not full
|
||||||
|
* @param item The item to add to the container
|
||||||
*/
|
*/
|
||||||
boolean addItem(Item item);
|
boolean addItem(IItem item);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a list with everything inside a container.
|
* Gets a list with everything inside a container.
|
||||||
*
|
*
|
||||||
* @return A list of Objects extending IItem
|
* @return A list of Objects extending IItem
|
||||||
*/
|
*/
|
||||||
List<Item> getContent();
|
List<IItem> getContent();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if we can add anything at all to the container.
|
* Checks if we can add anything at all to the container.
|
||||||
@ -51,7 +51,7 @@ public interface Container extends Item {
|
|||||||
* @param clazz The class type to accept
|
* @param clazz The class type to accept
|
||||||
* @return An IItem or null
|
* @return An IItem or null
|
||||||
*/
|
*/
|
||||||
Item getFirst(Class<?> clazz);
|
IItem getFirst(Class<?> clazz);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes an element at index i from the container.
|
* Removes an element at index i from the container.
|
12
src/main/java/inf101/v18/rogue101/items/IMagicWeapon.java
Normal file
12
src/main/java/inf101/v18/rogue101/items/IMagicWeapon.java
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package inf101.v18.rogue101.items;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface to extinguish different weapons for specific NPC.
|
||||||
|
*/
|
||||||
|
public interface IMagicWeapon extends IWeapon {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getSound() {
|
||||||
|
return "audio/Large Fireball-SoundBible.com-301502490.wav";
|
||||||
|
}
|
||||||
|
}
|
12
src/main/java/inf101/v18/rogue101/items/IMeleeWeapon.java
Normal file
12
src/main/java/inf101/v18/rogue101/items/IMeleeWeapon.java
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package inf101.v18.rogue101.items;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface to extinguish different weapons for specific NPC.
|
||||||
|
*/
|
||||||
|
public interface IMeleeWeapon extends IWeapon {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getSound() {
|
||||||
|
return "audio/Sword Swing-SoundBible.com-639083727.wav";
|
||||||
|
}
|
||||||
|
}
|
12
src/main/java/inf101/v18/rogue101/items/IRangedWeapon.java
Normal file
12
src/main/java/inf101/v18/rogue101/items/IRangedWeapon.java
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package inf101.v18.rogue101.items;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface to extinguish different weapons for specific NPC.
|
||||||
|
*/
|
||||||
|
public interface IRangedWeapon extends IWeapon {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getSound() {
|
||||||
|
return "audio/Bow_Fire_Arrow-Stephan_Schutze-2133929391.wav";
|
||||||
|
}
|
||||||
|
}
|
15
src/main/java/inf101/v18/rogue101/items/IStatic.java
Normal file
15
src/main/java/inf101/v18/rogue101/items/IStatic.java
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package inf101.v18.rogue101.items;
|
||||||
|
|
||||||
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
|
|
||||||
|
public interface IStatic extends IItem {
|
||||||
|
@Override
|
||||||
|
default int getDefence() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getSize() {
|
||||||
|
return 10000;
|
||||||
|
}
|
||||||
|
}
|
26
src/main/java/inf101/v18/rogue101/items/IWeapon.java
Normal file
26
src/main/java/inf101/v18/rogue101/items/IWeapon.java
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package inf101.v18.rogue101.items;
|
||||||
|
|
||||||
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
|
|
||||||
|
public interface IWeapon extends IItem {
|
||||||
|
/**
|
||||||
|
* Retrieves the damage points of a weapon.
|
||||||
|
*
|
||||||
|
* @return An int
|
||||||
|
*/
|
||||||
|
int getWeaponDamage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the attack ponts of a weapon.
|
||||||
|
*
|
||||||
|
* @return an int
|
||||||
|
*/
|
||||||
|
int getWeaponAttack();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the string path of the sound to play upon an attack.
|
||||||
|
*
|
||||||
|
* @return A string path
|
||||||
|
*/
|
||||||
|
String getSound();
|
||||||
|
}
|
@ -1,13 +1,9 @@
|
|||||||
package inf101.v18.rogue101.items.consumable;
|
package inf101.v18.rogue101.items;
|
||||||
|
|
||||||
import inf101.v18.rogue101.game.Game;
|
import inf101.v18.rogue101.game.IGame;
|
||||||
import inf101.v18.rogue101.object.Item;
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
|
|
||||||
/**
|
|
||||||
* A consumable that increases attack and defense permanently
|
|
||||||
*/
|
|
||||||
public class Manga implements Consumable {
|
|
||||||
|
|
||||||
|
public class Manga implements IConsumable {
|
||||||
private int hp = getMaxHealth();
|
private int hp = getMaxHealth();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -16,7 +12,7 @@ public class Manga implements Consumable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefense() {
|
public int getDefence() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +42,7 @@ public class Manga implements Consumable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int handleDamage(Game game, Item source, int amount) {
|
public int handleDamage(IGame game, IItem source, int amount) {
|
||||||
hp -= amount;
|
hp -= amount;
|
||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
@ -62,8 +58,7 @@ public class Manga implements Consumable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int defenseIncrease() {
|
public int defenceIncrease() {
|
||||||
return 5;
|
return 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,13 +1,13 @@
|
|||||||
package inf101.v18.rogue101.items.buff;
|
package inf101.v18.rogue101.items;
|
||||||
|
|
||||||
import inf101.v18.rogue101.game.Game;
|
import inf101.v18.rogue101.game.IGame;
|
||||||
import inf101.v18.rogue101.object.Item;
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
|
|
||||||
public class Shield implements BuffItem {
|
public class Shield implements IBuffItem {
|
||||||
private final int hp = getMaxHealth();
|
private final int hp = getMaxHealth();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getBuffDefene() {
|
public int getBuffDefence() {
|
||||||
return 10;
|
return 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ public class Shield implements BuffItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefense() {
|
public int getDefence() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ public class Shield implements BuffItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int handleDamage(Game game, Item source, int amount) {
|
public int handleDamage(IGame game, IItem source, int amount) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,11 +1,11 @@
|
|||||||
package inf101.v18.rogue101.items.weapon;
|
package inf101.v18.rogue101.items;
|
||||||
|
|
||||||
import inf101.v18.rogue101.game.Game;
|
import inf101.v18.rogue101.game.IGame;
|
||||||
import inf101.v18.rogue101.object.Item;
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
public class FireStaff implements MagicWeapon {
|
public class Staff implements IMagicWeapon {
|
||||||
private static final Random random = new Random();
|
private static final Random random = new Random();
|
||||||
private final int damage = 5 + random.nextInt(25);
|
private final int damage = 5 + random.nextInt(25);
|
||||||
private int hp = getMaxHealth();
|
private int hp = getMaxHealth();
|
||||||
@ -26,7 +26,7 @@ public class FireStaff implements MagicWeapon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefense() {
|
public int getDefence() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ public class FireStaff implements MagicWeapon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int handleDamage(Game game, Item source, int amount) {
|
public int handleDamage(IGame game, IItem source, int amount) {
|
||||||
hp -= amount;
|
hp -= amount;
|
||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
@ -1,11 +1,11 @@
|
|||||||
package inf101.v18.rogue101.items.weapon;
|
package inf101.v18.rogue101.items;
|
||||||
|
|
||||||
import inf101.v18.rogue101.game.Game;
|
import inf101.v18.rogue101.game.IGame;
|
||||||
import inf101.v18.rogue101.object.Item;
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
public class BasicSword implements MeleeWeapon {
|
public class Sword implements IMeleeWeapon {
|
||||||
private static final Random random = new Random();
|
private static final Random random = new Random();
|
||||||
private final int damage = 5 + random.nextInt(25);
|
private final int damage = 5 + random.nextInt(25);
|
||||||
private int hp = getMaxHealth();
|
private int hp = getMaxHealth();
|
||||||
@ -26,7 +26,7 @@ public class BasicSword implements MeleeWeapon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefense() {
|
public int getDefence() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ public class BasicSword implements MeleeWeapon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int handleDamage(Game game, Item source, int amount) {
|
public int handleDamage(IGame game, IItem source, int amount) {
|
||||||
hp -= amount;
|
hp -= amount;
|
||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
@ -1,11 +0,0 @@
|
|||||||
package inf101.v18.rogue101.items.consumable;
|
|
||||||
|
|
||||||
import inf101.v18.rogue101.object.Item;
|
|
||||||
|
|
||||||
public interface Consumable extends Item {
|
|
||||||
int hpIncrease();
|
|
||||||
|
|
||||||
int attackIncrease();
|
|
||||||
|
|
||||||
int defenseIncrease();
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package inf101.v18.rogue101.items.container;
|
|
||||||
|
|
||||||
import inf101.v18.rogue101.object.Item;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A static item that cannot be kept in a player's inventory
|
|
||||||
*/
|
|
||||||
public interface Static extends Item {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
default int getDefense() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
default int getSize() {
|
|
||||||
return 10000;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package inf101.v18.rogue101.items.weapon;
|
|
||||||
|
|
||||||
import inf101.v18.rogue101.state.Sound;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An interface to extinguish different weapons for specific NPC.
|
|
||||||
*/
|
|
||||||
public interface MagicWeapon extends Weapon {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
default Sound getSound() {
|
|
||||||
return Sound.RANGED_STAFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package inf101.v18.rogue101.items.weapon;
|
|
||||||
|
|
||||||
import inf101.v18.rogue101.state.Sound;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An interface to extinguish different weapons for specific NPC.
|
|
||||||
*/
|
|
||||||
public interface MeleeWeapon extends Weapon {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
default Sound getSound() {
|
|
||||||
return Sound.MELEE_SWORD;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package inf101.v18.rogue101.items.weapon;
|
|
||||||
|
|
||||||
import inf101.v18.rogue101.state.Sound;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An interface to extinguish different weapons for specific NPC.
|
|
||||||
*/
|
|
||||||
public interface RangedWeapon extends Weapon {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
default Sound getSound() {
|
|
||||||
return Sound.RANGED_BOW;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
package inf101.v18.rogue101.items.weapon;
|
|
||||||
|
|
||||||
import inf101.v18.rogue101.object.Item;
|
|
||||||
import inf101.v18.rogue101.state.Sound;
|
|
||||||
|
|
||||||
public interface Weapon extends Item {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the damage points of a weapon.
|
|
||||||
*
|
|
||||||
* @return An int
|
|
||||||
*/
|
|
||||||
int getWeaponDamage();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the attack points of a weapon
|
|
||||||
*
|
|
||||||
* @return <p>The attack points as an integer</p>
|
|
||||||
*/
|
|
||||||
int getWeaponAttack();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the sound to play upon an attack
|
|
||||||
*
|
|
||||||
* @return <p>The sound to use</p>
|
|
||||||
*/
|
|
||||||
Sound getSound();
|
|
||||||
|
|
||||||
}
|
|
@ -1,446 +0,0 @@
|
|||||||
package inf101.v18.rogue101.map;
|
|
||||||
|
|
||||||
import inf101.v18.gfx.gfxmode.ITurtle;
|
|
||||||
import inf101.v18.gfx.textmode.Printer;
|
|
||||||
import inf101.v18.grid.GridDirection;
|
|
||||||
import inf101.v18.grid.IArea;
|
|
||||||
import inf101.v18.grid.IMultiGrid;
|
|
||||||
import inf101.v18.grid.Location;
|
|
||||||
import inf101.v18.grid.MultiGrid;
|
|
||||||
import inf101.v18.rogue101.Main;
|
|
||||||
import inf101.v18.rogue101.examples.Carrot;
|
|
||||||
import inf101.v18.rogue101.game.IllegalMoveException;
|
|
||||||
import inf101.v18.rogue101.items.buff.Shield;
|
|
||||||
import inf101.v18.rogue101.items.consumable.Manga;
|
|
||||||
import inf101.v18.rogue101.items.container.Backpack;
|
|
||||||
import inf101.v18.rogue101.items.container.Chest;
|
|
||||||
import inf101.v18.rogue101.items.weapon.BasicBow;
|
|
||||||
import inf101.v18.rogue101.items.weapon.BasicSword;
|
|
||||||
import inf101.v18.rogue101.items.weapon.FireStaff;
|
|
||||||
import inf101.v18.rogue101.object.Actor;
|
|
||||||
import inf101.v18.rogue101.object.FakeWall;
|
|
||||||
import inf101.v18.rogue101.object.Item;
|
|
||||||
import inf101.v18.rogue101.object.PlayerCharacter;
|
|
||||||
import inf101.v18.rogue101.object.Wall;
|
|
||||||
import javafx.scene.canvas.GraphicsContext;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.IdentityHashMap;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class AGameMap implements GameMap {
|
|
||||||
/**
|
|
||||||
* The grid that makes up our map
|
|
||||||
*/
|
|
||||||
private final IMultiGrid<Item> grid;
|
|
||||||
/**
|
|
||||||
* These locations have changed, and need to be redrawn
|
|
||||||
*/
|
|
||||||
private final Set<Location> dirtyLocs = new HashSet<>();
|
|
||||||
/**
|
|
||||||
* An index of all the items in the map and their locations.
|
|
||||||
*/
|
|
||||||
// an IdentityHashMap uses object identity as a lookup key, so items are
|
|
||||||
// considered equal if they are the same object (a == b)
|
|
||||||
private final Map<Item, Location> items = new IdentityHashMap<>();
|
|
||||||
|
|
||||||
public AGameMap(IArea area) {
|
|
||||||
grid = new MultiGrid<>(area);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AGameMap(int width, int height) {
|
|
||||||
grid = new MultiGrid<>(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void add(Location loc, Item item) {
|
|
||||||
|
|
||||||
// keep track of location of all items
|
|
||||||
items.put(item, loc);
|
|
||||||
// also keep track of whether we need to redraw this cell
|
|
||||||
dirtyLocs.add(loc);
|
|
||||||
|
|
||||||
// do the actual adding
|
|
||||||
List<Item> list = grid.get(loc);
|
|
||||||
|
|
||||||
for (int i = 0; i < list.size(); i++) {
|
|
||||||
if (item.compareTo(list.get(i)) >= 0) {
|
|
||||||
list.add(i, item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
list.add(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addRandomItems(Location loc) {
|
|
||||||
if (!this.hasActors(loc) && !this.hasWall(loc)) {
|
|
||||||
Random random = new Random();
|
|
||||||
if (random.nextInt(10) < 2) {
|
|
||||||
this.add(loc, new Carrot());
|
|
||||||
}
|
|
||||||
if (random.nextInt(10) < 1) {
|
|
||||||
this.add(loc, new Manga());
|
|
||||||
}
|
|
||||||
if (random.nextInt(2) < 1) {
|
|
||||||
this.add(loc, new Chest());
|
|
||||||
}
|
|
||||||
if (random.nextInt(2) < 1) {
|
|
||||||
this.add(loc, new BasicSword());
|
|
||||||
}
|
|
||||||
if (random.nextInt(2) < 1) {
|
|
||||||
this.add(loc, new Shield());
|
|
||||||
}
|
|
||||||
if (random.nextInt(2) < 1) {
|
|
||||||
this.add(loc, new FireStaff());
|
|
||||||
}
|
|
||||||
if (random.nextInt(2) < 1) {
|
|
||||||
this.add(loc, new BasicBow());
|
|
||||||
}
|
|
||||||
if (random.nextInt(2) < 1) {
|
|
||||||
this.add(loc, new Backpack());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canGo(Location to) {
|
|
||||||
return !grid.contains(to, (i) -> ((i instanceof Wall && !(i instanceof FakeWall)) || i instanceof Actor));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNeighbour(Location from, GridDirection dir) {
|
|
||||||
return from.canGo(dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canGo(Location from, GridDirection dir) {
|
|
||||||
if (!from.canGo(dir)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Location loc = from.go(dir);
|
|
||||||
return canGo(loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void draw(ITurtle painter, Printer printer) {
|
|
||||||
Iterable<Location> cells;
|
|
||||||
if (Main.MAP_DRAW_ONLY_DIRTY_CELLS) {
|
|
||||||
if (dirtyLocs.isEmpty()) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
cells = dirtyLocs;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cells = grid.locations();
|
|
||||||
painter.as(GraphicsContext.class).clearRect(0, 0, getWidth() * printer.getCharWidth(),
|
|
||||||
getHeight() * printer.getCharHeight());
|
|
||||||
printer.clearRegion(1, 1, getWidth(), getHeight());
|
|
||||||
}
|
|
||||||
GraphicsContext ctx = painter.as(GraphicsContext.class);
|
|
||||||
double h = printer.getCharHeight();
|
|
||||||
double w = printer.getCharWidth();
|
|
||||||
if (Main.MAP_AUTO_SCALE_ITEM_DRAW) {
|
|
||||||
ctx.save();
|
|
||||||
ctx.scale(w / h, 1.0);
|
|
||||||
w = h;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
for (Location loc : cells) {
|
|
||||||
List<Item> list = grid.get(loc);
|
|
||||||
String sym = " ";
|
|
||||||
if (!list.isEmpty()) {
|
|
||||||
if (Main.MAP_DRAW_ONLY_DIRTY_CELLS) {
|
|
||||||
ctx.clearRect(loc.getX() * w, loc.getY() * h, w, h);
|
|
||||||
// ctx.fillRect(loc.getX() * w, loc.getY() * h, w, h);
|
|
||||||
}
|
|
||||||
painter.save();
|
|
||||||
painter.jumpTo((loc.getX() + 0.5) * w, (loc.getY() + 0.5) * h);
|
|
||||||
boolean dontPrint = list.get(0).draw(painter, w, h);
|
|
||||||
painter.restore();
|
|
||||||
if (!dontPrint) {
|
|
||||||
sym = list.get(0).getPrintSymbol();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
printer.printAt(loc.getX() + 1, loc.getY() + 1, sym);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (Main.MAP_AUTO_SCALE_ITEM_DRAW) {
|
|
||||||
ctx.restore();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dirtyLocs.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a black void unto the whole map.
|
|
||||||
* Properly draws only the tiles visible to the player.
|
|
||||||
*
|
|
||||||
* @param painter A painter
|
|
||||||
* @param printer A printer
|
|
||||||
*/
|
|
||||||
public void drawVisible(ITurtle painter, Printer printer) {
|
|
||||||
Iterable<Location> cells;
|
|
||||||
if (Main.MAP_DRAW_ONLY_DIRTY_CELLS) {
|
|
||||||
if (dirtyLocs.isEmpty()) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
cells = dirtyLocs;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cells = grid.locations();
|
|
||||||
painter.as(GraphicsContext.class).clearRect(0, 0, getWidth() * printer.getCharWidth(),
|
|
||||||
getHeight() * printer.getCharHeight());
|
|
||||||
printer.clearRegion(1, 1, getWidth(), getHeight());
|
|
||||||
}
|
|
||||||
GraphicsContext ctx = painter.as(GraphicsContext.class);
|
|
||||||
double h = printer.getCharHeight();
|
|
||||||
double w = printer.getCharWidth();
|
|
||||||
if (Main.MAP_AUTO_SCALE_ITEM_DRAW) {
|
|
||||||
ctx.save();
|
|
||||||
ctx.scale(w / h, 1.0);
|
|
||||||
w = h;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
PlayerCharacter player = null;
|
|
||||||
Location playerPos = null;
|
|
||||||
for (Location loc : cells) {
|
|
||||||
printer.printAt(loc.getX() + 1, loc.getY() + 1, " ");
|
|
||||||
//We need to get the player and its location from somewhere.
|
|
||||||
if (this.hasActors(loc) && this.getActors(loc).get(0) instanceof PlayerCharacter) {
|
|
||||||
player = (PlayerCharacter) this.getActors(loc).get(0);
|
|
||||||
playerPos = loc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (player == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
List<Location> positions = getVisible(getNeighbourhood(playerPos, player.getVision()), playerPos);
|
|
||||||
positions.add(playerPos);
|
|
||||||
for (Location loc : positions) {
|
|
||||||
List<Item> list = grid.get(loc);
|
|
||||||
String sym = ".";
|
|
||||||
if (!list.isEmpty()) {
|
|
||||||
if (Main.MAP_DRAW_ONLY_DIRTY_CELLS) {
|
|
||||||
ctx.clearRect(loc.getX() * w, loc.getY() * h, w, h);
|
|
||||||
// ctx.fillRect(loc.getX() * w, loc.getY() * h, w, h);
|
|
||||||
}
|
|
||||||
painter.save();
|
|
||||||
painter.jumpTo((loc.getX() + 0.5) * w, (loc.getY() + 0.5) * h);
|
|
||||||
boolean dontPrint = list.get(0).draw(painter, w, h);
|
|
||||||
painter.restore();
|
|
||||||
if (!dontPrint) {
|
|
||||||
sym = list.get(0).getPrintSymbol();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
printer.printAt(loc.getX() + 1, loc.getY() + 1, sym);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (Main.MAP_AUTO_SCALE_ITEM_DRAW) {
|
|
||||||
ctx.restore();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dirtyLocs.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Location> getVisible(List<Location> neighbours, Location loc) {
|
|
||||||
List<Location> invalid = new ArrayList<>();
|
|
||||||
for (Location neighbour : neighbours) {
|
|
||||||
if (!hasWall(neighbour)) {
|
|
||||||
for (Location tile : loc.gridLineTo(neighbour)) {
|
|
||||||
if (hasWall(tile)) {
|
|
||||||
invalid.add(neighbour);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
neighbours.removeAll(invalid);
|
|
||||||
return neighbours;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Actor> getActors(Location loc) {
|
|
||||||
List<Actor> items = new ArrayList<>();
|
|
||||||
for (Item item : grid.get(loc)) {
|
|
||||||
if (item instanceof Actor) {
|
|
||||||
items.add((Actor) item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Item> getAll(Location loc) {
|
|
||||||
return Collections.unmodifiableList(grid.get(loc));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Item> getAllModifiable(Location loc) {
|
|
||||||
dirtyLocs.add(loc);
|
|
||||||
return grid.get(loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clean(Location loc) {
|
|
||||||
// remove any items that have health < 0:
|
|
||||||
if (grid.get(loc).removeIf((item) -> {
|
|
||||||
if (item.isDestroyed()) {
|
|
||||||
items.remove(item);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
})) {
|
|
||||||
dirtyLocs.add(loc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IArea getArea() {
|
|
||||||
return grid.getArea();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getHeight() {
|
|
||||||
return grid.getHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Item> getItems(Location loc) {
|
|
||||||
List<Item> items = new ArrayList<>(grid.get(loc));
|
|
||||||
items.removeIf((i) -> i instanceof Actor);
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Location getLocation(Item item) {
|
|
||||||
return items.get(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Location getLocation(int x, int y) {
|
|
||||||
return grid.getArea().location(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Location getNeighbour(Location from, GridDirection dir) {
|
|
||||||
if (!hasNeighbour(from, dir)) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return from.go(dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getWidth() {
|
|
||||||
return grid.getWidth();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Location go(Location from, GridDirection dir) throws IllegalMoveException {
|
|
||||||
if (!from.canGo(dir)) {
|
|
||||||
throw new IllegalMoveException("Cannot move outside map!");
|
|
||||||
}
|
|
||||||
Location loc = from.go(dir);
|
|
||||||
if (!canGo(loc)) {
|
|
||||||
throw new IllegalMoveException("Occupied!");
|
|
||||||
}
|
|
||||||
return loc;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean has(Location loc, Item target) {
|
|
||||||
return grid.contains(loc, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasActors(Location loc) {
|
|
||||||
return grid.contains(loc, (i) -> i instanceof Actor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasItems(Location loc) {
|
|
||||||
// true if grid cell contains an item which is not an IActor
|
|
||||||
return grid.contains(loc, (i) -> !(i instanceof Actor));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasWall(Location loc) {
|
|
||||||
return grid.contains(loc, (i) -> i instanceof Wall);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remove(Location loc, Item item) {
|
|
||||||
grid.remove(loc, item);
|
|
||||||
items.remove(item);
|
|
||||||
dirtyLocs.add(loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Location> getNeighbourhood(Location startLocation, int distance) {
|
|
||||||
if (distance < 0 || startLocation == null) {
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
} else if (distance == 0) {
|
|
||||||
return new ArrayList<>(); // empty!
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run BFS to find all neighbors within the specified distance
|
|
||||||
Queue<Location> queue = new LinkedList<>();
|
|
||||||
List<Location> explored = new ArrayList<>();
|
|
||||||
explored.add(startLocation);
|
|
||||||
queue.add(startLocation);
|
|
||||||
|
|
||||||
while (!queue.isEmpty()) {
|
|
||||||
Location location = queue.poll();
|
|
||||||
|
|
||||||
// The required distance has been reached
|
|
||||||
if (Math.abs(startLocation.getX() - location.getX()) >= distance ||
|
|
||||||
Math.abs(startLocation.getY() - location.getY()) >= distance) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (GridDirection gridDirection : GridDirection.EIGHT_DIRECTIONS) {
|
|
||||||
addIfValid(queue, explored, location, gridDirection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// While the BFS requires the start location, it's not considered its own neighbor
|
|
||||||
explored.remove(startLocation);
|
|
||||||
return explored;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the specified location to the queue if valid
|
|
||||||
*
|
|
||||||
* @param queue <p>The queue to add the location to</p>
|
|
||||||
* @param explored <p>The already explored locations</p>
|
|
||||||
* @param startLocation <p>The location to start at</p>
|
|
||||||
* @param direction <p>The direction to go</p>
|
|
||||||
*/
|
|
||||||
private void addIfValid(Queue<Location> queue, List<Location> explored, Location startLocation,
|
|
||||||
GridDirection direction) {
|
|
||||||
Location location;
|
|
||||||
|
|
||||||
// If going in the specified direction would exit the map, just return
|
|
||||||
try {
|
|
||||||
location = startLocation.go(direction);
|
|
||||||
} catch (IndexOutOfBoundsException ignored) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!explored.contains(location) && grid.isValid(location)) {
|
|
||||||
explored.add(location);
|
|
||||||
queue.add(location);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,48 +1,393 @@
|
|||||||
package inf101.v18.rogue101.map;
|
package inf101.v18.rogue101.map;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
import inf101.v18.gfx.gfxmode.ITurtle;
|
import inf101.v18.gfx.gfxmode.ITurtle;
|
||||||
import inf101.v18.gfx.textmode.Printer;
|
import inf101.v18.gfx.textmode.Printer;
|
||||||
import inf101.v18.grid.Location;
|
import inf101.v18.grid.GridDirection;
|
||||||
import inf101.v18.rogue101.object.Item;
|
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.examples.Carrot;
|
||||||
|
import inf101.v18.rogue101.game.IllegalMoveException;
|
||||||
|
import inf101.v18.rogue101.items.*;
|
||||||
|
import inf101.v18.rogue101.items.Manga;
|
||||||
|
import inf101.v18.rogue101.objects.*;
|
||||||
|
import javafx.scene.canvas.GraphicsContext;
|
||||||
|
|
||||||
import java.util.List;
|
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) {
|
||||||
* Extra map methods that are for the game class only!
|
grid = new MultiGrid<>(area);
|
||||||
*
|
}
|
||||||
* @author anya
|
|
||||||
*/
|
|
||||||
public interface GameMap extends MapView {
|
|
||||||
|
|
||||||
/**
|
public GameMap(int width, int height) {
|
||||||
* Draw the map
|
grid = new MultiGrid<>(width, height);
|
||||||
*
|
}
|
||||||
* @param painter
|
|
||||||
* @param printer
|
|
||||||
*/
|
|
||||||
void draw(ITurtle painter, Printer printer);
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Get a modifiable list of items
|
public void add(ILocation loc, IItem item) {
|
||||||
*
|
|
||||||
* @param loc
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
List<Item> getAllModifiable(Location loc);
|
|
||||||
|
|
||||||
/**
|
// keep track of location of all items
|
||||||
* Remove any destroyed items at the given location (items where {@link Item#isDestroyed()} is true)
|
items.put(item, loc);
|
||||||
*
|
// also keep track of whether we need to redraw this cell
|
||||||
* @param loc
|
dirtyLocs.add(loc);
|
||||||
*/
|
|
||||||
void clean(Location loc);
|
|
||||||
|
|
||||||
/**
|
// do the actual adding
|
||||||
* Remove an item
|
List<IItem> list = grid.get(loc);
|
||||||
*
|
|
||||||
* @param loc
|
|
||||||
* @param item
|
|
||||||
*/
|
|
||||||
void remove(Location loc, Item item);
|
|
||||||
|
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
if (item.compareTo(list.get(i)) >= 0) {
|
||||||
|
list.add(i, item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list.add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addRandomItems(ILocation loc) {
|
||||||
|
if (!this.hasActors(loc) && !this.hasWall(loc)) {
|
||||||
|
Random random = new Random();
|
||||||
|
if (random.nextInt(10) < 2) {
|
||||||
|
this.add(loc, new Carrot());
|
||||||
|
}
|
||||||
|
if (random.nextInt(10) < 1) {
|
||||||
|
this.add(loc, new Manga());
|
||||||
|
}
|
||||||
|
if (random.nextInt(2) < 1) {
|
||||||
|
this.add(loc, new Chest());
|
||||||
|
}
|
||||||
|
if (random.nextInt(2) < 1) {
|
||||||
|
this.add(loc, new Sword());
|
||||||
|
}
|
||||||
|
if (random.nextInt(2) < 1) {
|
||||||
|
this.add(loc, new Shield());
|
||||||
|
}
|
||||||
|
if (random.nextInt(2) < 1) {
|
||||||
|
this.add(loc, new Staff());
|
||||||
|
}
|
||||||
|
if (random.nextInt(2) < 1) {
|
||||||
|
this.add(loc, new Bow());
|
||||||
|
}
|
||||||
|
if (random.nextInt(2) < 1) {
|
||||||
|
this.add(loc, new Backpack());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canGo(ILocation to) {
|
||||||
|
return !grid.contains(to, (i) -> (i instanceof Wall || i instanceof IActor));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNeighbour(ILocation from, GridDirection dir) {
|
||||||
|
return from.canGo(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canGo(ILocation from, GridDirection dir) {
|
||||||
|
if (!from.canGo(dir))
|
||||||
|
return false;
|
||||||
|
ILocation loc = from.go(dir);
|
||||||
|
return canGo(loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(ITurtle painter, Printer printer) {
|
||||||
|
Iterable<ILocation> cells;
|
||||||
|
if (Main.MAP_DRAW_ONLY_DIRTY_CELLS) {
|
||||||
|
if (dirtyLocs.isEmpty())
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
cells = dirtyLocs;
|
||||||
|
} else {
|
||||||
|
cells = grid.locations();
|
||||||
|
painter.as(GraphicsContext.class).clearRect(0, 0, getWidth() * printer.getCharWidth(),
|
||||||
|
getHeight() * printer.getCharHeight());
|
||||||
|
printer.clearRegion(1, 1, getWidth(), getHeight());
|
||||||
|
}
|
||||||
|
GraphicsContext ctx = painter.as(GraphicsContext.class);
|
||||||
|
double h = printer.getCharHeight();
|
||||||
|
double w = printer.getCharWidth();
|
||||||
|
if (Main.MAP_AUTO_SCALE_ITEM_DRAW) {
|
||||||
|
ctx.save();
|
||||||
|
ctx.scale(w / h, 1.0);
|
||||||
|
w = h;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
for (ILocation loc : cells) {
|
||||||
|
List<IItem> list = grid.get(loc);
|
||||||
|
String sym = " ";
|
||||||
|
if (!list.isEmpty()) {
|
||||||
|
if (Main.MAP_DRAW_ONLY_DIRTY_CELLS) {
|
||||||
|
ctx.clearRect(loc.getX() * w, loc.getY() * h, w, h);
|
||||||
|
// ctx.fillRect(loc.getX() * w, loc.getY() * h, w, h);
|
||||||
|
}
|
||||||
|
painter.save();
|
||||||
|
painter.jumpTo((loc.getX() + 0.5) * w, (loc.getY() + 0.5) * h);
|
||||||
|
boolean dontPrint = list.get(0).draw(painter, w, h);
|
||||||
|
painter.restore();
|
||||||
|
if (!dontPrint) {
|
||||||
|
sym = list.get(0).getPrintSymbol();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printer.printAt(loc.getX() + 1, loc.getY() + 1, sym);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (Main.MAP_AUTO_SCALE_ITEM_DRAW) {
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dirtyLocs.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a black void unto the whole map.
|
||||||
|
* Properly draws only the tiles visible to the player.
|
||||||
|
*
|
||||||
|
* @param painter A painter
|
||||||
|
* @param printer A printer
|
||||||
|
*/
|
||||||
|
public void drawVisible(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 {
|
||||||
|
IPlayer player = null;
|
||||||
|
ILocation playerPos = null;
|
||||||
|
for (ILocation loc : cells) {
|
||||||
|
printer.printAt(loc.getX() + 1, loc.getY() + 1, " ");
|
||||||
|
//We need to get the player and its location from somewhere.
|
||||||
|
if (this.hasActors(loc) && this.getActors(loc).get(0) instanceof IPlayer) {
|
||||||
|
player = (IPlayer) this.getActors(loc).get(0);
|
||||||
|
playerPos = loc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (player == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<ILocation> positions = getVisible(getNeighbourhood(playerPos,player.getVision()), playerPos);
|
||||||
|
positions.add(playerPos);
|
||||||
|
for (ILocation loc : positions) {
|
||||||
|
List<IItem> list = grid.get(loc);
|
||||||
|
String sym = ".";
|
||||||
|
if (!list.isEmpty()) {
|
||||||
|
if (Main.MAP_DRAW_ONLY_DIRTY_CELLS) {
|
||||||
|
ctx.clearRect(loc.getX() * w, loc.getY() * h, w, h);
|
||||||
|
// ctx.fillRect(loc.getX() * w, loc.getY() * h, w, h);
|
||||||
|
}
|
||||||
|
painter.save();
|
||||||
|
painter.jumpTo((loc.getX() + 0.5) * w, (loc.getY() + 0.5) * h);
|
||||||
|
boolean dontPrint = list.get(0).draw(painter, w, h);
|
||||||
|
painter.restore();
|
||||||
|
if (!dontPrint) {
|
||||||
|
sym = list.get(0).getPrintSymbol();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
printer.printAt(loc.getX() + 1, loc.getY() + 1, sym);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (Main.MAP_AUTO_SCALE_ITEM_DRAW) {
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dirtyLocs.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ILocation> getVisible(List<ILocation> neighbours, ILocation loc) {
|
||||||
|
List<ILocation> invalid = new ArrayList<>();
|
||||||
|
for (ILocation neighbour : neighbours) {
|
||||||
|
if (!hasWall(neighbour)) {
|
||||||
|
for (ILocation tile : loc.gridLineTo(neighbour)) {
|
||||||
|
if (hasWall(tile)) {
|
||||||
|
invalid.add(neighbour);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
neighbours.removeAll(invalid);
|
||||||
|
return neighbours;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 || i instanceof FakeWall);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(ILocation loc, IItem item) {
|
||||||
|
grid.remove(loc, item);
|
||||||
|
items.remove(item);
|
||||||
|
dirtyLocs.add(loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ILocation> getNeighbourhood(ILocation loc, int dist) {
|
||||||
|
if (dist < 0 || loc == null) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
} else if (dist == 0) {
|
||||||
|
return new ArrayList<>(); // empty!
|
||||||
|
}
|
||||||
|
List<ILocation> neighbours = new ArrayList<>();
|
||||||
|
int startX = loc.getX();
|
||||||
|
int startY = loc.getY();
|
||||||
|
for (int i = 1; i <= dist; i++) {
|
||||||
|
for (int x = startX - i + 1; x < startX + i; x++) { //Top and bottom
|
||||||
|
if (grid.isValid(x, startY - i)) {
|
||||||
|
neighbours.add(getLocation(x, startY - i));
|
||||||
|
}
|
||||||
|
if (grid.isValid(x, startY + i)) {
|
||||||
|
neighbours.add(getLocation(x, startY + i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int y = startY - i; y <= startY + i; y++) { //Sides
|
||||||
|
if (grid.isValid(startX - i, y)) {
|
||||||
|
neighbours.add(getLocation(startX - i, y));
|
||||||
|
}
|
||||||
|
if (grid.isValid(startX + i, y)) {
|
||||||
|
neighbours.add(getLocation(startX + i, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return neighbours;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
48
src/main/java/inf101/v18/rogue101/map/IGameMap.java
Normal file
48
src/main/java/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);
|
||||||
|
|
||||||
|
}
|
190
src/main/java/inf101/v18/rogue101/map/IMapView.java
Normal file
190
src/main/java/inf101/v18/rogue101/map/IMapView.java
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
package inf101.v18.rogue101.map;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import inf101.v18.grid.GridDirection;
|
||||||
|
import inf101.v18.grid.IArea;
|
||||||
|
import inf101.v18.grid.ILocation;
|
||||||
|
import inf101.v18.rogue101.game.IllegalMoveException;
|
||||||
|
import inf101.v18.rogue101.objects.IActor;
|
||||||
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
|
|
||||||
|
public interface IMapView {
|
||||||
|
/**
|
||||||
|
* Add an item to the map.
|
||||||
|
*
|
||||||
|
* @param loc
|
||||||
|
* A location
|
||||||
|
* @param item
|
||||||
|
* the item
|
||||||
|
*/
|
||||||
|
void add(ILocation loc, IItem item);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if it's legal for an IActor to go into the given location
|
||||||
|
*
|
||||||
|
* @param to
|
||||||
|
* A location
|
||||||
|
* @return True if the location isn't already occupied
|
||||||
|
*/
|
||||||
|
boolean canGo(ILocation to);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if it's legal for an IActor to go in the given direction from the given
|
||||||
|
* location
|
||||||
|
*
|
||||||
|
* @param from
|
||||||
|
* Current location
|
||||||
|
* @param dir
|
||||||
|
* Direction we want to move in
|
||||||
|
* @return True if the next location exists and isn't occupied
|
||||||
|
*/
|
||||||
|
boolean canGo(ILocation from, GridDirection dir);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all IActors at the given location
|
||||||
|
* <p>
|
||||||
|
* The returned list either can't be modified, or modifying it won't affect the
|
||||||
|
* map.
|
||||||
|
*
|
||||||
|
* @param loc
|
||||||
|
* @return A list of actors
|
||||||
|
*/
|
||||||
|
List<IActor> getActors(ILocation loc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all items (both IActors and other IItems) at the given location
|
||||||
|
* <p>
|
||||||
|
* The returned list either can't be modified, or modifying it won't affect the
|
||||||
|
* map.
|
||||||
|
*
|
||||||
|
* @param loc
|
||||||
|
* @return A list of items
|
||||||
|
*/
|
||||||
|
List<IItem> getAll(ILocation loc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all non-IActor items at the given location
|
||||||
|
* <p>
|
||||||
|
* The returned list either can't be modified, or modifying it won't affect the
|
||||||
|
* map.
|
||||||
|
*
|
||||||
|
* @param loc
|
||||||
|
* @return A list of items, non of which are instanceof IActor
|
||||||
|
*/
|
||||||
|
List<IItem> getItems(ILocation loc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return A 2D-area defining the legal map locations
|
||||||
|
*/
|
||||||
|
IArea getArea();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Height of the map, same as
|
||||||
|
* {@link #getArea()}.{@link IArea#getHeight()}
|
||||||
|
*/
|
||||||
|
int getHeight();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Width of the map, same as {@link #getArea()}.{@link IArea#getWidth()}
|
||||||
|
*/
|
||||||
|
int getWidth();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find location of an item
|
||||||
|
*
|
||||||
|
* @param item
|
||||||
|
* The item
|
||||||
|
* @return It's location, or <code>null</code> if it's not on the map
|
||||||
|
*/
|
||||||
|
ILocation getLocation(IItem item);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate (x,y)-coordinates to ILocation
|
||||||
|
*
|
||||||
|
* @param x
|
||||||
|
* @param y
|
||||||
|
* @return an ILocation
|
||||||
|
* @throws IndexOutOfBoundsException
|
||||||
|
* if (x,y) is outside {@link #getArea()}
|
||||||
|
*/
|
||||||
|
ILocation getLocation(int x, int y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the neighbouring location in the given direction
|
||||||
|
*
|
||||||
|
* @param from
|
||||||
|
* A location
|
||||||
|
* @param dir
|
||||||
|
* the Direction
|
||||||
|
* @return from's neighbour in direction dir, or null, if this would be outside
|
||||||
|
* the map
|
||||||
|
*/
|
||||||
|
ILocation getNeighbour(ILocation from, GridDirection dir);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute new location of an IActor moving the given direction
|
||||||
|
*
|
||||||
|
* @param from
|
||||||
|
* Original location
|
||||||
|
* @param dir
|
||||||
|
* Direction we're moving in
|
||||||
|
* @return The new location
|
||||||
|
* @throws IllegalMoveException
|
||||||
|
* if !{@link #canGo(ILocation, GridDirection)}
|
||||||
|
*/
|
||||||
|
ILocation go(ILocation from, GridDirection dir) throws IllegalMoveException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an item exists at a location
|
||||||
|
*
|
||||||
|
* @param loc
|
||||||
|
* The location
|
||||||
|
* @param target
|
||||||
|
* The item we're interested in
|
||||||
|
* @return True if target would appear in {@link #getAll(loc)}
|
||||||
|
*/
|
||||||
|
boolean has(ILocation loc, IItem target);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for actors.
|
||||||
|
*
|
||||||
|
* @param loc
|
||||||
|
* @return True if {@link #getActors(loc)} would be non-empty
|
||||||
|
*/
|
||||||
|
boolean hasActors(ILocation loc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for non-actors
|
||||||
|
*
|
||||||
|
* @param loc
|
||||||
|
* @return True if {@link #getItem(loc)} would be non-empty
|
||||||
|
*/
|
||||||
|
boolean hasItems(ILocation loc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for walls
|
||||||
|
*
|
||||||
|
* @param loc
|
||||||
|
* @return True if there is a wall at the given location ({@link #getAll(loc)}
|
||||||
|
* would contain an instance of {@link Wall})
|
||||||
|
*/
|
||||||
|
boolean hasWall(ILocation loc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a neighbour exists on the map
|
||||||
|
*
|
||||||
|
* @param from A location
|
||||||
|
* @param dir A direction
|
||||||
|
* @return True if {@link #getNeighbour(from, dir)} would return non-null
|
||||||
|
*/
|
||||||
|
boolean hasNeighbour(ILocation from, GridDirection dir);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all locations within i steps from the given centre
|
||||||
|
* @param centre
|
||||||
|
* @param dist
|
||||||
|
* @return A list of locations, all at most i grid cells away from centre
|
||||||
|
*/
|
||||||
|
List<ILocation> getNeighbourhood(ILocation centre, int dist);
|
||||||
|
}
|
@ -1,17 +1,17 @@
|
|||||||
package inf101.v18.rogue101.map;
|
package inf101.v18.rogue101.map;
|
||||||
|
|
||||||
import inf101.v18.grid.IGrid;
|
|
||||||
import inf101.v18.grid.MyGrid;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
import inf101.v18.grid.IGrid;
|
||||||
|
import inf101.v18.grid.MyGrid;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class to read a Boulder Dash map from a file. After the file is read, the
|
* A class to read a Boulder Dash map from a file. After the file is read, the
|
||||||
* map is stored as an {@link #IGrid} whose entries are characters. This can
|
* map is stored as an {@link #IGrid} whose entries are characters. This can
|
||||||
* then be used to be passed to the constructor in {@link #BDMap}.
|
* then be used to be passed to the constructor in {@link #BDMap}.
|
||||||
* <p>
|
*
|
||||||
* The first line of the file should could contain two numbers. First the width
|
* The first line of the file should could contain two numbers. First the width
|
||||||
* and then the height of the map. After that follows a matrix of characters
|
* and then the height of the map. After that follows a matrix of characters
|
||||||
* describing which object goes where in the map. '*' for wall, ' ' for empty,
|
* describing which object goes where in the map. '*' for wall, ' ' for empty,
|
||||||
@ -34,69 +34,67 @@ import java.util.Scanner;
|
|||||||
* @author anya (Rogue101 update, 2018)
|
* @author anya (Rogue101 update, 2018)
|
||||||
*/
|
*/
|
||||||
public class MapReader {
|
public class MapReader {
|
||||||
/**
|
/**
|
||||||
* This method fills the previously initialized {@link #symbolMap} with the
|
* This method fills the previously initialized {@link #symbolMap} with the
|
||||||
* characters read from the file.
|
* characters read from the file.
|
||||||
*/
|
*/
|
||||||
private static void fillMap(IGrid<String> symbolMap, Scanner in) {
|
private static void fillMap(IGrid<String> symbolMap, Scanner in) {
|
||||||
// we need to store x and y in an object (array) rather than as variables,
|
// we need to store x and y in an object (array) rather than as variables,
|
||||||
// otherwise the foreach and lambda below won't work.
|
// otherwise the foreach and lambda below won't work.
|
||||||
int[] xy = new int[2]; // xy[0] is x, xy[1] is y
|
int[] xy = new int[2]; // xy[0] is x, xy[1] is y
|
||||||
xy[1] = 0;
|
xy[1] = 0;
|
||||||
while (in.hasNextLine()) {
|
while (in.hasNextLine()) {
|
||||||
xy[0] = 0;
|
xy[0] = 0;
|
||||||
in.nextLine().codePoints().forEach((codePoint) -> {
|
in.nextLine().codePoints().forEach((codePoint) -> {
|
||||||
if (xy[0] < symbolMap.getWidth()) {
|
if (xy[0] < symbolMap.getWidth())
|
||||||
symbolMap.set(xy[0]++, xy[1], String.valueOf(Character.toChars(codePoint)));
|
symbolMap.set(xy[0]++, xy[1], String.valueOf(Character.toChars(codePoint)));
|
||||||
}
|
});
|
||||||
});
|
xy[1]++;
|
||||||
xy[1]++;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load map from file.
|
* Load map from file.
|
||||||
* <p>
|
* <p>
|
||||||
* Files are search for relative to the folder containing the MapReader class.
|
* 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
|
* @return the dungeon map as a grid of characters read from the file, or null
|
||||||
* if it failed
|
* if it failed
|
||||||
*/
|
*/
|
||||||
public static IGrid<String> readFile(String path) {
|
public static IGrid<String> readFile(String path) {
|
||||||
IGrid<String> symbolMap = null;
|
IGrid<String> symbolMap = null;
|
||||||
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
|
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
|
||||||
InputStream stream = classloader.getResourceAsStream(path);
|
InputStream stream = classloader.getResourceAsStream(path);
|
||||||
if (stream == null) {
|
if (stream == null)
|
||||||
return null;
|
return null;
|
||||||
}
|
try (Scanner in = new Scanner(stream, "UTF-8")) {
|
||||||
try (Scanner in = new Scanner(stream, "UTF-8")) {
|
int width = in.nextInt();
|
||||||
int width = in.nextInt();
|
int height = in.nextInt();
|
||||||
int height = in.nextInt();
|
// System.out.println(width + " " + height);
|
||||||
// System.out.println(width + " " + height);
|
symbolMap = new MyGrid<String>(width, height, " ");
|
||||||
symbolMap = new MyGrid<String>(width, height, " ");
|
in.nextLine();
|
||||||
in.nextLine();
|
fillMap(symbolMap, in);
|
||||||
fillMap(symbolMap, in);
|
}
|
||||||
}
|
try {
|
||||||
try {
|
stream.close();
|
||||||
stream.close();
|
} catch (IOException e) {
|
||||||
} catch (IOException e) {
|
}
|
||||||
}
|
return symbolMap;
|
||||||
return symbolMap;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the dungeon map as a grid of characters read from the input string,
|
* @return the dungeon map as a grid of characters read from the input string,
|
||||||
* or null if it failed
|
* or null if it failed
|
||||||
*/
|
*/
|
||||||
public static IGrid<String> readString(String input) {
|
public static IGrid<String> readString(String input) {
|
||||||
IGrid<String> symbolMap = null;
|
IGrid<String> symbolMap = null;
|
||||||
try (Scanner in = new Scanner(input)) {
|
try (Scanner in = new Scanner(input)) {
|
||||||
int width = in.nextInt();
|
int width = in.nextInt();
|
||||||
int height = in.nextInt();
|
int height = in.nextInt();
|
||||||
symbolMap = new MyGrid<String>(width, height, " ");
|
symbolMap = new MyGrid<String>(width, height, " ");
|
||||||
in.nextLine();
|
in.nextLine();
|
||||||
fillMap(symbolMap, in);
|
fillMap(symbolMap, in);
|
||||||
}
|
}
|
||||||
return symbolMap;
|
return symbolMap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,177 +0,0 @@
|
|||||||
package inf101.v18.rogue101.map;
|
|
||||||
|
|
||||||
import inf101.v18.grid.GridDirection;
|
|
||||||
import inf101.v18.grid.IArea;
|
|
||||||
import inf101.v18.grid.Location;
|
|
||||||
import inf101.v18.rogue101.game.IllegalMoveException;
|
|
||||||
import inf101.v18.rogue101.object.Actor;
|
|
||||||
import inf101.v18.rogue101.object.Item;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface MapView {
|
|
||||||
/**
|
|
||||||
* Add an item to the map.
|
|
||||||
*
|
|
||||||
* @param loc A location
|
|
||||||
* @param item the item
|
|
||||||
*/
|
|
||||||
void add(Location loc, Item item);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if it's legal for an IActor to go into the given location
|
|
||||||
*
|
|
||||||
* @param to A location
|
|
||||||
* @return True if the location isn't already occupied
|
|
||||||
*/
|
|
||||||
boolean canGo(Location to);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if it's legal for an IActor to go in the given direction from the given
|
|
||||||
* location
|
|
||||||
*
|
|
||||||
* @param from Current location
|
|
||||||
* @param dir Direction we want to move in
|
|
||||||
* @return True if the next location exists and isn't occupied
|
|
||||||
*/
|
|
||||||
boolean canGo(Location from, GridDirection dir);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all IActors at the given location
|
|
||||||
* <p>
|
|
||||||
* The returned list either can't be modified, or modifying it won't affect the
|
|
||||||
* map.
|
|
||||||
*
|
|
||||||
* @param loc
|
|
||||||
* @return A list of actors
|
|
||||||
*/
|
|
||||||
List<Actor> getActors(Location loc);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all items (both IActors and other IItems) at the given location
|
|
||||||
* <p>
|
|
||||||
* The returned list either can't be modified, or modifying it won't affect the
|
|
||||||
* map.
|
|
||||||
*
|
|
||||||
* @param loc
|
|
||||||
* @return A list of items
|
|
||||||
*/
|
|
||||||
List<Item> getAll(Location loc);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all non-IActor items at the given location
|
|
||||||
* <p>
|
|
||||||
* The returned list either can't be modified, or modifying it won't affect the
|
|
||||||
* map.
|
|
||||||
*
|
|
||||||
* @param loc
|
|
||||||
* @return A list of items, non of which are instanceof IActor
|
|
||||||
*/
|
|
||||||
List<Item> getItems(Location loc);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return A 2D-area defining the legal map locations
|
|
||||||
*/
|
|
||||||
IArea getArea();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Height of the map, same as
|
|
||||||
* {@link #getArea()}.{@link IArea#getHeight()}
|
|
||||||
*/
|
|
||||||
int getHeight();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Width of the map, same as {@link #getArea()}.{@link IArea#getWidth()}
|
|
||||||
*/
|
|
||||||
int getWidth();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find location of an item
|
|
||||||
*
|
|
||||||
* @param item The item
|
|
||||||
* @return It's location, or <code>null</code> if it's not on the map
|
|
||||||
*/
|
|
||||||
Location getLocation(Item item);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Translate (x,y)-coordinates to ILocation
|
|
||||||
*
|
|
||||||
* @param x
|
|
||||||
* @param y
|
|
||||||
* @return an ILocation
|
|
||||||
* @throws IndexOutOfBoundsException if (x,y) is outside {@link #getArea()}
|
|
||||||
*/
|
|
||||||
Location getLocation(int x, int y);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the neighbouring location in the given direction
|
|
||||||
*
|
|
||||||
* @param from A location
|
|
||||||
* @param dir the Direction
|
|
||||||
* @return from's neighbour in direction dir, or null, if this would be outside
|
|
||||||
* the map
|
|
||||||
*/
|
|
||||||
Location getNeighbour(Location from, GridDirection dir);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute new location of an IActor moving the given direction
|
|
||||||
*
|
|
||||||
* @param from Original location
|
|
||||||
* @param dir Direction we're moving in
|
|
||||||
* @return The new location
|
|
||||||
* @throws IllegalMoveException if !{@link #canGo(Location, GridDirection)}
|
|
||||||
*/
|
|
||||||
Location go(Location from, GridDirection dir) throws IllegalMoveException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if an item exists at a location
|
|
||||||
*
|
|
||||||
* @param loc The location
|
|
||||||
* @param target The item we're interested in
|
|
||||||
* @return True if target would appear in {@link #getAll(loc)}
|
|
||||||
*/
|
|
||||||
boolean has(Location loc, Item target);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check for actors.
|
|
||||||
*
|
|
||||||
* @param loc
|
|
||||||
* @return True if {@link #getActors(loc)} would be non-empty
|
|
||||||
*/
|
|
||||||
boolean hasActors(Location loc);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check for non-actors
|
|
||||||
*
|
|
||||||
* @param loc
|
|
||||||
* @return True if {@link #getItem(loc)} would be non-empty
|
|
||||||
*/
|
|
||||||
boolean hasItems(Location loc);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check for walls
|
|
||||||
*
|
|
||||||
* @param loc
|
|
||||||
* @return True if there is a wall at the given location ({@link #getAll(loc)}
|
|
||||||
* would contain an instance of {@link Wall})
|
|
||||||
*/
|
|
||||||
boolean hasWall(Location loc);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a neighbour exists on the map
|
|
||||||
*
|
|
||||||
* @param from A location
|
|
||||||
* @param dir A direction
|
|
||||||
* @return True if {@link #getNeighbour(from, dir)} would return non-null
|
|
||||||
*/
|
|
||||||
boolean hasNeighbour(Location from, GridDirection dir);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all locations within i steps from the given centre
|
|
||||||
*
|
|
||||||
* @param centre
|
|
||||||
* @param dist
|
|
||||||
* @return A list of locations, all at most i grid cells away from centre
|
|
||||||
*/
|
|
||||||
List<Location> getNeighbourhood(Location centre, int dist);
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
package inf101.v18.rogue101.object;
|
|
||||||
|
|
||||||
import inf101.v18.rogue101.game.Game;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An actor is an Item that can also do something, either controlled by the
|
|
||||||
* computer (NonPlayerCharacter) or the user (PlayerCharacter).
|
|
||||||
*
|
|
||||||
* @author anya
|
|
||||||
*/
|
|
||||||
public interface Actor extends Item {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return This actor's attack score (used against an item's
|
|
||||||
* {@link #getDefense()} score to see if an attack is successful)
|
|
||||||
*/
|
|
||||||
int getAttack();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The damage this actor deals on a successful attack (used together
|
|
||||||
* with
|
|
||||||
* {@link #handleDamage(Game, Item, int)} on
|
|
||||||
* the target)
|
|
||||||
*/
|
|
||||||
int getDamage();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* How many tiles the IActor is able to see in each direction.
|
|
||||||
*
|
|
||||||
* @return Number of tiles
|
|
||||||
*/
|
|
||||||
default int getVision() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets an item of the specified type, if the actor has the item.
|
|
||||||
*
|
|
||||||
* @return An IItem or null
|
|
||||||
*/
|
|
||||||
Item getItem(Class<?> type);
|
|
||||||
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
package inf101.v18.rogue101.object;
|
|
||||||
|
|
||||||
import inf101.v18.gfx.gfxmode.ITurtle;
|
|
||||||
import inf101.v18.gfx.textmode.BlocksAndBoxes;
|
|
||||||
import inf101.v18.rogue101.game.Game;
|
|
||||||
|
|
||||||
public class Dust implements Item {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean draw(ITurtle painter, double w, double h) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getCurrentHealth() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDefense() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxHealth() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "thick layer of dust";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSize() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getSymbol() {
|
|
||||||
return BlocksAndBoxes.BLOCK_HALF;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int handleDamage(Game game, Item source, int amount) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package inf101.v18.rogue101.object;
|
|
||||||
|
|
||||||
import inf101.v18.gfx.gfxmode.ITurtle;
|
|
||||||
import inf101.v18.gfx.textmode.BlocksAndBoxes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An objects that works like a wall in every sense, except it can be passed through
|
|
||||||
*/
|
|
||||||
public class FakeWall extends Wall {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean draw(ITurtle painter, double w, double h) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxHealth() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "fake wall";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getSymbol() {
|
|
||||||
return BlocksAndBoxes.BLOCK_HALF;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,175 +0,0 @@
|
|||||||
package inf101.v18.rogue101.object;
|
|
||||||
|
|
||||||
import inf101.v18.gfx.gfxmode.ITurtle;
|
|
||||||
import inf101.v18.rogue101.events.IEvent;
|
|
||||||
import inf101.v18.rogue101.game.Game;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An {@link Item} is something that can be placed on the map or into a
|
|
||||||
* container of items.
|
|
||||||
* <p>
|
|
||||||
* An {@link Actor} is a special case of {@link Item} used for things that are
|
|
||||||
* “alive”, such as the player or AI-controlled monsters/NPCs.
|
|
||||||
* <p>
|
|
||||||
* By default, all items have a hit point / health system (they can be damaged),
|
|
||||||
* and can be picked up. So you could potentially destroy an item or pick up a
|
|
||||||
* monster.
|
|
||||||
*
|
|
||||||
* @author anya
|
|
||||||
*/
|
|
||||||
public interface Item extends Comparable<Item> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
default int compareTo(Item other) {
|
|
||||||
return Integer.compare(getSize(), other.getSize());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw this item on the screen.
|
|
||||||
* <p>
|
|
||||||
* The turtle-painter will be positioned in the centre of the cell. You should
|
|
||||||
* avoid drawing outside the cell (size is indicated by the <code>w</code> and
|
|
||||||
* <code>h</code> parameters, so you can move ±w/2 and ±h/2 from the initial
|
|
||||||
* position). If you want to start in the lower left corner of the cell, you can
|
|
||||||
* start by doing <code>painter.jump(-w/2,h/2)</code> ((0,0) is in the top-left
|
|
||||||
* corner of the screen, so negative X points left and positive Y points down).
|
|
||||||
* <p>
|
|
||||||
* If this method returns <code>true</code>, the game will <em>not</em> print
|
|
||||||
* {@link #getSymbol()} in the cell.
|
|
||||||
* <p>
|
|
||||||
* All calls to <code>painter.save()</code> must be matched by a call to
|
|
||||||
* <code>painter.restore()</code>.
|
|
||||||
*
|
|
||||||
* @param painter A turtle-painter for drawing
|
|
||||||
* @param w The width of the cell
|
|
||||||
* @param h The height of the cell
|
|
||||||
* @return False if the letter from {@link #getSymbol()} should be drawn instead
|
|
||||||
*/
|
|
||||||
default boolean draw(ITurtle painter, double w, double h) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return "a" or "an", depending on the name
|
|
||||||
*/
|
|
||||||
default String getArticle() {
|
|
||||||
return "a";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get current remaining health points.
|
|
||||||
* <p>
|
|
||||||
* An object's <em>health points</em> determines how much damage it can take
|
|
||||||
* before it is destroyed / broken / killed.
|
|
||||||
*
|
|
||||||
* @return Current health points for this item
|
|
||||||
*/
|
|
||||||
int getCurrentHealth();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The defence score determines how hard an object/actor is to hit or grab.
|
|
||||||
*
|
|
||||||
* @return Defence score of this object
|
|
||||||
*/
|
|
||||||
int getDefense();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get item health as a 0.0..1.0 proportion.
|
|
||||||
*
|
|
||||||
* <li><code>getHealth() >= 1.0</code> means perfect condition
|
|
||||||
* <li><code>getHealth() <= 0.0</code> means broken or dead
|
|
||||||
* <li><code>0.0 < getHealth() < 1.0</code> means partially damaged
|
|
||||||
*
|
|
||||||
* @return Health, in the range 0.0 to 1.0
|
|
||||||
*/
|
|
||||||
default double getHealthStatus() {
|
|
||||||
return getMaxHealth() > 0 ? getCurrentHealth() / getMaxHealth() : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get maximum health points.
|
|
||||||
* <p>
|
|
||||||
* An object's <em>health points</em> determines how much damage it can take
|
|
||||||
* before it is destroyed / broken / killed.
|
|
||||||
*
|
|
||||||
* @return Max health points for this item
|
|
||||||
*/
|
|
||||||
int getMaxHealth();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a (user-friendly) name for the item
|
|
||||||
* <p>
|
|
||||||
* Used for things like <code>"You see " + getArticle() + " " + getName()</code>
|
|
||||||
*
|
|
||||||
* @return Item's name
|
|
||||||
*/
|
|
||||||
String getName();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a map symbol used for printing this item on the screen.
|
|
||||||
* <p>
|
|
||||||
* This is usually the same as {@link #getSymbol()}, but could also include
|
|
||||||
* special control characters for changing the text colour, for example.
|
|
||||||
*
|
|
||||||
* @return A string to be displayed for this item on the screen (should be only
|
|
||||||
* one column wide when printed)
|
|
||||||
* @see <a href="https://en.wikipedia.org/wiki/ANSI_escape_code#Colors">ANSI
|
|
||||||
* escape code (on Wikipedia)</a>
|
|
||||||
*/
|
|
||||||
default String getPrintSymbol() {
|
|
||||||
return getSymbol();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the size of the object.
|
|
||||||
* <p>
|
|
||||||
* The size determines how much space an item will use if put into a container.
|
|
||||||
*
|
|
||||||
* @return Size of the item
|
|
||||||
*/
|
|
||||||
int getSize();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the map symbol of this item.
|
|
||||||
* <p>
|
|
||||||
* The symbol can be used on a text-only map, or when loading a map from text.
|
|
||||||
* <p>
|
|
||||||
* The symbol should be a single Unicode codepoint (i.e.,
|
|
||||||
* <code>getSymbol().codePointCount(0, getSymbol().length()) == 1</code>). In
|
|
||||||
* most cases this means that the symbol should be a single character (i.e.,
|
|
||||||
* getSymbol().length() == 1); but there are a few Unicode characters (such as
|
|
||||||
* many emojis and special symbols) that require two Java <code>char</code>s.
|
|
||||||
*
|
|
||||||
* @return A single-codepoint string with the item's symbol
|
|
||||||
*/
|
|
||||||
String getSymbol();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inform the item that it has been damaged
|
|
||||||
*
|
|
||||||
* @param game The game
|
|
||||||
* @param source The item (usually an IActor) that caused the damage
|
|
||||||
* @param amount How much damage the item should take
|
|
||||||
* @return Amount of damage actually taken (could be less than
|
|
||||||
* <code>amount</code> due to armour/protection effects)
|
|
||||||
*/
|
|
||||||
int handleDamage(Game game, Item source, int amount);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inform the item that something has happened.
|
|
||||||
*
|
|
||||||
* @param event An object describing the event.
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
default <T> T handleEvent(IEvent<T> event) {
|
|
||||||
return event.getData();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return True if this item has been destroyed, and should be removed from the map
|
|
||||||
*/
|
|
||||||
default boolean isDestroyed() {
|
|
||||||
return getCurrentHealth() < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package inf101.v18.rogue101.object;
|
|
||||||
|
|
||||||
import inf101.v18.rogue101.game.Game;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An actor controlled by the computer
|
|
||||||
*
|
|
||||||
* @author anya
|
|
||||||
*/
|
|
||||||
public interface NonPlayerCharacter extends Actor {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Do one turn for this non-player
|
|
||||||
* <p>
|
|
||||||
* This INonPlayer will be the game's current actor ({@link Game#getActor()})
|
|
||||||
* for the duration of this method call.
|
|
||||||
*
|
|
||||||
* @param game Game, for interacting with the world
|
|
||||||
*/
|
|
||||||
void doTurn(Game game);
|
|
||||||
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
package inf101.v18.rogue101.object;
|
|
||||||
|
|
||||||
import inf101.v18.rogue101.game.Game;
|
|
||||||
import javafx.scene.input.KeyCode;
|
|
||||||
|
|
||||||
public interface PlayerCharacter extends Actor {
|
|
||||||
/**
|
|
||||||
* Send key presses from the human player to the player object.
|
|
||||||
* <p>
|
|
||||||
* The player object should interpret the key presses, and then perform its
|
|
||||||
* moves or whatever, according to the game's rules and the player's
|
|
||||||
* instructions.
|
|
||||||
* <p>
|
|
||||||
* This IPlayer will be the game's current actor ({@link Game#getActor()}) and
|
|
||||||
* be at {@link Game#getLocation()}, when this method is called.
|
|
||||||
* <p>
|
|
||||||
* This method may be called many times in a single turn; the turn ends
|
|
||||||
* {@link #keyPressed(Game, KeyCode)} returns and the player has used its
|
|
||||||
* movement points (e.g., by calling {@link Game#move(inf101.v18.grid.GridDirection)}).
|
|
||||||
*
|
|
||||||
* @param game Game, for interacting with the world
|
|
||||||
* @return True if the player has done anything consuming a turn. False otherwise
|
|
||||||
*/
|
|
||||||
boolean keyPressed(Game game, KeyCode key);
|
|
||||||
}
|
|
49
src/main/java/inf101/v18/rogue101/objects/Dust.java
Normal file
49
src/main/java/inf101/v18/rogue101/objects/Dust.java
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package inf101.v18.rogue101.objects;
|
||||||
|
|
||||||
|
import inf101.v18.gfx.gfxmode.ITurtle;
|
||||||
|
import inf101.v18.gfx.textmode.BlocksAndBoxes;
|
||||||
|
import inf101.v18.rogue101.game.IGame;
|
||||||
|
|
||||||
|
public class Dust implements IItem {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean draw(ITurtle painter, double w, double h) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentHealth() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDefence() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxHealth() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "thick layer of dust";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSymbol() {
|
||||||
|
return BlocksAndBoxes.BLOCK_HALF;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int handleDamage(IGame game, IItem source, int amount) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,14 +1,10 @@
|
|||||||
package inf101.v18.rogue101.object;
|
package inf101.v18.rogue101.objects;
|
||||||
|
|
||||||
import inf101.v18.gfx.gfxmode.ITurtle;
|
import inf101.v18.gfx.gfxmode.ITurtle;
|
||||||
import inf101.v18.gfx.textmode.BlocksAndBoxes;
|
import inf101.v18.gfx.textmode.BlocksAndBoxes;
|
||||||
import inf101.v18.rogue101.game.Game;
|
import inf101.v18.rogue101.game.IGame;
|
||||||
|
|
||||||
/**
|
|
||||||
* A solid wall blocking passage
|
|
||||||
*/
|
|
||||||
public class Wall implements Item {
|
|
||||||
|
|
||||||
|
public class FakeWall implements IItem {
|
||||||
private int hp = getMaxHealth();
|
private int hp = getMaxHealth();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -22,18 +18,18 @@ public class Wall implements Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefense() {
|
public int getDefence() {
|
||||||
return 10;
|
return 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getMaxHealth() {
|
public int getMaxHealth() {
|
||||||
return 1000;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "wall";
|
return "fake wall";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -43,13 +39,12 @@ public class Wall implements Item {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getSymbol() {
|
public String getSymbol() {
|
||||||
return BlocksAndBoxes.BLOCK_FULL;
|
return "\u001b[47m" + BlocksAndBoxes.BLOCK_FULL + "\u001b[0m";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int handleDamage(Game game, Item source, int amount) {
|
public int handleDamage(IGame game, IItem source, int amount) {
|
||||||
hp -= amount;
|
hp -= amount;
|
||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
40
src/main/java/inf101/v18/rogue101/objects/IActor.java
Normal file
40
src/main/java/inf101/v18/rogue101/objects/IActor.java
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package inf101.v18.rogue101.objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An actor is an IItem that can also do something, either controlled by the
|
||||||
|
* computer (INonPlayer) or the user (IPlayer).
|
||||||
|
*
|
||||||
|
* @author anya
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface IActor extends IItem {
|
||||||
|
/**
|
||||||
|
* @return This actor's attack score (used against an item's
|
||||||
|
* {@link #getDefence()} score to see if an attack is successful)
|
||||||
|
*/
|
||||||
|
int getAttack();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The damage this actor deals on a successful attack (used together
|
||||||
|
* with
|
||||||
|
* {@link #handleDamage(inf101.v18.rogue101.game.IGame, IItem, int)} on
|
||||||
|
* the target)
|
||||||
|
*/
|
||||||
|
int getDamage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many tiles the IActor is able to see in each direction.
|
||||||
|
*
|
||||||
|
* @return Number of tiles
|
||||||
|
*/
|
||||||
|
default int getVision() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an item of the specified type, if the actor has the item.
|
||||||
|
*
|
||||||
|
* @return An IItem or null
|
||||||
|
*/
|
||||||
|
IItem getItem(Class<?> type);
|
||||||
|
}
|
181
src/main/java/inf101/v18/rogue101/objects/IItem.java
Normal file
181
src/main/java/inf101/v18/rogue101/objects/IItem.java
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
package inf101.v18.rogue101.objects;
|
||||||
|
|
||||||
|
import inf101.v18.gfx.gfxmode.ITurtle;
|
||||||
|
import inf101.v18.rogue101.events.IEvent;
|
||||||
|
import inf101.v18.rogue101.game.IGame;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link IItem} is something that can be placed on the map or into a
|
||||||
|
* container of items.
|
||||||
|
* <p>
|
||||||
|
* An {@link IActor} is a special case of {@link IItem} used for things that are
|
||||||
|
* “alive”, such as the player or AI-controlled monsters/NPCs.
|
||||||
|
* <p>
|
||||||
|
* By default, all items have a hit point / health system (they can be damaged),
|
||||||
|
* and can be picked up. So you could potentially destroy an item or pick up a
|
||||||
|
* monster.
|
||||||
|
*
|
||||||
|
* @author anya
|
||||||
|
*/
|
||||||
|
public interface IItem extends Comparable<IItem> {
|
||||||
|
@Override
|
||||||
|
default int compareTo(IItem other) {
|
||||||
|
return Integer.compare(getSize(), other.getSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw this item on the screen.
|
||||||
|
* <p>
|
||||||
|
* The turtle-painter will be positioned in the centre of the cell. You should
|
||||||
|
* avoid drawing outside the cell (size is indicated by the <code>w</code> and
|
||||||
|
* <code>h</code> parameters, so you can move ±w/2 and ±h/2 from the initial
|
||||||
|
* position). If you want to start in the lower left corner of the cell, you can
|
||||||
|
* start by doing <code>painter.jump(-w/2,h/2)</code> ((0,0) is in the top-left
|
||||||
|
* corner of the screen, so negative X points left and positive Y points down).
|
||||||
|
* <p>
|
||||||
|
* If this method returns <code>true</code>, the game will <em>not</em> print
|
||||||
|
* {@link #getSymbol()} in the cell.
|
||||||
|
* <p>
|
||||||
|
* All calls to <code>painter.save()</code> must be matched by a call to
|
||||||
|
* <code>painter.restore()</code>.
|
||||||
|
*
|
||||||
|
* @param painter
|
||||||
|
* A turtle-painter for drawing
|
||||||
|
* @param w
|
||||||
|
* The width of the cell
|
||||||
|
* @param h
|
||||||
|
* The height of the cell
|
||||||
|
* @return False if the letter from {@link #getSymbol()} should be drawn instead
|
||||||
|
*/
|
||||||
|
default boolean draw(ITurtle painter, double w, double h) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return "a" or "an", depending on the name
|
||||||
|
*/
|
||||||
|
default String getArticle() {
|
||||||
|
return "a";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current remaining health points.
|
||||||
|
* <p>
|
||||||
|
* An object's <em>health points</em> determines how much damage it can take
|
||||||
|
* before it is destroyed / broken / killed.
|
||||||
|
*
|
||||||
|
* @return Current health points for this item
|
||||||
|
*/
|
||||||
|
int getCurrentHealth();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The defence score determines how hard an object/actor is to hit or grab.
|
||||||
|
*
|
||||||
|
* @return Defence score of this object
|
||||||
|
*/
|
||||||
|
int getDefence();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get item health as a 0.0..1.0 proportion.
|
||||||
|
*
|
||||||
|
* <li><code>getHealth() >= 1.0</code> means perfect condition
|
||||||
|
* <li><code>getHealth() <= 0.0</code> means broken or dead
|
||||||
|
* <li><code>0.0 < getHealth() < 1.0</code> means partially damaged
|
||||||
|
*
|
||||||
|
* @return Health, in the range 0.0 to 1.0
|
||||||
|
*/
|
||||||
|
default double getHealthStatus() {
|
||||||
|
return getMaxHealth() > 0 ? getCurrentHealth() / getMaxHealth() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get maximum health points.
|
||||||
|
*
|
||||||
|
* An object's <em>health points</em> determines how much damage it can take
|
||||||
|
* before it is destroyed / broken / killed.
|
||||||
|
*
|
||||||
|
* @return Max health points for this item
|
||||||
|
*/
|
||||||
|
int getMaxHealth();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a (user-friendly) name for the item
|
||||||
|
* <p>
|
||||||
|
* Used for things like <code>"You see " + getArticle() + " " + getName()</code>
|
||||||
|
*
|
||||||
|
* @return Item's name
|
||||||
|
*/
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a map symbol used for printing this item on the screen.
|
||||||
|
* <p>
|
||||||
|
* This is usually the same as {@link #getSymbol()}, but could also include
|
||||||
|
* special control characters for changing the text colour, for example.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @return A string to be displayed for this item on the screen (should be only
|
||||||
|
* one column wide when printed)
|
||||||
|
* @see <a href="https://en.wikipedia.org/wiki/ANSI_escape_code#Colors">ANSI
|
||||||
|
* escape code (on Wikipedia)</a>
|
||||||
|
*/
|
||||||
|
default String getPrintSymbol() {
|
||||||
|
return getSymbol();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the size of the object.
|
||||||
|
* <p>
|
||||||
|
* The size determines how much space an item will use if put into a container.
|
||||||
|
*
|
||||||
|
* @return Size of the item
|
||||||
|
*/
|
||||||
|
int getSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the map symbol of this item.
|
||||||
|
* <p>
|
||||||
|
* The symbol can be used on a text-only map, or when loading a map from text.
|
||||||
|
* <p>
|
||||||
|
* The symbol should be a single Unicode codepoint (i.e.,
|
||||||
|
* <code>getSymbol().codePointCount(0, getSymbol().length()) == 1</code>). In
|
||||||
|
* most cases this means that the symbol should be a single character (i.e.,
|
||||||
|
* getSymbol().length() == 1); but there are a few Unicode characters (such as
|
||||||
|
* many emojis and special symbols) that require two Java <code>char</code>s.
|
||||||
|
*
|
||||||
|
* @return A single-codepoint string with the item's symbol
|
||||||
|
*/
|
||||||
|
String getSymbol();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inform the item that it has been damaged
|
||||||
|
*
|
||||||
|
* @param game
|
||||||
|
* The game
|
||||||
|
* @param source
|
||||||
|
* The item (usually an IActor) that caused the damage
|
||||||
|
* @param amount
|
||||||
|
* How much damage the item should take
|
||||||
|
* @return Amount of damage actually taken (could be less than
|
||||||
|
* <code>amount</code> due to armour/protection effects)
|
||||||
|
*/
|
||||||
|
int handleDamage(IGame game, IItem source, int amount);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inform the item that something has happened.
|
||||||
|
*
|
||||||
|
* @param event
|
||||||
|
* An object describing the event.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
default <T> T handleEvent(IEvent<T> event) {
|
||||||
|
return event.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return True if this item has been destroyed, and should be removed from the map
|
||||||
|
*/
|
||||||
|
default boolean isDestroyed() {
|
||||||
|
return getCurrentHealth() < 0;
|
||||||
|
}
|
||||||
|
}
|
22
src/main/java/inf101/v18/rogue101/objects/INonPlayer.java
Normal file
22
src/main/java/inf101/v18/rogue101/objects/INonPlayer.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
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);
|
||||||
|
}
|
26
src/main/java/inf101/v18/rogue101/objects/IPlayer.java
Normal file
26
src/main/java/inf101/v18/rogue101/objects/IPlayer.java
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package inf101.v18.rogue101.objects;
|
||||||
|
|
||||||
|
import inf101.v18.rogue101.game.IGame;
|
||||||
|
import javafx.scene.input.KeyCode;
|
||||||
|
|
||||||
|
public interface IPlayer extends IActor {
|
||||||
|
/**
|
||||||
|
* Send key presses from the human player to the player object.
|
||||||
|
* <p>
|
||||||
|
* The player object should interpret the key presses, and then perform its
|
||||||
|
* moves or whatever, according to the game's rules and the player's
|
||||||
|
* instructions.
|
||||||
|
* <p>
|
||||||
|
* This IPlayer will be the game's current actor ({@link IGame#getActor()}) and
|
||||||
|
* be at {@link IGame#getLocation()}, when this method is called.
|
||||||
|
* <p>
|
||||||
|
* This method may be called many times in a single turn; the turn ends
|
||||||
|
* {@link #keyPressed(IGame, KeyCode)} returns and the player has used its
|
||||||
|
* movement points (e.g., by calling {@link IGame#move(inf101.v18.grid.GridDirection)}).
|
||||||
|
*
|
||||||
|
* @param game
|
||||||
|
* Game, for interacting with the world
|
||||||
|
* @return True if the player has done anything consuming a turn. False otherwise
|
||||||
|
*/
|
||||||
|
boolean keyPressed(IGame game, KeyCode key);
|
||||||
|
}
|
@ -1,40 +1,31 @@
|
|||||||
package inf101.v18.rogue101.object;
|
package inf101.v18.rogue101.objects;
|
||||||
|
|
||||||
import inf101.v18.gfx.textmode.Printer;
|
import inf101.v18.gfx.textmode.Printer;
|
||||||
import inf101.v18.grid.GridDirection;
|
import inf101.v18.grid.GridDirection;
|
||||||
import inf101.v18.grid.Location;
|
import inf101.v18.grid.ILocation;
|
||||||
import inf101.v18.rogue101.Main;
|
import inf101.v18.rogue101.Main;
|
||||||
import inf101.v18.rogue101.game.Game;
|
import inf101.v18.rogue101.game.Game;
|
||||||
import inf101.v18.rogue101.game.RogueGame;
|
import inf101.v18.rogue101.game.IGame;
|
||||||
import inf101.v18.rogue101.items.buff.BuffItem;
|
import inf101.v18.rogue101.items.*;
|
||||||
import inf101.v18.rogue101.items.consumable.Consumable;
|
import inf101.v18.rogue101.shared.NPC;
|
||||||
import inf101.v18.rogue101.items.container.Backpack;
|
import inf101.v18.rogue101.states.Attack;
|
||||||
import inf101.v18.rogue101.items.container.Container;
|
|
||||||
import inf101.v18.rogue101.items.container.Static;
|
|
||||||
import inf101.v18.rogue101.items.weapon.MagicWeapon;
|
|
||||||
import inf101.v18.rogue101.items.weapon.RangedWeapon;
|
|
||||||
import inf101.v18.rogue101.items.weapon.Weapon;
|
|
||||||
import inf101.v18.rogue101.state.AttackType;
|
|
||||||
import inf101.v18.util.NPCHelper;
|
|
||||||
import javafx.scene.input.KeyCode;
|
import javafx.scene.input.KeyCode;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class Player implements PlayerCharacter {
|
public class Player implements IPlayer {
|
||||||
|
private int hp = getMaxHealth();
|
||||||
private int healthPoints = getMaxHealth();
|
|
||||||
private int attack = 40;
|
private int attack = 40;
|
||||||
private int defence = 20;
|
private int defence = 20;
|
||||||
private final Backpack equipped = new Backpack();
|
private final Backpack equipped = new Backpack();
|
||||||
private boolean dropping = false; //True if the player wants to drop something using the digit keys
|
private boolean dropping = false; //True if the player wants to drop something using the digit keys
|
||||||
private boolean picking = false; //True if the player wants to pick up something using the digit keys
|
private boolean picking = false; //True if the player wants to pick up something using the digit keys
|
||||||
private boolean writing = false; //True if the user is writing a name
|
private boolean writing = false; //True if the user is writing a name
|
||||||
private boolean exploringChest = false;
|
private boolean exploringChest = false;
|
||||||
private String text = "";
|
private String text = "";
|
||||||
private String name = "Player";
|
private String name = "Player";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean keyPressed(Game game, KeyCode key) {
|
public boolean keyPressed(IGame game, KeyCode key) {
|
||||||
boolean turnConsumed = false;
|
boolean turnConsumed = false;
|
||||||
if (writing) {
|
if (writing) {
|
||||||
write(game, key);
|
write(game, key);
|
||||||
@ -81,9 +72,9 @@ public class Player implements PlayerCharacter {
|
|||||||
game.displayMessage("Please enter your name: ");
|
game.displayMessage("Please enter your name: ");
|
||||||
writing = true;
|
writing = true;
|
||||||
} else if (key == KeyCode.R) {
|
} else if (key == KeyCode.R) {
|
||||||
turnConsumed = nonMeleeAttack(game, getVision(), AttackType.RANGED, "ranged");
|
turnConsumed = nonMeleeAttack(game, getVision(), Attack.RANGED, "ranged");
|
||||||
} else if (key == KeyCode.F) {
|
} else if (key == KeyCode.F) {
|
||||||
turnConsumed = nonMeleeAttack(game, getVision() / 2 + getVision() % 2 == 0 ? 0 : 1, AttackType.MAGIC, "magic");
|
turnConsumed = nonMeleeAttack(game, getVision() / 2 + getVision() % 2 == 0 ? 0 : 1, Attack.MAGIC, "magic");
|
||||||
}
|
}
|
||||||
showStatus(game);
|
showStatus(game);
|
||||||
return turnConsumed;
|
return turnConsumed;
|
||||||
@ -92,21 +83,21 @@ public class Player implements PlayerCharacter {
|
|||||||
/**
|
/**
|
||||||
* Attacks if the user is able to use the desired attack.
|
* Attacks if the user is able to use the desired attack.
|
||||||
*
|
*
|
||||||
* @param game An IGame object
|
* @param game An IGame object
|
||||||
* @param range The range of the weapon
|
* @param range The range of the weapon
|
||||||
* @param type The type of the attack
|
* @param type The type of the attack
|
||||||
* @param weapon The required weapon type
|
* @param weapon The required weapon type
|
||||||
* @return True if an attack was possible. False otherwise
|
* @return True if an attack was possible. False otherwise
|
||||||
*/
|
*/
|
||||||
private boolean nonMeleeAttack(Game game, int range, AttackType type, String weapon) {
|
private boolean nonMeleeAttack(IGame game, int range, Attack type, String weapon) {
|
||||||
boolean turnConsumed = false;
|
boolean turnConsumed = false;
|
||||||
Item item = null;
|
IItem item = null;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case RANGED:
|
case RANGED:
|
||||||
item = getItem(RangedWeapon.class);
|
item = getItem(IRangedWeapon.class);
|
||||||
break;
|
break;
|
||||||
case MAGIC:
|
case MAGIC:
|
||||||
item = getItem(MagicWeapon.class);
|
item = getItem(IMagicWeapon.class);
|
||||||
}
|
}
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
game.displayMessage("You do not have a " + weapon + " weapon.");
|
game.displayMessage("You do not have a " + weapon + " weapon.");
|
||||||
@ -122,10 +113,10 @@ public class Player implements PlayerCharacter {
|
|||||||
/**
|
/**
|
||||||
* Lets the user write his/her name.
|
* Lets the user write his/her name.
|
||||||
*
|
*
|
||||||
* @param game An IGame object
|
* @param game An IGame object
|
||||||
* @param key The key pressed
|
* @param key The key pressed
|
||||||
*/
|
*/
|
||||||
private void write(Game game, KeyCode key) {
|
private void write(IGame game, KeyCode key) {
|
||||||
if (key == KeyCode.BACK_SPACE) {
|
if (key == KeyCode.BACK_SPACE) {
|
||||||
if (text.length() > 0) {
|
if (text.length() > 0) {
|
||||||
text = text.substring(0, text.length() - 1);
|
text = text.substring(0, text.length() - 1);
|
||||||
@ -149,22 +140,22 @@ public class Player implements PlayerCharacter {
|
|||||||
/**
|
/**
|
||||||
* Initializes the interaction with an item, and does what is needed.
|
* Initializes the interaction with an item, and does what is needed.
|
||||||
*
|
*
|
||||||
* @param game An IGame object
|
* @param game An IGame object
|
||||||
* @return True if a turn was consumed. False otherwise
|
* @return True if a turn was consumed. False otherwise
|
||||||
*/
|
*/
|
||||||
private boolean interactInit(Game game) {
|
private boolean interactInit(IGame game) {
|
||||||
List<Item> items = game.getLocalItems();
|
List<IItem> items = game.getLocalItems();
|
||||||
if (items.size() < 1) {
|
if (items.size() < 1) {
|
||||||
game.displayMessage("There is nothing to pick up");
|
game.displayMessage("There is nothing to pick up");
|
||||||
} else {
|
} else {
|
||||||
Item item = items.get(0);
|
IItem item = items.get(0);
|
||||||
if (item instanceof Static && item instanceof Container) { //A Static item is always the biggest (and hopefully the only) item on a tile.
|
if (item instanceof IStatic && item instanceof IContainer) { //A Static item is always the biggest (and hopefully the only) item on a tile.
|
||||||
openChest(game, items);
|
openChest(game, items);
|
||||||
} else if (item instanceof StairsUp) {
|
} else if (item instanceof IStatic && item instanceof StairsUp) {
|
||||||
((RogueGame) game).goUp();
|
((Game)game).goUp();
|
||||||
return true;
|
return true;
|
||||||
} else if (item instanceof StairsDown) {
|
} else if (item instanceof IStatic && item instanceof StairsDown) {
|
||||||
((RogueGame) game).goDown();
|
((Game)game).goDown();
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
if (items.size() == 1) {
|
if (items.size() == 1) {
|
||||||
@ -182,18 +173,18 @@ public class Player implements PlayerCharacter {
|
|||||||
/**
|
/**
|
||||||
* Uses the first consumable from the player's inventory
|
* Uses the first consumable from the player's inventory
|
||||||
*
|
*
|
||||||
* @param game An IGame object
|
* @param game An IGame object
|
||||||
* @return True if a potion was used. False otherwise
|
* @return True if a potion was used. False otherwise
|
||||||
*/
|
*/
|
||||||
private boolean useConsumable(Game game) {
|
private boolean useConsumable(IGame game) {
|
||||||
Consumable consumable = (Consumable) equipped.getFirst(Consumable.class);
|
IConsumable consumable = (IConsumable) equipped.getFirst(IConsumable.class);
|
||||||
if (consumable != null) {
|
if (consumable != null) {
|
||||||
healthPoints += consumable.hpIncrease();
|
hp += consumable.hpIncrease();
|
||||||
if (healthPoints > getMaxHealth()) {
|
if (hp > getMaxHealth()) {
|
||||||
healthPoints = getMaxHealth();
|
hp = getMaxHealth();
|
||||||
}
|
}
|
||||||
attack += consumable.attackIncrease();
|
attack += consumable.attackIncrease();
|
||||||
defence += consumable.defenseIncrease();
|
defence += consumable.defenceIncrease();
|
||||||
equipped.remove(consumable);
|
equipped.remove(consumable);
|
||||||
game.displayMessage("Used " + consumable.getName());
|
game.displayMessage("Used " + consumable.getName());
|
||||||
return true;
|
return true;
|
||||||
@ -209,8 +200,8 @@ public class Player implements PlayerCharacter {
|
|||||||
* @param game An IGame object
|
* @param game An IGame object
|
||||||
* @param items A list of items on the current tile
|
* @param items A list of items on the current tile
|
||||||
*/
|
*/
|
||||||
private void openChest(Game game, List<Item> items) {
|
private void openChest(IGame game, List<IItem> items) {
|
||||||
Container container = (Container) items.get(0);
|
IContainer container = (IContainer) items.get(0);
|
||||||
items = container.getContent();
|
items = container.getContent();
|
||||||
game.displayMessage(container.getInteractMessage() + niceList(items, 10));
|
game.displayMessage(container.getInteractMessage() + niceList(items, 10));
|
||||||
exploringChest = true;
|
exploringChest = true;
|
||||||
@ -219,11 +210,11 @@ public class Player implements PlayerCharacter {
|
|||||||
/**
|
/**
|
||||||
* Creates a string containing a set number of item names from a list.
|
* Creates a string containing a set number of item names from a list.
|
||||||
*
|
*
|
||||||
* @param list The list of items
|
* @param list The list of items
|
||||||
* @param max The maximum number of items to print
|
* @param max The maximum number of items to print
|
||||||
* @return A string
|
* @return A string
|
||||||
*/
|
*/
|
||||||
private String niceList(List<Item> list, int max) {
|
private String niceList(List<IItem> list, int max) {
|
||||||
StringBuilder msg = new StringBuilder();
|
StringBuilder msg = new StringBuilder();
|
||||||
for (int i = 0; i < Math.min(list.size(), max); i++) {
|
for (int i = 0; i < Math.min(list.size(), max); i++) {
|
||||||
msg.append(" [").append(i + 1).append("] ").append(firstCharToUpper(list.get(i).getName()));
|
msg.append(" [").append(i + 1).append("] ").append(firstCharToUpper(list.get(i).getName()));
|
||||||
@ -234,10 +225,10 @@ public class Player implements PlayerCharacter {
|
|||||||
/**
|
/**
|
||||||
* Initialized the dropping of an item, and does what is needed.
|
* Initialized the dropping of an item, and does what is needed.
|
||||||
*
|
*
|
||||||
* @param game An IGame object
|
* @param game An IGame object
|
||||||
* @return True if a turn was consumed. False otherwise
|
* @return True if a turn was consumed. False otherwise
|
||||||
*/
|
*/
|
||||||
private boolean dropInit(Game game) {
|
private boolean dropInit(IGame game) {
|
||||||
if (equipped.size() == 1) {
|
if (equipped.size() == 1) {
|
||||||
drop(game, 0);
|
drop(game, 0);
|
||||||
return true;
|
return true;
|
||||||
@ -253,14 +244,14 @@ public class Player implements PlayerCharacter {
|
|||||||
/**
|
/**
|
||||||
* Picks up an item at index i.
|
* Picks up an item at index i.
|
||||||
*
|
*
|
||||||
* @param game An IGame object
|
* @param game An IGame object
|
||||||
* @param i The wanted index
|
* @param i The wanted index
|
||||||
*/
|
*/
|
||||||
private void pickUp(Game game, int i) {
|
private void pickUp(IGame game, int i) {
|
||||||
if (equipped.hasSpace()) {
|
if (equipped.hasSpace()) {
|
||||||
List<Item> items = game.getLocalItems();
|
List<IItem> items = game.getLocalItems();
|
||||||
if (items.size() >= i) {
|
if (items.size() >= i) {
|
||||||
Item pickedUp = game.pickUp(items.get(i));
|
IItem pickedUp = game.pickUp(items.get(i));
|
||||||
if (pickedUp != null) {
|
if (pickedUp != null) {
|
||||||
equipped.add(pickedUp);
|
equipped.add(pickedUp);
|
||||||
game.displayMessage("Picked up " + pickedUp.getName());
|
game.displayMessage("Picked up " + pickedUp.getName());
|
||||||
@ -278,11 +269,11 @@ public class Player implements PlayerCharacter {
|
|||||||
/**
|
/**
|
||||||
* Takes an item from a chest, and gives it to the player.
|
* Takes an item from a chest, and gives it to the player.
|
||||||
*/
|
*/
|
||||||
private void loot(Game game, int i) {
|
private void loot(IGame game, int i) {
|
||||||
if (equipped.hasSpace()) {
|
if (equipped.hasSpace()) {
|
||||||
Container container = getStaticContainer(game);
|
IContainer container = getStaticContainer(game);
|
||||||
if (container != null && i < container.getContent().size()) {
|
if (container != null && i < container.getContent().size()) {
|
||||||
Item loot = container.getContent().get(i);
|
IItem loot = container.getContent().get(i);
|
||||||
equipped.add(loot);
|
equipped.add(loot);
|
||||||
container.remove(i);
|
container.remove(i);
|
||||||
game.displayMessage("Looted " + loot.getName());
|
game.displayMessage("Looted " + loot.getName());
|
||||||
@ -295,17 +286,17 @@ public class Player implements PlayerCharacter {
|
|||||||
/**
|
/**
|
||||||
* Gets any static containers on the current tile.
|
* Gets any static containers on the current tile.
|
||||||
*
|
*
|
||||||
* @param game An IGame object
|
* @param game An IGame object
|
||||||
* @return A static container
|
* @return A static container
|
||||||
*/
|
*/
|
||||||
private Container getStaticContainer(Game game) {
|
private IContainer getStaticContainer(IGame game) {
|
||||||
List<Item> items = game.getLocalItems();
|
List<IItem> items = game.getLocalItems();
|
||||||
if (items.size() < 1) {
|
if (items.size() < 1) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Item item = items.get(0);
|
IItem item = items.get(0);
|
||||||
if (item instanceof Static && item instanceof Container) {
|
if (item instanceof IStatic && item instanceof IContainer) {
|
||||||
return (Container) item;
|
return (IContainer) item;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -314,12 +305,12 @@ public class Player implements PlayerCharacter {
|
|||||||
/**
|
/**
|
||||||
* Drops an item at index i.
|
* Drops an item at index i.
|
||||||
*
|
*
|
||||||
* @param game An IGame object
|
* @param game An IGame object
|
||||||
* @param i The wanted index
|
* @param i The wanted index
|
||||||
*/
|
*/
|
||||||
private void drop(Game game, int i) {
|
private void drop(IGame game, int i) {
|
||||||
if (!equipped.isEmpty() && equipped.size() > i) {
|
if (!equipped.isEmpty() && equipped.size() > i) {
|
||||||
Container container = getStaticContainer(game);
|
IContainer container = getStaticContainer(game);
|
||||||
if (container != null) {
|
if (container != null) {
|
||||||
if (container.addItem(equipped.get(i))) {
|
if (container.addItem(equipped.get(i))) {
|
||||||
game.displayMessage(equipped.get(i).getName() + " stored in " + container.getName());
|
game.displayMessage(equipped.get(i).getName() + " stored in " + container.getName());
|
||||||
@ -344,18 +335,18 @@ public class Player implements PlayerCharacter {
|
|||||||
/**
|
/**
|
||||||
* Updates the status bar with the Player's current stats.
|
* Updates the status bar with the Player's current stats.
|
||||||
*
|
*
|
||||||
* @param game An IGame object
|
* @param game An IGame object
|
||||||
*/
|
*/
|
||||||
private void showStatus(Game game) {
|
private void showStatus(IGame game) {
|
||||||
game.formatStatus("HP: %s ATK: %d RATK: %d MATK: %d DEF: %s DMG: %s RDMG: %s MDMG: %s",
|
game.formatStatus("HP: %s ATK: %d RATK: %d MATK: %d DEF: %s DMG: %s RDMG: %s MDMG: %s",
|
||||||
NPCHelper.hpBar(this),
|
NPC.hpBar(this),
|
||||||
getAttack(AttackType.MELEE),
|
getAttack(Attack.MELEE),
|
||||||
getAttack(AttackType.RANGED),
|
getAttack(Attack.RANGED),
|
||||||
getAttack(AttackType.MAGIC),
|
getAttack(Attack.MAGIC),
|
||||||
getDefense(),
|
getDefence(),
|
||||||
getDamage(AttackType.MELEE),
|
getDamage(Attack.MELEE),
|
||||||
getDamage(AttackType.RANGED),
|
getDamage(Attack.RANGED),
|
||||||
getDamage(AttackType.MAGIC)
|
getDamage(Attack.MAGIC)
|
||||||
);
|
);
|
||||||
printInventory(game);
|
printInventory(game);
|
||||||
}
|
}
|
||||||
@ -365,13 +356,13 @@ public class Player implements PlayerCharacter {
|
|||||||
*
|
*
|
||||||
* @param game An IGame object
|
* @param game An IGame object
|
||||||
*/
|
*/
|
||||||
private void printInventory(Game game) {
|
private void printInventory(IGame game) {
|
||||||
Printer printer = game.getPrinter();
|
Printer printer = game.getPrinter();
|
||||||
List<Item> items = equipped.getContent();
|
List<IItem> items = equipped.getContent();
|
||||||
printer.clearRegion(Main.COLUMN_RIGHT_SIDE_START, 13, 45, 5);
|
printer.clearRegion(Main.COLUMN_RIGHTSIDE_START, 13, 45, 5);
|
||||||
printer.printAt(Main.COLUMN_RIGHT_SIDE_START, 12, "Inventory:");
|
printer.printAt(Main.COLUMN_RIGHTSIDE_START, 12, "Inventory:");
|
||||||
for (int i = 0; i < items.size(); i++) {
|
for (int i = 0; i < items.size(); i++) {
|
||||||
printer.printAt(Main.COLUMN_RIGHT_SIDE_START, 13 + i, items.get(i).getName());
|
printer.printAt(Main.COLUMN_RIGHTSIDE_START, 13 + i, items.get(i).getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -379,11 +370,11 @@ public class Player implements PlayerCharacter {
|
|||||||
* Changes the first character in a string to uppercase.
|
* Changes the first character in a string to uppercase.
|
||||||
*
|
*
|
||||||
* @param input The input string
|
* @param input The input string
|
||||||
* @return The input string with the first character uppercased
|
* @return The input string with the first character uppercased
|
||||||
*/
|
*/
|
||||||
private String firstCharToUpper(String input) {
|
private String firstCharToUpper(String input) {
|
||||||
if (input.length() < 1) {
|
if (input.length() < 1) {
|
||||||
return input;
|
return input;
|
||||||
} else {
|
} else {
|
||||||
return Character.toUpperCase(input.charAt(0)) + input.substring(1);
|
return Character.toUpperCase(input.charAt(0)) + input.substring(1);
|
||||||
}
|
}
|
||||||
@ -392,24 +383,24 @@ public class Player implements PlayerCharacter {
|
|||||||
/**
|
/**
|
||||||
* Lets the user move or attack if possible.
|
* Lets the user move or attack if possible.
|
||||||
*
|
*
|
||||||
* @param game An IGame object
|
* @param game An IGame object
|
||||||
* @param dir The direction the player wants to go
|
* @param dir The direction the player wants to go
|
||||||
*/
|
*/
|
||||||
private void tryToMove(Game game, GridDirection dir) {
|
private void tryToMove(IGame game, GridDirection dir) {
|
||||||
Location loc = game.getLocation();
|
ILocation loc = game.getLocation();
|
||||||
if (game.canGo(dir)) {
|
if (game.canGo(dir)) {
|
||||||
game.move(dir);
|
game.move(dir);
|
||||||
} else if (loc.canGo(dir) && game.getMap().hasActors(loc.go(dir))) {
|
} else if (loc.canGo(dir) && game.getMap().hasActors(loc.go(dir))) {
|
||||||
game.attack(dir, game.getMap().getActors(loc.go(dir)).get(0));
|
game.attack(dir, game.getMap().getActors(loc.go(dir)).get(0));
|
||||||
} else {
|
} else {
|
||||||
game.displayMessage("Umm, it is not possible to move in that direction.");
|
game.displayMessage("Umm, it is not possible to move in that direction.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getAttack() {
|
public int getAttack() {
|
||||||
BuffItem buff = (BuffItem) getItem(BuffItem.class);
|
IBuffItem buff = (IBuffItem)getItem(IBuffItem.class);
|
||||||
if (buff != null) {
|
if (buff != null) {
|
||||||
return attack + buff.getBuffDamage();
|
return attack + buff.getBuffDamage();
|
||||||
} else {
|
} else {
|
||||||
@ -420,11 +411,11 @@ public class Player implements PlayerCharacter {
|
|||||||
/**
|
/**
|
||||||
* Gets the attack with added weapon attack
|
* Gets the attack with added weapon attack
|
||||||
*
|
*
|
||||||
* @param type The attack type corresponding to the weapon type.
|
* @param type The attack type corresponding to the weapon type.
|
||||||
* @return Attack as int
|
* @return Attack as int
|
||||||
*/
|
*/
|
||||||
private int getAttack(AttackType type) {
|
private int getAttack(Attack type) {
|
||||||
Weapon weapon = NPCHelper.getWeapon(type, this);
|
IWeapon weapon = NPC.getWeapon(type, this);
|
||||||
if (weapon != null) {
|
if (weapon != null) {
|
||||||
return getAttack() + weapon.getWeaponAttack();
|
return getAttack() + weapon.getWeaponAttack();
|
||||||
} else {
|
} else {
|
||||||
@ -440,11 +431,11 @@ public class Player implements PlayerCharacter {
|
|||||||
/**
|
/**
|
||||||
* Gets the damage with added weapon damage
|
* Gets the damage with added weapon damage
|
||||||
*
|
*
|
||||||
* @param type The attack type corresponding to the weapon type.
|
* @param type The attack type corresponding to the weapon type.
|
||||||
* @return Damage as int
|
* @return Damage as int
|
||||||
*/
|
*/
|
||||||
private int getDamage(AttackType type) {
|
private int getDamage(Attack type) {
|
||||||
Weapon weapon = NPCHelper.getWeapon(type, this);
|
IWeapon weapon = NPC.getWeapon(type, this);
|
||||||
if (weapon != null) {
|
if (weapon != null) {
|
||||||
return getDamage() + weapon.getWeaponDamage();
|
return getDamage() + weapon.getWeaponDamage();
|
||||||
} else {
|
} else {
|
||||||
@ -453,8 +444,8 @@ public class Player implements PlayerCharacter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Item getItem(Class<?> type) {
|
public IItem getItem(Class<?> type) {
|
||||||
for (Item item : equipped.getContent()) {
|
for (IItem item : equipped.getContent()) {
|
||||||
if (type.isInstance(item)) {
|
if (type.isInstance(item)) {
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@ -464,12 +455,12 @@ public class Player implements PlayerCharacter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCurrentHealth() {
|
public int getCurrentHealth() {
|
||||||
return healthPoints;
|
return hp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefense() {
|
public int getDefence() {
|
||||||
BuffItem buff = (BuffItem) getItem(BuffItem.class);
|
IBuffItem buff = (IBuffItem)getItem(IBuffItem.class);
|
||||||
if (buff != null) {
|
if (buff != null) {
|
||||||
return defence + buff.getBuffDamage();
|
return defence + buff.getBuffDamage();
|
||||||
} else {
|
} else {
|
||||||
@ -494,7 +485,7 @@ public class Player implements PlayerCharacter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getPrintSymbol() {
|
public String getPrintSymbol() {
|
||||||
return "\u001b[96m" + "@" + "\u001b[0m";
|
return "\u001b[96m" + "@" + "\u001b[0m";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -503,10 +494,10 @@ public class Player implements PlayerCharacter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int handleDamage(Game game, Item source, int amount) {
|
public int handleDamage(IGame game, IItem source, int amount) {
|
||||||
healthPoints -= amount;
|
hp -= amount;
|
||||||
showStatus(game);
|
showStatus(game);
|
||||||
if (healthPoints < 1) {
|
if (hp < 1) {
|
||||||
game.displayStatus("Game Over");
|
game.displayStatus("Game Over");
|
||||||
}
|
}
|
||||||
return amount;
|
return amount;
|
||||||
@ -514,7 +505,7 @@ public class Player implements PlayerCharacter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getVision() {
|
public int getVision() {
|
||||||
BuffItem item = (BuffItem) getItem(BuffItem.class);
|
IBuffItem item = (IBuffItem) getItem(IBuffItem.class);
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
return 3 + item.getBuffVisibility();
|
return 3 + item.getBuffVisibility();
|
||||||
} else {
|
} else {
|
||||||
@ -525,19 +516,19 @@ public class Player implements PlayerCharacter {
|
|||||||
/**
|
/**
|
||||||
* Performs a ranged attack on the nearest visible enemy (if any);
|
* Performs a ranged attack on the nearest visible enemy (if any);
|
||||||
*
|
*
|
||||||
* @param game An IGame object
|
* @param game An IGame object
|
||||||
* @param range The range of the attack
|
* @param range The range of the attack
|
||||||
* @param type The attack type
|
* @param type The attack type
|
||||||
* @return True if the player attacked. False otherwise
|
* @return True if the player attacked. False otherwise
|
||||||
*/
|
*/
|
||||||
private boolean rangedAttack(Game game, int range, AttackType type) {
|
private boolean rangedAttack(IGame game, int range, Attack type) {
|
||||||
List<Location> neighbours = game.getVisible();
|
List<ILocation> neighbours = game.getVisible();
|
||||||
for (Location neighbour : neighbours) {
|
for (ILocation neighbour : neighbours) {
|
||||||
if (game.getMap().hasActors(neighbour)) {
|
if (game.getMap().hasActors(neighbour)) {
|
||||||
Location current = game.getLocation();
|
ILocation current = game.getLocation();
|
||||||
List<GridDirection> dirs = game.locationDirection(current, neighbour);
|
List<GridDirection> dirs = game.locationDirection(current, neighbour);
|
||||||
Actor actor = game.getMap().getActors(neighbour).get(0); //We assume there is only one actor.
|
IActor actor = game.getMap().getActors(neighbour).get(0); //We assume there is only one actor.
|
||||||
if (actor instanceof NonPlayerCharacter && current.gridDistanceTo(neighbour) <= range) {
|
if (actor instanceof INonPlayer && current.gridDistanceTo(neighbour) <= range) {
|
||||||
GridDirection dir = dirs.get(0); //Only ever has one item
|
GridDirection dir = dirs.get(0); //Only ever has one item
|
||||||
game.rangedAttack(dir, actor, type);
|
game.rangedAttack(dir, actor, type);
|
||||||
return true;
|
return true;
|
@ -1,20 +1,16 @@
|
|||||||
package inf101.v18.rogue101.object;
|
package inf101.v18.rogue101.objects;
|
||||||
|
|
||||||
import inf101.v18.rogue101.game.Game;
|
import inf101.v18.rogue101.game.IGame;
|
||||||
import inf101.v18.rogue101.items.container.Static;
|
import inf101.v18.rogue101.items.IStatic;
|
||||||
|
|
||||||
/**
|
|
||||||
* Stairs going downwards to the previous floor
|
|
||||||
*/
|
|
||||||
public class StairsDown implements Item, Static {
|
|
||||||
|
|
||||||
|
public class StairsDown implements IItem, IStatic {
|
||||||
@Override
|
@Override
|
||||||
public int getCurrentHealth() {
|
public int getCurrentHealth() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefense() {
|
public int getDefence() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,8 +35,7 @@ public class StairsDown implements Item, Static {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int handleDamage(Game game, Item source, int amount) {
|
public int handleDamage(IGame game, IItem source, int amount) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,20 +1,16 @@
|
|||||||
package inf101.v18.rogue101.object;
|
package inf101.v18.rogue101.objects;
|
||||||
|
|
||||||
import inf101.v18.rogue101.game.Game;
|
import inf101.v18.rogue101.game.IGame;
|
||||||
import inf101.v18.rogue101.items.container.Static;
|
import inf101.v18.rogue101.items.IStatic;
|
||||||
|
|
||||||
/**
|
|
||||||
* Stairs going upwards to the next floor
|
|
||||||
*/
|
|
||||||
public class StairsUp implements Item, Static {
|
|
||||||
|
|
||||||
|
public class StairsUp implements IItem, IStatic {
|
||||||
@Override
|
@Override
|
||||||
public int getCurrentHealth() {
|
public int getCurrentHealth() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDefense() {
|
public int getDefence() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,8 +35,7 @@ public class StairsUp implements Item, Static {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int handleDamage(Game game, Item source, int amount) {
|
public int handleDamage(IGame game, IItem source, int amount) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
50
src/main/java/inf101/v18/rogue101/objects/Wall.java
Normal file
50
src/main/java/inf101/v18/rogue101/objects/Wall.java
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package inf101.v18.rogue101.objects;
|
||||||
|
|
||||||
|
import inf101.v18.gfx.gfxmode.ITurtle;
|
||||||
|
import inf101.v18.gfx.textmode.BlocksAndBoxes;
|
||||||
|
import inf101.v18.rogue101.game.IGame;
|
||||||
|
|
||||||
|
public class Wall implements IItem {
|
||||||
|
private int hp = getMaxHealth();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean draw(ITurtle painter, double w, double h) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentHealth() {
|
||||||
|
return hp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDefence() {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxHealth() {
|
||||||
|
return 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "wall";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSymbol() {
|
||||||
|
return BlocksAndBoxes.BLOCK_FULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int handleDamage(IGame game, IItem source, int amount) {
|
||||||
|
hp -= amount;
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
}
|
226
src/main/java/inf101/v18/rogue101/shared/NPC.java
Normal file
226
src/main/java/inf101/v18/rogue101/shared/NPC.java
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
package inf101.v18.rogue101.shared;
|
||||||
|
|
||||||
|
import inf101.v18.gfx.textmode.BlocksAndBoxes;
|
||||||
|
import inf101.v18.grid.GridDirection;
|
||||||
|
import inf101.v18.grid.ILocation;
|
||||||
|
import inf101.v18.rogue101.game.IGame;
|
||||||
|
import inf101.v18.rogue101.items.*;
|
||||||
|
import inf101.v18.rogue101.map.IMapView;
|
||||||
|
import inf101.v18.rogue101.objects.IActor;
|
||||||
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
|
import inf101.v18.rogue101.objects.IPlayer;
|
||||||
|
import inf101.v18.rogue101.states.Attack;
|
||||||
|
import javafx.scene.media.AudioClip;
|
||||||
|
import javafx.scene.media.Media;
|
||||||
|
import javafx.scene.media.MediaPlayer;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A holder class for methods used by IActors.
|
||||||
|
*/
|
||||||
|
public class NPC {
|
||||||
|
|
||||||
|
private NPC() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a ranged attack on the closest actor (if any) observable by the current actor if range is > 1.
|
||||||
|
* Performs a melee attack on an actor (if any) on a neighbouring tile if range == 1.
|
||||||
|
* Moves closer to an enemy if an attack is not possible.
|
||||||
|
*
|
||||||
|
* @param game An IGame object
|
||||||
|
* @param range The range of the equipped weapon
|
||||||
|
* @return True if an attack or a move towards an enemy was successful. False otherwise
|
||||||
|
*/
|
||||||
|
public static boolean tryAttack(IGame game, int range, Attack type) {
|
||||||
|
List<ILocation> neighbours = game.getVisible();
|
||||||
|
for (ILocation neighbour : neighbours) {
|
||||||
|
IMapView map = game.getMap();
|
||||||
|
if (map.hasActors(neighbour) && map.getActors(neighbour).get(0) instanceof IPlayer) {
|
||||||
|
ILocation current = game.getLocation();
|
||||||
|
List<GridDirection> dirs = game.locationDirection(current, neighbour);
|
||||||
|
IActor actor = game.getMap().getActors(neighbour).get(0); //We assume there is only one actor.
|
||||||
|
for (GridDirection dir : dirs) {
|
||||||
|
if (type == Attack.MELEE
|
||||||
|
&& current.gridDistanceTo(neighbour) <= 1
|
||||||
|
&& current.canGo(dir)
|
||||||
|
&& game.getMap().has(current.go(dir), actor)
|
||||||
|
) {
|
||||||
|
game.attack(dir, actor);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (range > 1 && current.gridDistanceTo(neighbour) <= range) {
|
||||||
|
game.rangedAttack(dir, actor, type);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (GridDirection dir : dirs) {
|
||||||
|
if (game.canGo(dir)) {
|
||||||
|
game.move(dir);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to find and reach an item of a specified class.
|
||||||
|
*
|
||||||
|
* @param game An IGame object
|
||||||
|
* @param validItems A list of classes the NPC should look for
|
||||||
|
* @return True if an appropriate item was found in the range of the current actor. False otherwise.
|
||||||
|
*/
|
||||||
|
public static boolean trackItem(IGame game, List<Class<?>> validItems) {
|
||||||
|
List<ILocation> neighbours = game.getVisible();
|
||||||
|
for (ILocation neighbour : neighbours) {
|
||||||
|
for (IItem item : game.getMap().getAll(neighbour)) {
|
||||||
|
for (Class<?> validItem : validItems) {
|
||||||
|
if (validItem.isInstance(item)) {
|
||||||
|
ILocation current = game.getLocation();
|
||||||
|
List<GridDirection> dirs = game.locationDirection(current, neighbour);
|
||||||
|
for (GridDirection dir : dirs) {
|
||||||
|
if (game.canGo(dir)) {
|
||||||
|
game.move(dir);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Picks up an item of one of the specified class types.
|
||||||
|
*
|
||||||
|
* @param game An IGame object
|
||||||
|
* @param validItems A list of classes the NPC should accept
|
||||||
|
* @return An item if it was successfully picked up. Null otherwise
|
||||||
|
*/
|
||||||
|
public static IItem pickUp(IGame game, List<Class<?>> validItems) {
|
||||||
|
for (IItem item : game.getLocalItems()) {
|
||||||
|
for (Class<?> validItem : validItems) {
|
||||||
|
if (validItem.isInstance(item)) {
|
||||||
|
return game.pickUp(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes an IActor go to the opposite direction of the closest enemy.
|
||||||
|
* Will return false if there are no visible enemies.
|
||||||
|
*
|
||||||
|
* @param game An IGame object
|
||||||
|
* @return True if the IActor fled. False otherwise
|
||||||
|
*/
|
||||||
|
public static boolean flee(IGame game) {
|
||||||
|
List<ILocation> neighbours = game.getVisible();
|
||||||
|
for (ILocation neighbour : neighbours) {
|
||||||
|
if (game.getMap().hasActors(neighbour)) {
|
||||||
|
ILocation current = game.getLocation();
|
||||||
|
List<GridDirection> dirs = reverseDir(game.locationDirection(current, neighbour));
|
||||||
|
for (GridDirection dir : dirs) {
|
||||||
|
if (game.canGo(dir)) {
|
||||||
|
game.move(dir);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverses a list of directions.
|
||||||
|
*
|
||||||
|
* @param dirs A list directions
|
||||||
|
* @return A list of the opposite directions
|
||||||
|
*/
|
||||||
|
private static List<GridDirection> reverseDir(List<GridDirection> dirs) {
|
||||||
|
for (int i = 0; i < dirs.size(); i++) {
|
||||||
|
GridDirection dir = dirs.get(i);
|
||||||
|
switch (dir) {
|
||||||
|
case SOUTH:
|
||||||
|
dirs.set(i, GridDirection.NORTH);
|
||||||
|
break;
|
||||||
|
case NORTH:
|
||||||
|
dirs.set(i, GridDirection.SOUTH);
|
||||||
|
break;
|
||||||
|
case WEST:
|
||||||
|
dirs.set(i, GridDirection.EAST);
|
||||||
|
break;
|
||||||
|
case EAST:
|
||||||
|
dirs.set(i, GridDirection.WEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dirs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays a sound.
|
||||||
|
*
|
||||||
|
* @param filename The String path of the audio file to play
|
||||||
|
*/
|
||||||
|
public static void playSound(String filename) {
|
||||||
|
URL url = NPC.class.getClassLoader().getResource(filename);
|
||||||
|
if (url != null) {
|
||||||
|
AudioClip clip = new AudioClip(url.toString());
|
||||||
|
clip.play();
|
||||||
|
} else {
|
||||||
|
System.out.println("Could not play audio file " + filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a hp bar based on item's current hp
|
||||||
|
*
|
||||||
|
* @return HP bar as string
|
||||||
|
*/
|
||||||
|
public static String hpBar(IItem item) {
|
||||||
|
StringBuilder str = new StringBuilder(BlocksAndBoxes.BLOCK_HALF);
|
||||||
|
int hp = item.getCurrentHealth();
|
||||||
|
int hpLen = (int)((float)hp/item.getMaxHealth() * 10.0);
|
||||||
|
if (hp > 0 && hp < 100) {
|
||||||
|
str.append("\u001b[91m" + BlocksAndBoxes.BLOCK_BOTTOM + "\u001b[0m");
|
||||||
|
hpLen++;
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < hpLen; i++) {
|
||||||
|
str.append("\u001b[91m" + BlocksAndBoxes.BLOCK_HALF + "\u001b[0m");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 10 - hpLen; i++) {
|
||||||
|
str.append(" ");
|
||||||
|
}
|
||||||
|
str.append(BlocksAndBoxes.BLOCK_HALF);
|
||||||
|
return str.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the weapon appropriate for the used attack.
|
||||||
|
*
|
||||||
|
* @param type The attack type
|
||||||
|
* @return An IWeapon or null
|
||||||
|
*/
|
||||||
|
public static IWeapon getWeapon(Attack type, IActor actor) {
|
||||||
|
IWeapon weapon = null;
|
||||||
|
switch (type) {
|
||||||
|
case MELEE:
|
||||||
|
weapon = (IWeapon)actor.getItem(IMeleeWeapon.class);
|
||||||
|
break;
|
||||||
|
case RANGED:
|
||||||
|
weapon = (IWeapon)actor.getItem(IRangedWeapon.class);
|
||||||
|
break;
|
||||||
|
case MAGIC:
|
||||||
|
weapon = (IWeapon)actor.getItem(IMagicWeapon.class);
|
||||||
|
}
|
||||||
|
return weapon;
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +0,0 @@
|
|||||||
package inf101.v18.rogue101.state;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type of a given attack
|
|
||||||
*/
|
|
||||||
public enum AttackType {
|
|
||||||
|
|
||||||
MELEE,
|
|
||||||
MAGIC,
|
|
||||||
RANGED
|
|
||||||
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package inf101.v18.rogue101.state;
|
|
||||||
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The occupation of an enemy, which decides the type of attack it uses
|
|
||||||
*/
|
|
||||||
public enum Occupation {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A knight that always uses melee attacks
|
|
||||||
*/
|
|
||||||
KNIGHT,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A mage that always uses magic attacks
|
|
||||||
*/
|
|
||||||
MAGE,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An archer that always uses ranged attacks
|
|
||||||
*/
|
|
||||||
ARCHER;
|
|
||||||
|
|
||||||
private static final Random random = new Random();
|
|
||||||
|
|
||||||
public static Occupation getRandom() {
|
|
||||||
return values()[random.nextInt(values().length)];
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package inf101.v18.rogue101.state;
|
|
||||||
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The personality of an enemy, which defines its combat behavior
|
|
||||||
*/
|
|
||||||
public enum Personality {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An enemy that attacks on sight
|
|
||||||
*/
|
|
||||||
AGGRESSIVE,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An enemy that attacks if attacked
|
|
||||||
*/
|
|
||||||
CALM,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An enemy that flees from battle
|
|
||||||
*/
|
|
||||||
AFRAID;
|
|
||||||
|
|
||||||
private static final Random random = new Random();
|
|
||||||
|
|
||||||
public static Personality getRandom() {
|
|
||||||
return values()[random.nextInt(values().length)];
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
package inf101.v18.rogue101.state;
|
|
||||||
|
|
||||||
import inf101.v18.util.NPCHelper;
|
|
||||||
import javafx.scene.media.AudioClip;
|
|
||||||
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A sound that can be played
|
|
||||||
*/
|
|
||||||
public enum Sound {
|
|
||||||
|
|
||||||
MELEE_NO_WEAPON("Realistic_Punch-Mark_DiAngelo-1609462330.wav"),
|
|
||||||
MELEE_SWORD("Sword Swing-SoundBible.com-639083727.wav"),
|
|
||||||
RANGED_NO_WEAPON("Snow Ball Throw And Splat-SoundBible.com-992042947.wav"),
|
|
||||||
RANGED_BOW("Bow_Fire_Arrow-Stephan_Schutze-2133929391.wav"),
|
|
||||||
RANGED_STAFF("Large Fireball-SoundBible.com-301502490.wav"),
|
|
||||||
GAME_OVER("Dying-SoundBible.com-1255481835.wav"),
|
|
||||||
WIN("1_person_cheering-Jett_Rifkin-1851518140.wav"),
|
|
||||||
NPC_DEATH("oh-no-113125.wav"),
|
|
||||||
;
|
|
||||||
|
|
||||||
private final String soundFile;
|
|
||||||
private AudioClip audioClip = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiates a new sound file
|
|
||||||
*
|
|
||||||
* @param soundFile <p>The file containing this sound</p>
|
|
||||||
*/
|
|
||||||
Sound(String soundFile) {
|
|
||||||
this.soundFile = soundFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the sound file for this sound
|
|
||||||
*
|
|
||||||
* @return <p>The sound file for this sound</p>
|
|
||||||
*/
|
|
||||||
public String getSoundFile() {
|
|
||||||
return "audio/" + soundFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plays this sound
|
|
||||||
*/
|
|
||||||
public void play() {
|
|
||||||
if (audioClip == null) {
|
|
||||||
URL url = NPCHelper.class.getClassLoader().getResource(getSoundFile());
|
|
||||||
if (url != null) {
|
|
||||||
audioClip = new AudioClip(url.toString());
|
|
||||||
} else {
|
|
||||||
System.out.println("Could not play audio file " + getSoundFile());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Play the sound in a new thread if it could be loaded
|
|
||||||
if (audioClip != null) {
|
|
||||||
new Thread(() -> audioClip.play()).start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,18 +1,16 @@
|
|||||||
package inf101.v18.rogue101.state;
|
package inf101.v18.rogue101.states;
|
||||||
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The age of an enemy, which alters some stats
|
* An enum to distinguish different enemies of the same type.
|
||||||
*/
|
*/
|
||||||
public enum LifeStage {
|
public enum Age {
|
||||||
|
|
||||||
TODDLER, CHILD, TEEN, ADULT, ELDER;
|
TODDLER, CHILD, TEEN, ADULT, ELDER;
|
||||||
|
|
||||||
private static final Random random = new Random();
|
private static final Random random = new Random();
|
||||||
|
|
||||||
public static LifeStage getRandom() {
|
public static Age getRandom() {
|
||||||
return values()[random.nextInt(values().length)];
|
return values()[random.nextInt(values().length)];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
5
src/main/java/inf101/v18/rogue101/states/Attack.java
Normal file
5
src/main/java/inf101/v18/rogue101/states/Attack.java
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package inf101.v18.rogue101.states;
|
||||||
|
|
||||||
|
public enum Attack {
|
||||||
|
MELEE, MAGIC, RANGED
|
||||||
|
}
|
16
src/main/java/inf101/v18/rogue101/states/Occupation.java
Normal file
16
src/main/java/inf101/v18/rogue101/states/Occupation.java
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package inf101.v18.rogue101.states;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enum to distinguish different enemies of the same type.
|
||||||
|
*/
|
||||||
|
public enum Occupation {
|
||||||
|
KNIGHT, MAGE, BOWSMAN;
|
||||||
|
|
||||||
|
private static final Random random = new Random();
|
||||||
|
|
||||||
|
public static Occupation getRandom() {
|
||||||
|
return values()[random.nextInt(values().length)];
|
||||||
|
}
|
||||||
|
}
|
16
src/main/java/inf101/v18/rogue101/states/Personality.java
Normal file
16
src/main/java/inf101/v18/rogue101/states/Personality.java
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package inf101.v18.rogue101.states;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enum to distinguish different enemies of the same type.
|
||||||
|
*/
|
||||||
|
public enum Personality {
|
||||||
|
AGRESSIVE, CALM, AFRAID;
|
||||||
|
|
||||||
|
private static final Random random = new Random();
|
||||||
|
|
||||||
|
public static Personality getRandom() {
|
||||||
|
return values()[random.nextInt(values().length)];
|
||||||
|
}
|
||||||
|
}
|
@ -1,27 +0,0 @@
|
|||||||
package inf101.v18.util;
|
|
||||||
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An interface for generators of random data values (to be used in testing).
|
|
||||||
*
|
|
||||||
* @param <T> The type of data generated.
|
|
||||||
*/
|
|
||||||
public interface Generator<T> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a random object.
|
|
||||||
*
|
|
||||||
* @return An object of type T
|
|
||||||
*/
|
|
||||||
T generate();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a random object.
|
|
||||||
*
|
|
||||||
* @param r A random generator
|
|
||||||
* @return An object of type T
|
|
||||||
*/
|
|
||||||
T generate(Random r);
|
|
||||||
|
|
||||||
}
|
|
53
src/main/java/inf101/v18/util/IGenerator.java
Normal file
53
src/main/java/inf101/v18/util/IGenerator.java
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package inf101.v18.util;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for generators of random data values (to be used in testing).
|
||||||
|
*
|
||||||
|
* @param <T>
|
||||||
|
* The type of data generated.
|
||||||
|
*/
|
||||||
|
public interface IGenerator<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a random object.
|
||||||
|
*
|
||||||
|
* @return An object of type T
|
||||||
|
*/
|
||||||
|
T generate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a random object.
|
||||||
|
*
|
||||||
|
* @param r
|
||||||
|
* A random generator
|
||||||
|
* @return An object of type T
|
||||||
|
*/
|
||||||
|
T generate(Random r);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a number of equal objects.
|
||||||
|
*
|
||||||
|
* @param n
|
||||||
|
* The number of objects to generate.
|
||||||
|
* @return A list of objects, with the property that for any two objects a,b in
|
||||||
|
* the collection a.equals(b).
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
List<T> generateEquals(int n);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a number of equal objects.
|
||||||
|
*
|
||||||
|
* @param r
|
||||||
|
* A random generator
|
||||||
|
* @param n
|
||||||
|
* The number of objects to generate.
|
||||||
|
* @return A list of objects, with the property that for any two objects a,b in
|
||||||
|
* the collection a.equals(b).
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
List<T> generateEquals(Random r, int n);
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user