Make the AI more robust
Signed-off-by: Chris Cromer <chris@cromer.cl>
This commit is contained in:
parent
fb7c7ec227
commit
8c627bda8c
@ -318,7 +318,7 @@ public class Canvas extends java.awt.Canvas implements Constants {
|
||||
}
|
||||
|
||||
for (Key key : keys) {
|
||||
player.getAi().addDestination(new State(key.getCell().getX(), key.getCell().getY(), State.Type.KEY, null, 2));
|
||||
player.getAi().addDestination(new State(key.getCell().getX(), key.getCell().getY(), State.Type.KEY, null, 1));
|
||||
}
|
||||
|
||||
player.getAi().sortDestinations();
|
||||
|
@ -60,6 +60,7 @@ public class Scene extends JComponent implements Constants {
|
||||
* The cells of the game
|
||||
*/
|
||||
private final Cell[][] cells;
|
||||
//private final CopyOnWriteArrayList<CopyOnWriteArrayList<Cell>> cells;
|
||||
/**
|
||||
* The logger
|
||||
*/
|
||||
|
@ -76,6 +76,17 @@ public class AI implements Runnable {
|
||||
throw new AIException("The addDestination method should be run by the child only!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the picked up key from destinations if it is there
|
||||
*
|
||||
* @param x The x coordinate of the key
|
||||
* @param y The y coordinate of the key
|
||||
* @throws AIException Thrown when the parent method is called directly
|
||||
*/
|
||||
public void removeKeyDestination(int x, int y) throws AIException {
|
||||
throw new AIException("The addDestination method should be run by the child only!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the destinations
|
||||
*
|
||||
|
@ -129,10 +129,14 @@ public interface PlayerAI extends Runnable, Constants {
|
||||
if (player.getAnimation().getCurrentDirection() != Animation.Direction.UP) {
|
||||
player.keyPressed(KeyEvent.VK_UP);
|
||||
}
|
||||
player.interact();
|
||||
boolean portalWasActive = false;
|
||||
Portal portal = scene.getCanvas().getPortal();
|
||||
if (portal.getState() == Portal.State.ACTIVE) {
|
||||
addDestination(new State(portal.getCell().getX(), portal.getCell().getY(), State.Type.PORTAL, null, 2));
|
||||
portalWasActive = true;
|
||||
}
|
||||
player.interact();
|
||||
if (!portalWasActive) {
|
||||
addDestination(new State(portal.getCell().getX(), portal.getCell().getY(), State.Type.PORTAL, null, 1));
|
||||
}
|
||||
sortDestinations();
|
||||
return true;
|
||||
@ -213,6 +217,12 @@ public interface PlayerAI extends Runnable, Constants {
|
||||
if (player.getCell().getY() < VERTICAL_CELLS - 1 && scene.getCells()[player.getCell().getX()][player.getCell().getY() + 1].getObject() == null) {
|
||||
openSpaces.add(State.Type.DOWN);
|
||||
}
|
||||
|
||||
if (openSpaces.size() == 0) {
|
||||
// The player can't move
|
||||
return State.Type.EXIT;
|
||||
}
|
||||
|
||||
int random = random(0, openSpaces.size() - 1);
|
||||
return openSpaces.get(random);
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
* The class implements the A* search AI algorithm for the player
|
||||
@ -58,7 +59,7 @@ public class PlayerAStarAI extends AI implements PlayerAI, Constants {
|
||||
/**
|
||||
* The destinations the player needs to visit
|
||||
*/
|
||||
private List<State> destinations = new ArrayList<>();
|
||||
private List<State> destinations = new CopyOnWriteArrayList<>();
|
||||
/**
|
||||
* The objective that was found
|
||||
*/
|
||||
@ -95,7 +96,7 @@ public class PlayerAStarAI extends AI implements PlayerAI, Constants {
|
||||
cameFrom.put(start, start);
|
||||
costSoFar.put(start, 0.0);
|
||||
|
||||
while (frontier.size() > 0 && cameFrom.size() <= (HORIZONTAL_CELLS * VERTICAL_CELLS) * 2) {
|
||||
while (frontier.size() > 0 && cameFrom.size() <= (HORIZONTAL_CELLS * VERTICAL_CELLS) * 5) {
|
||||
State current = frontier.poll();
|
||||
|
||||
if (current.equals(goal)) {
|
||||
@ -200,52 +201,155 @@ public class PlayerAStarAI extends AI implements PlayerAI, Constants {
|
||||
private double getCost(State state) {
|
||||
// The cost increases based on how close the enemy is
|
||||
/*
|
||||
2
|
||||
444
|
||||
22222
|
||||
24442
|
||||
24842
|
||||
444
|
||||
2
|
||||
24442
|
||||
22222
|
||||
*/
|
||||
if (state.getOperation() == State.Type.ENEMY) {
|
||||
return 8;
|
||||
|
||||
EnemyCost enemyCost = EnemyCost.DIRECT_CORNERS;
|
||||
|
||||
if (enemyCost.getLevel() == EnemyCost.NONE.getLevel()) {
|
||||
return EnemyCost.NONE.getCost();
|
||||
}
|
||||
else if (state.getX() > 0 && scene.getCells()[state.getX() - 1][state.getY()].getObject() instanceof Enemy) {
|
||||
return 4;
|
||||
|
||||
if (enemyCost.getLevel() >= EnemyCost.DIRECT.getLevel()) {
|
||||
// The enemy
|
||||
if (scene.getCells()[state.getX()][state.getY()].getObject() instanceof Enemy) {
|
||||
return EnemyCost.DIRECT.getCost();
|
||||
}
|
||||
}
|
||||
|
||||
if (enemyCost.getLevel() >= EnemyCost.DIRECT_SIDES.getLevel()) {
|
||||
// Left
|
||||
if (state.getX() > 0 && scene.getCells()[state.getX() - 1][state.getY()].getObject() instanceof Enemy) {
|
||||
return EnemyCost.DIRECT_SIDES.getCost();
|
||||
}
|
||||
|
||||
// Right
|
||||
else if (state.getX() < HORIZONTAL_CELLS - 1 && scene.getCells()[state.getX() + 1][state.getY()].getObject() instanceof Enemy) {
|
||||
return 4;
|
||||
return EnemyCost.DIRECT_SIDES.getCost();
|
||||
}
|
||||
|
||||
// Up
|
||||
else if (state.getY() > 0 && scene.getCells()[state.getX()][state.getY() - 1].getObject() instanceof Enemy) {
|
||||
return 4;
|
||||
return EnemyCost.DIRECT_SIDES.getCost();
|
||||
}
|
||||
|
||||
// Down
|
||||
else if (state.getY() < VERTICAL_CELLS - 1 && scene.getCells()[state.getX()][state.getY() + 1].getObject() instanceof Enemy) {
|
||||
return 4;
|
||||
return EnemyCost.DIRECT_SIDES.getCost();
|
||||
}
|
||||
else if (state.getX() > 1 && scene.getCells()[state.getX() - 2][state.getY()].getObject() instanceof Enemy) {
|
||||
return 2;
|
||||
}
|
||||
else if (state.getX() < HORIZONTAL_CELLS - 2 && scene.getCells()[state.getX() + 2][state.getY()].getObject() instanceof Enemy) {
|
||||
return 2;
|
||||
}
|
||||
else if (state.getY() > 1 && scene.getCells()[state.getX()][state.getY() - 2].getObject() instanceof Enemy) {
|
||||
return 2;
|
||||
}
|
||||
else if (state.getY() < VERTICAL_CELLS - 2 && scene.getCells()[state.getX()][state.getY() + 2].getObject() instanceof Enemy) {
|
||||
return 2;
|
||||
}
|
||||
else if (state.getX() > 0 && state.getY() > 0 && scene.getCells()[state.getX() - 1][state.getY() - 1].getObject() instanceof Enemy) {
|
||||
return 4;
|
||||
|
||||
if (enemyCost.getLevel() >= EnemyCost.DIRECT_CORNERS.getLevel()) {
|
||||
// Upper left corner
|
||||
if (state.getX() > 0 && state.getY() > 0 && scene.getCells()[state.getX() - 1][state.getY() - 1].getObject() instanceof Enemy) {
|
||||
return EnemyCost.DIRECT_CORNERS.getCost();
|
||||
}
|
||||
|
||||
// Upper right corner
|
||||
else if (state.getX() < HORIZONTAL_CELLS - 1 && state.getY() > 0 && scene.getCells()[state.getX() + 1][state.getY() - 1].getObject() instanceof Enemy) {
|
||||
return 4;
|
||||
return EnemyCost.DIRECT_CORNERS.getCost();
|
||||
}
|
||||
|
||||
// Lower left corner
|
||||
else if (state.getX() > 0 && state.getY() < VERTICAL_CELLS - 1 && scene.getCells()[state.getX() - 1][state.getY() + 1].getObject() instanceof Enemy) {
|
||||
return 4;
|
||||
return EnemyCost.DIRECT_CORNERS.getCost();
|
||||
}
|
||||
|
||||
// Lower right corner
|
||||
else if (state.getX() < HORIZONTAL_CELLS - 1 && state.getY() < VERTICAL_CELLS - 1 && scene.getCells()[state.getX() + 1][state.getY() + 1].getObject() instanceof Enemy) {
|
||||
return 4;
|
||||
return EnemyCost.DIRECT_CORNERS.getCost();
|
||||
}
|
||||
}
|
||||
|
||||
if (enemyCost.getLevel() >= EnemyCost.FAR_SIDES.getLevel()) {
|
||||
// Left
|
||||
if (state.getX() > 1 && scene.getCells()[state.getX() - 2][state.getY()].getObject() instanceof Enemy) {
|
||||
return EnemyCost.FAR_SIDES.getCost();
|
||||
}
|
||||
|
||||
// Right
|
||||
else if (state.getX() < HORIZONTAL_CELLS - 2 && scene.getCells()[state.getX() + 2][state.getY()].getObject() instanceof Enemy) {
|
||||
return EnemyCost.FAR_SIDES.getCost();
|
||||
}
|
||||
|
||||
// Up
|
||||
else if (state.getY() > 1 && scene.getCells()[state.getX()][state.getY() - 2].getObject() instanceof Enemy) {
|
||||
return EnemyCost.FAR_SIDES.getCost();
|
||||
}
|
||||
|
||||
// Down
|
||||
else if (state.getY() < VERTICAL_CELLS - 2 && scene.getCells()[state.getX()][state.getY() + 2].getObject() instanceof Enemy) {
|
||||
return EnemyCost.FAR_SIDES.getCost();
|
||||
}
|
||||
}
|
||||
|
||||
if (enemyCost.getLevel() >= EnemyCost.FAR_CORNERS.getLevel()) {
|
||||
// Upper left corner
|
||||
if (state.getX() > 1 && state.getY() > 0 && scene.getCells()[state.getX() - 2][state.getY() - 1].getObject() instanceof Enemy) {
|
||||
return EnemyCost.FAR_CORNERS.getCost();
|
||||
}
|
||||
else if (state.getX() > 1 && state.getY() > 1 && scene.getCells()[state.getX() - 2][state.getY() - 2].getObject() instanceof Enemy) {
|
||||
return EnemyCost.FAR_CORNERS.getCost();
|
||||
}
|
||||
else if (state.getX() > 0 && state.getY() > 1 && scene.getCells()[state.getX() - 1][state.getY() - 2].getObject() instanceof Enemy) {
|
||||
return EnemyCost.FAR_CORNERS.getCost();
|
||||
}
|
||||
|
||||
// Upper right corner
|
||||
else if (state.getX() < HORIZONTAL_CELLS - 2 && state.getY() > 0 && scene.getCells()[state.getX() + 2][state.getY() - 1].getObject() instanceof Enemy) {
|
||||
return EnemyCost.FAR_CORNERS.getCost();
|
||||
}
|
||||
else if (state.getX() < HORIZONTAL_CELLS - 2 && state.getY() > 1 && scene.getCells()[state.getX() + 2][state.getY() - 2].getObject() instanceof Enemy) {
|
||||
return EnemyCost.FAR_CORNERS.getCost();
|
||||
}
|
||||
else if (state.getX() < HORIZONTAL_CELLS - 1 && state.getY() > 1 && scene.getCells()[state.getX() + 1][state.getY() - 2].getObject() instanceof Enemy) {
|
||||
return EnemyCost.FAR_CORNERS.getCost();
|
||||
}
|
||||
|
||||
// Lower left corner
|
||||
else if (state.getX() > 1 && state.getY() < VERTICAL_CELLS - 1 && scene.getCells()[state.getX() - 2][state.getY() + 1].getObject() instanceof Enemy) {
|
||||
return EnemyCost.FAR_CORNERS.getCost();
|
||||
}
|
||||
else if (state.getX() > 1 && state.getY() < VERTICAL_CELLS - 2 && scene.getCells()[state.getX() - 2][state.getY() + 2].getObject() instanceof Enemy) {
|
||||
return EnemyCost.FAR_CORNERS.getCost();
|
||||
}
|
||||
else if (state.getX() > 0 && state.getY() < VERTICAL_CELLS - 2 && scene.getCells()[state.getX() - 1][state.getY() + 2].getObject() instanceof Enemy) {
|
||||
return EnemyCost.FAR_CORNERS.getCost();
|
||||
}
|
||||
|
||||
// Lower right corner
|
||||
else if (state.getX() < HORIZONTAL_CELLS - 2 && state.getY() < VERTICAL_CELLS - 1 && scene.getCells()[state.getX() + 2][state.getY() + 1].getObject() instanceof Enemy) {
|
||||
return EnemyCost.FAR_CORNERS.getCost();
|
||||
}
|
||||
else if (state.getX() < HORIZONTAL_CELLS - 2 && state.getY() < VERTICAL_CELLS - 2 && scene.getCells()[state.getX() + 2][state.getY() + 2].getObject() instanceof Enemy) {
|
||||
return EnemyCost.FAR_CORNERS.getCost();
|
||||
}
|
||||
else if (state.getX() < HORIZONTAL_CELLS - 1 && state.getY() < VERTICAL_CELLS - 2 && scene.getCells()[state.getX() + 1][state.getY() + 2].getObject() instanceof Enemy) {
|
||||
return EnemyCost.FAR_CORNERS.getCost();
|
||||
}
|
||||
}
|
||||
|
||||
return EnemyCost.NONE.getCost();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the picked up key from destinations if it is there
|
||||
*
|
||||
* @param x The x coordinate of the key
|
||||
* @param y The y coordinate of the key
|
||||
*/
|
||||
public void removeKeyDestination(int x, int y) {
|
||||
for (State state : destinations) {
|
||||
if (state.getOperation() == State.Type.KEY && state.getX() == x && state.getY() == y) {
|
||||
destinations.remove(state);
|
||||
sortDestinations();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -257,37 +361,6 @@ public class PlayerAStarAI extends AI implements PlayerAI, Constants {
|
||||
destinations.add(destination);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the route from the objective to the player
|
||||
*/
|
||||
private void calculateRoute() {
|
||||
getLogger().info("Calculate the route!");
|
||||
State predecessor = foundObjective;
|
||||
do {
|
||||
steps.add(0, predecessor.getOperation());
|
||||
predecessor = predecessor.getPredecessor();
|
||||
}
|
||||
while (predecessor != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the destinations by importance, if the importance is the same then sort them by distance
|
||||
*/
|
||||
@Override
|
||||
public void sortDestinations() {
|
||||
destinations = sortDestinations(destinations, initial);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the states to be ready for a new search
|
||||
*/
|
||||
private void clearStates() {
|
||||
frontier.clear();
|
||||
cameFrom.clear();
|
||||
costSoFar.clear();
|
||||
steps.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run this in a loop
|
||||
*/
|
||||
@ -334,11 +407,14 @@ public class PlayerAStarAI extends AI implements PlayerAI, Constants {
|
||||
}
|
||||
destinationIndex++;
|
||||
if (destinationIndex >= destinations.size()) {
|
||||
destinationIndex = 0;
|
||||
getLogger().info("None of the destinations are reachable for A* Search!");
|
||||
// No destinations are reachable, make the player move around at random to help move the enemies
|
||||
if (steps.size() > 0) {
|
||||
steps.add(1, getOpenSpaceAroundPlayer(scene));
|
||||
if (steps.size() == 0) {
|
||||
steps.add(0, State.Type.PLAYER);
|
||||
}
|
||||
steps.add(1, getOpenSpaceAroundPlayer(scene));
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (!found && !destinations.isEmpty());
|
||||
@ -348,4 +424,103 @@ public class PlayerAStarAI extends AI implements PlayerAI, Constants {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the route from the objective to the player
|
||||
*/
|
||||
private void calculateRoute() {
|
||||
getLogger().info("Calculate the route!");
|
||||
State predecessor = foundObjective;
|
||||
do {
|
||||
steps.add(0, predecessor.getOperation());
|
||||
predecessor = predecessor.getPredecessor();
|
||||
}
|
||||
while (predecessor != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the destinations by importance, if the importance is the same then sort them by distance
|
||||
*/
|
||||
@Override
|
||||
public void sortDestinations() {
|
||||
destinations = sortDestinations(destinations, initial);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the states to be ready for a new search
|
||||
*/
|
||||
private void clearStates() {
|
||||
frontier.clear();
|
||||
cameFrom.clear();
|
||||
costSoFar.clear();
|
||||
steps.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* The cost based on enemy position
|
||||
*/
|
||||
private enum EnemyCost {
|
||||
/**
|
||||
* The enemy does not have a cost
|
||||
*/
|
||||
NONE(0, 1),
|
||||
/**
|
||||
* The enemy cell has a cost
|
||||
*/
|
||||
DIRECT(1, 8),
|
||||
/**
|
||||
* The cells to the side of the enemy have a cost
|
||||
*/
|
||||
DIRECT_SIDES(2, 4),
|
||||
/**
|
||||
* The cells on the corner of the enemy
|
||||
*/
|
||||
DIRECT_CORNERS(3, 4),
|
||||
/**
|
||||
* The cells father to the side of the enemy
|
||||
*/
|
||||
FAR_SIDES(4, 2),
|
||||
/**
|
||||
* The cells in the corner farthest from the enemy
|
||||
*/
|
||||
FAR_CORNERS(5, 2);
|
||||
|
||||
/**
|
||||
* The level of cost to use for the enemy
|
||||
*/
|
||||
private final int level;
|
||||
/**
|
||||
* The cost value to use
|
||||
*/
|
||||
private final int cost;
|
||||
|
||||
/**
|
||||
* Initialize the enemy cost and level
|
||||
*
|
||||
* @param level The level
|
||||
* @param cost The cost
|
||||
*/
|
||||
EnemyCost(int level, int cost) {
|
||||
this.level = level;
|
||||
this.cost = cost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cost level of the enemy
|
||||
*
|
||||
* @return Returns the cost level
|
||||
*/
|
||||
protected int getLevel() {
|
||||
return this.level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cost of the enemy
|
||||
*
|
||||
* @return Returns the cost
|
||||
*/
|
||||
protected int getCost() {
|
||||
return this.cost;
|
||||
}
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
* This is an implementation of the Breadth-First search algorithm with multiple objectives
|
||||
@ -59,7 +60,7 @@ public class PlayerBreadthFirstAI extends AI implements PlayerAI, Constants {
|
||||
/**
|
||||
* The destinations to visit
|
||||
*/
|
||||
private List<State> destinations = new ArrayList<>();
|
||||
private List<State> destinations = new CopyOnWriteArrayList<>();
|
||||
/**
|
||||
* The initial point to start searching from
|
||||
*/
|
||||
@ -220,6 +221,22 @@ public class PlayerBreadthFirstAI extends AI implements PlayerAI, Constants {
|
||||
destinations.add(destination);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the picked up key from destinations if it is there
|
||||
*
|
||||
* @param x The x coordinate of the key
|
||||
* @param y The y coordinate of the key
|
||||
*/
|
||||
public void removeKeyDestination(int x, int y) {
|
||||
for (State state : destinations) {
|
||||
if (state.getOperation() == State.Type.KEY && state.getX() == x && state.getY() == y) {
|
||||
destinations.remove(state);
|
||||
sortDestinations();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the destinations by importance, if the importance is the same then sort them by distance
|
||||
*/
|
||||
@ -284,10 +301,14 @@ public class PlayerBreadthFirstAI extends AI implements PlayerAI, Constants {
|
||||
}
|
||||
destinationIndex++;
|
||||
if (destinationIndex >= destinations.size()) {
|
||||
destinationIndex = 0;
|
||||
if (steps.size() > 0) {
|
||||
steps.add(1, getOpenSpaceAroundPlayer(scene));
|
||||
getLogger().info("None of the destinations are reachable for Breadth-First Search!");
|
||||
// No destinations are reachable, make the player move around at random to help move the enemies
|
||||
if (steps.size() == 0) {
|
||||
steps.add(0, State.Type.PLAYER);
|
||||
}
|
||||
steps.add(1, getOpenSpaceAroundPlayer(scene));
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (!found && !destinations.isEmpty());
|
||||
|
@ -18,6 +18,7 @@ package cl.cromer.azaraka.object;
|
||||
import cl.cromer.azaraka.Cell;
|
||||
import cl.cromer.azaraka.Constants;
|
||||
import cl.cromer.azaraka.Scene;
|
||||
import cl.cromer.azaraka.ai.AIException;
|
||||
import cl.cromer.azaraka.sound.Sound;
|
||||
import cl.cromer.azaraka.sound.SoundException;
|
||||
import cl.cromer.azaraka.sprite.Animation;
|
||||
@ -116,6 +117,12 @@ public class Key extends Object implements Constants {
|
||||
// Remove the key from the cell
|
||||
getCell().setObjectOnBottom(null);
|
||||
setState(State.HELD);
|
||||
try {
|
||||
getScene().getCanvas().getPlayer().getAi().removeKeyDestination(getCell().getX(), getCell().getY());
|
||||
}
|
||||
catch (AIException e) {
|
||||
getLogger().warning(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user