Improve intelligence

Rename main class
Dynamic gson version
Improve build time
Remove panels
Improve random generation of objects so that all objectives are obtainable
Add door, gem, and portal sounds

Signed-off-by: Chris Cromer <chris@cromer.cl>
master
Chris Cromer 3 years ago
parent 57d68a142e
commit a0c23e8cf3
  1. 1
      .idea/.name
  2. 4
      .idea/dictionaries/cromer.xml
  3. 13
      build.gradle
  4. 2
      settings.gradle
  5. 6
      src/main/java/cl/cromer/azaraka/Azaraka.java
  6. 110
      src/main/java/cl/cromer/azaraka/Constantes.java
  7. 118
      src/main/java/cl/cromer/azaraka/Escenario.java
  8. 177
      src/main/java/cl/cromer/azaraka/Lienzo.java
  9. 27
      src/main/java/cl/cromer/azaraka/VentanaPrincipal.java
  10. 81
      src/main/java/cl/cromer/azaraka/ai/BreadthFirstSearch.java
  11. 31
      src/main/java/cl/cromer/azaraka/ai/PlayerAI.java
  12. 17
      src/main/java/cl/cromer/azaraka/ai/State.java
  13. 22
      src/main/java/cl/cromer/azaraka/object/Chest.java
  14. 29
      src/main/java/cl/cromer/azaraka/object/Enemy.java
  15. 43
      src/main/java/cl/cromer/azaraka/object/Gem.java
  16. 22
      src/main/java/cl/cromer/azaraka/object/Key.java
  17. 2
      src/main/java/cl/cromer/azaraka/object/Object.java
  18. 44
      src/main/java/cl/cromer/azaraka/object/Player.java
  19. 32
      src/main/java/cl/cromer/azaraka/object/Portal.java
  20. 103
      src/main/java/cl/cromer/azaraka/panel/Config.java
  21. 55
      src/main/java/cl/cromer/azaraka/panel/Game.java
  22. 19
      src/main/java/cl/cromer/azaraka/panel/package-info.java
  23. 4
      src/main/java/cl/cromer/azaraka/sound/Sound.java
  24. BIN
      src/main/resources/snd/Door.wav
  25. BIN
      src/main/resources/snd/GetGem.wav
  26. BIN
      src/main/resources/snd/Portal.wav

@ -1 +0,0 @@
azaraka

@ -10,13 +10,17 @@
<w>cellspacing</w>
<w>constantes</w>
<w>cromer</w>
<w>derrotado</w>
<w>dorg</w>
<w>endlocal</w>
<w>errorlevel</w>
<w>escenario</w>
<w>gameover</w>
<w>ganaste</w>
<w>liberado</w>
<w>lienzo</w>
<w>sido</w>
<w>tomak</w>
<w>ventana</w>
</words>
</dictionary>

@ -27,15 +27,15 @@ sourceCompatibility = 1.8
//noinspection GroovyUnusedAssignment
targetCompatibility = 1.8
mainClassName = "cl.cromer.azaraka.Main"
mainClassName = "cl.cromer.azaraka.Azaraka"
repositories {
mavenCentral()
}
dependencies {
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.5'
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.2'
implementation group: 'com.google.code.gson', name: 'gson', version: '2.+'
//testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.+'
}
jar {
@ -61,4 +61,9 @@ build {
dependsOn copyDependencies
}
jar.dependsOn(copyDependencies)
jar.dependsOn(copyDependencies)
tasks.withType(JavaCompile) {
options.fork = true
options.incremental = true
}

@ -13,5 +13,5 @@
*
*/
rootProject.name = 'azaraka'
rootProject.name = 'Azaraka'

@ -21,12 +21,12 @@ import java.util.logging.Logger;
/**
* The main class of the game
*/
public class Main implements Constantes {
public class Azaraka implements Constantes {
/**
* Initialize the main class
*/
private Main() {
private Azaraka() {
Logger logger = getLogger(this.getClass(), LogLevel.MAIN);
logger.info("Load main window");
@ -41,7 +41,7 @@ public class Main implements Constantes {
* @param args The arguments passed to the application
*/
public static void main(String[] args) {
new Main();
new Azaraka();
}
}

@ -39,9 +39,9 @@ public interface Constantes {
*/
boolean PLAYER_AI = true;
/**
* Move the player to the portal if attacked
* Make logs
*/
boolean TRANSPORT_PLAYER_ON_ATTACK = false;
boolean LOG_TO_FILE = false;
/**
* Use a global log if true or individual logs if false
*/
@ -55,61 +55,33 @@ public interface Constantes {
*/
int CELL_PIXELS = 64;
/**
* The number of cells to draw horizontally
* The number of cells to draw horizontally, minimum 8
*/
int HORIZONTAL_CELLS = 18;
int HORIZONTAL_CELLS = 16;
/**
* The number of cells to draw vertically
* The number of cells to draw vertically, minimum 8
*/
int VERTICAL_CELLS = 10;
int VERTICAL_CELLS = 9;
/**
* The amount of margin before drawing the cells
* The amount of chests to draw, if less then 2 the game cannot be won
*/
int TOP_MARGIN = 40;
/**
* The amount of margin to the left and right of cells
*/
int LEFT_MARGIN = 40;
/**
* The amount of chests to draw
*/
int CHESTS = 5;
int CHESTS = 4;
/**
* The amount of enemies to draw
*/
int ENEMIES = 3;
/**
* The font size to use
*/
int FONT_SIZE = 12;
/**
* The minimum speed of the enemies
*/
int MINIMUM_SPEED = 100;
/**
* The maximum speed of the enemies
*/
int MAXIMUM_SPEED = 500;
/**
* The default speed of the enemies
*/
int DEFAULT_SPEED = 100;
/**
* The minimum volume
* The amount of obstacles to draw on the screen
*/
int MINIMUM_VOLUME = 0;
int OBSTACLES = (int) Math.floor((double) (HORIZONTAL_CELLS * VERTICAL_CELLS) * 0.10);
/**
* The maximum volume
* The default volume between 0 and 100
*/
int MAXIMUM_VOLUME = 100;
/**
* The default volume
*/
int DEFAULT_VOLUME = 100;
int VOLUME = 100;
/**
* Generates the scene manually instead of from the JSON file if true
*/
boolean GENERATE_SCENE = false;
boolean GENERATE_SCENE = true;
/**
* Exports the scene to a JSON file if true
*/
@ -119,9 +91,13 @@ public interface Constantes {
*/
boolean PRETTY_JSON = true;
/**
* The normal font to use
* The font size to use
*/
Font FONT = new Font("monospaced", Font.PLAIN, FONT_SIZE);
int FONT_SIZE = 20;
/**
* The big font to use
*/
Font FONT = new Font("monospaced", Font.BOLD, FONT_SIZE);
/**
* Get a logger object to use for debugging
@ -177,30 +153,32 @@ public interface Constantes {
else {
logger = Logger.getLogger(className);
}
FileHandler fileHandler = null;
File directory = new File("log");
if (!directory.exists()) {
if (!directory.mkdir()) {
System.out.println("Could not make directory \"log\"");
if (LOG_TO_FILE) {
FileHandler fileHandler = null;
File directory = new File("log");
if (!directory.exists()) {
if (!directory.mkdir()) {
System.out.println("Could not make directory \"log\"");
}
}
}
try {
if (GLOBAL_LOG) {
fileHandler = new FileHandler("log/log.html", APPEND_LOGS);
try {
if (GLOBAL_LOG) {
fileHandler = new FileHandler("log/log.html", APPEND_LOGS);
}
else {
fileHandler = new FileHandler("log/" + className + ".html", APPEND_LOGS);
}
}
else {
fileHandler = new FileHandler("log/" + className + ".html", APPEND_LOGS);
catch (IOException e) {
e.printStackTrace();
}
if (fileHandler != null) {
logger.addHandler(fileHandler);
}
Formatter formatter = new HtmlFormatter();
if (fileHandler != null) {
fileHandler.setFormatter(formatter);
}
}
catch (IOException e) {
e.printStackTrace();
}
if (fileHandler != null) {
logger.addHandler(fileHandler);
}
Formatter formatter = new HtmlFormatter();
if (fileHandler != null) {
fileHandler.setFormatter(formatter);
}
}
@ -240,10 +218,6 @@ public interface Constantes {
* The chest log level
*/
CHEST(Level.INFO),
/**
* The config log level
*/
CONFIG(Level.INFO),
/**
* The sound log level
*/

@ -15,10 +15,14 @@
package cl.cromer.azaraka;
import cl.cromer.azaraka.ai.BreadthFirstSearch;
import cl.cromer.azaraka.ai.State;
import cl.cromer.azaraka.json.Cell;
import cl.cromer.azaraka.json.Json;
import cl.cromer.azaraka.object.Object;
import cl.cromer.azaraka.object.*;
import cl.cromer.azaraka.sound.Sound;
import cl.cromer.azaraka.sound.SoundException;
import cl.cromer.azaraka.sprite.Sheet;
import cl.cromer.azaraka.sprite.SheetException;
import com.google.gson.Gson;
@ -39,14 +43,6 @@ import java.util.logging.Logger;
* The scene used for the game
*/
public class Escenario extends JComponent implements Constantes {
/**
* The width of the scene
*/
protected final int width = CELL_PIXELS * HORIZONTAL_CELLS;
/**
* The height of the scene
*/
protected final int height = CELL_PIXELS * VERTICAL_CELLS;
/**
* The canvas
*/
@ -67,6 +63,14 @@ public class Escenario extends JComponent implements Constantes {
* Whether or not the door is open
*/
private boolean doorOpen = true;
/**
* The sound the door makes
*/
private Sound doorSound;
/**
* The amount of tries before giving up
*/
private int tries = 0;
/**
* Initialize the scene
@ -118,7 +122,7 @@ public class Escenario extends JComponent implements Constantes {
for (int x = 0; x < cells.length; x++) {
for (int y = 0; y < cells[x].length; y++) {
celdas[x][y] = new Celda((x * CELL_PIXELS) + LEFT_MARGIN, (y * CELL_PIXELS) + TOP_MARGIN, x, y);
celdas[x][y] = new Celda((x * CELL_PIXELS) + canvas.getLeftMargin(), (y * CELL_PIXELS) + canvas.getTopMargin(), x, y);
if (cells[x][y].type.equals(Player.class.getName())) {
celdas[x][y].setObject(new Player(null, celdas[x][y]));
@ -160,9 +164,6 @@ public class Escenario extends JComponent implements Constantes {
* @return Returns a list of objects that where generated
*/
public ArrayList<Object> generateRandomObjects() {
final int cells = (HORIZONTAL_CELLS * VERTICAL_CELLS);
final int obstacles = (int) Math.floor((double) cells * 0.05);
int[] random;
ArrayList<Object> objectArrayList = new ArrayList<>();
@ -170,8 +171,11 @@ public class Escenario extends JComponent implements Constantes {
celdas[2][1].setObject(new Player(this, celdas[2][1]));
objectArrayList.add(celdas[2][1].getObject());
for (int i = 0; i < obstacles; i++) {
random = randomCoordinates();
for (int i = 0; i < OBSTACLES; i++) {
random = randomCoordinates(false);
if (random[0] == -1 || random[1] == -1) {
return null;
}
celdas[random[0]][random[1]].setObject(new Obstacle(this, celdas[random[0]][random[1]]));
try {
celdas[random[0]][random[1]].addTexture(textureSheet.getTexture(30), 30);
@ -183,33 +187,53 @@ public class Escenario extends JComponent implements Constantes {
final Lock lock = new ReentrantLock(true);
for (int i = 0; i < ENEMIES; i++) {
random = randomCoordinates();
random = randomCoordinates(true);
if (random[0] == -1 || random[1] == -1) {
return null;
}
celdas[random[0]][random[1]].setObject(new Enemy(this, celdas[random[0]][random[1]], lock));
objectArrayList.add(celdas[random[0]][random[1]].getObject());
}
random = randomCoordinates();
random = randomCoordinates(true);
if (random[0] == -1 || random[1] == -1) {
return null;
}
celdas[random[0]][random[1]].setObjectOnBottom(new Portal(this, celdas[random[0]][random[1]]));
objectArrayList.add(celdas[random[0]][random[1]].getObjectOnBottom());
// Generate enough keys for the chests that will exist
for (int i = 0; i < CHESTS; i++) {
random = randomCoordinates();
random = randomCoordinates(true);
if (random[0] == -1 || random[1] == -1) {
return null;
}
celdas[random[0]][random[1]].setObjectOnBottom(new Key(this, celdas[random[0]][random[1]]));
objectArrayList.add(celdas[random[0]][random[1]].getObjectOnBottom());
}
// Chests need to be last to make sure they are openable
for (int i = 0; i < CHESTS; i++) {
tries = 0;
int random_x = random(0, HORIZONTAL_CELLS - 1);
int random_y = random(0, VERTICAL_CELLS - 1);
// Don't put a chest if it can't be opened
while (random_y + 1 == VERTICAL_CELLS ||
celdas[random_x][random_y].containsObject() ||
celdas[random_x][random_y + 1].containsObject() ||
celdas[random_x][random_y - 1].containsObject()) {
celdas[random_x][random_y - 1].containsObject() ||
checkBreadthFirst(random_x, random_y + 1)) {
random_x = random(0, HORIZONTAL_CELLS - 1);
random_y = random(0, VERTICAL_CELLS - 1);
tries++;
if (tries == HORIZONTAL_CELLS) {
random[0] = -1;
random[1] = -1;
break;
}
}
if (random[0] == -1 || random[1] == -1) {
return null;
}
celdas[random_x][random_y].setObjectOnBottom(new Chest(this, celdas[random_x][random_y]));
objectArrayList.add(celdas[random_x][random_y].getObjectOnBottom());
@ -221,19 +245,47 @@ public class Escenario extends JComponent implements Constantes {
/**
* Get random x and y coordinates
*
* @param checkPath Check if the path can be reached using AI
* @return Returns an array with the coordinates
*/
private int[] randomCoordinates() {
private int[] randomCoordinates(boolean checkPath) {
tries = 0;
int[] random = new int[2];
random[0] = random(0, HORIZONTAL_CELLS - 1);
random[1] = random(0, VERTICAL_CELLS - 1);
while (celdas[random[0]][random[1]].containsObject()) {
// If the cell is not empty look for another
// If the cell is not reachable by the player look for another
// If the player can't reach the bottom right corner look for another
while (celdas[random[0]][random[1]].containsObject() || (checkPath && checkBreadthFirst(random[0], random[1]))) {
random[0] = random(0, HORIZONTAL_CELLS - 1);
random[1] = random(0, VERTICAL_CELLS - 1);
tries++;
if (tries == VERTICAL_CELLS) {
random[0] = -1;
random[1] = -1;
break;
}
}
return random;
}
/**
* Check the path using BreadFirst-Search
*
* @param x The x position to check
* @param y The y position to check
* @return Returns true if the object is reachable or false otherwise
*/
private boolean checkBreadthFirst(int x, int y) {
BreadthFirstSearch breadthFirstSearch = new BreadthFirstSearch(this);
return (!breadthFirstSearch.search(
new State(2, 1, State.Type.START, null, 0),
new State(x, y, State.Type.EXIT, null, 0)) ||
!breadthFirstSearch.search(
new State(2, 1, State.Type.START, null, 0),
new State(HORIZONTAL_CELLS - 2, VERTICAL_CELLS - 2, State.Type.EXIT, null, 0)));
}
/**
* Generate the scene manually without the JSON file
*/
@ -241,7 +293,7 @@ public class Escenario extends JComponent implements Constantes {
for (int x = 0; x < HORIZONTAL_CELLS; x++) {
for (int y = 0; y < VERTICAL_CELLS; y++) {
logger.info("Generate cell x: " + x + " y: " + y + " manually");
celdas[x][y] = new Celda((x * CELL_PIXELS) + LEFT_MARGIN, (y * CELL_PIXELS) + TOP_MARGIN, x, y);
celdas[x][y] = new Celda((x * CELL_PIXELS) + canvas.getLeftMargin(), (y * CELL_PIXELS) + canvas.getTopMargin(), x, y);
try {
celdas[x][y].addTexture(textureSheet.getTexture(0), 0);
}
@ -469,6 +521,28 @@ public class Escenario extends JComponent implements Constantes {
return canvas;
}
/**
* Set the door sound
*
* @param doorSound The sound
*/
public void setDoorSound(Sound doorSound) {
this.doorSound = doorSound;
}
/**
* Play the sound of the door
*/
private void playDoorSound() {
try {
doorSound.setVolume(canvas.getVolume());
doorSound.play();
}
catch (SoundException e) {
logger.warning(e.getMessage());
}
}
/**
* Check if door is open
*
@ -493,11 +567,13 @@ public class Escenario extends JComponent implements Constantes {
logger.warning(e.getMessage());
}
this.doorOpen = false;
playDoorSound();
}
else if (doorOpen && !isDoorOpen()) {
celdas[2][0].removeTexture(193);
celdas[2][0].setObject(null);
this.doorOpen = true;
playDoorSound();
}
}

@ -25,7 +25,6 @@ import cl.cromer.azaraka.sprite.Animation;
import cl.cromer.azaraka.sprite.AnimationException;
import javax.sound.sampled.Clip;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
@ -37,9 +36,9 @@ import java.util.logging.Logger;
*/
public class Lienzo extends Canvas implements Constantes {
/**
* The game scene
* The current volume
*/
private final Escenario escenario;
private final float volume = (float) VOLUME / 100;
/**
* The threads for the objects
*/
@ -85,9 +84,33 @@ public class Lienzo extends Canvas implements Constantes {
*/
private Animation heartAnimation;
/**
* The background music of the game
* The left margin of the game
*/
private Sound backgroundMusic;
private final int leftMargin;
/**
* The top margin of the game
*/
private final int topMargin;
/**
* The game scene
*/
private Escenario escenario;
/**
* The sound played when a key is picked up
*/
private Sound getKeySound;
/**
* The sound played when a chest is opened
*/
private Sound openChestSound;
/**
* The sound the portal makes
*/
private Sound portalSound;
/**
* The sound of the enemy attacking
*/
private Sound enemyAttackSound;
/**
* The music played when game over shows
*/
@ -109,25 +132,50 @@ public class Lienzo extends Canvas implements Constantes {
*/
private boolean gameOverRan = false;
/**
* The current volume
* The sound of the door opening or closing
*/
private float volume = (float) DEFAULT_VOLUME / 100;
private Sound doorSound;
/**
* The threads that control AI
*/
private final HashMap<AI, Thread> aiThreads = new HashMap<>();
/**
* The sound when a gem is received
*/
private Sound getGemSound;
/**
* The background music of the game
*/
private Sound backgroundMusic;
/**
* Has the game been won
*/
private boolean won = false;
/**
* Initialize the canvas
*
* @param width The width to set the canvas
* @param height The width to set the canvas
*/
public Lienzo() {
public Lienzo(int width, int height) {
logger = getLogger(this.getClass(), LogLevel.LIENZO);
setSize(width, height);
leftMargin = (width - CELL_PIXELS * HORIZONTAL_CELLS) / 2;
topMargin = (height - CELL_PIXELS * VERTICAL_CELLS) / 2;
// Load the sounds
try {
backgroundMusic = new Sound("/snd/GameLoop.wav");
gameOverMusic = new Sound("/snd/GameOver.wav");
successSound = new Sound("/snd/Success.wav");
getKeySound = new Sound("/snd/GetKey.wav");
openChestSound = new Sound("/snd/OpenChest.wav");
portalSound = new Sound("/snd/Portal.wav");
enemyAttackSound = new Sound("/snd/EnemyAttack.wav");
doorSound = new Sound("/snd/Door.wav");
getGemSound = new Sound("/snd/GetGem.wav");
}
catch (SoundException e) {
logger.warning(e.getMessage());
@ -138,21 +186,29 @@ public class Lienzo extends Canvas implements Constantes {
gameOverAnimation.addImage(Animation.Direction.NONE, "/img/gameover/gameover.png");
escenario = new Escenario(this);
ArrayList<Object> objectList = escenario.generateRandomObjects();
while (objectList == null) {
escenario = new Escenario(this);
objectList = escenario.generateRandomObjects();
}
escenario.setDoorSound(doorSound);
setBackground(Color.black);
setSize(escenario.width, escenario.height);
Enemy.Direction enemyDirection = Enemy.Direction.DOWN;
// Create the gems and later place them in 2 of the chests
ArrayList<Gem> gems = new ArrayList<>();
Gem lifeGem = new Gem(escenario, new Celda(0, 0, 0, 0));
lifeGem.setSound(getGemSound);
lifeGem.setType(Gem.Type.LIFE);
Gem deathGem = new Gem(escenario, new Celda(0, 0, 0, 0));
deathGem.setSound(getGemSound);
deathGem.setType(Gem.Type.DEATH);
gems.add(lifeGem);
gems.add(deathGem);
ArrayList<Object> objectList = escenario.generateRandomObjects();
for (Object object : objectList) {
if (object instanceof Player) {
object.getCelda().setObject(object);
@ -174,11 +230,13 @@ public class Lienzo extends Canvas implements Constantes {
enemyDirection = Enemy.Direction.UP;
}
((Enemy) object).setDirection(enemyDirection);
((Enemy) object).setSound(enemyAttackSound);
enemies.add((Enemy) object);
threads.put(object, new Thread(object));
}
else if (object instanceof Chest) {
object.getCelda().setObject(object);
((Chest) object).setSound(openChestSound);
if (gems.size() > 0) {
Gem gem = gems.get(0);
// Place the gem in the cell above the chest, but don't add it to object2 until we are ready to draw it
@ -192,12 +250,14 @@ public class Lienzo extends Canvas implements Constantes {
}
else if (object instanceof Key) {
object.getCelda().setObjectOnBottom(object);
((Key) object).setSound(getKeySound);
keys.add((Key) object);
threads.put(object, new Thread(object));
}
else if (object instanceof Portal) {
object.getCelda().setObjectOnBottom(object);
portal = (Portal) object;
portal.setSound(portalSound);
threads.put(object, new Thread(object));
}
}
@ -227,19 +287,17 @@ public class Lienzo extends Canvas implements Constantes {
/**
* Set up the player AI
*/
public void setupPlayerAI() {
player.getAi().addDestination(new State(2, 0, State.Type.EXIT, null));
//player.getAi().addDestination(new State(portal.getCelda().getX(), portal.getCelda().getY(), State.Type.PORTAL, null));
private void setupPlayerAI() {
player.getAi().addDestination(new State(2, 0, State.Type.EXIT, null, 3));
// 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) {
player.getAi().addDestination(new State(chest.getCelda().getX(), chest.getCelda().getY() + 1, State.Type.CHEST, null));
player.getAi().addDestination(new State(chest.getCelda().getX(), chest.getCelda().getY() + 1, State.Type.CHEST, null, 1));
}
for (Key key : keys) {
player.getAi().addDestination(new State(key.getCelda().getX(), key.getCelda().getY(), State.Type.KEY, null));
player.getAi().addDestination(new State(key.getCelda().getX(), key.getCelda().getY(), State.Type.KEY, null, 0));
}
Thread thread = new Thread(player.getAi());
@ -272,18 +330,18 @@ public class Lienzo extends Canvas implements Constantes {
graphicBuffer.setColor(getBackground());
graphicBuffer.fillRect(0, 0, this.getWidth(), this.getHeight());
int xKey = LEFT_MARGIN;
int xPixels = leftMargin;
for (Key key : keys) {
if (key.getState() == Key.State.HELD) {
key.drawAnimation(graphicBuffer, xKey, 8);
xKey = xKey + 3 + (key.getAnimationWidth());
key.drawAnimation(graphicBuffer, xPixels, 8);
xPixels = xPixels + 3 + (key.getAnimationWidth());
}
}
ArrayList<Gem> gems = player.getInventoryGems();
for (Gem gem : gems) {
gem.drawAnimation(graphicBuffer, xKey, 8);
xKey = xKey + 27;
gem.drawAnimation(graphicBuffer, xPixels, 8);
xPixels = xPixels + 3 + (gem.getAnimationWidth());
}
if (player != null) {
@ -301,7 +359,7 @@ public class Lienzo extends Canvas implements Constantes {
for (int i = 0; i < hearts; i++) {
try {
heartAnimation.setCurrentFrame(Math.min(health, 4));
int x = (HORIZONTAL_CELLS * CELL_PIXELS) + LEFT_MARGIN - (heartAnimation.getFrame().getWidth() * hearts) + (heartAnimation.getFrame().getWidth() * i);
int x = (HORIZONTAL_CELLS * CELL_PIXELS) + leftMargin - (heartAnimation.getFrame().getWidth() * hearts) + (heartAnimation.getFrame().getWidth() * i);
graphicBuffer.drawImage(heartAnimation.getFrame(), x, 8, null);
}
catch (AnimationException e) {
@ -336,6 +394,12 @@ public class Lienzo extends Canvas implements Constantes {
// Place the game over image on the screen
graphicBuffer.setColor(Color.black);
graphicBuffer.drawRect(0, 0, getWidth(), getHeight());
int alpha = (255 * 75) / 100; // 75% transparent
Color transparentColor = new Color(0, 0, 0, alpha);
graphicBuffer.setColor(transparentColor);
graphicBuffer.fillRect(0, 0, getWidth(), getHeight());
try {
int x = (getWidth() - gameOverAnimation.getFrame().getWidth()) / 2;
int y = (getHeight() - gameOverAnimation.getFrame().getHeight()) / 2;
@ -347,6 +411,23 @@ public class Lienzo extends Canvas implements Constantes {
}
else {
escenario.paintComponent(graphicBuffer);
if (won) {
int alpha = (255 * 75) / 100; // 75% transparent
Color transparentColor = new Color(0, 0, 0, alpha);
graphicBuffer.setColor(transparentColor);
graphicBuffer.fillRect(0, 0, getWidth(), getHeight());
// Write message at center of rectangle
graphicBuffer.setColor(Color.white);
String message = "Tomak ha sido derrotado y Azaraka ha sido liberado!";
graphicBuffer.setFont(FONT);
Rectangle rectangle = new Rectangle(0, 0, getWidth(), getHeight());
FontMetrics metrics = g.getFontMetrics(FONT);
int x = rectangle.x + (rectangle.width - metrics.stringWidth(message)) / 2;
int y = rectangle.y + ((rectangle.height - metrics.getHeight()) / 2) + metrics.getAscent();
graphicBuffer.drawString(message, x, y);
}
}
if (!gameStarted) {
@ -432,23 +513,7 @@ public class Lienzo extends Canvas implements Constantes {
logger.warning(e.getMessage());
}
JOptionPane.showMessageDialog(null, "Ganaste!");
System.exit(0);
}
/**
* Change the speed of the enemies
*
* @param speed The new speed
*/
public void changeSpeed(int speed) {
if (speed <= 0) {
speed = 1;
}
for (Enemy enemy : enemies) {
enemy.setSpeed(speed);
}
requestFocus();
won = true;
}
/**
@ -460,22 +525,6 @@ public class Lienzo extends Canvas implements Constantes {
return volume;
}
/**
* Change the volume of the game background music
*
* @param volume The new volume
*/
public void changeVolume(float volume) {
this.volume = volume;
try {
backgroundMusic.setVolume(volume);
}
catch (SoundException e) {
logger.warning(e.getMessage());
}
requestFocus();
}
/**
* Get the player
*
@ -511,4 +560,22 @@ public class Lienzo extends Canvas implements Constantes {
public ArrayList<Chest> getChests() {
return chests;
}
/**
* Get the left margin being used
*
* @return Returns the left margin
*/
public int getLeftMargin() {
return leftMargin;
}
/**
* Get the top margin being used
*
* @return Returns the top margin
*/
public int getTopMargin() {
return topMargin;
}
}

@ -15,9 +15,6 @@
package cl.cromer.azaraka;
import cl.cromer.azaraka.panel.Config;
import cl.cromer.azaraka.panel.Game;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
@ -39,7 +36,10 @@ public class VentanaPrincipal extends JFrame implements Constantes {
logger.info("Create panels");
setExtendedState(this.getExtendedState() | JFrame.MAXIMIZED_BOTH);
setSize(Toolkit.getDefaultToolkit().getScreenSize());
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
setSize(screenSize);
setLayout(new BorderLayout());
setTitle(TITLE);
String icon = "/img/icon.png";
@ -52,20 +52,9 @@ public class VentanaPrincipal extends JFrame implements Constantes {
logger.warning(e.getMessage());
}
Container contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
JSplitPane panelSeparator = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
panelSeparator.setOneTouchExpandable(true);
Game gamePanel = new Game();
Config configPanel = new Config(gamePanel);
panelSeparator.setLeftComponent(gamePanel);
panelSeparator.setRightComponent(configPanel);
panelSeparator.setDividerLocation(gamePanel.getWidth() + (LEFT_MARGIN * 2));
panelSeparator.setDividerSize(0);
contentPane.add(panelSeparator, BorderLayout.CENTER);
Lienzo canvas = new Lienzo(screenSize.width, screenSize.height - 50);
canvas.setFocusable(true);
canvas.requestFocus();
add(canvas);
}
}

@ -18,6 +18,7 @@ package cl.cromer.azaraka.ai;
import cl.cromer.azaraka.Constantes;
import cl.cromer.azaraka.Escenario;
import java.awt.geom.Point2D;
import java.util.ArrayList;
/**
@ -58,7 +59,7 @@ public class BreadthFirstSearch extends AI implements Constantes {
*
* @param escenario The scene the AI is in
*/
protected BreadthFirstSearch(Escenario escenario) {
public BreadthFirstSearch(Escenario escenario) {
super(escenario);
setLogger(getLogger(this.getClass(), LogLevel.AI));
}
@ -70,7 +71,7 @@ public class BreadthFirstSearch extends AI implements Constantes {
* @param searchObjective The objective
* @return Returns true if a path was found or false otherwise
*/
private boolean search(State searchInitial, State searchObjective) {
public boolean search(State searchInitial, State searchObjective) {
queuedStates.add(searchInitial);
history.add(searchInitial);
this.searchObjective = searchObjective;
@ -106,7 +107,7 @@ public class BreadthFirstSearch extends AI implements Constantes {
private void moveUp(State state) {
if (state.getY() > 0) {
if (getEscenario().getCeldas()[state.getX()][state.getY() - 1].getObject() == null) {
State up = new State(state.getX(), state.getY() - 1, State.Type.UP, state);
State up = new State(state.getX(), state.getY() - 1, State.Type.UP, state, state.getImportance());
if (!history.contains(up)) {
queuedStates.add(up);
history.add(up);
@ -128,7 +129,7 @@ public class BreadthFirstSearch extends AI implements Constantes {
private void moveDown(State state) {
if (state.getY() < VERTICAL_CELLS - 1) {
if (getEscenario().getCeldas()[state.getX()][state.getY() + 1].getObject() == null) {
State down = new State(state.getX(), state.getY() + 1, State.Type.DOWN, state);
State down = new State(state.getX(), state.getY() + 1, State.Type.DOWN, state, state.getImportance());
if (!history.contains(down)) {
queuedStates.add(down);
history.add(down);
@ -150,7 +151,7 @@ public class BreadthFirstSearch extends AI implements Constantes {
private void moveLeft(State state) {
if (state.getX() > 0) {
if (getEscenario().getCeldas()[state.getX() - 1][state.getY()].getObject() == null) {
State left = new State(state.getX() - 1, state.getY(), State.Type.LEFT, state);
State left = new State(state.getX() - 1, state.getY(), State.Type.LEFT, state, state.getImportance());
if (!history.contains(left)) {
queuedStates.add(left);
history.add(left);
@ -172,7 +173,7 @@ public class BreadthFirstSearch extends AI implements Constantes {
private void moveRight(State state) {
if (state.getX() < HORIZONTAL_CELLS - 1) {
if (getEscenario().getCeldas()[state.getX() + 1][state.getY()].getObject() == null) {
State right = new State(state.getX() + 1, state.getY(), State.Type.RIGHT, state);
State right = new State(state.getX() + 1, state.getY(), State.Type.RIGHT, state, state.getImportance());
if (!history.contains(right)) {
queuedStates.add(right);
history.add(right);
@ -209,21 +210,42 @@ public class BreadthFirstSearch extends AI implements Constantes {
}
/**
* Add a priority destination to the AI
*
* @param state The new state to add
* Sort the destinations by importance, if the importance is the same then sort them by distance
*/
protected void addPriorityDestination(State state) {
destinations.add(0, state);
protected void sortDestinations() {
destinations.sort((state1, state2) -> {
if (state1.getImportance() > state2.getImportance()) {
// The first state is more important
return -1;
}
else if (state1.getImportance() < state2.getImportance()) {
// The second state is more important
return 1;
}
else {
// The states have equal importance, so let's compare distances between them
if (initial != null) {
double state1Distance = Point2D.distance(initial.getX(), initial.getY(), state1.getX(), state1.getY());
double state2Distance = Point2D.distance(initial.getX(), initial.getY(), state2.getX(), state2.getY());
return Double.compare(state1Distance, state2Distance);
}
else {
return 0;
}
}
});
}
/**
* This method is called when the player arrives at a destination
*
* @param subObjective The objective the player arrived at
* @param objective The objective the player arrived at
* @throws AIException Thrown if the method is called via super
* @return Returns true if the destination condition is valid or false otherwise
*/
protected void destinationArrived(State subObjective) {
destinations.remove(subObjective);
protected boolean destinationArrived(State objective) throws AIException {
String methodName = new Throwable().getStackTrace()[0].getMethodName();
throw new AIException("Do not call " + methodName + "using super!");
}
/**
@ -231,9 +253,11 @@ public class BreadthFirstSearch extends AI implements Constantes {
*
* @param subObjective The objective to check
* @return Returns true or false based on whether the objective can be obtained
* @throws AIException Thrown if the method is called via super
*/
protected boolean checkCondition(State subObjective) {
return true;
protected boolean checkCondition(State subObjective) throws AIException {
String methodName = new Throwable().getStackTrace()[0].getMethodName();
throw new AIException("Do not call " + methodName + "using super!");
}
/**
@ -293,7 +317,7 @@ public class BreadthFirstSearch extends AI implements Constantes {
steps.clear();
State objective;
boolean found;
boolean found = false;
int destinationIndex = 0;
do {
@ -305,16 +329,25 @@ public class BreadthFirstSearch extends AI implements Constantes {
}
objective = destinations.get(destinationIndex);
if (checkCondition(objective)) {
found = search(initial, objective);
try {
if (checkCondition(objective)) {
found = search(initial, objective);
}
}
else {
found = false;
catch (AIException e) {
getLogger().warning(e.getMessage());
}
if (initial.equals(objective)) {
destinationArrived(objective);
destinationIndex = 0;
try {
if (destinationArrived(objective)) {
destinations.remove(objective);
destinationIndex = 0;
}
}
catch (AIException e) {
getLogger().warning(e.getMessage());
}
}
else {
if (!found) {
@ -346,4 +379,4 @@ public class BreadthFirstSearch extends AI implements Constantes {
}
}
}
}
}

@ -18,6 +18,7 @@ package cl.cromer.azaraka.ai;
import cl.cromer.azaraka.Escenario;
import cl.cromer.azaraka.object.Player;
import cl.cromer.azaraka.object.Portal;
import cl.cromer.azaraka.sprite.Animation;
import java.awt.event.KeyEvent;
@ -47,28 +48,34 @@ public class PlayerAI extends BreadthFirstSearch {
* @param objective The objective the player arrived at
*/
@Override
public void destinationArrived(State objective) {
public boolean destinationArrived(State objective) {
sortDestinations();
switch (objective.getOperation()) {
case CHEST:
if (player.hasKey()) {
player.keyPressed(KeyEvent.VK_UP);
if (player.getAnimation().getCurrentDirection() != Animation.Direction.UP) {
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));
addDestination(new State(portal.getCelda().getX(), portal.getCelda().getY(), State.Type.PORTAL, null, 2));
}
// Only call parent method if player opened a chest
super.destinationArrived(objective);
return true;
}
break;
case EXIT:
super.destinationArrived(objective);
player.keyPressed(KeyEvent.VK_UP);
break;
default:
super.destinationArrived(objective);
return true;
case KEY:
return true;
case PORTAL:
if (player.hasTaintedGem() && getEscenario().getCanvas().getPortal().getState() == Portal.State.ACTIVE) {
return true;
}
break;
}
return false;
}
/**
@ -79,7 +86,6 @@ public class PlayerAI extends BreadthFirstSearch {
*/
@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
@ -95,11 +101,12 @@ public class PlayerAI extends BreadthFirstSearch {
break;
case PORTAL:
// If the portal is active head towards it
if (getEscenario().getCanvas().getPortal().getState() == Portal.State.ACTIVE) {
if (player.hasTaintedGem() && getEscenario().getCanvas().getPortal().getState() == Portal.State.ACTIVE) {
return true;
}
break;
case EXIT:
// If the door is open head to it
if (getEscenario().isDoorOpen()) {
return true;
}
@ -138,6 +145,6 @@ public class PlayerAI extends BreadthFirstSearch {
*/
@Override
public void getNewInitial() {
setInitial(new State(player.getCelda().getX(), player.getCelda().getY(), State.Type.START, null));
setInitial(new State(player.getCelda().getX(), player.getCelda().getY(), State.Type.START, null, 0));
}
}

@ -35,6 +35,10 @@ public class State {
* The previous step
*/
private final State predecessor;
/**
* The importance of the objective, higher is more important
*/
private int importance;
/**
* Initialize the state
@ -43,12 +47,14 @@ public class State {
* @param y The y position
* @param operation The operation to perform
* @param predecessor The previous state
* @param importance The importance of the objective
*/
public State(int x, int y, Type operation, State predecessor) {
public State(int x, int y, Type operation, State predecessor, int importance) {
this.x = x;
this.y = y;
this.operation = operation;
this.predecessor = predecessor;
this.importance = importance;
}
/**
@ -87,6 +93,15 @@ public class State {
return predecessor;
}
/**
* Get the importance of the state
*
* @return Returns the importance
*/
public int getImportance() {
return importance;
}
/**
* Overridden equals to compare the x and y coordinates
*

@ -57,19 +57,6 @@ public class Chest extends Object implements Constantes {
setLogger(getLogger(this.getClass(), LogLevel.CHEST));
loadChestAnimation();
loadChestOpenSound();
}
/**
* Load the chest open sound
*/
private void loadChestOpenSound() {
try {
sound = new Sound("/snd/OpenChest.wav");
}
catch (SoundException e) {
getLogger().warning(e.getMessage());
}
}
/**
@ -157,6 +144,15 @@ public class Chest extends Object implements Constantes {
}
}
/**
* Set the chest open sound
*
* @param sound The sound to use