diff --git a/core/src/com/mygdx/game/Listener.java b/core/src/com/mygdx/game/Listener.java index 57b58b0..fb12586 100644 --- a/core/src/com/mygdx/game/Listener.java +++ b/core/src/com/mygdx/game/Listener.java @@ -9,9 +9,16 @@ package com.mygdx.game; * * @author Fazo */ -public interface Listener { +public abstract class Listener { public static int FPS_CHANGED = 0, CREATURE_LIST_CHANGED = 1, PAUSED_OR_RESUMED = 2; - public void on(int event); + public void pollAndHandleEvents() { + if(Game.get() == null || Game.get().getWorld() == null) return; + while(Game.get().getWorld().getEventQueue().size() > 0) { + on(Game.get().getWorld().getEventQueue().poll()); + } + } + + public abstract void on(int event); } diff --git a/core/src/logic/World.java b/core/src/logic/World.java index b0bad0a..e492d9f 100644 --- a/core/src/logic/World.java +++ b/core/src/logic/World.java @@ -10,9 +10,12 @@ import java.util.Comparator; import java.util.ConcurrentModificationException; import java.util.Date; import java.util.HashMap; +import java.util.LinkedList; import java.util.Map; +import java.util.Queue; import java.util.logging.Level; import java.util.logging.Logger; +import logic.creatures.Beak; import logic.creatures.Eye; import logic.creatures.Movement; import logic.creatures.Torso; @@ -34,6 +37,7 @@ public class World implements Runnable { private Map options; private long ticksSinceGenStart = 0, maximumTicksPerGen = 0; private Creature selected; + private Queue events = new LinkedList<>(); private final Comparator creatureComp; private final ArrayList elements; private final ArrayList toAdd; @@ -41,7 +45,6 @@ public class World implements Runnable { private final ArrayList graveyard; private final ArrayList plants; private final ArrayList deadPlants; - private final ArrayList listeners; /** * Create a new World. Can be customized with given options. @@ -56,13 +59,13 @@ public class World implements Runnable { this.options = options; } reloadOptions(); + events = new LinkedList<>(); elements = new ArrayList(); creatures = new ArrayList(); toAdd = new ArrayList(); plants = new ArrayList(); deadPlants = new ArrayList(); graveyard = new ArrayList(); - listeners = new ArrayList(); selected = null; creatureComp = new Comparator() { @@ -333,7 +336,15 @@ public class World implements Runnable { } while (overlaps && i++ < 20); if (isCreature) { Log.log(Log.DEBUG, "New Creat: " + x + " " + y); - Creature c = new Creature(x, y); + Creature c = new Creature(x, y) { + + @Override + public void buildBody() { + addBodyPart(new Beak(0, this)); + addBodyPart(new Eye(5, 0, this)); + addBodyPart(new Movement(this)); + } + }; if (brainMap != null) { c.getBrain().remap(brainMap); } @@ -380,11 +391,13 @@ public class World implements Runnable { */ public void fire(int eventCode) { Log.log(Log.DEBUG, "Firing Event. Code: " + eventCode); - for (Listener f : listeners) { - f.on(eventCode); - } + events.add(eventCode); } + public Queue getEventQueue(){ + return events; + } + public void spawnVegetable() { spawn(false, null); } @@ -409,10 +422,6 @@ public class World implements Runnable { return generation; } - public void addListener(Listener f) { - listeners.add(f); - } - public void add(Element e) { toAdd.add(e); } diff --git a/core/src/logic/creatures/BodyPart.java b/core/src/logic/creatures/BodyPart.java index 3d8d547..ce8e64f 100644 --- a/core/src/logic/creatures/BodyPart.java +++ b/core/src/logic/creatures/BodyPart.java @@ -4,6 +4,8 @@ import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import logic.Element; /** + * A body part. Used in creatures. Extend this class to create custom body + * parts. * * @author fazo */ @@ -14,6 +16,16 @@ public abstract class BodyPart { protected float outputs[]; protected Creature creature; + /** + * Create an instance of a Body Part. + * + * @param inputNeuronsUsed how many input neurons it'll need + * @param outputNeuronsUsed how many output neurons it'll need + * @param angle the angle relative to the center of the creature + * @param distFromCenter how distance from the center of the creature is + * this body part + * @param creature the creature that owns this body part + */ public BodyPart(int inputNeuronsUsed, int outputNeuronsUsed, float angle, float distFromCenter, Creature creature) { this.inputNeuronsUsed = inputNeuronsUsed; this.angle = angle; @@ -23,14 +35,18 @@ public abstract class BodyPart { } /** - * Prepare data to be sent to the brain + * Prepare data to be sent to the brain. This is called once every frame, + * before the interactions with other elements. * * @return the data to send to the brain, must be inputNeuronsUsed long */ public abstract float[] act(); - + /** - * Interact with another element + * Interact with another element. This will be called every time the body + * part has a chance to interact with another element. act() will be called + * once every frame, before all the interactions. readFromBrain will be + * called once every frame, after all the interactions. * * @param e the Element (creature or plant) * @param distance the distance @@ -39,29 +55,56 @@ public abstract class BodyPart { public abstract void interactWithElement(Element e, float distance, float relAngle); /** - * Receive some data from the brain + * Receive some data from the brain. This is called once every frame, after + * interactions with other elements. * * @param data the data received from the brain, will be outputNeuronsUsed * long */ public abstract void readFromBrain(float data[]); + /** + * This will be called when the + * + * @param s the ShapeRenderer used to draw this body part. + * @param relX the X position of this bodypart relative to the center its + * creature + * @param relY the Y position of this bodypart relative to the center its + * creature + */ protected abstract void draw(ShapeRenderer s, float relX, float relY); - public void render(ShapeRenderer s) { + /** + * Prepares data and calls draw + * + * @param s the ShapeRenderer used to draw this body part. + */ + public final void render(ShapeRenderer s) { double relX = Math.cos(creature.getDirection() + angle) * creature.getTorso().getRadius() * distFromCenter; double relY = Math.sin(creature.getDirection() + angle) * creature.getTorso().getRadius() * distFromCenter; draw(s, (float) relX, (float) relY); } + /** + * + * @return how many input neurons are used by this body part + */ public int getInputNeuronsUsed() { return inputNeuronsUsed; } + /** + * + * @return how many output neurons are used by this body part + */ public int getOutputNeuronsUsed() { return outputs.length; } + /** + * + * @return the angle of this bodypart relative to the center of the creature + */ public float getAngle() { return angle; } diff --git a/core/src/logic/creatures/Creature.java b/core/src/logic/creatures/Creature.java index fc7a31e..9c2bf65 100644 --- a/core/src/logic/creatures/Creature.java +++ b/core/src/logic/creatures/Creature.java @@ -10,11 +10,14 @@ import logic.Vegetable; import logic.neural.Brain; /** - * A (hopefully) smart biological creature in the simulated world. + * A (hopefully) smart biological creature in the simulated world. It is + * initialized with a set of body parts. Every creature has a brain which gets + * automatically wired to the body parts. Only creatures with matching brain + * structures can breed for now. * * @author fazo */ -public class Creature extends Element implements Runnable { +public abstract class Creature extends Element implements Runnable { public static int brain_hidden_layers = 2, brain_hidden_neurons = 10; public static float corpseDecayRate = 0, pointsForEatingPlants = 1f, pointsForAttacking = 2f, hpForAttacking = 1f, hpForEatingPlants = 1f; @@ -25,7 +28,6 @@ public class Creature extends Element implements Runnable { private final ArrayList bodyParts; private float dir, fitness = 0; private boolean workerDone = false, killWorker = false; - private Sight[] sights; private Thread workerThread; /** @@ -39,11 +41,8 @@ public class Creature extends Element implements Runnable { dir = (float) (Math.random() * 2 * Math.PI); bodyParts = new ArrayList(); bodyParts.add(torso = new Torso(this)); - bodyParts.add(new Beak(0, this)); - bodyParts.add(new Eye(5, 0, this)); - bodyParts.add(new Movement(this)); + buildBody(); brain = new Brain(howManyInputNeurons(), howManyOutputNeurons(), brain_hidden_layers, brain_hidden_neurons); - sights = new Sight[2]; } @Override @@ -196,13 +195,24 @@ public class Creature extends Element implements Runnable { for (BodyPart b : bodyParts) { if (b instanceof Beak) { beaks++; - danger += (((Beak)b).getLength() - Beak.min_length) / Beak.max_length; + danger += (((Beak) b).getLength() - Beak.min_length) / Beak.max_length; } } - if(beaks == 0) return 0; + if (beaks == 0) { + return 0; + } return danger / beaks; } + /** + * Compose this creature's body using the addBodyPart function. + */ + public abstract void buildBody(); + + public void addBodyPart(BodyPart p) { + bodyParts.add(p); + } + public Brain getBrain() { return brain; } diff --git a/core/src/logic/neural/Brain.java b/core/src/logic/neural/Brain.java index 000b28f..30670f3 100644 --- a/core/src/logic/neural/Brain.java +++ b/core/src/logic/neural/Brain.java @@ -115,7 +115,9 @@ public class Brain { */ public void input(float[] values) throws Exception { if (values.length != neurons[0].length) { - throw new Exception("Not enough or too many inputs"); + throw new Exception("Brain has " + neurons[0].length + + " input neurons," + " but was supplied with " + + values.length + " inputs"); } for (int i = 0; i < values.length; i++) { neurons[0][i].setOutput(values[i]); diff --git a/desktop/src/gui/GUI.java b/desktop/src/gui/GUI.java index 61a6c5a..1f0cf27 100644 --- a/desktop/src/gui/GUI.java +++ b/desktop/src/gui/GUI.java @@ -41,13 +41,14 @@ import logic.World; * * @author fazo */ -public class GUI extends javax.swing.JFrame implements LogListener, Listener { +public class GUI extends javax.swing.JFrame implements LogListener { private Game game; private World world; private LwjglApplication app; private boolean shouldUpdateGUI = false; private final Thread guiUpdater; + private final Listener listener; private Map options; private boolean updatingSliders = false, updatingTable = false; @@ -59,6 +60,13 @@ public class GUI extends javax.swing.JFrame implements LogListener, Listener { currentFpsLimit.setText("" + fpsLimitSlider.getValue()); setLocationRelativeTo(null); // Center the window Log.addListener(this); + listener = new Listener() { + + @Override + public void on(int event) { + updateGUI(); + } + }; options = new HashMap(); world = new World(options); updateSettingsUI(); @@ -90,14 +98,10 @@ public class GUI extends javax.swing.JFrame implements LogListener, Listener { @Override public void run() { for (;;) { - if (shouldUpdateGUI) { - updateGUI(); - shouldUpdateGUI = false; - } else { - try { - Thread.sleep(5000); - } catch (InterruptedException ex) { - } + listener.pollAndHandleEvents(); + try { + Thread.sleep(100); + } catch (InterruptedException ex) { } } } @@ -885,7 +889,6 @@ public class GUI extends javax.swing.JFrame implements LogListener, Listener { app = new LwjglApplication(game = new Game(world), config); startButton.setText("Restart"); pauseButton.setEnabled(true); - world.addListener(this); setCreatureList(); } updateGUI(); @@ -900,12 +903,6 @@ public class GUI extends javax.swing.JFrame implements LogListener, Listener { setScrollBarToTheBottom(); } - @Override - public void on(int event) { - shouldUpdateGUI = true; - guiUpdater.interrupt(); - } - public void enableControlButtons(boolean yn) { pauseButton.setEnabled(yn); pauseMenuButton.setEnabled(yn);