This commit is contained in:
Anya Helene Bagge 2018-03-17 22:22:11 +01:00
commit 3c5b291c26
63 changed files with 2317 additions and 190 deletions

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
/bin/ /bin/
.DS_Store .DS_Store
*.xml
*.iml

View File

@ -23,21 +23,66 @@ Dette prosjektet inneholder [Semesteroppgave 1](SEM-1.md). Du kan også [lese op
# Fyll inn egne svar/beskrivelse/kommentarer til prosjektet under # Fyll inn egne svar/beskrivelse/kommentarer til prosjektet under
* Levert av: *NAVN* (*BRUKERNAVN*) * Levert av: *Kristian Knarvik* (*kkn015*)
* Del A: [ ] helt ferdig, [ ] delvis ferdig * Del A: [x] helt ferdig, [ ] delvis ferdig
* Del B: [ ] helt ferdig, [ ] delvis ferdig * Del B: [ ] helt ferdig, [x] delvis ferdig
* Del C: [ ] helt ferdig, [ ] delvis ferdig * Del C: [ ] helt ferdig, [X] delvis ferdig
* [ ] hele semesteroppgaven er ferdig og klar til retting! * [ ] hele semesteroppgaven er ferdig og klar til retting!
# Del A # Del A
## Svar på spørsmål ## Svar på spørsmål
* ... a)
* IGame trenger et game map (IMapView) der ting kan plasseres. Den trenger også en referanse til brukergrensesnittet for å endre tekst og grafikk. Den trenger en TurtlePainter for å tegne grafikk. Den trenger en referanse til et Printer objekt til å (antagelig) printe fancy tekst.
* IMapView må ha en referanse til et objekt som implementerer IArea.
* Et IItem må ha et navn, en int som representerer hp, en annen int som representerer maksimal hp, en int som representerer størrelsen, og en int som representerer forsvar. Den må også ha et symbol som representerer en ting når den blir tegnet.
* En IActor er lik et IItem, men har i tillegg to int som representerer angrepsstyrke mot et IItem og en IIactor. Den kunne lagret en posisjon, men om det er en fordel spørs på implementasjonen av utførelsen av en tur.
* En INonPlayer ser ut til å være lik en IActor, men kan flytte seg selv.
* En IPlayer ser ut til å være lik en IActor, men kan gjøre mye rart ut ifra hvilken tast spilleren trykker.
b)
* IGame ser ut til å være på toppen, og behandler IMapView. IMapView holder styr på områder IArea. Hvert IArea holder styr på alle IItem (som også inneholder alt som utvider IItem). Noen typer IItem er av type IActor. Alle IActor er enten av type IPlayer eller INonPlayer. IPlayer og INonPlayer kan forsåvidt direkte manipulere IGame, så alt går egentlig litt i ring.
c)
* Jeg tror splittingen av IGameMap og IMapView er for å lettere kunne lage tester. IGameMap trenger en TurtlePainter, og da også en GUI. GUI er ikke særlig kompetibelt med tester. Den eneste andre logiske grunnen vil være for å kunne bruke IMapView til andre formål. For eksempel å simulere et kart med bare INonPlayer, gi dem lov til å drepe hverandre, og se hva som skjer etter 10000 steg.
d)
* Jeg tror INonPlayer og IPlayer er forskjellige for å ikke blande dem når en skal sjekke om hvem som kan flyttes hvor, og hvem som kan angripe hvem. De kunne alltids hatt en boolean player (dersom fiender ikke skal begynne å angripe hverandre) og en metode som tok inn game, og en annen som tok inn game og keypress. Det er litt teit å ha en boolean som er lik for alle objekter bortsett fra en. En fordel med å splitte dem opp, er at jeg enkelt kan lage en IImmovablePlayer som kan overskrive metoden som angriper den til at han/hun åpner butikken sin, eller gir meg et quest.
e)
* Det var uforventa at nesten alle tilstandsverdier ble hardkodet i stedet for å lagres i en variabel. Det gir mening når alle objekter er helt identiske (til å begynne med). Fordelen er at ting kan ha ganske unike implementeringer, men det virker litt "skittent".
f)
* Rabbit finner ut hvor den er ved å bruke Game sin getLocation(). Rabbit finner ut hvilke andre ting som er på stedet ved å bruke game sin getLocalItems(). Rabbit finner ut hvor den har lov å gå ved å bruke game sin canGo() (senere kan getPossibleMoves() hjelpe til).
g)
* GetLocation blir brukt når Game utfører turns. Game henter hvert objekt IActor og henter med en gang posisjonen fra Gamemap sin getLocation som henter verdien fra en hashmap. Jeg har aldri vært borti hashmaps i java, men det virker ekvivalent med måten Arrays virker i php. For eksempel: $items = array(rabbit1 => pos1). Når en da henter $items[rabbit1], vil en få pos1.
# Del B # Del B
## Svar på spørsmål ## Svar på spørsmål
* ... a)
* Nabo-celler blir behandlet en del annerledes, siden det skal være mulig å finne celler en viss avstand fra et punkt. I tillegg til at alle celler som er utenfor skal ignoreres, og være sortert. Det blir håndtert med en tilsynelatende ganske "skitten" metode, men den fungerer fint.
Måten å gjøre det på i Sem1 er mer praktisk, fordi det tillater å velge hvilket som helst størrelse på nabolaget.
b)
* De fleste spill-trekkene går igjennom game for å enkelt kunne utføre turen til alle objekter.
Fordelene er at det er lett å få tak i all nødvendig informasjon, uten å måtte lage spesialiserte metoder, eller sende inn mange parametere.
Ulempene er at alle IActors og IItem blir veldig sterkt knyttet til IGame. De kan ikke brukes uten å spesifikt opprette et objekt som implementerer IGame først, som forhinder dem i å bli brukt i hvilket som helst annet prosjekt.
c)
* Game.addItem burde strengt tatt sjekket at en IActor ikke blir plassert på en IActor.
d)
* Strukturen er definitivt mye mer kaotisk enn jeg først hadde trodd. Det er egentlig ganske vanskelig å finne det en er ute etter. I tillegg har jeg endret en del, som igjen endrer oppførselen til noen av de gamle klassene.
# Del C # Del C
## Oversikt over designvalg og hva du har gjort ## Oversikt over designvalg og hva du har gjort
* ... blah, blah, er implementert i klassen [KurtMario](src/inf101/v18/rogue101/player/KurtMario.java), blah, blah `ITurtleShell` ... * Oversikt over taster: WASD og piltaster kan brukes til bevegelse. Q for å legge ned ting. E for å plukke opp ting. 1-5 for å velge hvilke ting du ønsker å legge ned eller plukke opp.
* Main og Game har blitt endret til å tillate spilleren å selv velge når den har utført en tur. Dette har blitt gjort for å kunne ignorere uønskede tastetrykk, og for å tilby spilleren valg.
### Tredjepartsfiler
* Bow Fire Arrow Sound (http://soundbible.com, Stephan Schutze, Noncommercial 3.0)
* Sword Swing Sound (http://soundbible.com, Mike Koenig, Attribution 3.0)
* Large Fireball Sound (http://soundbible.com, Mike Koenig, Attribution 3.0)
* Realistic Punch Sound (http://soundbible.com, Mark DiAngelo, Attribution 3.0)
* Snow Ball Throw And Splat Sound (http://soundbible.com, Mike Koenig, Attribution 3.0)

View File

@ -177,7 +177,7 @@ Prøv også å justere gulrøttene litt ([Carrot](src/inf101/v18/rogue101/exampl
(Du trenger ikke legge ved tegningen din, men du kan gjerne lage og legge ved en oppdatert utgave når du har fått bedre/full forståelse av systemet.) (Du trenger ikke legge ved tegningen din, men du kan gjerne lage og legge ved en oppdatert utgave når du har fått bedre/full forståelse av systemet.)
### *(0%)* Deloppgave A5: Ting du ikke trenger å se på (0/100) ### *(0%)* Deloppgave A5: Ting du ikke trenger å se på
* Du trenger ikke se på koden i `gfx` (grafikkbibliotek), `grid` (utvidet IGrid) eller `util` (generering av testdata). * Du trenger ikke se på koden i `gfx` (grafikkbibliotek), `grid` (utvidet IGrid) eller `util` (generering av testdata).
* Hvis du lager grafikk selv, vil du gjerne komme til å *bruke* [`ITurtle`](src/inf101/v18/gfx/gfxmode/ITurtle.java) (fra `gfx`), men du trenger ikke se på implementasjone. * Hvis du lager grafikk selv, vil du gjerne komme til å *bruke* [`ITurtle`](src/inf101/v18/gfx/gfxmode/ITurtle.java) (fra `gfx`), men du trenger ikke se på implementasjone.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

10
flowchart.txt Normal file
View File

@ -0,0 +1,10 @@
Game -> IGame
GameMap -> IGameMap -> IMapView
IPlayer, INonPlayer -> IActor -> IItem -> Comparable<IItem>
GameEvent<T> -> Event<T>
Game -> Mapreader.readFile()
Game -> createItem()
Game - Rabbit.doTurn(Game) - Game.move()
Game.doTurn() - Game.beginTurn()

View File

@ -332,7 +332,8 @@ public class Screen {
private final Map<IPaintLayer, Canvas> layerCanvases = new IdentityHashMap<>(); private final Map<IPaintLayer, Canvas> layerCanvases = new IdentityHashMap<>();
private final Canvas background; private final Canvas background;
private final Group root; private final Group root;
private Paint bgColor = Color.CORNFLOWERBLUE; //private Paint bgColor = Color.CORNFLOWERBLUE;
private Paint bgColor = Color.BLACK;
private int aspect = 0; private int aspect = 0;
private double scaling = 0; private double scaling = 0;
private double currentScale = 1.0; private double currentScale = 1.0;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -12,7 +12,7 @@ public class BlocksAndBoxes {
private List<Integer> order; private List<Integer> order;
private 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);
} }
@ -44,7 +44,7 @@ public class BlocksAndBoxes {
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);

View File

@ -74,7 +74,7 @@ public class DemoPages {
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 = 0x00 + 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)) : " ");
} }

View File

@ -75,7 +75,7 @@ public class Printer implements IPaintLayer {
return r; return r;
} }
Color DEFAULT_FILL = Color.BLACK; Color DEFAULT_FILL = Color.WHITE;
Color DEFAULT_STROKE = Color.TRANSPARENT; Color DEFAULT_STROKE = Color.TRANSPARENT;
@ -91,7 +91,7 @@ public class Printer implements IPaintLayer {
// private int pageWidth = LINE_WIDTHS[resMode], pageHeight = // private int pageWidth = LINE_WIDTHS[resMode], pageHeight =
// PAGE_HEIGHTS[resMode]; // PAGE_HEIGHTS[resMode];
private int leftMargin = 1, topMargin = 1; private int leftMargin = 1, topMargin = 1;
private TextFont font = FONT_LMMONO; private TextFont font = FONT_SYMBOLA;
private int videoAttrs = 0; private int videoAttrs = 0;
private String csiSeq = null; private String csiSeq = null;

View File

@ -305,8 +305,6 @@ public class TextFont {
* @param font * @param font
* Name of the font file. Will search for the file in the same folder * Name of the font file. Will search for the file in the same folder
* as the TextFont class, as well as ".." and "../fonts". * as the TextFont class, as well as ".." and "../fonts".
* @param size
* Point size of the font.
* @param squareSize * @param squareSize
* The width and height of a square defining the bounds of letters * The width and height of a square defining the bounds of letters
* @param xTranslate * @param xTranslate
@ -350,10 +348,7 @@ public class TextFont {
* {@link #setGraphicsContext(GraphicsContext, double)} for extra on-the-fly * {@link #setGraphicsContext(GraphicsContext, double)} for extra on-the-fly
* horizontal scaling, e.g. to make half-width letters ("hires" mode). * horizontal scaling, e.g. to make half-width letters ("hires" mode).
* *
* *
* @param font
* Name of the font file. Will search for the file in the same folder
* as the TextFont class, as well as ".." and "../fonts".
* @param size * @param size
* Point size of the font. * Point size of the font.
* @param squareSize * @param squareSize
@ -400,10 +395,7 @@ public class TextFont {
* {@link #setGraphicsContext(GraphicsContext, double)} for extra on-the-fly * {@link #setGraphicsContext(GraphicsContext, double)} for extra on-the-fly
* horizontal scaling, e.g. to make half-width letters ("hires" mode). * horizontal scaling, e.g. to make half-width letters ("hires" mode).
* *
* *
* @param font
* Name of the font file. Will search for the file in the same folder
* as the TextFont class, as well as ".." and "../fonts".
* @param size * @param size
* Point size of the font. * Point size of the font.
* @param squareSize * @param squareSize
@ -739,13 +731,13 @@ public class TextFont {
case '0': case '0':
return -2 * thin; return -2 * thin;
case '2': case '2':
ctx.setLineDashes(new double[] { 14.75, 2.5 }); ctx.setLineDashes(14.75, 2.5);
break; break;
case '3': case '3':
ctx.setLineDashes(new double[] { 9, 2.5 }); ctx.setLineDashes(9, 2.5);
break; break;
case '4': case '4':
ctx.setLineDashes(new double[] { 6.125, 2.5 }); ctx.setLineDashes(6.125, 2.5);
break; break;
case '.': case '.':
return 0.0; return 0.0;

View File

@ -8,6 +8,8 @@ import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.stage.Stage; import javafx.stage.Stage;
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);
@ -219,7 +221,7 @@ public class TextFontAdjuster extends Application {
return false; return false;
}); });
screen.setKeyTypedHandler((KeyEvent event) -> { screen.setKeyTypedHandler((KeyEvent event) -> {
if (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;
} }

View File

@ -55,7 +55,7 @@ public enum TextMode {
private int vIndex; private int vIndex;
private 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;

View File

@ -32,7 +32,7 @@ public enum GridDirection {
private final int dy; private final int dy;
private final int mask; private final int mask;
private GridDirection(double degrees, int dx, int dy, int mask) { GridDirection(double degrees, int dx, int dy, int mask) {
this.degrees = degrees; this.degrees = degrees;
this.dx = dx; this.dx = dx;
this.dy = dy; this.dy = dy;

View File

@ -52,8 +52,8 @@ public interface IGrid<T> extends Iterable<T> {
* // clear the grid * // clear the grid
* grid.setAll(null); * grid.setAll(null);
* </pre> * </pre>
* *
* @param initialiser * @param element
*/ */
void fill(T element); void fill(T element);
@ -203,8 +203,6 @@ public interface IGrid<T> extends Iterable<T> {
* 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 element * @param element
* The contents the cell is to have. * The contents the cell is to have.
* @throws IndexOutOfBoundsException * @throws IndexOutOfBoundsException

View File

@ -91,7 +91,7 @@ public interface ILocation extends IPosition {
* Find the grid cells between this location (exclusive) and another location * Find the grid cells between this location (exclusive) and another location
* (inclusive). * (inclusive).
* *
* This is will be a list of length {@link #gridDistanceTo(other)}, containing * This is a list of length {@link #gridDistanceTo(other)}, containing
* the cells that a chess queen would visit when moving to <code>other</code>. * the cells that a chess queen would visit when moving to <code>other</code>.
* <p> * <p>
* Computes the maximum of the horizontal and the vertical distance. For * Computes the maximum of the horizontal and the vertical distance. For

View File

@ -23,8 +23,7 @@ public class MyGrid<T> implements IGrid<T> {
* 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 area
* @param height
* @param initialiser * @param initialiser
* The initialiser function * The initialiser function
*/ */
@ -43,8 +42,7 @@ public class MyGrid<T> implements IGrid<T> {
/** /**
* Construct a grid with the given dimensions. * Construct a grid with the given dimensions.
* *
* @param width * @param area
* @param height
* @param initElement * @param initElement
* What the cells should initially hold (possibly null) * What the cells should initially hold (possibly null)
*/ */
@ -95,9 +93,7 @@ public class MyGrid<T> implements IGrid<T> {
@Override @Override
public IGrid<T> copy() { public IGrid<T> copy() {
MyGrid<T> newGrid = new MyGrid<>(getWidth(), getHeight(), (l) -> get(l)); return new MyGrid<>(getWidth(), getHeight(), (l) -> get(l));
return newGrid;
} }
@Override @Override

View File

@ -80,10 +80,7 @@ public class RectArea implements IArea {
if (x != other.getX()) { if (x != other.getX()) {
return false; return false;
} }
if (y != other.getY()) { return y == other.getY();
return false;
}
return true;
} }
@Override @Override
@ -304,10 +301,7 @@ public class RectArea implements IArea {
@Override @Override
public String toString() { public String toString() {
StringBuilder builder = new StringBuilder(); return "RectArea [width=" + width + ", height=" + height + ", hWrap=" + hWrap + ", vWrap=" + vWrap + "]";
builder.append("RectArea [width=").append(width).append(", height=").append(height).append(", hWrap=")
.append(hWrap).append(", vWrap=").append(vWrap).append("]");
return builder.toString();
} }
@Override @Override

View File

@ -12,7 +12,7 @@ import static org.junit.Assert.*;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.junit.jupiter.api.Test; import org.junit.Test;
public class AreaRetting { public class AreaRetting {
private static final int N = 10000; private static final int N = 10000;

View File

@ -11,7 +11,7 @@ import static org.junit.Assert.assertEquals;
import java.util.function.Function; import java.util.function.Function;
import org.junit.jupiter.api.Test; import org.junit.Test;
public class GridRetting { public class GridRetting {
private static final int N = 10000; private static final int N = 10000;

View File

@ -20,7 +20,7 @@ import javafx.util.Duration;
public class Main extends Application { public class Main extends Application {
// you might want to tune these options // you might want to tune these options
public static final boolean MAIN_USE_BACKGROUND_GRID = true; public static final boolean MAIN_USE_BACKGROUND_GRID = false;
public static final boolean MAP_AUTO_SCALE_ITEM_DRAW = true; public static final boolean MAP_AUTO_SCALE_ITEM_DRAW = true;
public static final boolean MAP_DRAW_ONLY_DIRTY_CELLS = false; public static final boolean MAP_DRAW_ONLY_DIRTY_CELLS = false;
public static final TextMode MAIN_TEXT_MODE = TextMode.MODE_80X25; public static final TextMode MAIN_TEXT_MODE = TextMode.MODE_80X25;
@ -88,7 +88,7 @@ public class Main extends Application {
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();
@ -123,18 +123,21 @@ public class Main extends Application {
printer.redrawTextPage(); printer.redrawTextPage();
return true; return true;
} }
} else if (code == KeyCode.ENTER) { /*} else if (code == KeyCode.ENTER) {
try { try {
doTurn(); doTurn();
} catch (Exception e) { } catch (Exception e) {
printer.printAt(1, 25, "Exception: " + e.getMessage(), Color.RED); printer.printAt(1, 25, "Exception: " + e.getMessage(), Color.RED);
e.printStackTrace(); e.printStackTrace();
} }
return true; return true;*/ // This interferes with other code
} else { } else {
try { try {
game.keyPressed(code); if (game.keyPressed(code)) {
doTurn(); game.draw();
} else {
doTurn();
}
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
try { try {

View File

@ -0,0 +1,78 @@
package inf101.v18.rogue101.enemies;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.items.Backpack;
import inf101.v18.rogue101.objects.IItem;
import inf101.v18.rogue101.objects.INonPlayer;
public class Boss implements INonPlayer {
private int hp = getMaxHealth();
Backpack backpack = new Backpack();
@Override
public void doTurn(IGame game) {
}
@Override
public int getAttack() {
return 50;
}
@Override
public int getDamage() {
return 15;
}
@Override
public IItem getItem(Class<?> type) {
for (IItem item : backpack.getContent()) {
if (type.isInstance(item)) {
return item;
}
}
return null;
}
@Override
public int getCurrentHealth() {
return hp;
}
@Override
public int getDefence() {
return 50;
}
@Override
public int getMaxHealth() {
return 500;
}
@Override
public String getName() {
return "Lucifer";
}
@Override
public int getSize() {
return 50;
}
@Override
public String getPrintSymbol() {
return "\uD83D\uDE08";
}
@Override
public String getSymbol() {
return "B";
}
@Override
public int handleDamage(IGame game, IItem source, int amount) {
//TODO: Drop item on death.
hp -= amount;
return amount;
}
}

View File

@ -0,0 +1,260 @@
package inf101.v18.rogue101.enemies;
import inf101.v18.grid.GridDirection;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.items.*;
import inf101.v18.rogue101.objects.IItem;
import inf101.v18.rogue101.objects.INonPlayer;
import inf101.v18.rogue101.shared.NPC;
import inf101.v18.rogue101.states.Age;
import inf101.v18.rogue101.states.Occupation;
import inf101.v18.rogue101.states.Personality;
import java.nio.channels.ClosedSelectorException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
public class Girl implements INonPlayer {
private final String name = randomName();
private final Age age = Age.getRandom();
private final Personality personality = Personality.getRandom();
private final Occupation occupation = Occupation.getRandom();
private int maxhp;
private int hp;
private int attack;
private int defence;
private int visibility;
private Backpack backpack = new Backpack();
private static final Random random = new Random();
private List<Class<?>> validItems;
public Girl() {
setStats();
setValidItems();
}
//TODO: Game balance
private void setStats() {
switch (age) {
case TODDLER:
maxhp = 100;
attack = 10;
defence = 50;
visibility = 1;
break;
case CHILD:
maxhp = 150;
attack = 20;
defence = 40;
visibility = 2;
break;
case TEEN:
maxhp = 200;
attack = 25;
defence = 30;
visibility = 3;
break;
case ADULT:
maxhp = 250;
attack = 30;
defence = 20;
visibility = 4;
break;
case ELDER:
maxhp = 200;
attack = 15;
defence = 35;
visibility = 2;
break;
}
if (occupation == Occupation.KNIGHT) {
attack += 10; //Knights are quite powerful.
}
if (occupation == Occupation.MAGE) {
attack += 5; // Mages have lesser range than bowsmen, but more damage.
}
maxhp += (int)(random.nextGaussian() * 10);
hp = maxhp;
attack += (int)(random.nextGaussian() * 5);
defence += (int)(random.nextGaussian() * 5);
}
/**
* Specified which items the current Girl should be able to pick up.
*/
private void setValidItems() {
validItems = new ArrayList<>();
switch (occupation) {
case BOWSMAN:
validItems.add(IRangedWeapon.class);
break;
case MAGE:
validItems.add(IMagicWeapon.class);
break;
case KNIGHT:
validItems.add(IMeleeWeapon.class);
}
validItems.add(IBuffItem.class);
}
private String randomName() {
//TODO: Choose from a list of names, or generate name.
return "Girl";
}
@Override
public void doTurn(IGame game) {
if (!backpack.isFull()) {
IItem item = NPC.pickUp(game, validItems);
if (item != null) {
backpack.add(item);
return;
}
if (NPC.trackItem(game, validItems)) {
return;
}
}
if (personality == Personality.AFRAID && NPC.flee(game)) {
return;
}
if (willAttack(game) && attack(game)) {
return;
}
List<GridDirection> possibleMoves = game.getPossibleMoves();
if (!possibleMoves.isEmpty()) {
Collections.shuffle(possibleMoves);
game.move(possibleMoves.get(0));
}
}
/**
* Tries to attack with an attack specified by the Girl's occupation
*
* @param game An IGame object
* @return True if an attack occurred. False otherwise
*/
private boolean attack(IGame game) {
switch (occupation) {
case KNIGHT:
if (NPC.tryAttack(game)) {
return true;
}
break;
case MAGE:
if (NPC.tryAttackRanged(game, 2)) {
return true;
}
case BOWSMAN:
if (NPC.tryAttackRanged(game, 4)) {
return true;
}
}
return false;
}
/**
* Checks if the current girl will try to attack.
*
* @param game An IGame object
* @return True if the girl will attack. False otherwise
*/
private boolean willAttack(IGame game) {
boolean attack = false;
switch (personality) {
case CALM:
if (hp < maxhp) {
attack = true;
}
break;
case AFRAID:
if (game.getPossibleMoves().isEmpty()) {
attack = true;
}
break;
case AGRESSIVE:
attack = true;
break;
}
return attack;
}
/**
* Retrieves a girl's occupation.
*
* @return
*/
public Occupation getOccupation() {
return occupation;
}
@Override
public int getAttack() {
return attack;
}
@Override
public int getDamage() {
return 5;
}
@Override
public int getCurrentHealth() {
return hp;
}
@Override
public int getDefence() {
return defence;
}
@Override
public int getMaxHealth() {
return maxhp;
}
@Override
public String getName() {
return name;
}
@Override
public int getSize() {
return 30;
}
@Override
public String getPrintSymbol() {
return "\u001b[95m" + "\uD83D\uDEB6" + "\u001b[0m";
}
@Override
public String getSymbol() {
return "G";
}
@Override
public int getVision() {
return visibility;
}
@Override
public IItem getItem(Class<?> type) {
for (IItem item : backpack.getContent()) {
if (type.isInstance(item)) {
return item;
}
}
return null;
}
@Override
public int handleDamage(IGame game, IItem source, int amount) {
hp -= amount;
return amount;
}
}

View File

@ -8,7 +8,7 @@ import javafx.scene.paint.Color;
public class Carrot implements IItem { public class Carrot implements IItem {
private int hp = getMaxHealth(); private int hp = getMaxHealth();
public void doTurn(IGame game) { public void doTurn() {
hp = Math.min(hp + 1, getMaxHealth()); hp = Math.min(hp + 1, getMaxHealth());
} }
@ -50,7 +50,7 @@ public class Carrot implements IItem {
@Override @Override
public int getMaxHealth() { public int getMaxHealth() {
return 10; return 23;
} }
@Override @Override
@ -74,7 +74,7 @@ public class Carrot implements IItem {
if (hp < 0) { if (hp < 0) {
// we're all eaten! // we're all eaten!
hp = -1; hp = 0;
} }
return amount; return amount;
} }

View File

@ -9,39 +9,49 @@ import inf101.v18.grid.GridDirection;
import inf101.v18.rogue101.game.IGame; import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.objects.IItem; import inf101.v18.rogue101.objects.IItem;
import inf101.v18.rogue101.objects.INonPlayer; import inf101.v18.rogue101.objects.INonPlayer;
import inf101.v18.rogue101.shared.NPC;
public class Rabbit implements INonPlayer { public class Rabbit implements INonPlayer {
private int food = 0; private int food = 0;
private int hp = getMaxHealth(); private int hp = getMaxHealth();
private static List<Class<?>> validItems = new ArrayList<>();
static {
validItems.add(Carrot.class);
}
@Override @Override
public void doTurn(IGame 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 (NPC.tryAttack(game)) {
return;
}
for (IItem 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, 5); 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() + " " game.displayMessage("You hear a faint crunching (" + getName() + " eats " + item.getArticle() + " " + item.getName() + ")");
+ item.getName() + ")");
return; return;
} }
} }
} }
// TODO: prøv forskjellige varianter her
List<GridDirection> possibleMoves = new ArrayList<>(); if (NPC.trackItem(game, validItems)) {
for (GridDirection dir : GridDirection.FOUR_DIRECTIONS) { return;
if (game.canGo(dir))
possibleMoves.add(dir);
} }
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));
@ -55,7 +65,7 @@ public class Rabbit implements INonPlayer {
@Override @Override
public int getAttack() { public int getAttack() {
return 1000; return 10;
} }
@Override @Override
@ -65,22 +75,22 @@ public class Rabbit implements INonPlayer {
@Override @Override
public int getDamage() { public int getDamage() {
return 1000; return 5;
} }
@Override @Override
public int getDefence() { public int getDefence() {
return 1000;
}
@Override
public int getMaxHealth() {
return 10; return 10;
} }
@Override
public int getMaxHealth() {
return 30;
}
@Override @Override
public String getName() { public String getName() {
return "rabbit"; return "Rabbit";
} }
@Override @Override
@ -88,6 +98,16 @@ public class Rabbit implements INonPlayer {
return 4; return 4;
} }
@Override
public int getVision() {
return 4;
}
@Override
public IItem getItem(Class<?> type) {
return null;
}
@Override @Override
public String getSymbol() { public String getSymbol() {
return hp > 0 ? "R" : "¤"; return hp > 0 ? "R" : "¤";
@ -98,5 +118,4 @@ public class Rabbit implements INonPlayer {
hp -= amount; hp -= amount;
return amount; return amount;
} }
} }

View File

@ -17,18 +17,17 @@ import inf101.v18.grid.GridDirection;
import inf101.v18.grid.IGrid; import inf101.v18.grid.IGrid;
import inf101.v18.grid.ILocation; import inf101.v18.grid.ILocation;
import inf101.v18.rogue101.Main; 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.Carrot;
import inf101.v18.rogue101.items.*;
import inf101.v18.rogue101.examples.Rabbit; import inf101.v18.rogue101.examples.Rabbit;
import inf101.v18.rogue101.map.GameMap; import inf101.v18.rogue101.map.GameMap;
import inf101.v18.rogue101.map.IGameMap; import inf101.v18.rogue101.map.IGameMap;
import inf101.v18.rogue101.map.IMapView; import inf101.v18.rogue101.map.IMapView;
import inf101.v18.rogue101.map.MapReader; import inf101.v18.rogue101.map.MapReader;
import inf101.v18.rogue101.objects.Dust; import inf101.v18.rogue101.objects.*;
import inf101.v18.rogue101.objects.IActor; import inf101.v18.rogue101.shared.NPC;
import inf101.v18.rogue101.objects.IItem;
import inf101.v18.rogue101.objects.INonPlayer;
import inf101.v18.rogue101.objects.IPlayer;
import inf101.v18.rogue101.objects.Wall;
import javafx.scene.canvas.GraphicsContext; import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
@ -46,6 +45,12 @@ public class Game implements IGame {
* Useful random generator * Useful random generator
*/ */
private Random random = new Random(); private Random random = new Random();
/**
* Saves the last three messages
*/
private List<String> lastMessages = new ArrayList<>();
/** /**
* The game map. {@link IGameMap} gives us a few more details than * The game map. {@link IGameMap} gives us a few more details than
* {@link IMapView} (write access to item lists); the game needs this but * {@link IMapView} (write access to item lists); the game needs this but
@ -63,9 +68,7 @@ public class Game implements IGame {
this.painter = painter; this.painter = painter;
this.printer = printer; this.printer = printer;
// TODO: (*very* optional) for advanced factory technique, use addFactory();
// something like "itemFactories.put("R", () -> new Rabbit());"
// must be done *before* you read the map
// NOTE: in a more realistic situation, we will have multiple levels (one map // 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 // per level), and (at least for a Roguelike game) the levels should be
@ -74,7 +77,7 @@ public class Game implements IGame {
// inputGrid will be filled with single-character strings indicating what (if // inputGrid will be filled with single-character strings indicating what (if
// anything) // anything)
// should be placed at that map square // should be placed at that map square
IGrid<String> inputGrid = MapReader.readFile("maps/level1.txt"); IGrid<String> inputGrid = MapReader.readFile("maps/testmap.txt");
if (inputGrid == null) { if (inputGrid == null) {
System.err.println("Map not found falling back to builtin map"); System.err.println("Map not found falling back to builtin map");
inputGrid = MapReader.readString(Main.BUILTIN_MAP); inputGrid = MapReader.readString(Main.BUILTIN_MAP);
@ -87,11 +90,19 @@ public class Game implements IGame {
} }
} }
// 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-5 to choose an item", "N to change name", "ENTER to confirm", "R to use a ranged attack", "F to use a magic attack"};
for (int i = 0; i < info.length; i++) {
this.printer.printAt(map.getWidth() + 2, 1 + i, info[i]);
}
} }
public Game(String mapString) { public Game(String mapString) {
printer = new Printer(1280, 720); printer = new Printer(1280, 720);
painter = new TurtlePainter(1280, 720); painter = new TurtlePainter(1280, 720);
addFactory();
IGrid<String> inputGrid = MapReader.readString(mapString); IGrid<String> inputGrid = MapReader.readString(mapString);
this.map = new GameMap(inputGrid.getArea()); this.map = new GameMap(inputGrid.getArea());
for (ILocation loc : inputGrid.locations()) { for (ILocation loc : inputGrid.locations()) {
@ -114,14 +125,74 @@ public class Game implements IGame {
map.add(currentLocation, item); map.add(currentLocation, item);
} }
/**
* Calculates the attack of an IActor based on equipped items.
*
* @return The attack
*/
private int getAttack() {
int damage = currentActor.getAttack() + random.nextInt(20) + 1;
IWeapon weapon = (IWeapon)currentActor.getItem(IWeapon.class);
if (weapon != null) {
damage += weapon.getWeaponDamage();
}
IBuffItem buff = (IBuffItem)currentActor.getItem(IBuffItem.class);
if (buff != null) {
damage += buff.getBuffDamage();
}
return damage;
}
/**
* Gets the defence of the current target.
*
* @param target The target to evaluate
* @return The defence of the target
*/
private int getDefence(IItem target) {
int defence = target.getDefence() + 10;
IActor actor = (IActor) target;
IBuffItem item = (IBuffItem) actor.getItem(IBuffItem.class);
if (item != null) {
defence += item.getBuffDefence();
}
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(IItem target) {
int damage = currentActor.getDamage();
IActor actor = (IActor) target;
IBuffItem item = (IBuffItem) actor.getItem(IBuffItem.class);
if (item != null) {
damage -= item.getBuffDamageReduction();
}
return damage;
}
@Override @Override
public ILocation attack(GridDirection dir, IItem target) { public ILocation attack(GridDirection dir, IItem target) {
ILocation loc = map.go(currentLocation, dir); ILocation loc = currentLocation.go(dir);
if (map.has(loc, target)) if (!map.has(loc, target)) {
throw new IllegalMoveException("Target isn't there!"); throw new IllegalMoveException("Target isn't there!");
}
// TODO: implement attack IWeapon weapon = (IWeapon) currentActor.getItem(IWeapon.class);
if (weapon != null) {
NPC.playSound(weapon.getSound());
} else {
NPC.playSound("audio/Realistic_Punch-Mark_DiAngelo-1609462330.wav");
}
if (getAttack() >= getDefence(target)) {
int actualDamage = target.handleDamage(this, target, getDamage(target));
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); map.clean(loc);
@ -133,6 +204,30 @@ public class Game implements IGame {
} }
} }
@Override
public ILocation rangedAttack(GridDirection dir, IItem target) {
ILocation loc = currentLocation;
IWeapon weapon = (IWeapon) currentActor.getItem(IWeapon.class);
if (weapon != null) {
NPC.playSound(weapon.getSound());
} else {
NPC.playSound("audio/Snow Ball Throw And Splat-SoundBible.com-992042947.wav");
}
if (getAttack() >= getDefence(target)) {
int damage = getDamage(target) / 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 * Begin a new game turn, or continue to the previous turn
* *
@ -148,8 +243,15 @@ public class Game implements IGame {
beginTurn(); 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());
}
}
// process actors one by one; for the IPlayer, we return and wait for keypresses // process actors one by one; for the IPlayer, we return and wait for keypresses
// Possible TODO: for INonPlayer, we could also return early (returning // Possible for INonPlayer, we could also return early (returning
// *false*), and then insert a little timer delay between each non-player move // *false*), and then insert a little timer delay between each non-player move
// (the timer // (the timer
// is already set up in Main) // is already set up in Main)
@ -169,6 +271,7 @@ public class Game implements IGame {
((INonPlayer) currentActor).doTurn(this); ((INonPlayer) currentActor).doTurn(this);
// remove any dead items from current location // remove any dead items from current location
map.clean(currentLocation); map.clean(currentLocation);
return false;
} else if (currentActor instanceof IPlayer) { } else if (currentActor instanceof IPlayer) {
if (currentActor.isDestroyed()) { if (currentActor.isDestroyed()) {
// a dead human player gets removed from the game // a dead human player gets removed from the game
@ -231,6 +334,8 @@ public class Game implements IGame {
} }
} else if (item instanceof IActor) { } else if (item instanceof IActor) {
actors.add((IActor) item); // add other actors to the end of the list actors.add((IActor) item); // add other actors to the end of the list
} else if (item instanceof Carrot) {
((Carrot) item).doTurn();
} }
} }
}); });
@ -241,20 +346,22 @@ public class Game implements IGame {
return map.canGo(currentLocation, 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", Sword::new);
itemFactories.put("c", Chest::new);
itemFactories.put("B", Boss::new);
}
@Override @Override
public IItem createItem(String sym) { public IItem createItem(String sym) {
switch (sym) { switch (sym) {
case "#":
return new Wall();
case ".":
// TODO: add Dust
return null;
case "R":
return new Rabbit();
case "C":
return new Carrot();
case "@":
// TODO: add Player
case " ": case " ":
return null; return null;
default: default:
@ -278,11 +385,22 @@ public class Game implements IGame {
@Override @Override
public void displayMessage(String s) { public void displayMessage(String s) {
// it should be safe to print to lines Main.LINE_MSG1, Main.LINE_MSG2, if (lastMessages.size() >= 3) {
// Main.LINE_MSG3 lastMessages.remove(2);
// TODO: you can save the last three lines, and display/scroll them }
lastMessages.add(0, s);
printer.clearLine(Main.LINE_MSG1); printer.clearLine(Main.LINE_MSG1);
printer.printAt(1, Main.LINE_MSG1, s); printer.clearLine(Main.LINE_MSG2);
printer.clearLine(Main.LINE_MSG3);
if (lastMessages.size() > 0) {
printer.printAt(1, Main.LINE_MSG1, lastMessages.get(0));
}
if (lastMessages.size() > 1) {
printer.printAt(1, Main.LINE_MSG2, lastMessages.get(1));
}
if (lastMessages.size() > 2) {
printer.printAt(1, Main.LINE_MSG3, lastMessages.get(2));
}
System.out.println("Message: «" + s + "»"); System.out.println("Message: «" + s + "»");
} }
@ -294,7 +412,9 @@ public class Game implements IGame {
} }
public void draw() { public void draw() {
map.draw(painter, printer); //map.draw(painter, printer);
GameMap aMap = (GameMap) map;
aMap.drawVisible(painter, printer, this);
} }
@Override @Override
@ -356,15 +476,29 @@ public class Game implements IGame {
@Override @Override
public List<GridDirection> getPossibleMoves() { public List<GridDirection> getPossibleMoves() {
// TODO List<GridDirection> moves = new ArrayList<>();
throw new UnsupportedOperationException(); for (GridDirection dir : GridDirection.FOUR_DIRECTIONS) {
if (canGo(dir)) {
moves.add(dir);
}
}
return moves;
} }
@Override @Override
public List<ILocation> getVisible() { public List<ILocation> getVisible() {
// TODO: maybe replace 3 by some sort of visibility range obtained from List<ILocation> neighbours = map.getNeighbourhood(currentLocation, currentActor.getVision());
// currentActor? List<ILocation> invalid = new ArrayList<>();
return map.getNeighbourhood(currentLocation, 3); for (ILocation neighbour : neighbours) {
for (ILocation tile : currentLocation.gridLineTo(neighbour)) {
if (map.hasWall(tile)) {
invalid.add(neighbour);
break;
}
}
}
neighbours.removeAll(invalid);
return neighbours;
} }
@Override @Override
@ -372,14 +506,13 @@ public class Game implements IGame {
return map.getWidth(); return map.getWidth();
} }
public void keyPressed(KeyCode code) { public boolean keyPressed(KeyCode code) {
// only an IPlayer/human can handle keypresses, and only if it's the human's // only an IPlayer/human can handle keypresses, and only if it's the human's
// turn // turn
if (currentActor instanceof IPlayer) { if (currentActor instanceof IPlayer) {
((IPlayer) currentActor).keyPressed(this, code); // do your thing return !((IPlayer) currentActor).keyPressed(this, code);
if (movePoints <= 0)
doTurn(); // proceed with turn if we're out of moves
} }
return true;
} }
@Override @Override
@ -396,22 +529,25 @@ public class Game implements IGame {
@Override @Override
public IItem pickUp(IItem item) { public IItem pickUp(IItem item) {
if (item != null && map.has(currentLocation, item)) { if (item != null && map.has(currentLocation, item) && !(item instanceof IStatic)) {
// TODO: bruk getAttack()/getDefence() til å avgjøre om man får til å plukke opp if (item instanceof IActor) {
// tingen if (item.getCurrentHealth() / item.getMaxHealth() < 3) {
// evt.: en IActor kan bare plukkes opp hvis den har /ingen helsepoeng igjen map.remove(currentLocation, item);
map.remove(currentLocation, item); return item;
return item; } else {
return null;
}
} else if (currentActor.getAttack() > item.getDefence()) {
map.remove(currentLocation, item);
return item;
} else {
return null;
}
} else { } else {
return null; return null;
} }
} }
@Override
public ILocation rangedAttack(GridDirection dir, IItem target) {
return currentLocation;
}
@Override @Override
public ITurtle getPainter() { public ITurtle getPainter() {
return painter; return painter;
@ -486,4 +622,45 @@ public class Game implements IGame {
public Random getRandom() { public Random getRandom() {
return random; return random;
} }
@Override
public GridDirection locationDirection(ILocation start, ILocation target) {
int targetX = target.getX(), targetY = target.getY();
int startX = start.getX(), startY = start.getY();
GridDirection dir = GridDirection.CENTER;
if (targetX > startX && targetY > startY) {
if (Math.abs(targetX - startX) < Math.abs(targetY - startY)) {
dir = GridDirection.SOUTH;
} else {
dir = GridDirection.EAST;
}
} else if (targetX > startX && targetY < startY) {
if (Math.abs(targetX - startX) < Math.abs(targetY - startY)) {
dir = GridDirection.NORTH;
} else {
dir = GridDirection.EAST;
}
} else if (targetX < startX && targetY > startY) {
if (Math.abs(targetX - startX) < Math.abs(targetY - startY)) {
dir = GridDirection.SOUTH;
} else {
dir = GridDirection.WEST;
}
} else if (targetX < startX && targetY < startY) {
if (Math.abs(targetX - startX) < Math.abs(targetY - startY)) {
dir = GridDirection.NORTH;
} else {
dir = GridDirection.WEST;
}
} else if (targetX > startX) {
dir = GridDirection.EAST;
} else if (targetX < startX) {
dir = GridDirection.WEST;
} else if (targetY > startY) {
dir = GridDirection.SOUTH;
} else if (targetY < startY) {
dir = GridDirection.NORTH;
}
return dir;
}
} }

View File

@ -187,7 +187,7 @@ public interface IGame {
double[] getFreeGraphicsAreaBounds(); double[] getFreeGraphicsAreaBounds();
/** /**
* Get the bounds of the free texxt area. * Get the bounds of the free text area.
* <p> * <p>
* You can fill this with whatever you want, using {@link #getPrinter()} and * You can fill this with whatever you want, using {@link #getPrinter()} and
* {@link #clearFreeTextArea()}. * {@link #clearFreeTextArea()}.
@ -317,4 +317,14 @@ public interface IGame {
* @return A random generator * @return A random generator
*/ */
Random getRandom(); Random getRandom();
/**
* Gets the best direction 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
*/
GridDirection locationDirection(ILocation current, ILocation neighbour);
} }

View File

@ -0,0 +1,123 @@
package inf101.v18.rogue101.items;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.objects.IItem;
import java.util.ArrayList;
import java.util.List;
public class Backpack implements IContainer {
/**
* A list containing everything in the backpack.
*/
private List<IItem> content = new ArrayList<>();
/**
* The maximum amount of items allowed in a single backpack.
*/
private final int MAX_SIZE = 50;
@Override
public int getCurrentHealth() {
return 0;
}
@Override
public int getDefence() {
return 0;
}
@Override
public int getMaxHealth() {
return 0;
}
@Override
public String getName() {
return "Backpack";
}
@Override
public int getSize() {
return 20;
}
@Override
public String getSymbol() {
return "B";
}
@Override
public int handleDamage(IGame game, IItem source, int amount) {
return 0;
}
/**
* Retrieves the current size of the Backpack.
*
* @return
*/
public int size() {
int totalSize = 0;
for (IItem item : content) {
totalSize += item.getSize();
}
return totalSize;
}
/**
* Checks if the Backpack is empty
*
* @return True if empty. False otherwise
*/
public boolean isEmpty() {
return content.isEmpty();
}
/**
* Tries to add an item to the Backpack
*
* @param item The item to add
* @return
*/
public boolean add(IItem item) {
if (size() < MAX_SIZE) {
content.add(item);
return true;
} else {
return false;
}
}
/**
* Removes an element at index i
*
* @param i The index of an element
*/
public void remove(int i) {
content.remove(i);
}
/**
* Gets a T at index i,
*
* @param i The index of an element
* @return An object of type T
*/
public IItem get(int i) {
return content.get(i);
}
/**
* Gets the content List for direct manipulation.
*
* @return A list of T
*/
public List<IItem> getContent() {
return content;
}
@Override
public boolean isFull() {
return size() >= MAX_SIZE;
}
}

View File

@ -0,0 +1,52 @@
package inf101.v18.rogue101.items;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.objects.IItem;
import java.util.Random;
public class Bow implements IRangedWeapon {
private static final Random random = new Random();
private final int damage = 3 + random.nextInt(20);
private final int hp = getMaxHealth();
@Override
public int getWeaponDamage() {
return damage;
}
@Override
public int getCurrentHealth() {
return hp;
}
@Override
public int getDefence() {
return 0;
}
@Override
public int getMaxHealth() {
return 110;
}
@Override
public String getName() {
return "Unknown bow";
}
@Override
public int getSize() {
return 2;
}
@Override
public String getSymbol() {
return "B";
}
@Override
public int handleDamage(IGame game, IItem source, int amount) {
return 0;
}
}

View File

@ -0,0 +1,73 @@
package inf101.v18.rogue101.items;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.objects.IItem;
import java.util.ArrayList;
import java.util.List;
public class Chest implements IContainer, IStatic {
private List<IItem> container;
public Chest() {
this.container = new ArrayList<>();
}
public Chest(List<IItem> items) {
this.container = items;
}
@Override
public IItem get(int i) {
return null;
}
@Override
public List getContent() {
return container;
}
@Override
public boolean isFull() {
return false;
}
@Override
public int getCurrentHealth() {
return 0;
}
@Override
public int getMaxHealth() {
return 0;
}
@Override
public String getName() {
return "Chest";
}
@Override
public String getInteractMessage() {
return "Items in " + getName() + ": ";
}
@Override
public int getSize() {
return 10000;
}
public String getPrintSymbol() {
return "\uD83D\uDDC3";
}
@Override
public String getSymbol() {
return "C";
}
@Override
public int handleDamage(IGame game, IItem source, int amount) {
return 0;
}
}

View File

@ -0,0 +1,33 @@
package inf101.v18.rogue101.items;
import inf101.v18.rogue101.objects.IItem;
public interface IBuffItem extends IItem {
/**
* Retrieve damage increase done by the buff item.
*
* @return An int, May be 0
*/
int getBuffDamage();
/**
* Retrieve defence increase done by the buff item.
*
* @return An int, May be 0
*/
int getBuffDefence();
/**
* Retrieve defence increase done by the buff item.
*
* @return An int, May be 0
*/
int getBuffDamageReduction();
/**
* Retrieve visibility increase done by the buff item.
*
* @return An int, May be 0
*/
int getBuffVisibility();
}

View File

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

View File

@ -0,0 +1,30 @@
package inf101.v18.rogue101.items;
import inf101.v18.rogue101.objects.IItem;
import java.util.List;
public interface IContainer extends IItem {
/**
* Retrieves an item from a container in index i
*
* @param i The index of an element
* @return An IItem
* @throws IndexOutOfBoundsException If the index is out of range.
*/
IItem get(int i);
/**
* Gets a list with everything inside a container.
*
* @return A list of Objects extending IItem
*/
List<IItem> getContent();
/**
* Checks if we can add anything at all to the container.
*
* @return True if it has no space left
*/
boolean isFull();
}

View 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/Bow_Fire_Arrow-Stephan_Schutze-2133929391.wav";
}
}

View 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";
}
}

View 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";
}
}

View File

@ -0,0 +1,22 @@
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;
}
/**
* A message to display when an interaction with the player happens.
*
* @return A message
*/
String getInteractMessage();
}

View File

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

View File

@ -0,0 +1,65 @@
package inf101.v18.rogue101.items;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.objects.IItem;
public class Manga implements IItem {
private int hp = getMaxHealth();
/*@Override
public boolean draw(ITurtle painter, double w, double h) {
painter.save();
painter.jump(-10);
painter.setInk(Color.BLACK);
painter.draw(getSize());
painter.turn(90);
painter.draw(getSize()/2);
painter.turn(90);
painter.draw(getSize());
painter.turn(90);
painter.draw(getSize()/2);
painter.restore();
return true;
}*/
@Override
public int getCurrentHealth() {
return hp;
}
@Override
public int getDefence() {
return 0;
}
@Override
public int getMaxHealth() {
return 1;
}
@Override
public String getName() {
return "Manga";
}
@Override
public int getSize() {
return 5;
}
@Override
public String getPrintSymbol() {
return "🕮";
}
@Override
public String getSymbol() {
return "M";
}
@Override
public int handleDamage(IGame game, IItem source, int amount) {
hp -= amount;
return amount;
}
}

View File

@ -0,0 +1,63 @@
package inf101.v18.rogue101.items;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.objects.IItem;
public class Shield implements IBuffItem {
private final int hp = getMaxHealth();
@Override
public int getBuffDamage() {
return 0;
}
@Override
public int getBuffDefence() {
return 10;
}
@Override
public int getBuffDamageReduction() {
return 5;
}
@Override
public int getBuffVisibility() {
return 0;
}
@Override
public int getCurrentHealth() {
return hp;
}
@Override
public int getDefence() {
return 0;
}
@Override
public int getMaxHealth() {
return 150;
}
@Override
public String getName() {
return "Unknown shield";
}
@Override
public int getSize() {
return 2;
}
@Override
public String getSymbol() {
return "";
}
@Override
public int handleDamage(IGame game, IItem source, int amount) {
return 0;
}
}

View File

@ -0,0 +1,53 @@
package inf101.v18.rogue101.items;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.objects.IItem;
import java.util.Random;
public class Staff implements IMagicWeapon {
private static final Random random = new Random();
private final int damage = 5 + random.nextInt(25);
private int hp = getMaxHealth();
@Override
public int getWeaponDamage() {
return damage;
}
@Override
public int getCurrentHealth() {
return hp;
}
@Override
public int getDefence() {
return 0;
}
@Override
public int getMaxHealth() {
return 90;
}
@Override
public String getName() {
return "Unknown staff";
}
@Override
public int getSize() {
return 0;
}
@Override
public String getSymbol() {
return "s";
}
@Override
public int handleDamage(IGame game, IItem source, int amount) {
hp -= amount;
return amount;
}
}

View File

@ -0,0 +1,53 @@
package inf101.v18.rogue101.items;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.objects.IItem;
import java.util.Random;
public class Sword implements IMeleeWeapon {
private static final Random random = new Random();
private final int damage = 5 + random.nextInt(25);
private int hp = getMaxHealth();
@Override
public int getWeaponDamage() {
return damage;
}
@Override
public int getCurrentHealth() {
return hp;
}
@Override
public int getDefence() {
return 0;
}
@Override
public int getMaxHealth() {
return 100;
}
@Override
public String getName() {
return "Unknown sword";
}
@Override
public int getSize() {
return 2;
}
@Override
public String getSymbol() {
return "S";
}
@Override
public int handleDamage(IGame game, IItem source, int amount) {
hp -= amount;
return amount;
}
}

View File

@ -1,12 +1,6 @@
package inf101.v18.rogue101.map; package inf101.v18.rogue101.map;
import java.util.ArrayList; import java.util.*;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import inf101.v18.gfx.gfxmode.ITurtle; import inf101.v18.gfx.gfxmode.ITurtle;
import inf101.v18.gfx.textmode.Printer; import inf101.v18.gfx.textmode.Printer;
@ -16,9 +10,13 @@ import inf101.v18.grid.ILocation;
import inf101.v18.grid.IMultiGrid; import inf101.v18.grid.IMultiGrid;
import inf101.v18.grid.MultiGrid; import inf101.v18.grid.MultiGrid;
import inf101.v18.rogue101.Main; import inf101.v18.rogue101.Main;
import inf101.v18.rogue101.examples.Carrot;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.game.IllegalMoveException; import inf101.v18.rogue101.game.IllegalMoveException;
import inf101.v18.rogue101.items.Manga;
import inf101.v18.rogue101.objects.IActor; import inf101.v18.rogue101.objects.IActor;
import inf101.v18.rogue101.objects.IItem; import inf101.v18.rogue101.objects.IItem;
import inf101.v18.rogue101.objects.IPlayer;
import inf101.v18.rogue101.objects.Wall; import inf101.v18.rogue101.objects.Wall;
import javafx.scene.canvas.GraphicsContext; import javafx.scene.canvas.GraphicsContext;
@ -48,6 +46,7 @@ public class GameMap implements IGameMap {
@Override @Override
public void add(ILocation loc, IItem item) { public void add(ILocation loc, IItem item) {
// keep track of location of all items // keep track of location of all items
items.put(item, loc); items.put(item, loc);
// also keep track of whether we need to redraw this cell // also keep track of whether we need to redraw this cell
@ -55,8 +54,26 @@ public class GameMap implements IGameMap {
// do the actual adding // do the actual adding
List<IItem> list = grid.get(loc); List<IItem> list = grid.get(loc);
for (int i = 0; i < list.size(); i++) {
if (item.compareTo(list.get(i)) >= 0) {
list.add(i, item);
return;
}
}
list.add(item); list.add(item);
// TODO: should be sorted! }
public void addRandomItems(ILocation loc) {
if (!this.hasActors(loc) && !this.hasWall(loc)) {
Random random = new Random();
if (random.nextInt(100) < 20) {
this.add(loc, new Carrot());
}
if (random.nextInt(100) < 1) {
this.add(loc, new Manga());
}
}
} }
@Override @Override
@ -102,7 +119,7 @@ public class GameMap implements IGameMap {
try { try {
for (ILocation loc : cells) { for (ILocation loc : cells) {
List<IItem> list = grid.get(loc); List<IItem> list = grid.get(loc);
String sym = " "; String sym = ".";
if (!list.isEmpty()) { if (!list.isEmpty()) {
if (Main.MAP_DRAW_ONLY_DIRTY_CELLS) { if (Main.MAP_DRAW_ONLY_DIRTY_CELLS) {
ctx.clearRect(loc.getX() * w, loc.getY() * h, w, h); ctx.clearRect(loc.getX() * w, loc.getY() * h, w, h);
@ -126,6 +143,88 @@ public class GameMap implements IGameMap {
dirtyLocs.clear(); dirtyLocs.clear();
} }
/**
* Draws only the tiles visible to the player.
*
* @param painter
* @param printer
* @param game
*/
public void drawVisible(ITurtle painter, Printer printer, IGame game) {
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, " ");
if (this.hasActors(loc) && this.getActors(loc).get(0) instanceof IPlayer) {
player = (IPlayer) this.getActors(loc).get(0);
playerPos = loc;
}
}
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 @Override
public List<IActor> getActors(ILocation loc) { public List<IActor> getActors(ILocation loc) {
List<IActor> items = new ArrayList<>(); List<IActor> items = new ArrayList<>();
@ -241,14 +340,72 @@ public class GameMap implements IGameMap {
dirtyLocs.add(loc); dirtyLocs.add(loc);
} }
@Override @Override
public List<ILocation> getNeighbourhood(ILocation loc, int dist) { public List<ILocation> getNeighbourhood(ILocation loc, int dist) {
if (dist < 0 || loc == null) if (dist < 0 || loc == null) {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
else if (dist == 0) } else if (dist == 0) {
return new ArrayList<>(); // empty! return new ArrayList<>(); // empty!
}
// TODO: implement this! List<ILocation> neighbours = new ArrayList<>();
throw new UnsupportedOperationException(); int startX = loc.getX();
int startY = loc.getY();
for (int i = 1; i <= dist; i++) {
int leftX = startX - i;
int rightX = startX + i;
int topY = startY - i;
int bottomY = startY + i;
for (int x = leftX + 1; x < rightX; x++) {
if (grid.isValid(x, topY)) {
neighbours.add(getLocation(x, topY));
}
if (grid.isValid(x, bottomY)) {
neighbours.add(getLocation(x, bottomY));
}
}
for (int y = topY; y <= bottomY; y++) {
if (grid.isValid(leftX, y)) {
neighbours.add(getLocation(leftX, y));
}
if (grid.isValid(rightX, y)) {
neighbours.add(getLocation(rightX, y));
}
}
}
return neighbours;
} }
/**
* Goes in a direction, and saves all locations it passes to a list.
*
* @param locations Target list
* @param loc The location to start from
* @param dir Which direction to go
* @param max Maximum amount of steps
*/
private void addLoc(List<ILocation> locations, ILocation loc, GridDirection dir, int max) {
for (int j = 0; j < max; j++) {
locations.add(loc);
if (loc.canGo(dir)) {
loc = loc.go(dir);
} else {
return;
}
}
}
/**
* Goes a set amount of steps in a set direction, from a location, and returns the new location.
*
* @param loc Start location
* @param dir Direction to go
* @param max Maximum amount of steps
* @return The new location
*/
private ILocation goTo(ILocation loc, GridDirection dir, int max) {
for (int j = 0; j < max; j++) {
loc = loc.go(dir);
}
return loc;
}
} }

View File

@ -9,7 +9,7 @@
#. ...R..C. ..R.R..........C.RC....... # #. ...R..C. ..R.R..........C.RC....... #
#..C.....R..... ........RR R..R.....R..# #..C.....R..... ........RR R..R.....R..#
#...R..R.R..............R .R..R........# #...R..R.R..............R .R..R........#
#.R.....R........RRR.......R.. .C....R.# #.R.....R...M....RRR.......R.. .C....R.#
#.C.. ..R. .....R.RC..C....R...R..C. .# #.C.. ..R. .....R.RC..C....R...R..C. .#
#. R..............R R..R........C.....R# #. R..............R R..R........C.....R#
#........############################### #........###############################

View File

@ -0,0 +1,21 @@
40 20
########################################
# #
# #
# #
# G G #
# #
############### # #
# # #
# S # #
# @ ######## #########
# # #
# # #
# # #
# G #
# G #
# c #
# ####
# # B#
# G #
########################################

View File

@ -21,4 +21,20 @@ public interface IActor extends IItem {
* the target) * the target)
*/ */
int getDamage(); int getDamage();
/**
* How many tiles the IActor is able to see in each direction.
*
* @return Number of tiles
*/
default int getVision() {
return 1;
}
/**
* Gets an item of the specified type, if the actor has the item.
*
* @return An IItem or null
*/
IItem getItem(Class<?> type);
} }

View File

@ -20,6 +20,7 @@ public interface IPlayer extends IActor {
* *
* @param game * @param game
* Game, for interacting with the world * Game, for interacting with the world
* @return True if the player has done anything consuming a turn. False otherwise
*/ */
void keyPressed(IGame game, KeyCode key); boolean keyPressed(IGame game, KeyCode key);
} }

View File

@ -0,0 +1,351 @@
package inf101.v18.rogue101.objects;
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.shared.NPC;
import javafx.scene.input.KeyCode;
import java.util.ArrayList;
import java.util.List;
public class Player implements IPlayer {
private int hp = getMaxHealth();
private final Backpack equipped = new Backpack();
// True if the player wants to pick up 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;
private boolean writing = false;
private boolean exploringChest = false;
private String text = "";
private String name = "Player";
@Override
public boolean keyPressed(IGame game, KeyCode key) {
boolean turnConsumed = false;
if (writing) {
write(game, key);
return false;
}
if (key == KeyCode.LEFT || key == KeyCode.A) {
tryToMove(game, GridDirection.WEST);
turnConsumed = true;
} else if (key == KeyCode.RIGHT || key == KeyCode.D) {
tryToMove(game, GridDirection.EAST);
turnConsumed = true;
} else if (key == KeyCode.UP || key == KeyCode.W) {
tryToMove(game, GridDirection.NORTH);
turnConsumed = true;
} else if (key == KeyCode.DOWN || key == KeyCode.S) {
tryToMove(game, GridDirection.SOUTH);
turnConsumed = true;
} else if (key == KeyCode.E) {
turnConsumed = pickUpInit(game);
} else if (key == KeyCode.Q) {
turnConsumed = dropInit(game);
} else if (key.isDigitKey()) {
//Takes care of all digit keys, but we only use the first 5 for picking up and dropping.
int keyValue = Integer.parseInt(key.getName());
if (keyValue <= 5 && keyValue > 0) {
if (dropping) {
drop(game, keyValue - 1);
dropping = false;
turnConsumed = true;
} else if (picking) {
pickUp(game, keyValue - 1);
picking = false;
turnConsumed = true;
} else if (exploringChest) {
loot(game, keyValue - 1);
exploringChest = false;
turnConsumed = true;
}
}
} else if (key == KeyCode.N) {
game.displayMessage("Please enter your name: ");
writing = true;
} else if (key == KeyCode.R) {
IItem item = getItem(IRangedWeapon.class);
if (item == null) {
game.displayMessage("You do not have a ranged weapon.");
} else {
turnConsumed = NPC.tryAttackRanged(game, 3);
}
} else if (key == KeyCode.F) {
IItem item = getItem(IMagicWeapon.class);
if (item == null) {
game.displayMessage("You do not have a magic weapon.");
} else {
turnConsumed = NPC.tryAttackRanged(game, 2);
}
}
showStatus(game);
return turnConsumed;
}
/**
* Lets the user write his/her name.
*
* @param game An IGame object
* @param key The key pressed
*/
private void write(IGame game, KeyCode key) {
if (key == KeyCode.BACK_SPACE) {
text = text.substring(0, text.length() - 1);
game.displayMessage("Please enter your name: " + text);
} else if (key != KeyCode.ENTER) {
text += key.toString();
game.displayMessage("Please enter your name: " + text);
} else {
name = text.toLowerCase();
text = "";
game.displayMessage("Name set.");
writing = false;
}
}
/**
* Initializes the picking up of an item, and does what is needed.
*
* @param game An IGame object
* @return True if a turn was consumed. False otherwise
*/
private boolean pickUpInit(IGame game) {
List<IItem> items = game.getLocalItems();
if (items.size() < 1) {
game.displayMessage("There is nothing to pick up");
} else {
if (items.get(0) instanceof IStatic && items.get(0) instanceof IContainer) { //Static items are always the biggest (and hopefully the only) item on a tile.
openChest(game, items);
} else {
if (items.size() == 1) {
pickUp(game, 0);
return true;
} else {
StringBuilder msg = new StringBuilder("Items on this tile:");
for (int i = 0; i < Math.min(items.size(), 5); i++) {
msg.append(" [").append(i + 1).append("] ").append(firstCharToUpper(items.get(i).getName()));
}
game.displayMessage(msg.toString());
picking = true;
}
}
}
return false;
}
private void openChest(IGame game, List<IItem> items) {
IStatic item = (IStatic)items.get(0);
StringBuilder msg = new StringBuilder(item.getInteractMessage());
IContainer container = (IContainer) item;
items = container.getContent();
for (int i = 0; i < Math.min(items.size(), 5); i++) {
msg.append(" [").append(i + 1).append("] ").append(firstCharToUpper(items.get(i).getName()));
}
game.displayMessage(msg.toString());
exploringChest = true;
}
/**
* Initialized the dropping of an item, and does what is needed.
*
* @param game An IGame object
* @return True if a turn was consumed. False otherwise
*/
private boolean dropInit(IGame game) {
if (equipped.getContent().size() == 1) {
drop(game, 0);
return true;
} else if (equipped.getContent().size() < 1) {
game.displayMessage("You have nothing to drop");
} else {
StringBuilder msg = new StringBuilder("Items to drop:");
for (int i = 0; i < equipped.getContent().size(); i++) {
msg.append(" [").append(i + 1).append("] ").append(firstCharToUpper(equipped.get(i).getName()));
}
game.displayMessage(msg.toString());
dropping = true;
}
return false;
}
/**
* Picks up an item at index i.
*
* @param game An IGame object
* @param i The wanted index
*/
private void pickUp(IGame game, int i) {
if (!equipped.isFull()) {
List<IItem> items = game.getLocalItems();
if (items.size() >= i) {
IItem pickedUp = game.pickUp(items.get(i));
if (pickedUp != null) {
equipped.add(pickedUp);
game.displayMessage("Picked up " + pickedUp.getName());
} else {
game.displayMessage("The item could not be picked up.");
}
} else {
game.displayMessage("That item does not exist.");
}
} else {
game.displayMessage("Your inventory is full.");
}
}
/**
* Takes an item from a chest, and gives it to the player.
*/
private void loot(IGame game, int i) {
if (!equipped.isFull()) {
List<IItem> items = game.getLocalItems();
IItem item = items.get(0);
if (item instanceof IStatic && item instanceof IContainer) {
IContainer container = (IContainer) item;
IItem loot = container.getContent().get(i);
equipped.add(loot);
container.getContent().remove(i);
game.displayMessage("Looted " + loot.getName());
} else {
game.displayMessage("It seems you are trying to loot a chest, without a chest nearby.");
}
} else {
game.displayMessage("Your inventory is full.");
}
}
/**
* Drops an item at index i.
*
* @param game An IGame object
* @param i The wanted index
*/
private void drop(IGame game, int i) {
if (!equipped.isEmpty() && equipped.size() > i) {
if (game.drop(equipped.get(i))) {
equipped.remove(i);
game.displayMessage("Item dropped.");
} else {
game.displayMessage("The ground rejected the item.");
}
} else {
game.displayMessage("You have nothing to drop.");
}
}
/**
* Updates the status bar with the Player's current stats.
*
* @param game An IGame object
*/
private void showStatus(IGame game) {
List<String> items = new ArrayList<>();
for (IItem item : equipped.getContent()) {
String name = item.getName();
if (name != null) {
items.add(firstCharToUpper(name));
}
}
//TODO: Add item bonuses to visible stats.
game.formatStatus("HP: %d/%d ATK: %d DEF: %s DMG: %s INV: %s", hp, getMaxHealth(), getAttack(), getDefence(), getDamage(), String.join(",", items));
}
/**
* Changes the first character in a string to uppercase.
*
* @param input The input string
* @return The input string with the first character uppercased
*/
private String firstCharToUpper(String input) {
if (input.length() < 1) {
return input;
} else {
return Character.toUpperCase(input.charAt(0)) + input.substring(1);
}
}
private void tryToMove(IGame game, GridDirection dir) {
ILocation loc = game.getLocation();
if (game.canGo(dir)) {
game.move(dir);
} else if (loc.canGo(dir) && game.getMap().hasActors(loc.go(dir))) {
NPC.playSound("audio/Sword Swing-SoundBible.com-639083727.wav");
game.attack(dir, game.getMap().getActors(loc.go(dir)).get(0));
} else {
game.displayMessage("Umm, it is not possible to move in that direction.");
}
}
@Override
public int getAttack() {
return 50;
}
@Override
public int getDamage() {
return 20;
}
@Override
public IItem getItem(Class<?> type) {
for (IItem item : equipped.getContent()) {
if (type.isInstance(item)) {
return item;
}
}
return null;
}
@Override
public int getCurrentHealth() {
return hp;
}
@Override
public int getDefence() {
return 30;
}
@Override
public int getMaxHealth() {
return 1000;
}
@Override
public String getName() {
return firstCharToUpper(name);
}
@Override
public int getSize() {
return 10;
}
@Override
public String getPrintSymbol() {
return "\u001b[96m" + "@" + "\u001b[0m";
}
@Override
public String getSymbol() {
return "@";
}
@Override
public int handleDamage(IGame game, IItem source, int amount) {
hp -= amount;
showStatus(game);
if (hp < 1) {
game.displayStatus("Game Over");
}
return amount;
}
@Override
public int getVision() {
//TODO: Increase vision based on equipped items
return 3;
}
}

View File

@ -0,0 +1,171 @@
package inf101.v18.rogue101.shared;
import inf101.v18.grid.GridDirection;
import inf101.v18.grid.ILocation;
import inf101.v18.rogue101.enemies.Girl;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.objects.IActor;
import inf101.v18.rogue101.objects.IItem;
import inf101.v18.rogue101.states.Occupation;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import java.io.File;
import java.util.List;
/**
* A holder class for methods used by IActors.
*/
public class NPC {
/**
* Makes a NPC attack anything it can, or move closer to any enemy in its line of sight.
*
* @param game An IGame object
* @return True if the NPC made a move. False otherwise
*/
public static boolean tryAttack(IGame game) {
List<ILocation> neighbours = game.getVisible();
for (ILocation neighbour : neighbours) {
if (game.getMap().hasActors(neighbour)) {
ILocation current = game.getLocation();
GridDirection dir = game.locationDirection(current, neighbour);
IActor actor = game.getMap().getActors(neighbour).get(0); //We assume there is only one actor.
if (current.gridDistanceTo(neighbour) <= 1 && current.canGo(dir) && game.getMap().has(current.go(dir), actor) && !actor.isDestroyed()) {
game.attack(dir, actor);
return true;
} else if (game.canGo(dir)) {
game.move(dir);
return true;
}
}
}
return false;
}
/**
* Performs a ranged attack on the closest actor (if any) observable by the current actor.
*
* @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 tryAttackRanged(IGame game, int range) {
// Ranged attacks don't care about walls.
List<ILocation> neighbours = game.getMap().getNeighbourhood(game.getLocation(), game.getActor().getVision());
for (ILocation neighbour : neighbours) {
if (game.getMap().hasActors(neighbour)) {
ILocation current = game.getLocation();
GridDirection dir = game.locationDirection(current, neighbour);
IActor actor = game.getMap().getActors(neighbour).get(0); //We assume there is only one actor.
if (current.gridDistanceTo(neighbour) <= range && !actor.isDestroyed()) {
game.rangedAttack(dir, actor);
return true;
} else 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) {
boolean hasItem = false;
for (IItem item : game.getMap().getAll(neighbour)) {
for (Class<?> validItem : validItems) {
if (validItem.isInstance(item)) {
hasItem = true;
}
}
}
if (hasItem) {
ILocation current = game.getLocation();
GridDirection dir = game.locationDirection(current, neighbour);
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();
GridDirection dir = reverseDir(game.locationDirection(current, neighbour));
if (game.canGo(dir)) {
game.move(dir);
return true;
}
}
}
return false;
}
/**
* Reverses the main four directions.
*
* @param dir A direction
* @return An opposite direction
*/
private static GridDirection reverseDir(GridDirection dir) {
if (dir == GridDirection.SOUTH) {
return GridDirection.NORTH;
} else if (dir == GridDirection.NORTH) {
return GridDirection.SOUTH;
} else if (dir == GridDirection.WEST) {
return GridDirection.EAST;
} else {
return GridDirection.WEST;
}
}
/**
* Plays a sound.
*
* @param filename The String path of the audio file to play
*/
public static void playSound(String filename) {
Media sound = new Media(new File(filename).toURI().toString());
MediaPlayer mediaPlayer = new MediaPlayer(sound);
mediaPlayer.play();
}
}

View 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 Age {
TODDLER, CHILD, TEEN, ADULT, ELDER;
private static final Random random = new Random();
public static Age getRandom() {
return values()[random.nextInt(values().length)];
}
}

View 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)];
}
}

View 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)];
}
}

View File

@ -2,19 +2,111 @@ package inf101.v18.rogue101.tests;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import org.junit.jupiter.api.Test; import inf101.v18.rogue101.objects.IItem;
import org.junit.Test;
import inf101.v18.grid.ILocation; import inf101.v18.grid.ILocation;
import inf101.v18.rogue101.map.GameMap; import inf101.v18.rogue101.map.GameMap;
class GameMapTest { import java.util.List;
public class GameMapTest {
@Test @Test
void testSortedAdd() { public void testSortedAdd() {
GameMap gameMap = new GameMap(20, 20); GameMap gameMap = new GameMap(20, 20);
ILocation location = gameMap.getLocation(10, 10); ILocation location = gameMap.getLocation(10, 10);
// TODO: for (int i = 0; i < 30; i++) {
fail("Not yet implemented"); gameMap.addRandomItems(location);
}
List<IItem> items = gameMap.getAll(location);
for (int i = 0; i < items.size() - 1; i++) {
assertTrue(items.get(i).compareTo(items.get(i + 1)) >= 0);
}
} }
@Test
public void testGetNeighbours() {
GameMap gameMap = new GameMap(20, 20);
ILocation location = gameMap.getLocation(10, 10);
List<ILocation> neighbours = gameMap.getNeighbourhood(location, 1);
for (ILocation neighbour : neighbours) {
assertTrue(location.gridDistanceTo(neighbour) <= 1);
}
assertEquals(8, neighbours.size());
neighbours = gameMap.getNeighbourhood(location, 2);
for (ILocation neighbour : neighbours) {
assertTrue(location.gridDistanceTo(neighbour) <= 2);
}
assertEquals(24, neighbours.size());
neighbours = gameMap.getNeighbourhood(location, 3);
for (ILocation neighbour : neighbours) {
assertTrue(location.gridDistanceTo(neighbour) <= 3);
}
assertEquals(48, neighbours.size());
}
@Test
public void testNeighboursOutOfBounds() {
GameMap gameMap = new GameMap(20, 20);
ILocation location = gameMap.getLocation(0, 0);
List<ILocation> neighbours = gameMap.getNeighbourhood(location, 3);
for (ILocation neighbour : neighbours) {
assertTrue(location.gridDistanceTo(neighbour) <= 3);
}
assertEquals(15, neighbours.size());
location = gameMap.getLocation(19, 0);
neighbours = gameMap.getNeighbourhood(location, 3);
for (ILocation neighbour : neighbours) {
assertTrue(location.gridDistanceTo(neighbour) <= 3);
}
assertEquals(15, neighbours.size());
location = gameMap.getLocation(19, 19);
neighbours = gameMap.getNeighbourhood(location, 3);
for (ILocation neighbour : neighbours) {
assertTrue(location.gridDistanceTo(neighbour) <= 3);
}
assertEquals(15, neighbours.size());
location = gameMap.getLocation(0, 19);
neighbours = gameMap.getNeighbourhood(location, 3);
for (ILocation neighbour : neighbours) {
assertTrue(location.gridDistanceTo(neighbour) <= 3);
}
assertEquals(15, neighbours.size());
location = gameMap.getLocation(0, 10);
neighbours = gameMap.getNeighbourhood(location, 3);
for (ILocation neighbour : neighbours) {
assertTrue(location.gridDistanceTo(neighbour) <= 3);
}
assertEquals(27, neighbours.size());
location = gameMap.getLocation(19, 10);
neighbours = gameMap.getNeighbourhood(location, 3);
for (ILocation neighbour : neighbours) {
assertTrue(location.gridDistanceTo(neighbour) <= 3);
}
assertEquals(27, neighbours.size());
location = gameMap.getLocation(10, 0);
neighbours = gameMap.getNeighbourhood(location, 3);
for (ILocation neighbour : neighbours) {
assertTrue(location.gridDistanceTo(neighbour) <= 3);
}
assertEquals(27, neighbours.size());
location = gameMap.getLocation(10, 19);
neighbours = gameMap.getNeighbourhood(location, 3);
for (ILocation neighbour : neighbours) {
assertTrue(location.gridDistanceTo(neighbour) <= 3);
}
assertEquals(27, neighbours.size());
}
} }

View File

@ -2,41 +2,97 @@ package inf101.v18.rogue101.tests;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import org.junit.jupiter.api.Test; import org.junit.Test;
import inf101.v18.grid.GridDirection; import inf101.v18.grid.GridDirection;
import inf101.v18.grid.ILocation; import inf101.v18.grid.ILocation;
import inf101.v18.rogue101.game.Game; import inf101.v18.rogue101.game.Game;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.map.GameMap;
import inf101.v18.rogue101.objects.IItem;
import inf101.v18.rogue101.objects.IPlayer; import inf101.v18.rogue101.objects.IPlayer;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
class PlayerTest { public class PlayerTest {
public static String TEST_MAP = "40 5\n" //
+ "########################################\n" //
+ "#...... ..C.R ......R.R......... ..R...#\n" //
+ "#.R@R...... ..........RC..R...... ... .#\n" //
+ "#... ..R........R......R. R........R.RR#\n" //
+ "########################################\n" //
;
@Test @Test
void testPlayer1() { public void testOutOfBounds() {
// new game with our test map String NO_WALLS_MAP = "1 1\n"
Game game = new Game(TEST_MAP); + "@\n";
// pick (3,2) as the "current" position; this is where the player is on the Game game = new Game(NO_WALLS_MAP);
// test map, so it'll set up the player and return it IPlayer player = (IPlayer) game.setCurrent(0, 0);
IPlayer player = (IPlayer) game.setCurrent(3, 2);
// find players location
ILocation loc = game.getLocation(); ILocation loc = game.getLocation();
// press "UP" key player.keyPressed(game, KeyCode.LEFT);
player.keyPressed(game, KeyCode.UP); assertEquals(loc, game.getLocation());
// see that we moved north
assertEquals(loc.go(GridDirection.NORTH), game.getLocation());
} }
/*@Test
public void testActor() {
String RABBIT_MAP = "5 5\n"
+ "#####\n"
+ "#RRR#\n"
+ "#R@R#\n"
+ "#RRR#\n"
+ "#####\n";
Game game = new Game(RABBIT_MAP);
IPlayer player = (IPlayer) game.setCurrent(2, 2);
ILocation loc = game.getLocation();
player.keyPressed(game, KeyCode.LEFT);
assertEquals(loc, game.getLocation());
player.keyPressed(game, KeyCode.RIGHT);
assertEquals(loc, game.getLocation());
player.keyPressed(game, KeyCode.UP);
assertEquals(loc, game.getLocation());
player.keyPressed(game, KeyCode.DOWN);
assertEquals(loc, game.getLocation());
}*/ //TODO: Fix error when playing sound
@Test
public void testItem() {
String CARROT_MAP = "5 5\n"
+ "#####\n"
+ "#CCC#\n"
+ "#C@C#\n"
+ "#CCC#\n"
+ "#####\n";
Game game = new Game(CARROT_MAP);
IPlayer player = (IPlayer) game.setCurrent(2, 2);
ILocation loc = game.getLocation();
player.keyPressed(game, KeyCode.RIGHT);
assertEquals(loc.go(GridDirection.EAST), game.getLocation());
game.doTurn();
player.keyPressed(game, KeyCode.LEFT);
game.doTurn();
player.keyPressed(game, KeyCode.LEFT);
assertEquals(loc.go(GridDirection.WEST), game.getLocation());
}
@Test
public void testWallsAndKeys() { //This probably seems a little overkill, but we need to check all keys.
String EMPTY_MAP = "5 5\n"
+ "#####\n"
+ "# #\n"
+ "# @ #\n"
+ "# #\n"
+ "#####\n";
Game game = new Game(EMPTY_MAP);
IPlayer player = (IPlayer) game.setCurrent(2, 2);
ILocation loc = game.getLocation();
player.keyPressed(game, KeyCode.UP);
game.doTurn();
player.keyPressed(game, KeyCode.UP);
game.doTurn();
assertEquals(loc.go(GridDirection.NORTH), game.getLocation());
player.keyPressed(game, KeyCode.DOWN);
game.doTurn();
player.keyPressed(game, KeyCode.RIGHT);
game.doTurn();
assertEquals(loc.go(GridDirection.EAST), game.getLocation());
player.keyPressed(game, KeyCode.LEFT);
game.doTurn();
player.keyPressed(game, KeyCode.DOWN);
game.doTurn();
assertEquals(loc.go(GridDirection.SOUTH), game.getLocation());
player.keyPressed(game, KeyCode.UP);
game.doTurn();
player.keyPressed(game, KeyCode.LEFT);
assertEquals(loc.go(GridDirection.WEST), game.getLocation());
}
} }

View File

@ -82,8 +82,6 @@ public class DoubleGenerator extends AbstractGenerator<Double> {
public Double generate(Random rng) { public Double generate(Random rng) {
double d = rng.nextDouble(); double d = rng.nextDouble();
double r = minValue + (d * diff); return minValue + (d * diff);
return r;
} }
} }

View File

@ -73,8 +73,6 @@ public class GridGenerator<T> extends AbstractGenerator<IGrid<T>> {
int w = widthGenerator.generate(r); int w = widthGenerator.generate(r);
int h = heightGenerator.generate(r); int h = heightGenerator.generate(r);
IGrid<T> grid = new MyGrid<>(w, h, (l) -> elementGenerator.generate(r)); return new MyGrid<>(w, h, (l) -> elementGenerator.generate(r));
return grid;
} }
} }