/**
*
*/
package cz.cuni.mff.abacs.burglar.visual.play_state;
import cz.cuni.mff.abacs.burglar.logics.DataMap;
import cz.cuni.mff.abacs.burglar.logics.ExecutingMap;
import cz.cuni.mff.abacs.burglar.logics.GameMap;
import cz.cuni.mff.abacs.burglar.logics.Player;
import cz.cuni.mff.abacs.burglar.logics.objects.BaseInterface;
import cz.cuni.mff.abacs.burglar.logics.objects.BaseObject;
import cz.cuni.mff.abacs.burglar.logics.objects.agents.Agent;
import cz.cuni.mff.abacs.burglar.logics.objects.agents.Burglar;
import cz.cuni.mff.abacs.burglar.logics.objects.agents.Guard;
import cz.cuni.mff.abacs.burglar.logics.objects.positions.Active;
import cz.cuni.mff.abacs.burglar.logics.objects.positions.Lockable;
import cz.cuni.mff.abacs.burglar.logics.objects.positions.Vender;
import cz.cuni.mff.abacs.burglar.logics.planning.instructions.Instruction;
import cz.cuni.mff.abacs.burglar.visual.VisualBurglar;
import cz.cuni.mff.abacs.burglar.visual.multithreading.LoadingListener;
import cz.cuni.mff.abacs.burglar.visual.multithreading.LoadingThread;
import cz.cuni.mff.abacs.burglar.visual.multithreading.PlanningListener;
import cz.cuni.mff.abacs.burglar.visual.multithreading.PlanningThread;
import cz.cuni.mff.abacs.burglar.visual.multithreading.PlanningThread.Type;
import java.util.List;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.state.StateBasedGame;
/**
* Base of the game play implementation. It contains the game logics.
*
* @author abacs
*
*/
public abstract class PlayState
extends BasicGameState
implements PlanningListener, LoadingListener {
// constants:
/** Time spent in milliseconds between two movement steps. */
protected static int DEFAULT_MOVEMENT_DELAY = 600;
/** Time change in milliseconds when game speed is lowered or increased. */
protected static int GAME_SPEED_DELTA = 200;
protected static final String DEFAULT_MAP = null;
/** */
public static enum MapState {
UNSET,
UNLOADED,
SELECING_TRAP_ROOMS,
PLANNING,
PLANNED,
UNSOLVABLE,
SOLVED,
LOST,
};
/** */
public static enum RuningState {
PAUSED,
MOVING,
}
// -------------------------------------------------------------------------
// properties:
// game engine properties:
/** Id of the current state. */
private final int _stateID;
/** Pointer to the main game container. */
protected GameContainer _container = null;
/** Pointer to the whole program. */
protected StateBasedGame _stateBasedGame = null;
// game states:
/** */
protected MapState _mapState = MapState.UNSET;
/** */
protected RuningState _runingState = RuningState.PAUSED;
/** Name of the map file. */
protected String _mapName = null;
/** The loaded map file. */
protected GameMap _map = null;
/** Secondary map file.
* (currently not in use) */
protected GameMap _secondaryMap = null;
/** Implements the player interaction with the map. */
protected Player _player;
/** The thread executing the planning. */
protected PlanningThread _planningThread = null;
/** Selection rectangle on the game map. */
protected Selection _selection = null;
/** Screen representation on the game map. */
protected Screen _screen = null;
/** Milliseconds to wait between two game moves. */
protected int _movementDelay = DEFAULT_MOVEMENT_DELAY;
/** Milliseconds to wait until the next game move. */
protected int _msToWait = 0;
/** */
public int _trapsAvioded = 0;
// -------------------------------------------------------------------------
// constructor:
/**
*
* @param stateID
*/
protected PlayState(int stateID) throws SlickException {
this._stateID = stateID;
}
// -------------------------------------------------------------------------
@Override
public int getID() {
return this._stateID;
}
/**
* Initialize any needed data before the game loop.
* Warning: it may be called multiple times:
*
* @param container
* @param game
* */
@Override
public void init(GameContainer container, StateBasedGame game)
throws SlickException {
this._container = container;
this._stateBasedGame = game;
// view settings:
this._selection = new Selection(this);
this._screen = new Screen(
this,
container.getWidth() / VisualBurglar.RESOURCE_BLOCK_SIZE,
container.getHeight() / VisualBurglar.RESOURCE_BLOCK_SIZE
);
}
/**
* Resets the map if the state is lost.
*/
@Override
public void leave(GameContainer container, StateBasedGame game) throws SlickException {
super.leave(container, game);
if(this._mapState == MapState.LOST || this._mapState == MapState.SOLVED)
this.reload();
this._movementDelay = DEFAULT_MOVEMENT_DELAY;
}
/**
* Called during the game to update the logic in our world,
* within this method we obtain the user input,
* calculate the world response to the input, do the calculation.
*
* @param container
* @param game
* @param delta
* */
@Override
public void update(GameContainer container, StateBasedGame game, int delta)
throws SlickException {
this._msToWait -= delta;
// before and after the gameplay do nothing:
if(this._mapState != MapState.PLANNED)
return;
if(this._map.needsReplanning()){
this._planningThread =
new PlanningThread(
this._map,
this,
this._map.getAgentsToReplan()
);
this._planningThread.start();
this._map.clearAgentsToReplan();
this._mapState = MapState.PLANNING;
}
// if the game is not paused - redraw:
if(
this._runingState == RuningState.MOVING &&
this._msToWait <= 0
){
// reset delay:
this._msToWait = this._movementDelay;
// controls:
if(this._map.getBurglar().isSatisfied()){
this._mapState = MapState.SOLVED;
System.out.println("- Solved");
return;
}
if(this._map.isBurglarTrapped()){
this._mapState = MapState.LOST;
System.out.println("- Lost");
return;
}
// actions:
this._map.executeStep();
this._map.lookAround();
}
}
// -------------------------------------------------------------------------
/**
* shortcuts:
* - directions: moves the selection-box
* - exit: quits the program
* - space bar: pauses the game
* - + increase game speed
* - - decrease game speed
*
* @param key
* @param c
*/
@Override
public void keyReleased(int key, char c) {
//
switch(key){
case Input.KEY_UP:
if(VisualBurglar.FLAG_PLAYER_CONTROL_MODE){
if(this._player.moveBurglar(GameMap.Direction.NORTH))
this._screen.moveUp();
break;
}
this._screen.moveUp();
break;
case Input.KEY_DOWN:
if(VisualBurglar.FLAG_PLAYER_CONTROL_MODE){
if(this._player.moveBurglar(GameMap.Direction.SOUTH))
this._screen.moveDown();
break;
}
this._screen.moveDown();
break;
case Input.KEY_LEFT:
if(VisualBurglar.FLAG_PLAYER_CONTROL_MODE){
if(this._player.moveBurglar(GameMap.Direction.WEST))
this._screen.moveLeft();
break;
}
this._screen.moveLeft();
break;
case Input.KEY_RIGHT:
if(VisualBurglar.FLAG_PLAYER_CONTROL_MODE){
if(this._player.moveBurglar(GameMap.Direction.EAST))
this._screen.moveRight();
break;
}
this._screen.moveRight();
break;
case Input.KEY_SPACE:
this.pauseUnpause();
break;
case Input.KEY_ADD:
this.increaseSpeed();
break;
case Input.KEY_SUBTRACT:
this.decreaseSpeed();
break;
case Input.KEY_ESCAPE:
this._container.exit();
break;
default:
}
}
@Override
public void mouseMoved(int oldX, int oldY, int newX, int newY){
super.mouseMoved(oldX, oldY, newX, newY);
int mapX = this._screen.screenXToMapX(newX);
int mapY = this._screen.screenYToMapY(newY);
this._selection.set(mapX, mapY);
}
@Override
public void mouseClicked(int button, int x, int y, int clickCount) {
}
// -------------------------------------------------------------------------
/**
* Pause or continue the plan execution.
*/
public void pauseUnpause() {
switch(this._runingState){
case PAUSED:
this._runingState = RuningState.MOVING;
break;
case MOVING:
this._runingState = RuningState.PAUSED;
break;
default:
break;
}
}
/**
* Increase the speed of the plan execution.
*
* @return success indicator
*/
public boolean increaseSpeed() {
if(this._movementDelay > 2 * GAME_SPEED_DELTA){
this._movementDelay -= GAME_SPEED_DELTA;
return true;
}else{
this._movementDelay = GAME_SPEED_DELTA;
}
return false;
}
/**
* Decrease the speed of the plan execution.
*
* @return success indicator
*/
public boolean decreaseSpeed() {
this._movementDelay += GAME_SPEED_DELTA;
return true;
}
/**
* Reloads the current map.
*/
public void reload() {
this._secondaryMap = null;
this._planningThread = null;
this._trapsAvioded = 0;
this._screen.resetPosition();
// load the map:
this._mapState = MapState.UNLOADED;
this._map = null;
this._secondaryMap = null;
LoadingThread loader = new LoadingThread(this._mapName, this);
loader.start();
}
/**
* Loads a new map.
*/
public void load(String mapName) {
if(this._mapName != null && this._mapName.equals(mapName))
return;
this._mapName = mapName;
this.reload();
}
/**
* Open any Lockable object in selection.
*/
public void closeOpenSelection() {
List<BaseObject> objects =
this._map.getObjectsOnPosition(
this._selection.getX(),
this._selection.getY()
);
for(BaseObject object : objects){
switch(object.getType()){
case DOOR:
case CONTAINER:
this._player.openClosePosition((Lockable)object);
break;
}
}
if(VisualBurglar.FLAG_PLAYER_CONTROL_MODE)
this._map.getBurglar().lookAround();
}
/**
* Lock any lockable object in selection.
*/
public void lockUnlockSelection() {
List<BaseObject> objects =
this._map.getObjectsOnPosition(
this._selection.getX(),
this._selection.getY()
);
for(BaseObject object : objects){
switch(object.getType()){
case DOOR:
case CONTAINER:
this._player.lockUnlockPosition((Lockable)object);
break;
}
}
}
/**
* Activates or deactivates any Active position in selection.
*/
public void activateDeactivateSelection() {
List<BaseObject> objects =
this._map.getObjectsOnPosition(
this._selection.getX(),
this._selection.getY()
);
for(BaseObject object : objects){
switch(object.getType()){
case CAMERA:
case SWITCH:
case PHONE:
this._player.activateDeactivatePosition((Active)object);
break;
case VENDER:
this._player.activateVender((Vender)object);
break;
}
}
}
/**
* Dazes any Guard in selection.
*/
public void dazeSelected() {
int mapX = this._selection.getX();
int mapY = this._selection.getY();
for(BaseObject object : this._map.getObjectsOnPosition(mapX, mapY)){
if(object.isTypeOf(BaseInterface.Type.GUARD))
this._player.dazeAgent((Guard)object);
}
}
// -------------------------------------------------------------------------
/**
* Not in use
*/
protected void synchronizeSecondaryMap() {
// TODO
}
// -------------------------------------------------------------------------
// callbacks:
@Override
public void planningFinished(
List<Instruction> resultedInstructions,
PlanningThread thread
) {
// do nothing, if not the most recent planning
if(this._planningThread != thread)
return;
this._map.addInstructions(resultedInstructions);
}
@Override
public void planningFinished(
List<Instruction> resultedInstructions,
List<Integer> avoidedTrapRooms,
PlanningThread thread
) {
// do nothing, if not the most recent planning
if(this._planningThread != thread)
return;
this._map.addInstructions(resultedInstructions);
this._trapsAvioded = avoidedTrapRooms.size();
}
@Override
public void planningFinished(
PlanningThread thread
) {
this._mapState = MapState.PLANNED;
Burglar burglar = this._map.getBurglar();
if(
burglar.hasInstructions() == false &&
burglar.isSatisfied() == false
){
this._mapState = MapState.LOST;
System.out.println("- Lost");
}
}
@Override
public void loadingFinished(ExecutingMap map, ExecutingMap secondaryMap) {
this._map = map;
this._secondaryMap = secondaryMap;
if(VisualBurglar.FLAG_IN_GAME_LEVEL_DESIGN){
// start placing guards on the map:
this._mapState = MapState.SELECING_TRAP_ROOMS;
this._planningThread =
new PlanningThread(
this._map,
Type.SELECTING_TRAP_ROOMS,
this
);
this._planningThread.start();
}else{
for(Agent agent : this._map.getAgents())
this._map.addAgentToReplan(agent);
this._mapState = MapState.PLANNED;
}
}
@Override
public void selectingTrapRoomsFinished(
List<Integer> trapRooms,
PlanningThread thread
) {
if(thread != this._planningThread)
return;
DataMap map = (DataMap)this._map;
List<List<Integer>> components = map.breakToComponents(trapRooms);
for(List<Integer> component : components){
if(component.size() > 1){
((ExecutingMap)map).addGuardPatrol(component);
}else{
map.addCameraToRoom(component.get(0));
}
}
for(Agent agent : this._map.getAgents())
this._map.addAgentToReplan(agent);
this._mapState = MapState.PLANNED;
}
// =========================================================================
// subclasses:
/**
* Representation of the mouse cursor's position on the screen.
*/
protected class Selection {
// properties:
/** selection's horizontal position on the game map */
protected int _x = 0;
/** selection's vertical position on the game map */
protected int _y = 0;
/** */
protected PlayState _parent = null;
// ---------------------------------------------------------------------
// constructors:
/**
*
*
* @param parent
*/
protected Selection(PlayState parent) {
this._parent = parent;
}
// ---------------------------------------------------------------------
/**
* Allows the selection to move to the left on the map.
*
* If the selection reaches the left edge of the map, does nothing.
*
* @return success indicator
*/
public boolean moveLeft() {
if(this._parent._map == null)
return false;
if(this._x > 1){
this._x--;
return true;
}
return false;
}
/**
* Allows the selection to move to the right left the map.
*
* If the selection reaches the right edge of the map, does nothing.
*
* @return success indicator
*/
public boolean moveRight() {
if(this._parent._map == null)
return false;
if(this._x < this._parent._map.getWidth() + 1){
this._x++;
return true;
}
return false;
}
/**
* Allows the selection to move up to the top of the map.
*
* If the selection reaches the top of the map, does nothing.
*
* @return success indicator
*/
public boolean moveUp() {
if(this._parent._map == null)
return false;
if(this._y > 1){
this._y--;
return true;
}
return false;
}
/**
* Allows the selection to move down to the bottom of the map.
*
* If the selection reaches the bottom of the map, does nothing.
*
* @return success indicator
*/
public boolean moveDown() {
if(this._parent._map == null)
return false;
if(this._y < this._parent._map.getHeight() + 1){
this._y++;
return true;
}
return false;
}
/**
* Sets the position of the selection on the map.
*
* @param val x coordinate on the map.
* @return success indicator
*/
public boolean setX(int val) {
if(this._parent._map == null)
return false;
if(val > 0 && val < this._parent._map.getWidth() + 1){
this._x = val;
return true;
}
return false;
}
/**
* Sets the position of the selection on the map.
*
* @param val y coordinate on the map.
* @return success indicator
*/
public boolean setY(int val) {
if(this._parent._map == null)
return false;
if(val > 0 && val < this._parent._map.getHeight() + 1){
this._y = val;
return true;
}
return false;
}
/**
* Sets the position of the selection on the map.
*
* @param x x coordinate on the map.
* @param y y coordinate on the map.
* @return success indicator
*/
public boolean set(int x, int y) {
if(this._parent._map == null)
return false;
if(x > 0 && x < this._parent._map.getWidth() + 1 &&
y > 0 && y < this._parent._map.getHeight() + 1){
this._x = x;
this._y = y;
return true;
}
return false;
}
// ---------------------------------------------------------------------
/** selection's horizontal position on the game map */
public int getX() {
return this._x;
}
/** selection's vertical position on the game map */
public int getY() {
return this._y;
}
}
/**
* Representation of the visible part of the GameMap.
*/
protected class Screen {
// properties:
/** Left screen corner on the game map. */
protected int _x = 0;
/** Top screen corner on the game map. */
protected int _y = 0;
/** Screen width in game map positions. */
protected int _width = 0;
/** Screen height in game map positions. */
protected int _height = 0;
/** Parent object holding the map. */
protected PlayState _parent = null;
// ---------------------------------------------------------------------
// constructors:
/**
*
*
* @param parent
* @param width
* @param height
*/
protected Screen(PlayState parent, int width, int height) {
this._parent = parent;
this._width = width;
this._height = height;
}
// ---------------------------------------------------------------------
/**
* Resets the screen position to the upper left corner of the map.
*/
public void resetPosition() {
this._x = 0;
this._y = 0;
}
/**
* Allows to move left to show the most right and one unused
* wall position.
*
* If the screen reaches the left border of the map, does nothing.
*
* @return success indicator
*/
public boolean moveLeft() {
if(this._x > 0){
this._x--;
return true;
}
return false;
}
/**
* Allows to move right to show the most right and one unused
* wall position.
*
* If the screen reaches the right border of the map, does nothing.
*
* @return success indicator
*/
public boolean moveRight() {
if(this._x + this._width < this._parent._map.getWidth() + 2){
this._x++;
return true;
}
return false;
}
/**
* Allows to move up to show the highest and one unused wall position.
*
* If the screen reaches the top of the map, does nothing.
*
* @return success indicator
*/
public boolean moveUp() {
if(this._y > 0){
this._y--;
return true;
}
return false;
}
/**
* Allows to move down to show the lowest and one unused wall position.
*
* If the screen reaches the bottom of the map, does nothing.
*
* @return success indicator
*/
public boolean moveDown() {
if(this._y + this._height < this._parent._map.getHeight() + 2){
this._y++;
return true;
}
return false;
}
/**
* Sets the width of the game map.
*
* If the value is invalid (less than 1), does nothing, prints to error
* output.
*
* @param value new width.
*/
public void setWidth(int value) {
if(value < 1){
System.err.println(value + " is not a valid game map width.");
return;
}
this._width = value;
}
/**
* Sets the height of the game map.
*
* If the value is invalid (less than 1), does nothing, prints to error
* output.
*
* @param value new height.
*/
public void setHeight(int value) {
if(value < 1){
System.err.println(value + " is not a valid game map height.");
return;
}
this._height = value;
}
// ---------------------------------------------------------------------
/** Screen's left corner on the game map. */
public int getX() {
return this._x;
}
/** Screen's top corner on the game map. */
public int getY() {
return this._y;
}
/** Screen's width in game map positions. */
public int getWidth() {
return this._width;
}
/** Screen's height in game map positions. */
public int getHeight() {
return this._height;
}
// ---------------------------------------------------------------------
/**
* Converts the screen position to the map position.
*
* @param screenX x coordinate on the screen
* @return x coordinate on the map
*/
public int screenXToMapX(int screenX) {
return screenX / VisualBurglar.RESOURCE_BLOCK_SIZE + this._x;
}
/**
* Converts the screen position to the map position.
*
* @param screenX x coordinate on the screen
* @return x coordinate on the map
*/
public int screenXToMapX(float screenX) {
return this.screenXToMapX((int)screenX);
}
/**
* Converts the screen position to the map position.
*
* @param screenY y coordinate on the screen
* @return y coordinate on the map
*/
public int screenYToMapY(int screenY) {
return screenY / VisualBurglar.RESOURCE_BLOCK_SIZE + this._y;
}
/**
* Converts the screen position to the map position.
*
* @param screenY y coordinate on the screen
* @return y coordinate on the map
*/
public int screenYToMapY(float screenY) {
return this.screenYToMapY((int)screenY);
}
/**
* Converts the map position to the screen position.
*
* @param mapX x coordinate on the map
* @return x coordinate on the screen
*/
public int mapXToScreenX(int mapX) {
return (mapX - this._x) * VisualBurglar.RESOURCE_BLOCK_SIZE;
}
/**
* Converts the map position to the screen position.
*
* @param mapY y coordinate on the map
* @return y coordinate on the screen
*/
public int mapYToScreenY(int mapY) {
return (mapY - this._y) * VisualBurglar.RESOURCE_BLOCK_SIZE;
}
}
}