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>
This commit is contained in:
Chris Cromer 2019-10-10 15:33:03 -03:00
parent 57d68a142e
commit a0c23e8cf3
26 changed files with 509 additions and 453 deletions

View File

@ -1 +0,0 @@
azaraka

View File

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

View File

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

View File

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

View File

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

View File

@ -39,9 +39,9 @@ public interface Constantes {
*/ */
boolean PLAYER_AI = true; 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 * Use a global log if true or individual logs if false
*/ */
@ -55,61 +55,33 @@ public interface Constantes {
*/ */
int CELL_PIXELS = 64; 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; int CHESTS = 4;
/**
* The amount of margin to the left and right of cells
*/
int LEFT_MARGIN = 40;
/**
* The amount of chests to draw
*/
int CHESTS = 5;
/** /**
* The amount of enemies to draw * The amount of enemies to draw
*/ */
int ENEMIES = 3; int ENEMIES = 3;
/** /**
* The font size to use * The amount of obstacles to draw on the screen
*/ */
int FONT_SIZE = 12; int OBSTACLES = (int) Math.floor((double) (HORIZONTAL_CELLS * VERTICAL_CELLS) * 0.10);
/** /**
* The minimum speed of the enemies * The default volume between 0 and 100
*/ */
int MINIMUM_SPEED = 100; int VOLUME = 100;
/**
* The maximum speed of the enemies
*/
int MAXIMUM_SPEED = 500;
/**
* The default speed of the enemies
*/
int DEFAULT_SPEED = 100;
/**
* The minimum volume
*/
int MINIMUM_VOLUME = 0;
/**
* The maximum volume
*/
int MAXIMUM_VOLUME = 100;
/**
* The default volume
*/
int DEFAULT_VOLUME = 100;
/** /**
* Generates the scene manually instead of from the JSON file if true * 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 * Exports the scene to a JSON file if true
*/ */
@ -119,9 +91,13 @@ public interface Constantes {
*/ */
boolean PRETTY_JSON = true; 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 * Get a logger object to use for debugging
@ -177,30 +153,32 @@ public interface Constantes {
else { else {
logger = Logger.getLogger(className); logger = Logger.getLogger(className);
} }
FileHandler fileHandler = null; if (LOG_TO_FILE) {
File directory = new File("log"); FileHandler fileHandler = null;
if (!directory.exists()) { File directory = new File("log");
if (!directory.mkdir()) { if (!directory.exists()) {
System.out.println("Could not make directory \"log\""); if (!directory.mkdir()) {
System.out.println("Could not make directory \"log\"");
}
} }
} try {
try { if (GLOBAL_LOG) {
if (GLOBAL_LOG) { fileHandler = new FileHandler("log/log.html", APPEND_LOGS);
fileHandler = new FileHandler("log/log.html", APPEND_LOGS); }
else {
fileHandler = new FileHandler("log/" + className + ".html", APPEND_LOGS);
}
} }
else { catch (IOException e) {
fileHandler = new FileHandler("log/" + className + ".html", APPEND_LOGS); 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 * The chest log level
*/ */
CHEST(Level.INFO), CHEST(Level.INFO),
/**
* The config log level
*/
CONFIG(Level.INFO),
/** /**
* The sound log level * The sound log level
*/ */

View File

@ -15,10 +15,14 @@
package cl.cromer.azaraka; 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.Cell;
import cl.cromer.azaraka.json.Json; import cl.cromer.azaraka.json.Json;
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.SoundException;
import cl.cromer.azaraka.sprite.Sheet; import cl.cromer.azaraka.sprite.Sheet;
import cl.cromer.azaraka.sprite.SheetException; import cl.cromer.azaraka.sprite.SheetException;
import com.google.gson.Gson; import com.google.gson.Gson;
@ -39,14 +43,6 @@ import java.util.logging.Logger;
* The scene used for the game * The scene used for the game
*/ */
public class Escenario extends JComponent implements Constantes { 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 * The canvas
*/ */
@ -67,6 +63,14 @@ public class Escenario extends JComponent implements Constantes {
* Whether or not the door is open * Whether or not the door is open
*/ */
private boolean doorOpen = true; 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 * Initialize the scene
@ -118,7 +122,7 @@ public class Escenario extends JComponent implements Constantes {
for (int x = 0; x < cells.length; x++) { for (int x = 0; x < cells.length; x++) {
for (int y = 0; y < cells[x].length; y++) { 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())) { if (cells[x][y].type.equals(Player.class.getName())) {
celdas[x][y].setObject(new Player(null, celdas[x][y])); 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 * @return Returns a list of objects that where generated
*/ */
public ArrayList<Object> generateRandomObjects() { public ArrayList<Object> generateRandomObjects() {
final int cells = (HORIZONTAL_CELLS * VERTICAL_CELLS);
final int obstacles = (int) Math.floor((double) cells * 0.05);
int[] random; int[] random;
ArrayList<Object> objectArrayList = new ArrayList<>(); 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])); celdas[2][1].setObject(new Player(this, celdas[2][1]));
objectArrayList.add(celdas[2][1].getObject()); objectArrayList.add(celdas[2][1].getObject());
for (int i = 0; i < obstacles; i++) { for (int i = 0; i < OBSTACLES; i++) {
random = randomCoordinates(); 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]])); celdas[random[0]][random[1]].setObject(new Obstacle(this, celdas[random[0]][random[1]]));
try { try {
celdas[random[0]][random[1]].addTexture(textureSheet.getTexture(30), 30); 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); final Lock lock = new ReentrantLock(true);
for (int i = 0; i < ENEMIES; i++) { 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)); celdas[random[0]][random[1]].setObject(new Enemy(this, celdas[random[0]][random[1]], lock));
objectArrayList.add(celdas[random[0]][random[1]].getObject()); 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]])); celdas[random[0]][random[1]].setObjectOnBottom(new Portal(this, celdas[random[0]][random[1]]));
objectArrayList.add(celdas[random[0]][random[1]].getObjectOnBottom()); objectArrayList.add(celdas[random[0]][random[1]].getObjectOnBottom());
// Generate enough keys for the chests that will exist // Generate enough keys for the chests that will exist
for (int i = 0; i < CHESTS; i++) { 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]])); celdas[random[0]][random[1]].setObjectOnBottom(new Key(this, celdas[random[0]][random[1]]));
objectArrayList.add(celdas[random[0]][random[1]].getObjectOnBottom()); objectArrayList.add(celdas[random[0]][random[1]].getObjectOnBottom());
} }
// Chests need to be last to make sure they are openable // Chests need to be last to make sure they are openable
for (int i = 0; i < CHESTS; i++) { for (int i = 0; i < CHESTS; i++) {
tries = 0;
int random_x = random(0, HORIZONTAL_CELLS - 1); int random_x = random(0, HORIZONTAL_CELLS - 1);
int random_y = random(0, VERTICAL_CELLS - 1); int random_y = random(0, VERTICAL_CELLS - 1);
// Don't put a chest if it can't be opened // Don't put a chest if it can't be opened
while (random_y + 1 == VERTICAL_CELLS || while (random_y + 1 == VERTICAL_CELLS ||
celdas[random_x][random_y].containsObject() || 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()) { celdas[random_x][random_y - 1].containsObject() ||
checkBreadthFirst(random_x, random_y + 1)) {
random_x = random(0, HORIZONTAL_CELLS - 1); random_x = random(0, HORIZONTAL_CELLS - 1);
random_y = random(0, VERTICAL_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])); celdas[random_x][random_y].setObjectOnBottom(new Chest(this, celdas[random_x][random_y]));
objectArrayList.add(celdas[random_x][random_y].getObjectOnBottom()); 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 * Get random x and y coordinates
* *
* @param checkPath Check if the path can be reached using AI
* @return Returns an array with the coordinates * @return Returns an array with the coordinates
*/ */
private int[] randomCoordinates() { private int[] randomCoordinates(boolean checkPath) {
tries = 0;
int[] random = new int[2]; int[] random = new int[2];
random[0] = random(0, HORIZONTAL_CELLS - 1); random[0] = random(0, HORIZONTAL_CELLS - 1);
random[1] = random(0, VERTICAL_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[0] = random(0, HORIZONTAL_CELLS - 1);
random[1] = random(0, VERTICAL_CELLS - 1); random[1] = random(0, VERTICAL_CELLS - 1);
tries++;
if (tries == VERTICAL_CELLS) {
random[0] = -1;
random[1] = -1;
break;
}
} }
return random; 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 * 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 x = 0; x < HORIZONTAL_CELLS; x++) {
for (int y = 0; y < VERTICAL_CELLS; y++) { for (int y = 0; y < VERTICAL_CELLS; y++) {
logger.info("Generate cell x: " + x + " y: " + y + " manually"); 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 { try {
celdas[x][y].addTexture(textureSheet.getTexture(0), 0); celdas[x][y].addTexture(textureSheet.getTexture(0), 0);
} }
@ -469,6 +521,28 @@ public class Escenario extends JComponent implements Constantes {
return canvas; 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 * Check if door is open
* *
@ -493,11 +567,13 @@ public class Escenario extends JComponent implements Constantes {
logger.warning(e.getMessage()); logger.warning(e.getMessage());
} }
this.doorOpen = false; this.doorOpen = false;
playDoorSound();
} }
else if (doorOpen && !isDoorOpen()) { else if (doorOpen && !isDoorOpen()) {
celdas[2][0].removeTexture(193); celdas[2][0].removeTexture(193);
celdas[2][0].setObject(null); celdas[2][0].setObject(null);
this.doorOpen = true; this.doorOpen = true;
playDoorSound();
} }
} }

View File

@ -25,7 +25,6 @@ import cl.cromer.azaraka.sprite.Animation;
import cl.cromer.azaraka.sprite.AnimationException; import cl.cromer.azaraka.sprite.AnimationException;
import javax.sound.sampled.Clip; import javax.sound.sampled.Clip;
import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.awt.event.KeyAdapter; import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
@ -37,9 +36,9 @@ import java.util.logging.Logger;
*/ */
public class Lienzo extends Canvas implements Constantes { 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 * The threads for the objects
*/ */
@ -85,9 +84,33 @@ public class Lienzo extends Canvas implements Constantes {
*/ */
private Animation heartAnimation; 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 * The music played when game over shows
*/ */
@ -109,25 +132,50 @@ public class Lienzo extends Canvas implements Constantes {
*/ */
private boolean gameOverRan = false; 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 * The threads that control AI
*/ */
private final HashMap<AI, Thread> aiThreads = new HashMap<>(); 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 * 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); 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 // Load the sounds
try { try {
backgroundMusic = new Sound("/snd/GameLoop.wav"); backgroundMusic = new Sound("/snd/GameLoop.wav");
gameOverMusic = new Sound("/snd/GameOver.wav"); gameOverMusic = new Sound("/snd/GameOver.wav");
successSound = new Sound("/snd/Success.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) { catch (SoundException e) {
logger.warning(e.getMessage()); logger.warning(e.getMessage());
@ -138,21 +186,29 @@ public class Lienzo extends Canvas implements Constantes {
gameOverAnimation.addImage(Animation.Direction.NONE, "/img/gameover/gameover.png"); gameOverAnimation.addImage(Animation.Direction.NONE, "/img/gameover/gameover.png");
escenario = new Escenario(this); 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); setBackground(Color.black);
setSize(escenario.width, escenario.height);
Enemy.Direction enemyDirection = Enemy.Direction.DOWN; Enemy.Direction enemyDirection = Enemy.Direction.DOWN;
// Create the gems and later place them in 2 of the chests // Create the gems and later place them in 2 of the chests
ArrayList<Gem> gems = new ArrayList<>(); ArrayList<Gem> gems = new ArrayList<>();
Gem lifeGem = new Gem(escenario, new Celda(0, 0, 0, 0)); Gem lifeGem = new Gem(escenario, new Celda(0, 0, 0, 0));
lifeGem.setSound(getGemSound);
lifeGem.setType(Gem.Type.LIFE); lifeGem.setType(Gem.Type.LIFE);
Gem deathGem = new Gem(escenario, new Celda(0, 0, 0, 0)); Gem deathGem = new Gem(escenario, new Celda(0, 0, 0, 0));
deathGem.setSound(getGemSound);
deathGem.setType(Gem.Type.DEATH); deathGem.setType(Gem.Type.DEATH);
gems.add(lifeGem); gems.add(lifeGem);
gems.add(deathGem); gems.add(deathGem);
ArrayList<Object> objectList = escenario.generateRandomObjects();
for (Object object : objectList) { for (Object object : objectList) {
if (object instanceof Player) { if (object instanceof Player) {
object.getCelda().setObject(object); object.getCelda().setObject(object);
@ -174,11 +230,13 @@ public class Lienzo extends Canvas implements Constantes {
enemyDirection = Enemy.Direction.UP; enemyDirection = Enemy.Direction.UP;
} }
((Enemy) object).setDirection(enemyDirection); ((Enemy) object).setDirection(enemyDirection);
((Enemy) object).setSound(enemyAttackSound);
enemies.add((Enemy) object); enemies.add((Enemy) object);
threads.put(object, new Thread(object)); threads.put(object, new Thread(object));
} }
else if (object instanceof Chest) { else if (object instanceof Chest) {
object.getCelda().setObject(object); object.getCelda().setObject(object);
((Chest) object).setSound(openChestSound);
if (gems.size() > 0) { if (gems.size() > 0) {
Gem gem = gems.get(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 // 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) { else if (object instanceof Key) {
object.getCelda().setObjectOnBottom(object); object.getCelda().setObjectOnBottom(object);
((Key) object).setSound(getKeySound);
keys.add((Key) object); keys.add((Key) object);
threads.put(object, new Thread(object)); threads.put(object, new Thread(object));
} }
else if (object instanceof Portal) { else if (object instanceof Portal) {
object.getCelda().setObjectOnBottom(object); object.getCelda().setObjectOnBottom(object);
portal = (Portal) object; portal = (Portal) object;
portal.setSound(portalSound);
threads.put(object, new Thread(object)); threads.put(object, new Thread(object));
} }
} }
@ -227,19 +287,17 @@ public class Lienzo extends Canvas implements Constantes {
/** /**
* Set up the player AI * Set up the player AI
*/ */
public void setupPlayerAI() { private void setupPlayerAI() {
player.getAi().addDestination(new State(2, 0, State.Type.EXIT, null)); player.getAi().addDestination(new State(2, 0, State.Type.EXIT, null, 3));
//player.getAi().addDestination(new State(portal.getCelda().getX(), portal.getCelda().getY(), State.Type.PORTAL, null));
// Shuffle the chests so that the AI doesn't open the correct chests on the first go // Shuffle the chests so that the AI doesn't open the correct chests on the first go
Collections.shuffle(chests, new Random(23)); Collections.shuffle(chests, new Random(23));
for (Chest chest : chests) { 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) { 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()); Thread thread = new Thread(player.getAi());
@ -272,18 +330,18 @@ public class Lienzo extends Canvas implements Constantes {
graphicBuffer.setColor(getBackground()); graphicBuffer.setColor(getBackground());
graphicBuffer.fillRect(0, 0, this.getWidth(), this.getHeight()); graphicBuffer.fillRect(0, 0, this.getWidth(), this.getHeight());
int xKey = LEFT_MARGIN; int xPixels = leftMargin;
for (Key key : keys) { for (Key key : keys) {
if (key.getState() == Key.State.HELD) { if (key.getState() == Key.State.HELD) {
key.drawAnimation(graphicBuffer, xKey, 8); key.drawAnimation(graphicBuffer, xPixels, 8);
xKey = xKey + 3 + (key.getAnimationWidth()); xPixels = xPixels + 3 + (key.getAnimationWidth());
} }
} }
ArrayList<Gem> gems = player.getInventoryGems(); ArrayList<Gem> gems = player.getInventoryGems();
for (Gem gem : gems) { for (Gem gem : gems) {
gem.drawAnimation(graphicBuffer, xKey, 8); gem.drawAnimation(graphicBuffer, xPixels, 8);
xKey = xKey + 27; xPixels = xPixels + 3 + (gem.getAnimationWidth());
} }
if (player != null) { if (player != null) {
@ -301,7 +359,7 @@ public class Lienzo extends Canvas implements Constantes {
for (int i = 0; i < hearts; i++) { for (int i = 0; i < hearts; i++) {
try { try {
heartAnimation.setCurrentFrame(Math.min(health, 4)); 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); graphicBuffer.drawImage(heartAnimation.getFrame(), x, 8, null);
} }
catch (AnimationException e) { catch (AnimationException e) {
@ -336,6 +394,12 @@ public class Lienzo extends Canvas implements Constantes {
// Place the game over image on the screen // Place the game over image on the screen
graphicBuffer.setColor(Color.black); graphicBuffer.setColor(Color.black);
graphicBuffer.drawRect(0, 0, getWidth(), getHeight()); 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 { try {
int x = (getWidth() - gameOverAnimation.getFrame().getWidth()) / 2; int x = (getWidth() - gameOverAnimation.getFrame().getWidth()) / 2;
int y = (getHeight() - gameOverAnimation.getFrame().getHeight()) / 2; int y = (getHeight() - gameOverAnimation.getFrame().getHeight()) / 2;
@ -347,6 +411,23 @@ public class Lienzo extends Canvas implements Constantes {
} }
else { else {
escenario.paintComponent(graphicBuffer); 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) { if (!gameStarted) {
@ -432,23 +513,7 @@ public class Lienzo extends Canvas implements Constantes {
logger.warning(e.getMessage()); logger.warning(e.getMessage());
} }
JOptionPane.showMessageDialog(null, "Ganaste!"); won = true;
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();
} }
/** /**
@ -460,22 +525,6 @@ public class Lienzo extends Canvas implements Constantes {
return volume; 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 * Get the player
* *
@ -511,4 +560,22 @@ public class Lienzo extends Canvas implements Constantes {
public ArrayList<Chest> getChests() { public ArrayList<Chest> getChests() {
return chests; 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;
}
} }

View File

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

View File

@ -18,6 +18,7 @@ package cl.cromer.azaraka.ai;
import cl.cromer.azaraka.Constantes; import cl.cromer.azaraka.Constantes;
import cl.cromer.azaraka.Escenario; import cl.cromer.azaraka.Escenario;
import java.awt.geom.Point2D;
import java.util.ArrayList; import java.util.ArrayList;
/** /**
@ -58,7 +59,7 @@ public class BreadthFirstSearch extends AI implements Constantes {
* *
* @param escenario The scene the AI is in * @param escenario The scene the AI is in
*/ */
protected BreadthFirstSearch(Escenario escenario) { public BreadthFirstSearch(Escenario escenario) {
super(escenario); super(escenario);
setLogger(getLogger(this.getClass(), LogLevel.AI)); setLogger(getLogger(this.getClass(), LogLevel.AI));
} }
@ -70,7 +71,7 @@ public class BreadthFirstSearch extends AI implements Constantes {
* @param searchObjective The objective * @param searchObjective The objective
* @return Returns true if a path was found or false otherwise * @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); queuedStates.add(searchInitial);
history.add(searchInitial); history.add(searchInitial);
this.searchObjective = searchObjective; this.searchObjective = searchObjective;
@ -106,7 +107,7 @@ public class BreadthFirstSearch extends AI implements Constantes {
private void moveUp(State state) { private void moveUp(State state) {
if (state.getY() > 0) { if (state.getY() > 0) {
if (getEscenario().getCeldas()[state.getX()][state.getY() - 1].getObject() == null) { 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)) { if (!history.contains(up)) {
queuedStates.add(up); queuedStates.add(up);
history.add(up); history.add(up);
@ -128,7 +129,7 @@ public class BreadthFirstSearch extends AI implements Constantes {
private void moveDown(State state) { private void moveDown(State state) {
if (state.getY() < VERTICAL_CELLS - 1) { if (state.getY() < VERTICAL_CELLS - 1) {
if (getEscenario().getCeldas()[state.getX()][state.getY() + 1].getObject() == null) { 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)) { if (!history.contains(down)) {
queuedStates.add(down); queuedStates.add(down);
history.add(down); history.add(down);
@ -150,7 +151,7 @@ public class BreadthFirstSearch extends AI implements Constantes {
private void moveLeft(State state) { private void moveLeft(State state) {
if (state.getX() > 0) { if (state.getX() > 0) {
if (getEscenario().getCeldas()[state.getX() - 1][state.getY()].getObject() == null) { 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)) { if (!history.contains(left)) {
queuedStates.add(left); queuedStates.add(left);
history.add(left); history.add(left);
@ -172,7 +173,7 @@ public class BreadthFirstSearch extends AI implements Constantes {
private void moveRight(State state) { private void moveRight(State state) {
if (state.getX() < HORIZONTAL_CELLS - 1) { if (state.getX() < HORIZONTAL_CELLS - 1) {
if (getEscenario().getCeldas()[state.getX() + 1][state.getY()].getObject() == null) { 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)) { if (!history.contains(right)) {
queuedStates.add(right); queuedStates.add(right);
history.add(right); history.add(right);
@ -209,21 +210,42 @@ public class BreadthFirstSearch extends AI implements Constantes {
} }
/** /**
* Add a priority destination to the AI * Sort the destinations by importance, if the importance is the same then sort them by distance
*
* @param state The new state to add
*/ */
protected void addPriorityDestination(State state) { protected void sortDestinations() {
destinations.add(0, state); 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 * 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) { protected boolean destinationArrived(State objective) throws AIException {
destinations.remove(subObjective); 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 * @param subObjective The objective to check
* @return Returns true or false based on whether the objective can be obtained * @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) { protected boolean checkCondition(State subObjective) throws AIException {
return true; 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(); steps.clear();
State objective; State objective;
boolean found; boolean found = false;
int destinationIndex = 0; int destinationIndex = 0;
do { do {
@ -305,16 +329,25 @@ public class BreadthFirstSearch extends AI implements Constantes {
} }
objective = destinations.get(destinationIndex); objective = destinations.get(destinationIndex);
if (checkCondition(objective)) { try {
found = search(initial, objective); if (checkCondition(objective)) {
found = search(initial, objective);
}
} }
else { catch (AIException e) {
found = false; getLogger().warning(e.getMessage());
} }
if (initial.equals(objective)) { if (initial.equals(objective)) {
destinationArrived(objective); try {
destinationIndex = 0; if (destinationArrived(objective)) {
destinations.remove(objective);
destinationIndex = 0;
}
}
catch (AIException e) {
getLogger().warning(e.getMessage());
}
} }
else { else {
if (!found) { if (!found) {

View File

@ -18,6 +18,7 @@ package cl.cromer.azaraka.ai;
import cl.cromer.azaraka.Escenario; import cl.cromer.azaraka.Escenario;
import cl.cromer.azaraka.object.Player; import cl.cromer.azaraka.object.Player;
import cl.cromer.azaraka.object.Portal; import cl.cromer.azaraka.object.Portal;
import cl.cromer.azaraka.sprite.Animation;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
@ -47,28 +48,34 @@ public class PlayerAI extends BreadthFirstSearch {
* @param objective The objective the player arrived at * @param objective The objective the player arrived at
*/ */
@Override @Override
public void destinationArrived(State objective) { public boolean destinationArrived(State objective) {
sortDestinations();
switch (objective.getOperation()) { switch (objective.getOperation()) {
case CHEST: case CHEST:
if (player.hasKey()) { if (player.hasKey()) {
player.keyPressed(KeyEvent.VK_UP); if (player.getAnimation().getCurrentDirection() != Animation.Direction.UP) {
player.keyPressed(KeyEvent.VK_UP);
}
player.interact(); player.interact();
Portal portal = getEscenario().getCanvas().getPortal(); Portal portal = getEscenario().getCanvas().getPortal();
if (portal.getState() == Portal.State.ACTIVE) { 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 return true;
super.destinationArrived(objective);
} }
break; break;
case EXIT: case EXIT:
super.destinationArrived(objective);
player.keyPressed(KeyEvent.VK_UP); player.keyPressed(KeyEvent.VK_UP);
break; return true;
default: case KEY:
super.destinationArrived(objective); return true;
case PORTAL:
if (player.hasTaintedGem() && getEscenario().getCanvas().getPortal().getState() == Portal.State.ACTIVE) {
return true;
}
break; break;
} }
return false;
} }
/** /**
@ -79,7 +86,6 @@ public class PlayerAI extends BreadthFirstSearch {
*/ */
@Override @Override
public boolean checkCondition(State objective) { public boolean checkCondition(State objective) {
super.checkCondition(objective);
switch (objective.getOperation()) { switch (objective.getOperation()) {
case KEY: case KEY:
// If the player doesn't have the gems yet, get keys // If the player doesn't have the gems yet, get keys
@ -95,11 +101,12 @@ public class PlayerAI extends BreadthFirstSearch {
break; break;
case PORTAL: case PORTAL:
// If the portal is active head towards it // 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; return true;
} }
break; break;
case EXIT: case EXIT:
// If the door is open head to it
if (getEscenario().isDoorOpen()) { if (getEscenario().isDoorOpen()) {
return true; return true;
} }
@ -138,6 +145,6 @@ public class PlayerAI extends BreadthFirstSearch {
*/ */
@Override @Override
public void getNewInitial() { 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));
} }
} }

View File

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

View File

@ -57,19 +57,6 @@ public class Chest extends Object implements Constantes {
setLogger(getLogger(this.getClass(), LogLevel.CHEST)); setLogger(getLogger(this.getClass(), LogLevel.CHEST));
loadChestAnimation(); 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
*/
public void setSound(Sound sound) {
this.sound = sound;
}
/** /**
* Animate the chest opening * Animate the chest opening
*/ */

View File

@ -38,10 +38,6 @@ public class Enemy extends Object implements Constantes {
* The current direction the enemy is facing * The current direction the enemy is facing
*/ */
private Direction direction = Direction.LEFT; private Direction direction = Direction.LEFT;
/**
* The speed of the enemy
*/
private int speed = 500;
/** /**
* The enemy attack sound * The enemy attack sound
*/ */
@ -59,7 +55,6 @@ public class Enemy extends Object implements Constantes {
setLogger(getLogger(this.getClass(), LogLevel.ENEMY)); setLogger(getLogger(this.getClass(), LogLevel.ENEMY));
this.lock = lock; this.lock = lock;
loadEnemyAnimation(); loadEnemyAnimation();
loadAttackSound();
} }
/** /**
@ -70,15 +65,12 @@ public class Enemy extends Object implements Constantes {
} }
/** /**
* Load the attack sound * Set the enemy attack sound
*
* @param sound The sound
*/ */
private void loadAttackSound() { public void setSound(Sound sound) {
try { this.sound = sound;
sound = new Sound("/snd/EnemyAttack.wav");
}
catch (SoundException e) {
getLogger().warning(e.getMessage());
}
} }
/** /**
@ -267,7 +259,7 @@ public class Enemy extends Object implements Constantes {
super.run(); super.run();
while (getActive()) { while (getActive()) {
try { try {
Thread.sleep(speed); Thread.sleep(500);
} }
catch (InterruptedException e) { catch (InterruptedException e) {
getLogger().info(e.getMessage()); getLogger().info(e.getMessage());
@ -281,15 +273,6 @@ public class Enemy extends Object implements Constantes {
} }
} }
/**
* Set the speed of the enemy
*
* @param speed The new speed
*/
public void setSpeed(int speed) {
this.speed = speed;
}
/** /**
* The possible directions the enemy can face * The possible directions the enemy can face
*/ */

View File

@ -17,6 +17,8 @@ package cl.cromer.azaraka.object;
import cl.cromer.azaraka.Celda; import cl.cromer.azaraka.Celda;
import cl.cromer.azaraka.Escenario; import cl.cromer.azaraka.Escenario;
import cl.cromer.azaraka.sound.Sound;
import cl.cromer.azaraka.sound.SoundException;
import cl.cromer.azaraka.sprite.Animation; import cl.cromer.azaraka.sprite.Animation;
import cl.cromer.azaraka.sprite.AnimationException; import cl.cromer.azaraka.sprite.AnimationException;
@ -36,6 +38,10 @@ public class Gem extends Object {
* The animation to use when the gem is purified * The animation to use when the gem is purified
*/ */
private Animation purifiedAnimation; private Animation purifiedAnimation;
/**
* The sound the gem makes
*/
private Sound sound;
/** /**
* Initialize the gem object * Initialize the gem object
@ -84,6 +90,43 @@ public class Gem extends Object {
} }
} }
/**
* Get the width of the gem animation
*
* @return Returns the gem animation width
*/
public int getAnimationWidth() {
try {
return getAnimation().getFrame().getWidth();
}
catch (AnimationException e) {
getLogger().warning(e.getMessage());
}
return 0;
}
/**
* Set the gem sound
*
* @param sound The gem sound
*/
public void setSound(Sound sound) {
this.sound = sound;
}
/**
* Play the gem sound
*/
public void playGemSound() {
try {
sound.setVolume(getEscenario().getCanvas().getVolume());
sound.play();
}
catch (SoundException e) {
getLogger().warning(e.getMessage());
}
}
/** /**
* Set the gem type * Set the gem type
* *

View File

@ -48,19 +48,6 @@ public class Key extends Object implements Constantes {
super(escenario, celda); super(escenario, celda);
setLogger(getLogger(this.getClass(), LogLevel.KEY)); setLogger(getLogger(this.getClass(), LogLevel.KEY));
loadKeyAnimation(); loadKeyAnimation();
loadGetKeySound();
}
/**
* Load the key sound
*/
private void loadGetKeySound() {
try {
sound = new Sound("/snd/GetKey.wav");
}
catch (SoundException e) {
getLogger().warning(e.getMessage());
}
} }
/** /**
@ -113,6 +100,15 @@ public class Key extends Object implements Constantes {
} }
} }
/**
* Set the sound the key object will use
*
* @param sound The sound to use
*/
public void setSound(Sound sound) {
this.sound = sound;
}
/** /**
* Get the key * Get the key
*/ */

View File

@ -171,7 +171,7 @@ public class Object implements Runnable, Constantes {
* *
* @return Returns an animation * @return Returns an animation
*/ */
protected Animation getAnimation() { public Animation getAnimation() {
return animation; return animation;
} }

View File

@ -415,6 +415,7 @@ public class Player extends Object implements Constantes {
chest.setState(Chest.State.OPENING); chest.setState(Chest.State.OPENING);
Gem gem = chest.getGem(); Gem gem = chest.getGem();
if (gem != null) { if (gem != null) {
gem.playGemSound();
gem.getCelda().setObjectOnTop(gem); gem.getCelda().setObjectOnTop(gem);
getEscenario().getCanvas().getPortal().setState(Portal.State.ACTIVE); getEscenario().getCanvas().getPortal().setState(Portal.State.ACTIVE);
} }
@ -442,6 +443,24 @@ public class Player extends Object implements Constantes {
return false; return false;
} }
/**
* Check if player has a tainted gem in his inventory
*
* @return Returns true if he has one or false otherwise
*/
public boolean hasTaintedGem() {
boolean has = false;
for (Object object : carrying) {
if (object instanceof Gem) {
if (((Gem) object).getState() == Gem.State.TAINTED) {
has = true;
break;
}
}
}
return has;
}
/** /**
* Get the number of gems the player has * Get the number of gems the player has
* *
@ -474,21 +493,9 @@ public class Player extends Object implements Constantes {
/** /**
* This is called when the player gets attacked * This is called when the player gets attacked
*/ */
@SuppressWarnings("EmptyMethod")
public void attacked() { public void attacked() {
if (TRANSPORT_PLAYER_ON_ATTACK) { // TODO: what to do if the player gets attacked
if (PLAYER_AI) {
ai.setActive(false);
}
getCelda().setObject(null);
getAnimation().setCurrentDirection(Animation.Direction.DOWN);
setCelda(getEscenario().getCanvas().getPortal().getCelda());
getCelda().setObject(this);
setX(getCelda().getX());
setY(getCelda().getY());
if (PLAYER_AI) {
getEscenario().getCanvas().setupPlayerAI();
}
}
} }
/** /**
@ -578,8 +585,13 @@ public class Player extends Object implements Constantes {
getLogger().info(e.getMessage()); getLogger().info(e.getMessage());
} }
synchronized (this) { synchronized (this) {
loseHealth(1); if (health > 0) {
getEscenario().getCanvas().repaint(); loseHealth(1);
getEscenario().getCanvas().repaint();
}
else {
setActive(false);
}
} }
} }
} }

View File

@ -18,6 +18,8 @@ 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.sound.Sound;
import cl.cromer.azaraka.sound.SoundException;
import cl.cromer.azaraka.sprite.Animation; import cl.cromer.azaraka.sprite.Animation;
import cl.cromer.azaraka.sprite.AnimationException; import cl.cromer.azaraka.sprite.AnimationException;
@ -39,6 +41,10 @@ public class Portal extends Object implements Constantes {
* The inactive animation * The inactive animation
*/ */
private Animation inactiveAnimation; private Animation inactiveAnimation;
/**
* The portal sound when a gem is purified
*/
private Sound sound;
/** /**
* Initialize the portal * Initialize the portal
@ -94,12 +100,35 @@ public class Portal extends Object implements Constantes {
} }
} }
setState(State.INACTIVE); setState(State.INACTIVE);
playPortalSound();
if (gems.size() == 2) { if (gems.size() == 2) {
getEscenario().openDoor(true); getEscenario().openDoor(true);
} }
} }
} }
/**
* Set the portal sound
*
* @param sound The portal sound
*/
public void setSound(Sound sound) {
this.sound = sound;
}
/**
* Play the portal sound
*/
private void playPortalSound() {
try {
sound.setVolume(getEscenario().getCanvas().getVolume());
sound.play();
}
catch (SoundException e) {
getLogger().warning(e.getMessage());
}
}
/** /**
* This method animates the portal * This method animates the portal
*/ */
@ -118,6 +147,9 @@ public class Portal extends Object implements Constantes {
* @param state The new status * @param state The new status
*/ */
public void setState(State state) { public void setState(State state) {
if (state == State.ACTIVE && this.state == State.INACTIVE) {
playPortalSound();
}
this.state = state; this.state = state;
int frame = 0; int frame = 0;
try { try {

View File

@ -1,103 +0,0 @@
/*
* 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.panel;
import cl.cromer.azaraka.Constantes;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import java.awt.*;
import java.util.logging.Logger;
/**
* The config panel that is shown on the right
*/
public class Config extends JPanel implements Constantes {
/**
* The game panel to modify with the new configuration
*/
private final Game gamePanel;
/**
* The logger
*/
private final Logger logger;
/**
* The game panel used to modify with the new config
*
* @param gamePanel The game panel
*/
public Config(Game gamePanel) {
this.gamePanel = gamePanel;
logger = getLogger(this.getClass(), LogLevel.CONFIG);
JLabel speed = new JLabel("Speed");
speed.setForeground(Color.yellow);
speed.setFont(FONT);
speed.setHorizontalAlignment(JLabel.CENTER);
speed.setBackground(Color.gray);
JLabel volume = new JLabel("Volume");
volume.setForeground(Color.yellow);
volume.setFont(FONT);
volume.setHorizontalAlignment(JLabel.CENTER);
volume.setBackground(Color.gray);
JSlider changeVolume = new JSlider(JSlider.HORIZONTAL, MINIMUM_VOLUME, MAXIMUM_VOLUME, DEFAULT_VOLUME);
changeVolume.addChangeListener(this::volumeSliderListener);
changeVolume.setMajorTickSpacing(10);
changeVolume.setPaintTicks(true);
changeVolume.setBackground(Color.gray);
JSlider changeSpeed = new JSlider(JSlider.HORIZONTAL, MINIMUM_SPEED, MAXIMUM_SPEED, DEFAULT_SPEED);
changeSpeed.addChangeListener(this::speedSliderListener);
changeSpeed.setMajorTickSpacing(100);
changeSpeed.setPaintTicks(true);
changeSpeed.setBackground(Color.gray);
setLayout(new GridLayout(2, 2, 5, 5));
setBackground(Color.gray);
add(speed);
add(changeSpeed);
add(volume);
add(changeVolume);
}
/**
* Listener for the speed slider control
*
* @param changeEvent The event that caused the listener to fire
*/
private void speedSliderListener(ChangeEvent changeEvent) {
JSlider jSlider = (JSlider) changeEvent.getSource();
int speed = 500 - jSlider.getValue() + 100;
logger.info("Speed slider adjusted: " + speed);
gamePanel.getCanvas().changeSpeed(speed);
}
/**
* Listener for the volume slider control
*
* @param changeEvent The event that caused the listener to fire
*/
private void volumeSliderListener(ChangeEvent changeEvent) {
JSlider jSlider = (JSlider) changeEvent.getSource();
float volume = (float) jSlider.getValue() / 100;
gamePanel.getCanvas().changeVolume(volume);
}
}

View File

@ -1,55 +0,0 @@
/*
* 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.panel;
import cl.cromer.azaraka.Constantes;
import cl.cromer.azaraka.Lienzo;
import javax.swing.*;
import java.awt.*;
/**
* The game panel
*/
public class Game extends JPanel implements Constantes {
/**
* The canvas
*/
private final Lienzo canvas;
/**
* Initialize the game panel
*/
public Game() {
setLayout(new BorderLayout());
canvas = new Lienzo();
canvas.setFocusable(true);
canvas.requestFocus();
add(canvas);
setSize(canvas.getWidth(), canvas.getHeight());
}
/**
* Get the canvas
*
* @return Returns the game canvas
*/
public Lienzo getCanvas() {
return canvas;
}
}

View File

@ -1,19 +0,0 @@
/*
* 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 handles creating panels
*/
package cl.cromer.azaraka.panel;

View File

@ -64,7 +64,9 @@ public class Sound implements Constantes {
logger.warning(e.getMessage()); logger.warning(e.getMessage());
} }
finally { finally {
sound.stop(); if (sound != null && isPlaying()) {
sound.stop();
}
} }
logger.info("Opened sound: " + path); logger.info("Opened sound: " + path);
} }

BIN
src/main/resources/snd/Door.wav Executable file

Binary file not shown.

BIN
src/main/resources/snd/GetGem.wav Executable file

Binary file not shown.

BIN
src/main/resources/snd/Portal.wav Executable file

Binary file not shown.