Add Breadth-First Search AI

Signed-off-by: Chris Cromer <chris@cromer.cl>
This commit is contained in:
Chris Cromer 2019-10-07 21:45:39 -03:00
parent f6a9518717
commit 58a29acf10
11 changed files with 814 additions and 29 deletions

124
.idea/uiDesigner.xml Normal file
View File

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

View File

@ -34,6 +34,10 @@ public interface Constantes {
* The name of the game * The name of the game
*/ */
String TITLE = "La Aventura de Azaraka"; String TITLE = "La Aventura de Azaraka";
/**
* Whether or not the player should be controlled by AI
*/
boolean PLAYER_AI = true;
/** /**
* Use a global log if true or individual logs if false * Use a global log if true or individual logs if false
*/ */
@ -203,7 +207,7 @@ public interface Constantes {
/** /**
* The global log level is used if the individual log levels are not * The global log level is used if the individual log levels are not
*/ */
GLOBAL(Level.SEVERE), GLOBAL(Level.ALL),
/** /**
* The main log level * The main log level
*/ */
@ -260,6 +264,10 @@ public interface Constantes {
* The gem log level * The gem log level
*/ */
GEM(Level.INFO), GEM(Level.INFO),
/**
* The AI log level
*/
AI(Level.INFO),
/** /**
* The portal log level * The portal log level
*/ */

View File

@ -170,8 +170,6 @@ public class Escenario extends JComponent implements Constantes {
celdas[2][1].setObject(new Player(this, celdas[2][1])); celdas[2][1].setObject(new Player(this, celdas[2][1]));
objectArrayList.add(celdas[2][1].getObject()); objectArrayList.add(celdas[2][1].getObject());
final Lock lock = new ReentrantLock(true);
for (int i = 0; i < obstacles; i++) { for (int i = 0; i < obstacles; i++) {
random = randomCoordinates(); random = randomCoordinates();
celdas[random[0]][random[1]].setObject(new Obstacle(this, celdas[random[0]][random[1]])); celdas[random[0]][random[1]].setObject(new Obstacle(this, celdas[random[0]][random[1]]));
@ -183,6 +181,7 @@ public class Escenario extends JComponent implements Constantes {
} }
} }
final Lock lock = new ReentrantLock(false);
for (int i = 0; i < ENEMIES; i++) { for (int i = 0; i < ENEMIES; i++) {
random = randomCoordinates(); random = randomCoordinates();
celdas[random[0]][random[1]].setObject(new Enemy(this, celdas[random[0]][random[1]], lock)); celdas[random[0]][random[1]].setObject(new Enemy(this, celdas[random[0]][random[1]], lock));

View File

@ -15,6 +15,7 @@
package cl.cromer.azaraka; package cl.cromer.azaraka;
import cl.cromer.azaraka.ai.AI;
import cl.cromer.azaraka.object.Object; import cl.cromer.azaraka.object.Object;
import cl.cromer.azaraka.object.*; import cl.cromer.azaraka.object.*;
import cl.cromer.azaraka.sound.Sound; import cl.cromer.azaraka.sound.Sound;
@ -112,6 +113,10 @@ public class Lienzo extends Canvas implements Constantes {
* The current volume * The current volume
*/ */
private float volume = (float) DEFAULT_VOLUME / 100; private float volume = (float) DEFAULT_VOLUME / 100;
/**
* The threads that control AI
*/
private final HashMap<AI, Thread> aiThreads = new HashMap<>();
/** /**
* Initialize the canvas * Initialize the canvas
@ -203,6 +208,10 @@ public class Lienzo extends Canvas implements Constantes {
thread.start(); thread.start();
} }
if (PLAYER_AI) {
playerAiLauncher();
}
else {
addKeyListener(new KeyAdapter() { addKeyListener(new KeyAdapter() {
@Override @Override
public void keyPressed(KeyEvent event) { public void keyPressed(KeyEvent event) {
@ -214,6 +223,70 @@ public class Lienzo extends Canvas implements Constantes {
} }
}); });
} }
}
/**
* Launch a new player AI task
*/
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;
}
}
}
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;
}
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;
}
}
if (!escenario.isDoorClosed()) {
if (player.getCelda().getX() == 2 && player.getCelda().getY() == 0) {
player.keyPressed(KeyEvent.VK_UP);
}
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 * Override the paint method of Canvas to paint all the scene components
@ -352,6 +425,7 @@ public class Lienzo extends Canvas implements Constantes {
* Stop all active threads * Stop all active threads
*/ */
private void stopThreads() { private void stopThreads() {
// Stop normal threads
for (Map.Entry<Object, Thread> entry : threads.entrySet()) { for (Map.Entry<Object, Thread> entry : threads.entrySet()) {
Thread thread = entry.getValue(); Thread thread = entry.getValue();
if (thread.isAlive()) { if (thread.isAlive()) {
@ -366,6 +440,22 @@ public class Lienzo extends Canvas implements Constantes {
} }
} }
} }
// Stop AI threads
for (Map.Entry<AI, Thread> entry : aiThreads.entrySet()) {
Thread thread = entry.getValue();
if (thread.isAlive()) {
AI ai = entry.getKey();
ai.setActive(false);
thread.interrupt();
try {
thread.join();
}
catch (InterruptedException e) {
logger.info(e.getMessage());
}
}
}
} }
/** /**

View File

@ -0,0 +1,52 @@
/*
* 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;
/**
* AI algorithms extends this class
*/
public class AI implements Runnable {
/**
* Whether or not the run loop of the AI is active
*/
private boolean active;
/**
* Get the active state of the AI
*
* @return Returns true if the AI is active or false otherwise
*/
protected boolean getActive() {
return active;
}
/**
* Set the active state for the AI loop
*
* @param active Set to true to have the run method loop run indefinitely or false to stop the loop
*/
public void setActive(boolean active) {
this.active = active;
}
/**
* The run method
*/
@Override
public void run() {
setActive(true);
}
}

View File

@ -0,0 +1,279 @@
/*
* 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.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
*/
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
*/
private final ArrayList<State> queuedStates = new ArrayList<>();
/**
* The history of states that have been checked
*/
private final ArrayList<State> history = new ArrayList<>();
/**
* The steps to get to the objective
*/
private final ArrayList<State.Direction> steps = new ArrayList<>();
/**
* Which step the object is on
*/
private int stepIndex = 0;
/**
* The state of the objective
*/
private State objective;
/**
* If the search was successful or not
*/
private boolean success = false;
/**
* Interact with object once the objective is reached
*/
private boolean interact = false;
/**
* Initialize the algorithm
*
* @param escenario The scene the AI is in
*/
public BreadthFirstSearch(Escenario escenario) {
this.escenario = escenario;
logger = getLogger(this.getClass(), LogLevel.AI);
}
/**
* Search for a path that gets from the start point to the end point
*
* @param startX The start x coordinate
* @param startY The start y coordinate
* @param endX The end x coordinate
* @param endY The end y coordinate
*/
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);
queuedStates.add(initial);
history.add(initial);
if (initial.equals(objective)) {
success = true;
}
while (!queuedStates.isEmpty() && !success) {
State temp = queuedStates.get(0);
queuedStates.remove(0);
moveUp(temp);
moveDown(temp);
moveLeft(temp);
moveRight(temp);
}
if (success) {
logger.info("Route to objective calculated!");
}
else {
logger.info("Route to objective not possible!");
}
}
/**
* Move up if possible
*
* @param state The previous state
*/
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 (!history.contains(up)) {
queuedStates.add(up);
history.add(up);
if (up.equals(objective)) {
objective = up;
success = true;
}
}
}
}
}
/**
* Move down if possible
*
* @param state The previous state
*/
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 (!history.contains(down)) {
queuedStates.add(down);
history.add(down);
if (down.equals(objective)) {
objective = down;
success = true;
}
}
}
}
}
/**
* Move left if possible
*
* @param state The previous state
*/
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 (!history.contains(left)) {
queuedStates.add(left);
history.add(left);
if (left.equals(objective)) {
objective = left;
success = true;
}
}
}
}
}
/**
* Move right if possible
*
* @param state The previous state
*/
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 (!history.contains(right)) {
queuedStates.add(right);
history.add(right);
if (right.equals(objective)) {
objective = right;
success = true;
}
}
}
}
}
/**
* Calculate the route to the object
*/
public void calculateRoute() {
logger.info("Calculate the route!");
State predecessor = objective;
do {
steps.add(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
*
* @param interact Set to true to interact or false otherwise
*/
public void setInteract(boolean interact) {
this.interact = interact;
}
/**
* Run the steps in a loop, then launch the next objective when finished
*/
@Override
public void run() {
super.run();
while (getActive()) {
if (stepIndex >= 0) {
try {
Thread.sleep(500);
}
catch (InterruptedException e) {
logger.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;
}
}
escenario.getCanvas().repaint();
if (moved) {
stepIndex--;
}
}
}
else {
setActive(false);
if (interact) {
escenario.getCanvas().getPlayer().keyPressed(KeyEvent.VK_UP);
escenario.getCanvas().getPlayer().interact();
escenario.getCanvas().repaint();
}
// Launch the next objective
escenario.getCanvas().playerAiLauncher();
}
}
}
}

View File

@ -0,0 +1,139 @@
/*
* 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;
/**
* The state of the Breadth-First Search algorithm
*/
public class State {
/**
* The x position being checked
*/
private final int x;
/**
* The y position being checked
*/
private final int y;
/**
* The direction to move
*/
private final Direction operation;
/**
* The previous step
*/
private final State predecessor;
/**
* Initialize the state
*
* @param x The x position
* @param y The y position
* @param operation The operation to perform
* @param predecessor The previous state
*/
public State(int x, int y, Direction operation, State predecessor) {
this.x = x;
this.y = y;
this.operation = operation;
this.predecessor = predecessor;
}
/**
* Get the x position of the state
*
* @return The x coordinate
*/
public int getX() {
return x;
}
/**
* Get the y position of the state
*
* @return The y coordinate
*/
public int getY() {
return y;
}
/**
* Get the operation to perform
*
* @return The operation to perform
*/
public Direction getOperation() {
return operation;
}
/**
* Get the previous state
*
* @return The previous state
*/
public State getPredecessor() {
return predecessor;
}
/**
* Overridden equals to compare the x and y coordinates
*
* @param object The object to compare with this
* @return Returns true if they are the same or false otherwise
*/
@Override
public boolean equals(Object object) {
if (object == this) {
return true;
}
if (!(object instanceof State)) {
return false;
}
State that = (State) object;
return (this.x == that.getX() && this.y == that.getY());
}
/**
* The direction to move in and the start and end positions
*/
public enum Direction {
/**
* Where to start the search
*/
START,
/**
* Where to end the search
*/
END,
/**
* Move up
*/
UP,
/**
* Move down
*/
DOWN,
/**
* Move left
*/
LEFT,
/**
* Move right
*/
RIGHT
}
}

View File

@ -0,0 +1,19 @@
/*
* 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.
*
*/
/**
* This package contains AI algorithms
*/
package cl.cromer.azaraka.ai;

View File

@ -227,6 +227,7 @@ public class Enemy extends Object implements Constantes {
*/ */
private void attackPlayer(int x, int y) { private void attackPlayer(int x, int y) {
if (getEscenario().getCanvas().getPlayer().getHealth() > 0) { if (getEscenario().getCanvas().getPlayer().getHealth() > 0) {
getLogger().info("Attacked player at x: " + x + " y: " + y); getLogger().info("Attacked player at x: " + x + " y: " + y);
playAttackSound(); playAttackSound();
@ -238,6 +239,23 @@ public class Enemy extends Object implements Constantes {
catch (SheetException e) { catch (SheetException e) {
getLogger().warning(e.getMessage()); getLogger().warning(e.getMessage());
} }
if (direction == Direction.UP) {
getAnimation().setCurrentDirection(Animation.Direction.LEFT);
direction = Direction.LEFT;
}
else if (direction == Direction.DOWN) {
getAnimation().setCurrentDirection(Animation.Direction.RIGHT);
direction = Direction.RIGHT;
}
else if (direction == Direction.LEFT) {
getAnimation().setCurrentDirection(Animation.Direction.UP);
direction = Direction.UP;
}
else {
getAnimation().setCurrentDirection(Animation.Direction.DOWN);
direction = Direction.DOWN;
}
} }
} }

View File

@ -18,6 +18,7 @@ package cl.cromer.azaraka.object;
import cl.cromer.azaraka.Celda; import cl.cromer.azaraka.Celda;
import cl.cromer.azaraka.Constantes; import cl.cromer.azaraka.Constantes;
import cl.cromer.azaraka.Escenario; import cl.cromer.azaraka.Escenario;
import cl.cromer.azaraka.ai.BreadthFirstSearch;
import cl.cromer.azaraka.sprite.Animation; import cl.cromer.azaraka.sprite.Animation;
import cl.cromer.azaraka.sprite.AnimationException; import cl.cromer.azaraka.sprite.AnimationException;
@ -40,6 +41,10 @@ public class Player extends Object implements Constantes {
* The current health of the player * The current health of the player
*/ */
private int health = MAX_HEALTH; private int health = MAX_HEALTH;
/**
* The artificial intelligence of the player
*/
private BreadthFirstSearch ai;
/** /**
* Initialize the player * Initialize the player
@ -66,6 +71,16 @@ public class Player extends Object implements Constantes {
* @param event The event from the keyboard * @param event The event from the keyboard
*/ */
public void keyPressed(KeyEvent event) { public void keyPressed(KeyEvent event) {
keyPressed(event.getKeyCode());
}
/**
* 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()) { if (!getEscenario().isDoorClosed()) {
ArrayList<Gem> gems = getInventoryGems(); ArrayList<Gem> gems = getInventoryGems();
if (gems.size() < 2) { if (gems.size() < 2) {
@ -79,29 +94,28 @@ public class Player extends Object implements Constantes {
} }
} }
} }
switch (event.getKeyCode()) { switch (keyCode) {
case KeyEvent.VK_UP: case KeyEvent.VK_UP:
moveUp(); return moveUp();
break;
case KeyEvent.VK_DOWN: case KeyEvent.VK_DOWN:
moveDown(); return moveDown();
break;
case KeyEvent.VK_LEFT: case KeyEvent.VK_LEFT:
moveLeft(); return moveLeft();
break;
case KeyEvent.VK_RIGHT: case KeyEvent.VK_RIGHT:
moveRight(); return moveRight();
break;
case KeyEvent.VK_SPACE: case KeyEvent.VK_SPACE:
interact(); interact();
break; return false;
} }
return false;
} }
/** /**
* Move the player up * Move the player up
*
* @return Returns true if player moved
*/ */
private void moveUp() { private boolean moveUp() {
int x = getX(); int x = getX();
int y = getY(); int y = getY();
getLogger().info("Up key pressed"); getLogger().info("Up key pressed");
@ -139,6 +153,7 @@ public class Player extends Object implements Constantes {
} }
setY(getY() - 1); setY(getY() - 1);
return true;
} }
else { else {
if (changeDirection(Animation.Direction.UP)) { if (changeDirection(Animation.Direction.UP)) {
@ -161,12 +176,15 @@ public class Player extends Object implements Constantes {
} }
} }
} }
return false;
} }
/** /**
* Move the player down * Move the player down
*
* @return Returns true if player moved
*/ */
private void moveDown() { private boolean moveDown() {
int x = getX(); int x = getX();
int y = getY(); int y = getY();
getLogger().info("Down key pressed"); getLogger().info("Down key pressed");
@ -201,6 +219,7 @@ public class Player extends Object implements Constantes {
} }
setY(getY() + 1); setY(getY() + 1);
return true;
} }
else { else {
if (changeDirection(Animation.Direction.DOWN)) { if (changeDirection(Animation.Direction.DOWN)) {
@ -223,12 +242,15 @@ public class Player extends Object implements Constantes {
} }
} }
} }
return false;
} }
/** /**
* Move the player to the left * Move the player to the left
*
* @return Returns true if player moved
*/ */
private void moveLeft() { private boolean moveLeft() {
int x = getX(); int x = getX();
int y = getY(); int y = getY();
getLogger().info("Left key pressed"); getLogger().info("Left key pressed");
@ -263,6 +285,7 @@ public class Player extends Object implements Constantes {
} }
setX(getX() - 1); setX(getX() - 1);
return true;
} }
else { else {
if (changeDirection(Animation.Direction.LEFT)) { if (changeDirection(Animation.Direction.LEFT)) {
@ -285,12 +308,15 @@ public class Player extends Object implements Constantes {
} }
} }
} }
return false;
} }
/** /**
* Move the player to the right * Move the player to the right
*
* @return Returns true if player moved
*/ */
private void moveRight() { private boolean moveRight() {
int x = getX(); int x = getX();
int y = getY(); int y = getY();
getLogger().info("Right key pressed"); getLogger().info("Right key pressed");
@ -325,6 +351,7 @@ public class Player extends Object implements Constantes {
} }
setX(getX() + 1); setX(getX() + 1);
return true;
} }
else { else {
if (changeDirection(Animation.Direction.RIGHT)) { if (changeDirection(Animation.Direction.RIGHT)) {
@ -347,6 +374,7 @@ public class Player extends Object implements Constantes {
} }
} }
} }
return false;
} }
/** /**
@ -383,7 +411,7 @@ public class Player extends Object implements Constantes {
/** /**
* Interact with an object in the game * Interact with an object in the game
*/ */
private void interact() { public void interact() {
int x = getX(); int x = getX();
int y = getY(); int y = getY();
getLogger().info("Space bar pressed"); getLogger().info("Space bar pressed");
@ -401,6 +429,7 @@ public class Player extends Object implements Constantes {
Gem gem = chest.getGem(); Gem gem = chest.getGem();
gem.getCelda().setObjectOnTop(gem); gem.getCelda().setObjectOnTop(gem);
useKey(); useKey();
getEscenario().getCanvas().getPortal().setState(Portal.State.ACTIVE);
break; break;
} }
} }
@ -415,7 +444,7 @@ public class Player extends Object implements Constantes {
* *
* @return Returns true if the player has a key or false if they have no keys * @return Returns true if the player has a key or false if they have no keys
*/ */
private boolean hasKey() { public boolean hasKey() {
for (Object object : carrying) { for (Object object : carrying) {
if (object instanceof Key) { if (object instanceof Key) {
return true; return true;
@ -456,6 +485,22 @@ public class Player extends Object implements Constantes {
carrying.add(object); carrying.add(object);
} }
/**
* Get the AI in use by the player
*
* @return Returns the current AI in use
*/
public BreadthFirstSearch 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 * Get the gems the player has
* *
@ -492,7 +537,7 @@ public class Player extends Object implements Constantes {
* *
* @param amount The amount of health to gain * @param amount The amount of health to gain
*/ */
private void gainHealth(int amount) { public void gainHealth(int amount) {
if (health < MAX_HEALTH) { if (health < MAX_HEALTH) {
getLogger().info("Gain " + amount + " health"); getLogger().info("Gain " + amount + " health");
health = health + amount; health = health + amount;

View File

@ -88,7 +88,10 @@ public class Portal extends Object implements Constantes {
if (state == State.ACTIVE) { if (state == State.ACTIVE) {
ArrayList<Gem> gems = getEscenario().getCanvas().getPlayer().getInventoryGems(); ArrayList<Gem> gems = getEscenario().getCanvas().getPlayer().getInventoryGems();
for (Gem gem : gems) { for (Gem gem : gems) {
if (gem.getState() == Gem.State.TAINTED) {
gem.setState(Gem.State.PURIFIED); gem.setState(Gem.State.PURIFIED);
getEscenario().getCanvas().getPlayer().gainHealth(2);
}
} }
setState(State.INACTIVE); setState(State.INACTIVE);
if (gems.size() == 2) { if (gems.size() == 2) {
@ -144,6 +147,15 @@ public class Portal extends Object implements Constantes {
} }
} }
/**
* Get the current state of the portal
*
* @return Returns the state of the portal
*/
public State getState() {
return state;
}
/** /**
* This method is run when the thread starts * This method is run when the thread starts
*/ */