Make the AI more robust

Signed-off-by: Chris Cromer <chris@cromer.cl>
This commit is contained in:
Chris Cromer 2019-10-26 17:35:11 -03:00
parent fb7c7ec227
commit 8c627bda8c
7 changed files with 305 additions and 80 deletions

View File

@ -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();

View File

@ -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
*/

View File

@ -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
*

View File

@ -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);
}

View File

@ -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;
}
}
}

View File

@ -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());

View File

@ -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());
}
}
/**