AI optimization
Signed-off-by: Chris Cromer <chris@cromer.cl>
This commit is contained in:
parent
b718d7b40a
commit
e6624b527d
@ -38,6 +38,10 @@ public interface Constantes {
|
||||
* Whether or not the player should be controlled by AI
|
||||
*/
|
||||
boolean PLAYER_AI = true;
|
||||
/**
|
||||
* Move the player to the portal if attacked
|
||||
*/
|
||||
boolean TRANSPORT_PLAYER_ON_ATTACK = false;
|
||||
/**
|
||||
* Use a global log if true or individual logs if false
|
||||
*/
|
||||
@ -69,7 +73,7 @@ public interface Constantes {
|
||||
/**
|
||||
* The amount of chests to draw
|
||||
*/
|
||||
int CHESTS = 2;
|
||||
int CHESTS = 5;
|
||||
/**
|
||||
* The amount of enemies to draw
|
||||
*/
|
||||
@ -207,7 +211,7 @@ public interface Constantes {
|
||||
/**
|
||||
* The global log level is used if the individual log levels are not
|
||||
*/
|
||||
GLOBAL(Level.ALL),
|
||||
GLOBAL(Level.SEVERE),
|
||||
/**
|
||||
* The main log level
|
||||
*/
|
||||
|
@ -64,9 +64,9 @@ public class Escenario extends JComponent implements Constantes {
|
||||
*/
|
||||
private Sheet textureSheet;
|
||||
/**
|
||||
* Whether or not the door is closed yet
|
||||
* Whether or not the door is open
|
||||
*/
|
||||
private boolean doorClosed = false;
|
||||
private boolean doorOpen = true;
|
||||
|
||||
/**
|
||||
* Initialize the scene
|
||||
@ -181,7 +181,7 @@ public class Escenario extends JComponent implements Constantes {
|
||||
}
|
||||
}
|
||||
|
||||
final Lock lock = new ReentrantLock(false);
|
||||
final Lock lock = new ReentrantLock(true);
|
||||
for (int i = 0; i < ENEMIES; i++) {
|
||||
random = randomCoordinates();
|
||||
celdas[random[0]][random[1]].setObject(new Enemy(this, celdas[random[0]][random[1]], lock));
|
||||
@ -470,21 +470,21 @@ public class Escenario extends JComponent implements Constantes {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if door is closed or not
|
||||
* Check if door is open
|
||||
*
|
||||
* @return Returns true if closed or false if open
|
||||
* @return Returns true if open or false if closed
|
||||
*/
|
||||
public boolean isDoorClosed() {
|
||||
return doorClosed;
|
||||
public boolean isDoorOpen() {
|
||||
return doorOpen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the state of the door
|
||||
*
|
||||
* @param doorClosed Set to true to the close the door or false to open it
|
||||
* @param doorOpen Set to true to open the door or false to close it
|
||||
*/
|
||||
public void setDoorClosed(boolean doorClosed) {
|
||||
if (doorClosed && !isDoorClosed()) {
|
||||
public void openDoor(boolean doorOpen) {
|
||||
if (!doorOpen && isDoorOpen()) {
|
||||
celdas[2][0].setObject(new Obstacle(this, celdas[2][0]));
|
||||
try {
|
||||
celdas[2][0].addTexture(textureSheet.getTexture(193), 193);
|
||||
@ -492,12 +492,12 @@ public class Escenario extends JComponent implements Constantes {
|
||||
catch (SheetException e) {
|
||||
logger.warning(e.getMessage());
|
||||
}
|
||||
this.doorClosed = true;
|
||||
this.doorOpen = false;
|
||||
}
|
||||
else if (!doorClosed && isDoorClosed()) {
|
||||
else if (doorOpen && !isDoorOpen()) {
|
||||
celdas[2][0].removeTexture(193);
|
||||
celdas[2][0].setObject(null);
|
||||
this.doorClosed = false;
|
||||
this.doorOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
package cl.cromer.azaraka;
|
||||
|
||||
import cl.cromer.azaraka.ai.AI;
|
||||
import cl.cromer.azaraka.ai.State;
|
||||
import cl.cromer.azaraka.object.Object;
|
||||
import cl.cromer.azaraka.object.*;
|
||||
import cl.cromer.azaraka.sound.Sound;
|
||||
@ -28,9 +29,7 @@ import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
@ -209,7 +208,7 @@ public class Lienzo extends Canvas implements Constantes {
|
||||
}
|
||||
|
||||
if (PLAYER_AI) {
|
||||
playerAiLauncher();
|
||||
setupPlayerAI();
|
||||
}
|
||||
else {
|
||||
addKeyListener(new KeyAdapter() {
|
||||
@ -226,67 +225,27 @@ public class Lienzo extends Canvas implements Constantes {
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch a new player AI task
|
||||
* Set up the player AI
|
||||
*/
|
||||
public void playerAiLauncher() {
|
||||
player.resetAi();
|
||||
Thread aiThread = aiThreads.get(player.getAi());
|
||||
if (aiThread != null) {
|
||||
if (aiThread.isAlive()) {
|
||||
aiThread.interrupt();
|
||||
}
|
||||
try {
|
||||
aiThread.join();
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
logger.info(e.getMessage());
|
||||
}
|
||||
}
|
||||
if (!player.hasKey()) {
|
||||
for (Key key : keys) {
|
||||
if (key.getState() == Key.State.UNUSED) {
|
||||
player.getAi().search(player.getCelda().getX(), player.getCelda().getY(), key.getCelda().getX(), key.getCelda().getY());
|
||||
player.getAi().calculateRoute();
|
||||
Thread thread = new Thread(player.getAi());
|
||||
thread.start();
|
||||
aiThreads.put(player.getAi(), thread);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
public void setupPlayerAI() {
|
||||
player.getAi().addDestination(new State(2, 0, State.Type.EXIT, null));
|
||||
|
||||
if (portal.getState() == Portal.State.ACTIVE) {
|
||||
player.getAi().search(player.getCelda().getX(), player.getCelda().getY(), portal.getCelda().getX(), portal.getCelda().getY());
|
||||
player.getAi().calculateRoute();
|
||||
Thread thread = new Thread(player.getAi());
|
||||
thread.start();
|
||||
aiThreads.put(player.getAi(), thread);
|
||||
return;
|
||||
}
|
||||
//player.getAi().addDestination(new State(portal.getCelda().getX(), portal.getCelda().getY(), State.Type.PORTAL, null));
|
||||
|
||||
// Shuffle the chests so that the AI doesn't open the correct chests on the first go
|
||||
Collections.shuffle(chests, new Random(23));
|
||||
for (Chest chest : chests) {
|
||||
if (chest.getState() == Chest.State.CLOSED) {
|
||||
player.getAi().search(player.getCelda().getX(), player.getCelda().getY(), chest.getCelda().getX(), chest.getCelda().getY() + 1);
|
||||
player.getAi().calculateRoute();
|
||||
player.getAi().setInteract(true);
|
||||
Thread thread = new Thread(player.getAi());
|
||||
thread.start();
|
||||
aiThreads.put(player.getAi(), thread);
|
||||
return;
|
||||
}
|
||||
player.getAi().addDestination(new State(chest.getCelda().getX(), chest.getCelda().getY() + 1, State.Type.CHEST, null));
|
||||
}
|
||||
|
||||
if (!escenario.isDoorClosed()) {
|
||||
if (player.getCelda().getX() == 2 && player.getCelda().getY() == 0) {
|
||||
player.keyPressed(KeyEvent.VK_UP);
|
||||
for (Key key : keys) {
|
||||
player.getAi().addDestination(new State(key.getCelda().getX(), key.getCelda().getY(), State.Type.KEY, null));
|
||||
}
|
||||
player.getAi().search(player.getCelda().getX(), player.getCelda().getY(), 2, 0);
|
||||
player.getAi().calculateRoute();
|
||||
|
||||
Thread thread = new Thread(player.getAi());
|
||||
thread.start();
|
||||
aiThreads.put(player.getAi(), thread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the paint method of Canvas to paint all the scene components
|
||||
@ -462,6 +421,7 @@ public class Lienzo extends Canvas implements Constantes {
|
||||
* Called when the game is won
|
||||
*/
|
||||
public void win() {
|
||||
stopThreads();
|
||||
stopBackgroundMusic();
|
||||
|
||||
try {
|
||||
@ -472,7 +432,6 @@ public class Lienzo extends Canvas implements Constantes {
|
||||
logger.warning(e.getMessage());
|
||||
}
|
||||
|
||||
stopThreads();
|
||||
JOptionPane.showMessageDialog(null, "Ganaste!");
|
||||
System.exit(0);
|
||||
}
|
||||
|
@ -15,15 +15,45 @@
|
||||
|
||||
package cl.cromer.azaraka.ai;
|
||||
|
||||
import cl.cromer.azaraka.Escenario;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* AI algorithms extends this class
|
||||
*/
|
||||
public class AI implements Runnable {
|
||||
/**
|
||||
* The scene of the game
|
||||
*/
|
||||
private final Escenario escenario;
|
||||
/**
|
||||
* The logger
|
||||
*/
|
||||
private Logger logger;
|
||||
/**
|
||||
* Whether or not the run loop of the AI is active
|
||||
*/
|
||||
private boolean active;
|
||||
|
||||
/**
|
||||
* Initialize the AI
|
||||
*
|
||||
* @param escenario The scene of the game
|
||||
*/
|
||||
protected AI(Escenario escenario) {
|
||||
this.escenario = escenario;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get teh scene
|
||||
*
|
||||
* @return Returns the scene
|
||||
*/
|
||||
protected Escenario getEscenario() {
|
||||
return escenario;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the active state of the AI
|
||||
*
|
||||
@ -42,6 +72,24 @@ public class AI implements Runnable {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the logger
|
||||
*
|
||||
* @return Returns a logger
|
||||
*/
|
||||
protected Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the logger
|
||||
*
|
||||
* @param logger The logger to set
|
||||
*/
|
||||
protected void setLogger(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* The run method
|
||||
*/
|
||||
|
30
src/main/java/cl/cromer/azaraka/ai/AIException.java
Normal file
30
src/main/java/cl/cromer/azaraka/ai/AIException.java
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2019 Chris Cromer
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
package cl.cromer.azaraka.ai;
|
||||
|
||||
/**
|
||||
* This class handles exceptions thrown by the AI
|
||||
*/
|
||||
public class AIException extends Exception {
|
||||
/**
|
||||
* Initialize the AI exception
|
||||
*
|
||||
* @param errorMessage The message thrown
|
||||
*/
|
||||
public AIException(String errorMessage) {
|
||||
super(errorMessage);
|
||||
}
|
||||
}
|
@ -18,22 +18,12 @@ package cl.cromer.azaraka.ai;
|
||||
import cl.cromer.azaraka.Constantes;
|
||||
import cl.cromer.azaraka.Escenario;
|
||||
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* This is an implementation of the Breadth-First search algorithm
|
||||
* This is an implementation of the Breadth-First search algorithm with multiple objectives
|
||||
*/
|
||||
public class BreadthFirstSearch extends AI implements Constantes {
|
||||
/**
|
||||
* The logger
|
||||
*/
|
||||
private final Logger logger;
|
||||
/**
|
||||
* The scene the AI needs to search
|
||||
*/
|
||||
private final Escenario escenario;
|
||||
/**
|
||||
* The queued states to check
|
||||
*/
|
||||
@ -45,52 +35,47 @@ public class BreadthFirstSearch extends AI implements Constantes {
|
||||
/**
|
||||
* The steps to get to the objective
|
||||
*/
|
||||
private final ArrayList<State.Direction> steps = new ArrayList<>();
|
||||
private final ArrayList<State.Type> steps = new ArrayList<>();
|
||||
/**
|
||||
* Which step the object is on
|
||||
* The destinations the player should visit
|
||||
*/
|
||||
private int stepIndex = 0;
|
||||
private final ArrayList<State> destinations = new ArrayList<>();
|
||||
/**
|
||||
* The state of the objective
|
||||
* The state of the search objective
|
||||
*/
|
||||
private State objective;
|
||||
private State searchObjective;
|
||||
/**
|
||||
* If the search was successful or not
|
||||
*/
|
||||
private boolean success = false;
|
||||
/**
|
||||
* Interact with object once the objective is reached
|
||||
* The subInitial point to start searching from
|
||||
*/
|
||||
private boolean interact = false;
|
||||
private State initial;
|
||||
|
||||
/**
|
||||
* Initialize the algorithm
|
||||
*
|
||||
* @param escenario The scene the AI is in
|
||||
*/
|
||||
public BreadthFirstSearch(Escenario escenario) {
|
||||
this.escenario = escenario;
|
||||
logger = getLogger(this.getClass(), LogLevel.AI);
|
||||
protected BreadthFirstSearch(Escenario escenario) {
|
||||
super(escenario);
|
||||
setLogger(getLogger(this.getClass(), LogLevel.AI));
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for a path that gets from the start point to the end point
|
||||
* Find a path to the objective
|
||||
*
|
||||
* @param startX The start x coordinate
|
||||
* @param startY The start y coordinate
|
||||
* @param endX The end x coordinate
|
||||
* @param endY The end y coordinate
|
||||
* @param searchInitial The start point
|
||||
* @param searchObjective The objective
|
||||
* @return Returns true if a path was found or false otherwise
|
||||
*/
|
||||
public void search(int startX, int startY, int endX, int endY) {
|
||||
State initial = new State(startX, startY, State.Direction.START, null);
|
||||
objective = new State(endX, endY, State.Direction.END, null);
|
||||
private boolean search(State searchInitial, State searchObjective) {
|
||||
queuedStates.add(searchInitial);
|
||||
history.add(searchInitial);
|
||||
this.searchObjective = searchObjective;
|
||||
|
||||
queuedStates.add(initial);
|
||||
history.add(initial);
|
||||
|
||||
if (initial.equals(objective)) {
|
||||
success = true;
|
||||
}
|
||||
success = searchInitial.equals(searchObjective);
|
||||
|
||||
while (!queuedStates.isEmpty() && !success) {
|
||||
State temp = queuedStates.get(0);
|
||||
@ -103,10 +88,13 @@ public class BreadthFirstSearch extends AI implements Constantes {
|
||||
}
|
||||
|
||||
if (success) {
|
||||
logger.info("Route to objective found!");
|
||||
getLogger().info("Route to objective found!");
|
||||
calculateRoute();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
logger.info("Route to objective not possible!");
|
||||
getLogger().info("Route to objective not found!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,14 +105,14 @@ public class BreadthFirstSearch extends AI implements Constantes {
|
||||
*/
|
||||
private void moveUp(State state) {
|
||||
if (state.getY() > 0) {
|
||||
if (escenario.getCeldas()[state.getX()][state.getY() - 1].getObject() == null) {
|
||||
State up = new State(state.getX(), state.getY() - 1, State.Direction.UP, state);
|
||||
if (getEscenario().getCeldas()[state.getX()][state.getY() - 1].getObject() == null) {
|
||||
State up = new State(state.getX(), state.getY() - 1, State.Type.UP, state);
|
||||
if (!history.contains(up)) {
|
||||
queuedStates.add(up);
|
||||
history.add(up);
|
||||
|
||||
if (up.equals(objective)) {
|
||||
objective = up;
|
||||
if (up.equals(searchObjective)) {
|
||||
searchObjective = up;
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
@ -139,14 +127,14 @@ public class BreadthFirstSearch extends AI implements Constantes {
|
||||
*/
|
||||
private void moveDown(State state) {
|
||||
if (state.getY() < VERTICAL_CELLS - 1) {
|
||||
if (escenario.getCeldas()[state.getX()][state.getY() + 1].getObject() == null) {
|
||||
State down = new State(state.getX(), state.getY() + 1, State.Direction.DOWN, state);
|
||||
if (getEscenario().getCeldas()[state.getX()][state.getY() + 1].getObject() == null) {
|
||||
State down = new State(state.getX(), state.getY() + 1, State.Type.DOWN, state);
|
||||
if (!history.contains(down)) {
|
||||
queuedStates.add(down);
|
||||
history.add(down);
|
||||
|
||||
if (down.equals(objective)) {
|
||||
objective = down;
|
||||
if (down.equals(searchObjective)) {
|
||||
searchObjective = down;
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
@ -161,14 +149,14 @@ public class BreadthFirstSearch extends AI implements Constantes {
|
||||
*/
|
||||
private void moveLeft(State state) {
|
||||
if (state.getX() > 0) {
|
||||
if (escenario.getCeldas()[state.getX() - 1][state.getY()].getObject() == null) {
|
||||
State left = new State(state.getX() - 1, state.getY(), State.Direction.LEFT, state);
|
||||
if (getEscenario().getCeldas()[state.getX() - 1][state.getY()].getObject() == null) {
|
||||
State left = new State(state.getX() - 1, state.getY(), State.Type.LEFT, state);
|
||||
if (!history.contains(left)) {
|
||||
queuedStates.add(left);
|
||||
history.add(left);
|
||||
|
||||
if (left.equals(objective)) {
|
||||
objective = left;
|
||||
if (left.equals(searchObjective)) {
|
||||
searchObjective = left;
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
@ -183,14 +171,14 @@ public class BreadthFirstSearch extends AI implements Constantes {
|
||||
*/
|
||||
private void moveRight(State state) {
|
||||
if (state.getX() < HORIZONTAL_CELLS - 1) {
|
||||
if (escenario.getCeldas()[state.getX() + 1][state.getY()].getObject() == null) {
|
||||
State right = new State(state.getX() + 1, state.getY(), State.Direction.RIGHT, state);
|
||||
if (getEscenario().getCeldas()[state.getX() + 1][state.getY()].getObject() == null) {
|
||||
State right = new State(state.getX() + 1, state.getY(), State.Type.RIGHT, state);
|
||||
if (!history.contains(right)) {
|
||||
queuedStates.add(right);
|
||||
history.add(right);
|
||||
|
||||
if (right.equals(objective)) {
|
||||
objective = right;
|
||||
if (right.equals(searchObjective)) {
|
||||
searchObjective = right;
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
@ -201,24 +189,89 @@ public class BreadthFirstSearch extends AI implements Constantes {
|
||||
/**
|
||||
* Calculate the route to the object
|
||||
*/
|
||||
public void calculateRoute() {
|
||||
logger.info("Calculate the route!");
|
||||
State predecessor = objective;
|
||||
private void calculateRoute() {
|
||||
getLogger().info("Calculate the route!");
|
||||
State predecessor = searchObjective;
|
||||
do {
|
||||
steps.add(predecessor.getOperation());
|
||||
steps.add(0, predecessor.getOperation());
|
||||
predecessor = predecessor.getPredecessor();
|
||||
}
|
||||
while (predecessor != null);
|
||||
stepIndex = steps.size() - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not the object should interact when it arrives at the destination
|
||||
* Add a destination to the AI
|
||||
*
|
||||
* @param interact Set to true to interact or false otherwise
|
||||
* @param state The new state to add
|
||||
*/
|
||||
public void setInteract(boolean interact) {
|
||||
this.interact = interact;
|
||||
public void addDestination(State state) {
|
||||
destinations.add(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a priority destination to the AI
|
||||
*
|
||||
* @param state The new state to add
|
||||
*/
|
||||
protected void addPriorityDestination(State state) {
|
||||
destinations.add(0, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when the player arrives at a destination
|
||||
*
|
||||
* @param subObjective The objective the player arrived at
|
||||
*/
|
||||
protected void destinationArrived(State subObjective) {
|
||||
destinations.remove(subObjective);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the condition is true go to the objective
|
||||
*
|
||||
* @param subObjective The objective to check
|
||||
* @return Returns true or false based on whether the objective can be obtained
|
||||
*/
|
||||
protected boolean checkCondition(State subObjective) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the steps needed to arrive at the objective
|
||||
*
|
||||
* @return Returns an array of steps
|
||||
*/
|
||||
protected ArrayList<State.Type> getSteps() {
|
||||
return steps;
|
||||
}
|
||||
|
||||
/**
|
||||
* The child class should call this to set a new initial point
|
||||
*
|
||||
* @param initial The new state to start from
|
||||
*/
|
||||
protected void setInitial(State initial) {
|
||||
this.initial = initial;
|
||||
}
|
||||
|
||||
/**
|
||||
* The child class should override this to trigger a new initial state
|
||||
*
|
||||
* @throws AIException Thrown if the method is called via super
|
||||
*/
|
||||
protected void getNewInitial() throws AIException {
|
||||
String methodName = new Throwable().getStackTrace()[0].getMethodName();
|
||||
throw new AIException("Do not call " + methodName + "using super!");
|
||||
}
|
||||
|
||||
/**
|
||||
* The child class should override this to do actions
|
||||
*
|
||||
* @throws AIException Thrown if the method is called via super
|
||||
*/
|
||||
protected void doAction() throws AIException {
|
||||
String methodName = new Throwable().getStackTrace()[0].getMethodName();
|
||||
throw new AIException("Do not call " + methodName + "using super!");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -228,51 +281,68 @@ public class BreadthFirstSearch extends AI implements Constantes {
|
||||
public void run() {
|
||||
super.run();
|
||||
while (getActive()) {
|
||||
if (stepIndex >= 0) {
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
logger.info(e.getMessage());
|
||||
getLogger().info(e.getMessage());
|
||||
}
|
||||
synchronized (this) {
|
||||
boolean moved = false;
|
||||
if (steps.size() - 1 >= stepIndex && stepIndex >= 0) {
|
||||
switch (steps.get(stepIndex)) {
|
||||
case UP:
|
||||
moved = escenario.getCanvas().getPlayer().keyPressed(KeyEvent.VK_UP);
|
||||
break;
|
||||
case DOWN:
|
||||
moved = escenario.getCanvas().getPlayer().keyPressed(KeyEvent.VK_DOWN);
|
||||
break;
|
||||
case LEFT:
|
||||
moved = escenario.getCanvas().getPlayer().keyPressed(KeyEvent.VK_LEFT);
|
||||
break;
|
||||
case RIGHT:
|
||||
moved = escenario.getCanvas().getPlayer().keyPressed(KeyEvent.VK_RIGHT);
|
||||
break;
|
||||
default:
|
||||
stepIndex--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
queuedStates.clear();
|
||||
history.clear();
|
||||
steps.clear();
|
||||
|
||||
escenario.getCanvas().repaint();
|
||||
if (moved) {
|
||||
stepIndex--;
|
||||
State objective;
|
||||
boolean found;
|
||||
int destinationIndex = 0;
|
||||
|
||||
do {
|
||||
try {
|
||||
getNewInitial();
|
||||
}
|
||||
catch (AIException e) {
|
||||
getLogger().warning(e.getMessage());
|
||||
}
|
||||
objective = destinations.get(destinationIndex);
|
||||
|
||||
if (checkCondition(objective)) {
|
||||
found = search(initial, objective);
|
||||
}
|
||||
else {
|
||||
setActive(false);
|
||||
|
||||
if (interact) {
|
||||
escenario.getCanvas().getPlayer().keyPressed(KeyEvent.VK_UP);
|
||||
escenario.getCanvas().getPlayer().interact();
|
||||
escenario.getCanvas().repaint();
|
||||
found = false;
|
||||
}
|
||||
|
||||
if (initial.equals(objective)) {
|
||||
destinationArrived(objective);
|
||||
destinationIndex = 0;
|
||||
}
|
||||
else {
|
||||
if (!found) {
|
||||
queuedStates.clear();
|
||||
history.clear();
|
||||
steps.clear();
|
||||
// Don't run this because the destination might return to be available again at some point
|
||||
//destinationArrived(subObjective);
|
||||
}
|
||||
}
|
||||
|
||||
if (destinations.isEmpty()) {
|
||||
getLogger().info("No more destinations!");
|
||||
setActive(false);
|
||||
}
|
||||
destinationIndex++;
|
||||
if (destinationIndex >= destinations.size()) {
|
||||
destinationIndex = 0;
|
||||
}
|
||||
}
|
||||
while (!found && !destinations.isEmpty());
|
||||
|
||||
try {
|
||||
doAction();
|
||||
}
|
||||
catch (AIException e) {
|
||||
getLogger().warning(e.getMessage());
|
||||
}
|
||||
// Launch the next objective
|
||||
escenario.getCanvas().playerAiLauncher();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
143
src/main/java/cl/cromer/azaraka/ai/PlayerAI.java
Normal file
143
src/main/java/cl/cromer/azaraka/ai/PlayerAI.java
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 2019 Chris Cromer
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
package cl.cromer.azaraka.ai;
|
||||
|
||||
import cl.cromer.azaraka.Escenario;
|
||||
import cl.cromer.azaraka.object.Player;
|
||||
import cl.cromer.azaraka.object.Portal;
|
||||
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
/**
|
||||
* This class handles player based interactions mixed with AI
|
||||
*/
|
||||
public class PlayerAI extends BreadthFirstSearch {
|
||||
/**
|
||||
* The player
|
||||
*/
|
||||
private final Player player;
|
||||
|
||||
/**
|
||||
* Initialize the algorithm
|
||||
*
|
||||
* @param escenario The scene the AI is in
|
||||
* @param player The player controlled by the AI
|
||||
*/
|
||||
public PlayerAI(Escenario escenario, Player player) {
|
||||
super(escenario);
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
/**
|
||||
* This handles what to do when the player arrives at a destination
|
||||
*
|
||||
* @param objective The objective the player arrived at
|
||||
*/
|
||||
@Override
|
||||
public void destinationArrived(State objective) {
|
||||
switch (objective.getOperation()) {
|
||||
case CHEST:
|
||||
if (player.hasKey()) {
|
||||
player.keyPressed(KeyEvent.VK_UP);
|
||||
player.interact();
|
||||
Portal portal = getEscenario().getCanvas().getPortal();
|
||||
if (portal.getState() == Portal.State.ACTIVE) {
|
||||
addPriorityDestination(new State(portal.getCelda().getX(), portal.getCelda().getY(), State.Type.PORTAL, null));
|
||||
}
|
||||
// Only call parent method if player opened a chest
|
||||
super.destinationArrived(objective);
|
||||
}
|
||||
break;
|
||||
case EXIT:
|
||||
super.destinationArrived(objective);
|
||||
player.keyPressed(KeyEvent.VK_UP);
|
||||
break;
|
||||
default:
|
||||
super.destinationArrived(objective);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check conditions to to make sure that the AI doesn't go after unobtainable objectives
|
||||
*
|
||||
* @param objective The objective to check
|
||||
* @return Returns true if the objective is obtainable
|
||||
*/
|
||||
@Override
|
||||
public boolean checkCondition(State objective) {
|
||||
super.checkCondition(objective);
|
||||
switch (objective.getOperation()) {
|
||||
case KEY:
|
||||
// If the player doesn't have the gems yet, get keys
|
||||
if (player.getGemCount() < 2) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case CHEST:
|
||||
// If the player has a key and doesn't have both gems yet
|
||||
if (player.hasKey() && player.getGemCount() < 2) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case PORTAL:
|
||||
// If the portal is active head towards it
|
||||
if (getEscenario().getCanvas().getPortal().getState() == Portal.State.ACTIVE) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case EXIT:
|
||||
if (getEscenario().isDoorOpen()) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle actions based on the states
|
||||
*/
|
||||
@Override
|
||||
public void doAction() {
|
||||
if (getSteps().size() > 1) {
|
||||
switch (getSteps().get(1)) {
|
||||
case UP:
|
||||
player.keyPressed(KeyEvent.VK_UP);
|
||||
break;
|
||||
case DOWN:
|
||||
player.keyPressed(KeyEvent.VK_DOWN);
|
||||
break;
|
||||
case LEFT:
|
||||
player.keyPressed(KeyEvent.VK_LEFT);
|
||||
break;
|
||||
case RIGHT:
|
||||
player.keyPressed(KeyEvent.VK_RIGHT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
getEscenario().getCanvas().repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when the algorithm wants to know where the player is located at now
|
||||
*/
|
||||
@Override
|
||||
public void getNewInitial() {
|
||||
setInitial(new State(player.getCelda().getX(), player.getCelda().getY(), State.Type.START, null));
|
||||
}
|
||||
}
|
@ -30,7 +30,7 @@ public class State {
|
||||
/**
|
||||
* The direction to move
|
||||
*/
|
||||
private final Direction operation;
|
||||
private final Type operation;
|
||||
/**
|
||||
* The previous step
|
||||
*/
|
||||
@ -44,7 +44,7 @@ public class State {
|
||||
* @param operation The operation to perform
|
||||
* @param predecessor The previous state
|
||||
*/
|
||||
public State(int x, int y, Direction operation, State predecessor) {
|
||||
public State(int x, int y, Type operation, State predecessor) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.operation = operation;
|
||||
@ -74,7 +74,7 @@ public class State {
|
||||
*
|
||||
* @return The operation to perform
|
||||
*/
|
||||
public Direction getOperation() {
|
||||
public Type getOperation() {
|
||||
return operation;
|
||||
}
|
||||
|
||||
@ -108,17 +108,29 @@ public class State {
|
||||
}
|
||||
|
||||
/**
|
||||
* The direction to move in and the start and end positions
|
||||
* The type of operation
|
||||
*/
|
||||
public enum Direction {
|
||||
public enum Type {
|
||||
/**
|
||||
* Where to start the search
|
||||
*/
|
||||
START,
|
||||
/**
|
||||
* Where to end the search
|
||||
* Arrive at the key
|
||||
*/
|
||||
END,
|
||||
KEY,
|
||||
/**
|
||||
* Arrive at the chest
|
||||
*/
|
||||
CHEST,
|
||||
/**
|
||||
* Arrive at the portal
|
||||
*/
|
||||
PORTAL,
|
||||
/**
|
||||
* Arrive at the exit
|
||||
*/
|
||||
EXIT,
|
||||
/**
|
||||
* Move up
|
||||
*/
|
||||
|
@ -187,6 +187,7 @@ public class Chest extends Object implements Constantes {
|
||||
}
|
||||
synchronized (this) {
|
||||
if (state == State.OPENED) {
|
||||
if (gem != null) {
|
||||
if (gemLoops > 0) {
|
||||
gemLoops--;
|
||||
}
|
||||
@ -200,6 +201,7 @@ public class Chest extends Object implements Constantes {
|
||||
gemLoops--;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (state == State.OPENING) {
|
||||
animate();
|
||||
getEscenario().getCanvas().repaint();
|
||||
|
@ -239,6 +239,7 @@ public class Enemy extends Object implements Constantes {
|
||||
catch (SheetException e) {
|
||||
getLogger().warning(e.getMessage());
|
||||
}
|
||||
getEscenario().getCanvas().getPlayer().attacked();
|
||||
|
||||
if (direction == Direction.UP) {
|
||||
getAnimation().setCurrentDirection(Animation.Direction.LEFT);
|
||||
|
@ -18,7 +18,7 @@ package cl.cromer.azaraka.object;
|
||||
import cl.cromer.azaraka.Celda;
|
||||
import cl.cromer.azaraka.Constantes;
|
||||
import cl.cromer.azaraka.Escenario;
|
||||
import cl.cromer.azaraka.ai.BreadthFirstSearch;
|
||||
import cl.cromer.azaraka.ai.PlayerAI;
|
||||
import cl.cromer.azaraka.sprite.Animation;
|
||||
import cl.cromer.azaraka.sprite.AnimationException;
|
||||
|
||||
@ -44,7 +44,7 @@ public class Player extends Object implements Constantes {
|
||||
/**
|
||||
* The artificial intelligence of the player
|
||||
*/
|
||||
private BreadthFirstSearch ai;
|
||||
private final PlayerAI ai;
|
||||
|
||||
/**
|
||||
* Initialize the player
|
||||
@ -56,6 +56,7 @@ public class Player extends Object implements Constantes {
|
||||
super(escenario, celda);
|
||||
setLogger(getLogger(this.getClass(), LogLevel.PLAYER));
|
||||
loadPlayerAnimation();
|
||||
ai = new PlayerAI(escenario, this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -78,44 +79,44 @@ public class Player extends Object implements Constantes {
|
||||
* Handle keys being pressed in the game
|
||||
*
|
||||
* @param keyCode The key code to handle
|
||||
* @return Returns true if player moved
|
||||
*/
|
||||
public boolean keyPressed(int keyCode) {
|
||||
if (!getEscenario().isDoorClosed()) {
|
||||
public void keyPressed(int keyCode) {
|
||||
if (getEscenario().isDoorOpen()) {
|
||||
ArrayList<Gem> gems = getInventoryGems();
|
||||
if (gems.size() < 2) {
|
||||
getEscenario().setDoorClosed(true);
|
||||
getEscenario().openDoor(false);
|
||||
}
|
||||
else {
|
||||
for (Gem gem : gems) {
|
||||
if (gem.getState() == Gem.State.TAINTED) {
|
||||
getEscenario().setDoorClosed(true);
|
||||
getEscenario().openDoor(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (keyCode) {
|
||||
case KeyEvent.VK_UP:
|
||||
return moveUp();
|
||||
moveUp();
|
||||
break;
|
||||
case KeyEvent.VK_DOWN:
|
||||
return moveDown();
|
||||
moveDown();
|
||||
break;
|
||||
case KeyEvent.VK_LEFT:
|
||||
return moveLeft();
|
||||
moveLeft();
|
||||
break;
|
||||
case KeyEvent.VK_RIGHT:
|
||||
return moveRight();
|
||||
moveRight();
|
||||
break;
|
||||
case KeyEvent.VK_SPACE:
|
||||
interact();
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the player up
|
||||
*
|
||||
* @return Returns true if player moved
|
||||
*/
|
||||
private boolean moveUp() {
|
||||
private void moveUp() {
|
||||
int x = getX();
|
||||
int y = getY();
|
||||
getLogger().info("Up key pressed");
|
||||
@ -153,7 +154,6 @@ public class Player extends Object implements Constantes {
|
||||
}
|
||||
|
||||
setY(getY() - 1);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
if (changeDirection(Animation.Direction.UP)) {
|
||||
@ -176,15 +176,12 @@ public class Player extends Object implements Constantes {
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the player down
|
||||
*
|
||||
* @return Returns true if player moved
|
||||
*/
|
||||
private boolean moveDown() {
|
||||
private void moveDown() {
|
||||
int x = getX();
|
||||
int y = getY();
|
||||
getLogger().info("Down key pressed");
|
||||
@ -219,7 +216,6 @@ public class Player extends Object implements Constantes {
|
||||
}
|
||||
|
||||
setY(getY() + 1);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
if (changeDirection(Animation.Direction.DOWN)) {
|
||||
@ -242,15 +238,12 @@ public class Player extends Object implements Constantes {
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the player to the left
|
||||
*
|
||||
* @return Returns true if player moved
|
||||
*/
|
||||
private boolean moveLeft() {
|
||||
private void moveLeft() {
|
||||
int x = getX();
|
||||
int y = getY();
|
||||
getLogger().info("Left key pressed");
|
||||
@ -285,7 +278,6 @@ public class Player extends Object implements Constantes {
|
||||
}
|
||||
|
||||
setX(getX() - 1);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
if (changeDirection(Animation.Direction.LEFT)) {
|
||||
@ -308,15 +300,12 @@ public class Player extends Object implements Constantes {
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the player to the right
|
||||
*
|
||||
* @return Returns true if player moved
|
||||
*/
|
||||
private boolean moveRight() {
|
||||
private void moveRight() {
|
||||
int x = getX();
|
||||
int y = getY();
|
||||
getLogger().info("Right key pressed");
|
||||
@ -351,7 +340,6 @@ public class Player extends Object implements Constantes {
|
||||
}
|
||||
|
||||
setX(getX() + 1);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
if (changeDirection(Animation.Direction.RIGHT)) {
|
||||
@ -374,7 +362,6 @@ public class Player extends Object implements Constantes {
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -427,9 +414,11 @@ public class Player extends Object implements Constantes {
|
||||
if (chest.getState() == Chest.State.CLOSED) {
|
||||
chest.setState(Chest.State.OPENING);
|
||||
Gem gem = chest.getGem();
|
||||
if (gem != null) {
|
||||
gem.getCelda().setObjectOnTop(gem);
|
||||
useKey();
|
||||
getEscenario().getCanvas().getPortal().setState(Portal.State.ACTIVE);
|
||||
}
|
||||
useKey();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -453,6 +442,21 @@ public class Player extends Object implements Constantes {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of gems the player has
|
||||
*
|
||||
* @return Returns the number of gems the player has
|
||||
*/
|
||||
public int getGemCount() {
|
||||
int count = 0;
|
||||
for (Object object : carrying) {
|
||||
if (object instanceof Gem) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a key from the player inventory
|
||||
*/
|
||||
@ -467,6 +471,26 @@ public class Player extends Object implements Constantes {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called when the player gets attacked
|
||||
*/
|
||||
public void attacked() {
|
||||
if (TRANSPORT_PLAYER_ON_ATTACK) {
|
||||
if (PLAYER_AI) {
|
||||
ai.setActive(false);
|
||||
}
|
||||
getCelda().setObject(null);
|
||||
getAnimation().setCurrentDirection(Animation.Direction.DOWN);
|
||||
setCelda(getEscenario().getCanvas().getPortal().getCelda());
|
||||
getCelda().setObject(this);
|
||||
setX(getCelda().getX());
|
||||
setY(getCelda().getY());
|
||||
if (PLAYER_AI) {
|
||||
getEscenario().getCanvas().setupPlayerAI();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current health of the player
|
||||
*
|
||||
@ -490,17 +514,10 @@ public class Player extends Object implements Constantes {
|
||||
*
|
||||
* @return Returns the current AI in use
|
||||
*/
|
||||
public BreadthFirstSearch getAi() {
|
||||
public PlayerAI getAi() {
|
||||
return ai;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the AI state to start looking for a new objective
|
||||
*/
|
||||
public void resetAi() {
|
||||
ai = new BreadthFirstSearch(getEscenario());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the gems the player has
|
||||
*
|
||||
|
@ -95,7 +95,7 @@ public class Portal extends Object implements Constantes {
|
||||
}
|
||||
setState(State.INACTIVE);
|
||||
if (gems.size() == 2) {
|
||||
getEscenario().setDoorClosed(false);
|
||||
getEscenario().openDoor(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user