2018-02-28 23:51:40 +01:00
package inf101.v18.rogue101.game ;
import java.util.ArrayList ;
import java.util.Collections ;
import java.util.HashMap ;
import java.util.Iterator ;
import java.util.List ;
import java.util.Map ;
import java.util.Random ;
import java.util.function.Supplier ;
import inf101.v18.gfx.Screen ;
import inf101.v18.gfx.gfxmode.ITurtle ;
import inf101.v18.gfx.gfxmode.TurtlePainter ;
import inf101.v18.gfx.textmode.Printer ;
import inf101.v18.grid.GridDirection ;
import inf101.v18.grid.IGrid ;
import inf101.v18.grid.ILocation ;
import inf101.v18.rogue101.Main ;
2018-03-15 00:16:26 +01:00
import inf101.v18.rogue101.enemies.Boss ;
2018-03-09 23:18:00 +01:00
import inf101.v18.rogue101.enemies.Girl ;
2018-02-28 23:51:40 +01:00
import inf101.v18.rogue101.examples.Carrot ;
2018-03-20 01:10:30 +01:00
import inf101.v18.rogue101.examples.Manga ;
2018-03-14 00:00:52 +01:00
import inf101.v18.rogue101.items.* ;
2018-02-28 23:51:40 +01:00
import inf101.v18.rogue101.examples.Rabbit ;
import inf101.v18.rogue101.map.GameMap ;
import inf101.v18.rogue101.map.IGameMap ;
import inf101.v18.rogue101.map.IMapView ;
import inf101.v18.rogue101.map.MapReader ;
2018-03-07 21:40:06 +01:00
import inf101.v18.rogue101.objects.* ;
2018-03-15 00:16:26 +01:00
import inf101.v18.rogue101.shared.NPC ;
2018-03-20 11:05:15 +01:00
import inf101.v18.rogue101.states.Attack ;
2018-02-28 23:51:40 +01:00
import javafx.scene.canvas.GraphicsContext ;
import javafx.scene.input.KeyCode ;
import javafx.scene.paint.Color ;
public class Game implements IGame {
/ * *
* All the IActors that have things left to do this turn
* /
private List < IActor > actors = Collections . synchronizedList ( new ArrayList < > ( ) ) ;
/ * *
* For fancy solution to factory problem
* /
private Map < String , Supplier < IItem > > itemFactories = new HashMap < > ( ) ;
/ * *
* Useful random generator
* /
private Random random = new Random ( ) ;
2018-03-10 15:17:50 +01:00
/ * *
* Saves the last three messages
* /
private List < String > lastMessages = new ArrayList < > ( ) ;
2018-02-28 23:51:40 +01:00
/ * *
* The game map . { @link IGameMap } gives us a few more details than
* { @link IMapView } ( write access to item lists ) ; the game needs this but
* individual items don ' t .
* /
private IGameMap map ;
2018-03-20 21:41:18 +01:00
private List < IGameMap > maps = new ArrayList < > ( ) ;
private int currentLVL = 0 ;
2018-02-28 23:51:40 +01:00
private IActor currentActor ;
private ILocation currentLocation ;
private int movePoints = 0 ;
private final ITurtle painter ;
private final Printer printer ;
private int numPlayers = 0 ;
2018-03-21 00:10:42 +01:00
boolean won = false ;
2018-02-28 23:51:40 +01:00
2018-03-13 21:35:38 +01:00
public Game ( Screen screen , ITurtle painter , Printer printer ) {
2018-02-28 23:51:40 +01:00
this . painter = painter ;
this . printer = printer ;
2018-03-07 21:40:06 +01:00
addFactory ( ) ;
2018-02-28 23:51:40 +01:00
// 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
2018-03-20 21:41:18 +01:00
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
2018-02-28 23:51:40 +01:00
2018-03-14 00:00:52 +01:00
// Prints some helpful information.
2018-03-20 01:10:30 +01:00
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 potion " } ;
2018-03-14 00:00:52 +01:00
for ( int i = 0 ; i < info . length ; i + + ) {
2018-03-21 00:10:42 +01:00
this . printer . printAt ( Main . COLUMN_RIGHTSIDE_START , 1 + i , info [ i ] ) ;
2018-03-14 00:00:52 +01:00
}
2018-02-28 23:51:40 +01:00
}
public Game ( String mapString ) {
2018-03-08 00:57:53 +01:00
printer = new Printer ( 1280 , 720 ) ;
2018-03-07 22:34:18 +01:00
painter = new TurtlePainter ( 1280 , 720 ) ;
2018-03-07 21:40:06 +01:00
addFactory ( ) ;
2018-02-28 23:51:40 +01:00
IGrid < String > inputGrid = MapReader . readString ( mapString ) ;
this . map = new GameMap ( inputGrid . getArea ( ) ) ;
for ( ILocation loc : inputGrid . locations ( ) ) {
IItem item = createItem ( inputGrid . get ( loc ) ) ;
if ( item ! = null ) {
map . add ( loc , item ) ;
}
}
}
@Override
public void addItem ( IItem item ) {
map . add ( currentLocation , item ) ;
}
@Override
public void addItem ( String sym ) {
IItem item = createItem ( sym ) ;
if ( item ! = null )
map . add ( currentLocation , item ) ;
}
2018-03-13 21:35:38 +01:00
/ * *
* Calculates the attack of an IActor based on equipped items .
*
* @return The attack
* /
private int getAttack ( ) {
int damage = currentActor . getAttack ( ) + random . nextInt ( 20 ) + 1 ;
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 ;
}
2018-03-20 21:41:18 +01:00
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 " ) ;
}
}
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 " ) ;
}
}
2018-03-13 21:35:38 +01:00
/ * *
* 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 ( ) ;
2018-03-20 01:10:30 +01:00
IWeapon weapon = ( IWeapon ) currentActor . getItem ( IWeapon . class ) ;
if ( weapon ! = null ) {
damage + = weapon . getWeaponDamage ( ) ;
}
2018-03-13 21:35:38 +01:00
IActor actor = ( IActor ) target ;
IBuffItem item = ( IBuffItem ) actor . getItem ( IBuffItem . class ) ;
if ( item ! = null ) {
damage - = item . getBuffDamageReduction ( ) ;
}
return damage ;
}
2018-02-28 23:51:40 +01:00
@Override
public ILocation attack ( GridDirection dir , IItem target ) {
2018-03-08 23:38:59 +01:00
ILocation loc = currentLocation . go ( dir ) ;
if ( ! map . has ( loc , target ) ) {
2018-02-28 23:51:40 +01:00
throw new IllegalMoveException ( " Target isn't there! " ) ;
2018-03-08 23:38:59 +01:00
}
2018-03-20 11:05:15 +01:00
IWeapon weapon = ( IWeapon ) currentActor . getItem ( IMeleeWeapon . class ) ;
2018-03-15 00:16:26 +01:00
if ( weapon ! = null ) {
NPC . playSound ( weapon . getSound ( ) ) ;
} else {
NPC . playSound ( " audio/Realistic_Punch-Mark_DiAngelo-1609462330.wav " ) ;
}
2018-03-13 21:35:38 +01:00
if ( getAttack ( ) > = getDefence ( target ) ) {
int actualDamage = target . handleDamage ( this , target , getDamage ( target ) ) ;
2018-03-19 19:11:09 +01:00
if ( currentActor ! = null ) {
formatMessage ( " %s hits %s for %d damage " , currentActor . getName ( ) , target . getName ( ) , actualDamage ) ;
}
2018-03-08 23:38:59 +01:00
} else {
2018-03-13 21:35:38 +01:00
formatMessage ( " %s tried to hit %s, but missed " , currentActor . getName ( ) , target . getName ( ) ) ;
2018-03-08 23:38:59 +01:00
}
2018-02-28 23:51:40 +01:00
map . clean ( loc ) ;
2018-03-21 00:10:42 +01:00
if ( target . isDestroyed ( ) & & currentLocation ! = null ) {
2018-02-28 23:51:40 +01:00
return move ( dir ) ;
} else {
movePoints - - ;
return currentLocation ;
}
}
2018-03-13 21:35:38 +01:00
@Override
2018-03-20 11:05:15 +01:00
public ILocation rangedAttack ( GridDirection dir , IItem target , Attack type ) {
2018-03-13 21:35:38 +01:00
ILocation loc = currentLocation ;
2018-03-20 11:05:15 +01:00
IWeapon weapon = null ;
switch ( type ) {
case MAGIC :
weapon = ( IWeapon ) currentActor . getItem ( IMagicWeapon . class ) ;
break ;
case RANGED :
weapon = ( IWeapon ) currentActor . getItem ( IRangedWeapon . class ) ;
}
2018-03-15 00:16:26 +01:00
if ( weapon ! = null ) {
NPC . playSound ( weapon . getSound ( ) ) ;
} else {
NPC . playSound ( " audio/Snow Ball Throw And Splat-SoundBible.com-992042947.wav " ) ;
}
2018-03-13 21:35:38 +01:00
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 ;
}
}
2018-02-28 23:51:40 +01:00
/ * *
* 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 ( ) ;
}
2018-03-19 19:11:09 +01:00
/ * if ( random . nextInt ( 100 ) < 20 ) {
2018-03-07 21:40:06 +01:00
ILocation loc = map . getLocation ( random . nextInt ( map . getWidth ( ) ) , random . nextInt ( map . getHeight ( ) ) ) ;
2018-03-01 21:13:25 +01:00
if ( ! map . hasActors ( loc ) & & ! map . hasItems ( loc ) & & ! map . hasWall ( loc ) ) {
map . add ( loc , new Carrot ( ) ) ;
}
2018-03-19 19:11:09 +01:00
} * / //We don't want this in the actual game.
2018-03-01 21:13:25 +01:00
2018-02-28 23:51:40 +01:00
// process actors one by one; for the IPlayer, we return and wait for keypresses
2018-03-10 15:17:50 +01:00
// Possible for INonPlayer, we could also return early (returning
2018-02-28 23:51:40 +01:00
// *false*), and then insert a little timer delay between each non-player move
// (the timer
// is already set up in Main)
2018-03-21 00:10:42 +01:00
if ( numPlayers = = 0 & & ! won ) {
2018-03-19 19:11:09 +01:00
kill ( ) ;
}
2018-02-28 23:51:40 +01:00
while ( ! actors . isEmpty ( ) ) {
2018-03-20 21:41:18 +01:00
// get the next player or non-player in the queue
currentActor = actors . remove ( 0 ) ;
if ( currentActor = = null ) {
2018-03-21 00:10:42 +01:00
return false ; //TODO: Find out why a girl suddenly becomes null (only after map change, not caught in beginTurn, happens randomly)
2018-03-20 21:41:18 +01:00
}
if ( currentActor . isDestroyed ( ) ) // skip if it's dead
continue ;
2018-02-28 23:51:40 +01:00
currentLocation = map . getLocation ( currentActor ) ;
if ( currentLocation = = null ) {
displayDebug ( " doTurn(): Whoops! Actor has disappeared from the map: " + currentActor ) ;
}
movePoints = 1 ; // everyone gets to do one thing
if ( currentActor instanceof INonPlayer ) {
// computer-controlled players do their stuff right away
( ( INonPlayer ) currentActor ) . doTurn ( this ) ;
// remove any dead items from current location
map . clean ( currentLocation ) ;
2018-03-10 15:17:50 +01:00
return false ;
2018-02-28 23:51:40 +01:00
} else if ( currentActor instanceof IPlayer ) {
2018-03-20 01:10:30 +01:00
if ( ! currentActor . isDestroyed ( ) ) {
2018-02-28 23:51:40 +01:00
// For the human player, we need to wait for input, so we just return.
// Further keypresses will cause keyPressed() to be called, and once the human
// makes a move, it'll lose its movement point and doTurn() will be called again
//
// NOTE: currentActor and currentLocation are set to the IPlayer (above),
// so the game remembers who the player is whenever new keypresses occur. This
// is also how e.g., getLocalItems() work – the game always keeps track of
// whose turn it is.
return true ;
}
} else {
displayDebug ( " doTurn(): Hmm, this is a very strange actor: " + currentActor ) ;
}
}
} while ( numPlayers > 0 ) ; // we can safely repeat if we have players, since we'll return (and break out of
// the loop) once we hit the player
return true ;
}
2018-03-19 19:11:09 +01:00
/ * *
* Player is dead . It needs to be properly killed off .
* /
2018-03-20 21:41:18 +01:00
private void kill ( ) {
2018-03-19 19:11:09 +01:00
map . remove ( currentLocation , currentActor ) ;
currentActor = null ;
currentLocation = null ;
actors = new ArrayList < > ( ) ;
loadMap ( " gameover.txt " ) ;
2018-03-20 01:10:30 +01:00
NPC . playSound ( " audio/Dying-SoundBible.com-1255481835.wav " ) ;
2018-03-19 19:11:09 +01:00
}
2018-03-21 00:10:42 +01:00
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 ) ;
NPC . playSound ( " audio/1_person_cheering-Jett_Rifkin-1851518140.wav " ) ;
won = true ;
}
2018-03-19 19:11:09 +01:00
/ * *
* 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 ) ;
}
2018-03-20 21:41:18 +01:00
IGameMap map = new GameMap ( inputGrid . getArea ( ) ) ;
2018-03-19 19:11:09 +01:00
for ( ILocation loc : inputGrid . locations ( ) ) {
IItem item = createItem ( inputGrid . get ( loc ) ) ;
if ( item ! = null ) {
map . add ( loc , item ) ;
}
}
2018-03-20 21:41:18 +01:00
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 ) ;
}
IGameMap map = new GameMap ( inputGrid . getArea ( ) ) ;
for ( ILocation loc : inputGrid . locations ( ) ) {
IItem item = createItem ( inputGrid . get ( loc ) ) ;
if ( item instanceof Chest ) {
( ( Chest ) item ) . fill ( lvl ) ;
2018-03-21 00:10:42 +01:00
} else if ( item instanceof Girl ) {
( ( Girl ) item ) . giveWeapon ( lvl ) ;
2018-03-20 21:41:18 +01:00
}
if ( item ! = null ) {
map . add ( loc , item ) ;
}
}
maps . add ( map ) ;
2018-03-19 19:11:09 +01:00
}
2018-02-28 23:51:40 +01:00
/ * *
* Go through the map and collect all the actors .
* /
private void beginTurn ( ) {
numPlayers = 0 ;
// this extra fancy iteration over each map location runs *in parallel* on
// multicore systems!
// that makes some things more tricky, hence the "synchronized" block and
// "Collections.synchronizedList()" in the initialization of "actors".
// NOTE: If you want to modify this yourself, it might be a good idea to replace
// "parallelStream()" by "stream()", because weird things can happen when many
// things happen
// at the same time! (or do INF214 or DAT103 to learn about locks and threading)
map . getArea ( ) . parallelStream ( ) . forEach ( ( loc ) - > { // will do this for each location in map
List < IItem > list = map . getAllModifiable ( loc ) ; // all items at loc
Iterator < IItem > li = list . iterator ( ) ; // manual iterator lets us remove() items
while ( li . hasNext ( ) ) { // this is what "for(IItem item : list)" looks like on the inside
IItem item = li . next ( ) ;
if ( item . getCurrentHealth ( ) < 0 ) {
// normally, we expect these things to be removed when they are destroyed, so
// this shouldn't happen
synchronized ( this ) {
formatDebug ( " beginTurn(): found and removed leftover destroyed item %s '%s' at %s%n " ,
item . getName ( ) , item . getSymbol ( ) , loc ) ;
}
li . remove ( ) ;
map . remove ( loc , item ) ; // need to do this too, to update item map
} else if ( item instanceof IPlayer ) {
actors . add ( 0 , ( IActor ) item ) ; // we let the human player go first
synchronized ( this ) {
numPlayers + + ;
}
} else if ( item instanceof IActor ) {
actors . add ( ( IActor ) item ) ; // add other actors to the end of the list
2018-03-01 21:13:25 +01:00
} else if ( item instanceof Carrot ) {
( ( Carrot ) item ) . doTurn ( ) ;
2018-02-28 23:51:40 +01:00
}
}
} ) ;
}
@Override
public boolean canGo ( GridDirection dir ) {
return map . canGo ( currentLocation , dir ) ;
}
2018-03-07 21:40:06 +01:00
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 ) ;
2018-03-09 23:18:00 +01:00
itemFactories . put ( " G " , Girl : : new ) ;
2018-03-07 21:40:06 +01:00
itemFactories . put ( " . " , Dust : : new ) ;
2018-03-12 21:26:36 +01:00
itemFactories . put ( " S " , Sword : : new ) ;
2018-03-14 00:00:52 +01:00
itemFactories . put ( " c " , Chest : : new ) ;
2018-03-15 00:16:26 +01:00
itemFactories . put ( " B " , Boss : : new ) ;
2018-03-19 19:11:09 +01:00
itemFactories . put ( " s " , Staff : : new ) ;
itemFactories . put ( " b " , Bow : : new ) ;
2018-03-20 01:10:30 +01:00
itemFactories . put ( " H " , HealthPotion : : new ) ;
2018-03-20 21:41:18 +01:00
itemFactories . put ( " = " , FakeWall : : new ) ;
itemFactories . put ( " < " , StairsUp : : new ) ;
itemFactories . put ( " > " , StairsDown : : new ) ;
itemFactories . put ( " m " , Binoculars : : new ) ;
itemFactories . put ( " f " , Shield : : new ) ;
2018-03-07 21:40:06 +01:00
}
2018-02-28 23:51:40 +01:00
@Override
public IItem createItem ( String sym ) {
switch ( sym ) {
case " " :
return null ;
default :
// alternative/advanced method
Supplier < IItem > factory = itemFactories . get ( sym ) ;
if ( factory ! = null ) {
return factory . get ( ) ;
} else {
System . err . println ( " createItem: Don't know how to create a ' " + sym + " ' " ) ;
return null ;
}
}
}
@Override
public void displayDebug ( String s ) {
printer . clearLine ( Main . LINE_DEBUG ) ;
printer . printAt ( 1 , Main . LINE_DEBUG , s , Color . DARKRED ) ;
System . err . println ( s ) ;
}
@Override
public void displayMessage ( String s ) {
2018-03-10 15:17:50 +01:00
if ( lastMessages . size ( ) > = 3 ) {
lastMessages . remove ( 2 ) ;
}
lastMessages . add ( 0 , s ) ;
2018-02-28 23:51:40 +01:00
printer . clearLine ( Main . LINE_MSG1 ) ;
2018-03-10 15:17:50 +01:00
printer . clearLine ( Main . LINE_MSG2 ) ;
printer . clearLine ( Main . LINE_MSG3 ) ;
2018-03-19 19:11:09 +01:00
int maxLen = 80 ; //The maximum length of a message to not overflow.
boolean secondLineWritten = false ;
boolean thirdLineWritten = false ;
2018-03-10 15:17:50 +01:00
if ( lastMessages . size ( ) > 0 ) {
2018-03-19 19:11:09 +01:00
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 ) ;
}
2018-03-10 15:17:50 +01:00
}
2018-03-19 19:11:09 +01:00
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 ) ;
}
2018-03-10 15:17:50 +01:00
}
2018-03-19 19:11:09 +01:00
if ( lastMessages . size ( ) > 2 & & ! thirdLineWritten ) {
2018-03-10 15:17:50 +01:00
printer . printAt ( 1 , Main . LINE_MSG3 , lastMessages . get ( 2 ) ) ;
}
2018-02-28 23:51:40 +01:00
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 ( ) {
2018-03-19 19:11:09 +01:00
if ( numPlayers = = 0 ) {
map . draw ( painter , printer ) ;
} else {
( ( GameMap ) map ) . drawVisible ( painter , printer ) ;
}
2018-02-28 23:51:40 +01:00
}
@Override
public boolean drop ( IItem item ) {
if ( item ! = null ) {
map . add ( currentLocation , item ) ;
return true ;
} else
return false ;
}
2018-03-19 19:11:09 +01:00
@Override
public boolean dropAt ( ILocation loc , IItem item ) {
if ( item ! = null ) {
map . add ( loc , item ) ;
return true ;
} else
return false ;
}
2018-02-28 23:51:40 +01:00
@Override
public void formatDebug ( String s , Object . . . args ) {
displayDebug ( String . format ( s , args ) ) ;
}
@Override
public void formatMessage ( String s , Object . . . args ) {
displayMessage ( String . format ( s , args ) ) ;
}
@Override
public void formatStatus ( String s , Object . . . args ) {
displayStatus ( String . format ( s , args ) ) ;
}
@Override
public int getHeight ( ) {
return map . getHeight ( ) ;
}
@Override
public List < IItem > getLocalItems ( ) {
return map . getItems ( currentLocation ) ;
}
@Override
public ILocation getLocation ( ) {
return currentLocation ;
}
@Override
public ILocation getLocation ( GridDirection dir ) {
if ( currentLocation . canGo ( dir ) )
return currentLocation . go ( dir ) ;
else
return null ;
}
/ * *
* Return the game map . { @link IGameMap } gives us a few more details than
* { @link IMapView } ( write access to item lists ) ; the game needs this but
* individual items don ' t .
* /
@Override
public IMapView getMap ( ) {
return map ;
}
@Override
public List < GridDirection > getPossibleMoves ( ) {
2018-03-01 21:13:25 +01:00
List < GridDirection > moves = new ArrayList < > ( ) ;
for ( GridDirection dir : GridDirection . FOUR_DIRECTIONS ) {
if ( canGo ( dir ) ) {
moves . add ( dir ) ;
}
}
return moves ;
2018-02-28 23:51:40 +01:00
}
@Override
public List < ILocation > getVisible ( ) {
2018-03-09 21:29:56 +01:00
List < ILocation > neighbours = map . getNeighbourhood ( currentLocation , currentActor . getVision ( ) ) ;
2018-03-14 14:00:48 +01:00
List < ILocation > invalid = new ArrayList < > ( ) ;
2018-03-09 21:29:56 +01:00
for ( ILocation neighbour : neighbours ) {
for ( ILocation tile : currentLocation . gridLineTo ( neighbour ) ) {
if ( map . hasWall ( tile ) ) {
2018-03-14 14:00:48 +01:00
invalid . add ( neighbour ) ;
2018-03-09 21:29:56 +01:00
break ;
}
}
}
2018-03-14 14:00:48 +01:00
neighbours . removeAll ( invalid ) ;
return neighbours ;
2018-02-28 23:51:40 +01:00
}
@Override
public int getWidth ( ) {
return map . getWidth ( ) ;
}
2018-03-13 21:35:38 +01:00
public boolean keyPressed ( KeyCode code ) {
2018-03-21 00:10:42 +01:00
// only an IPlayer/human can handle keypresses, and only if it's the human's
// turn
return ! ( currentActor instanceof IPlayer ) | | ! ( ( IPlayer ) currentActor ) . keyPressed ( this , code ) ;
}
2018-02-28 23:51:40 +01:00
@Override
public ILocation move ( GridDirection dir ) {
if ( movePoints < 1 )
throw new IllegalMoveException ( " You're out of moves! " ) ;
ILocation newLoc = map . go ( currentLocation , dir ) ;
map . remove ( currentLocation , currentActor ) ;
map . add ( newLoc , currentActor ) ;
currentLocation = newLoc ;
movePoints - - ;
return currentLocation ;
}
@Override
public IItem pickUp ( IItem item ) {
2018-03-15 00:16:26 +01:00
if ( item ! = null & & map . has ( currentLocation , item ) & & ! ( item instanceof IStatic ) ) {
2018-03-10 15:17:50 +01:00
if ( item instanceof IActor ) {
if ( item . getCurrentHealth ( ) / item . getMaxHealth ( ) < 3 ) {
map . remove ( currentLocation , item ) ;
return item ;
} else {
return null ;
}
} else if ( currentActor . getAttack ( ) > item . getDefence ( ) ) {
map . remove ( currentLocation , item ) ;
return item ;
} else {
return null ;
}
2018-02-28 23:51:40 +01:00
} 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 IActor getActor ( ) {
return currentActor ;
}
public ILocation setCurrent ( IActor actor ) {
currentLocation = map . getLocation ( actor ) ;
if ( currentLocation ! = null ) {
currentActor = actor ;
movePoints = 1 ;
}
return currentLocation ;
}
public IActor setCurrent ( ILocation loc ) {
List < IActor > list = map . getActors ( loc ) ;
if ( ! list . isEmpty ( ) ) {
currentActor = list . get ( 0 ) ;
currentLocation = loc ;
movePoints = 1 ;
}
return currentActor ;
}
public IActor setCurrent ( int x , int y ) {
return setCurrent ( map . getLocation ( x , y ) ) ;
}
@Override
public Random getRandom ( ) {
return random ;
}
2018-03-08 23:38:59 +01:00
@Override
2018-03-19 19:11:09 +01:00
public List < GridDirection > locationDirection ( ILocation start , ILocation target ) {
2018-03-08 23:38:59 +01:00
int targetX = target . getX ( ) , targetY = target . getY ( ) ;
int startX = start . getX ( ) , startY = start . getY ( ) ;
2018-03-19 19:11:09 +01:00
List < GridDirection > dirs = new ArrayList < > ( ) ;
2018-03-08 23:38:59 +01:00
if ( targetX > startX & & targetY > startY ) {
if ( Math . abs ( targetX - startX ) < Math . abs ( targetY - startY ) ) {
2018-03-19 19:11:09 +01:00
dirs . add ( GridDirection . SOUTH ) ;
dirs . add ( GridDirection . EAST ) ;
2018-03-08 23:38:59 +01:00
} else {
2018-03-19 19:11:09 +01:00
dirs . add ( GridDirection . EAST ) ;
dirs . add ( GridDirection . SOUTH ) ;
2018-03-08 23:38:59 +01:00
}
} else if ( targetX > startX & & targetY < startY ) {
if ( Math . abs ( targetX - startX ) < Math . abs ( targetY - startY ) ) {
2018-03-19 19:11:09 +01:00
dirs . add ( GridDirection . NORTH ) ;
dirs . add ( GridDirection . EAST ) ;
2018-03-08 23:38:59 +01:00
} else {
2018-03-19 19:11:09 +01:00
dirs . add ( GridDirection . EAST ) ;
dirs . add ( GridDirection . NORTH ) ;
2018-03-08 23:38:59 +01:00
}
} else if ( targetX < startX & & targetY > startY ) {
if ( Math . abs ( targetX - startX ) < Math . abs ( targetY - startY ) ) {
2018-03-19 19:11:09 +01:00
dirs . add ( GridDirection . SOUTH ) ;
dirs . add ( GridDirection . WEST ) ;
2018-03-08 23:38:59 +01:00
} else {
2018-03-19 19:11:09 +01:00
dirs . add ( GridDirection . WEST ) ;
dirs . add ( GridDirection . SOUTH ) ;
2018-03-08 23:38:59 +01:00
}
} else if ( targetX < startX & & targetY < startY ) {
if ( Math . abs ( targetX - startX ) < Math . abs ( targetY - startY ) ) {
2018-03-19 19:11:09 +01:00
dirs . add ( GridDirection . NORTH ) ;
dirs . add ( GridDirection . WEST ) ;
2018-03-08 23:38:59 +01:00
} else {
2018-03-19 19:11:09 +01:00
dirs . add ( GridDirection . WEST ) ;
dirs . add ( GridDirection . NORTH ) ;
2018-03-08 23:38:59 +01:00
}
} else if ( targetX > startX ) {
2018-03-19 19:11:09 +01:00
dirs . add ( GridDirection . EAST ) ;
2018-03-08 23:38:59 +01:00
} else if ( targetX < startX ) {
2018-03-19 19:11:09 +01:00
dirs . add ( GridDirection . WEST ) ;
2018-03-08 23:38:59 +01:00
} else if ( targetY > startY ) {
2018-03-19 19:11:09 +01:00
dirs . add ( GridDirection . SOUTH ) ;
2018-03-08 23:38:59 +01:00
} else if ( targetY < startY ) {
2018-03-19 19:11:09 +01:00
dirs . add ( GridDirection . NORTH ) ;
2018-03-08 23:38:59 +01:00
}
2018-03-19 19:11:09 +01:00
return dirs ;
2018-03-08 23:38:59 +01:00
}
2018-02-28 23:51:40 +01:00
}