1
0
mirror of https://github.com/fazo96/AIrium.git synced 2025-01-10 09:34:20 +01:00

improved API and docs

This commit is contained in:
Enrico Fasoli 2015-08-18 21:59:58 +02:00
parent 4528f787cb
commit 7650ff44eb
6 changed files with 111 additions and 43 deletions

View File

@ -9,9 +9,16 @@ package com.mygdx.game;
* *
* @author Fazo * @author Fazo
*/ */
public interface Listener { public abstract class Listener {
public static int FPS_CHANGED = 0, CREATURE_LIST_CHANGED = 1, PAUSED_OR_RESUMED = 2; 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);
} }

View File

@ -10,9 +10,12 @@ import java.util.Comparator;
import java.util.ConcurrentModificationException; import java.util.ConcurrentModificationException;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map; import java.util.Map;
import java.util.Queue;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import logic.creatures.Beak;
import logic.creatures.Eye; import logic.creatures.Eye;
import logic.creatures.Movement; import logic.creatures.Movement;
import logic.creatures.Torso; import logic.creatures.Torso;
@ -34,6 +37,7 @@ public class World implements Runnable {
private Map<String, Float> options; private Map<String, Float> options;
private long ticksSinceGenStart = 0, maximumTicksPerGen = 0; private long ticksSinceGenStart = 0, maximumTicksPerGen = 0;
private Creature selected; private Creature selected;
private Queue<Integer> events = new LinkedList<>();
private final Comparator creatureComp; private final Comparator creatureComp;
private final ArrayList<Element> elements; private final ArrayList<Element> elements;
private final ArrayList<Element> toAdd; private final ArrayList<Element> toAdd;
@ -41,7 +45,6 @@ public class World implements Runnable {
private final ArrayList<Creature> graveyard; private final ArrayList<Creature> graveyard;
private final ArrayList<Vegetable> plants; private final ArrayList<Vegetable> plants;
private final ArrayList<Vegetable> deadPlants; private final ArrayList<Vegetable> deadPlants;
private final ArrayList<Listener> listeners;
/** /**
* Create a new World. Can be customized with given options. * Create a new World. Can be customized with given options.
@ -56,13 +59,13 @@ public class World implements Runnable {
this.options = options; this.options = options;
} }
reloadOptions(); reloadOptions();
events = new LinkedList<>();
elements = new ArrayList(); elements = new ArrayList();
creatures = new ArrayList(); creatures = new ArrayList();
toAdd = new ArrayList(); toAdd = new ArrayList();
plants = new ArrayList(); plants = new ArrayList();
deadPlants = new ArrayList(); deadPlants = new ArrayList();
graveyard = new ArrayList(); graveyard = new ArrayList();
listeners = new ArrayList();
selected = null; selected = null;
creatureComp = new Comparator<Creature>() { creatureComp = new Comparator<Creature>() {
@ -333,7 +336,15 @@ public class World implements Runnable {
} while (overlaps && i++ < 20); } while (overlaps && i++ < 20);
if (isCreature) { if (isCreature) {
Log.log(Log.DEBUG, "New Creat: " + x + " " + y); 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) { if (brainMap != null) {
c.getBrain().remap(brainMap); c.getBrain().remap(brainMap);
} }
@ -380,11 +391,13 @@ public class World implements Runnable {
*/ */
public void fire(int eventCode) { public void fire(int eventCode) {
Log.log(Log.DEBUG, "Firing Event. Code: " + eventCode); Log.log(Log.DEBUG, "Firing Event. Code: " + eventCode);
for (Listener f : listeners) { events.add(eventCode);
f.on(eventCode);
}
} }
public Queue<Integer> getEventQueue(){
return events;
}
public void spawnVegetable() { public void spawnVegetable() {
spawn(false, null); spawn(false, null);
} }
@ -409,10 +422,6 @@ public class World implements Runnable {
return generation; return generation;
} }
public void addListener(Listener f) {
listeners.add(f);
}
public void add(Element e) { public void add(Element e) {
toAdd.add(e); toAdd.add(e);
} }

View File

@ -4,6 +4,8 @@ import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import logic.Element; import logic.Element;
/** /**
* A body part. Used in creatures. Extend this class to create custom body
* parts.
* *
* @author fazo * @author fazo
*/ */
@ -14,6 +16,16 @@ public abstract class BodyPart {
protected float outputs[]; protected float outputs[];
protected Creature creature; 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) { public BodyPart(int inputNeuronsUsed, int outputNeuronsUsed, float angle, float distFromCenter, Creature creature) {
this.inputNeuronsUsed = inputNeuronsUsed; this.inputNeuronsUsed = inputNeuronsUsed;
this.angle = angle; 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 * @return the data to send to the brain, must be inputNeuronsUsed long
*/ */
public abstract float[] act(); 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 e the Element (creature or plant)
* @param distance the distance * @param distance the distance
@ -39,29 +55,56 @@ public abstract class BodyPart {
public abstract void interactWithElement(Element e, float distance, float relAngle); 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 * @param data the data received from the brain, will be outputNeuronsUsed
* long * long
*/ */
public abstract void readFromBrain(float data[]); 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); 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 relX = Math.cos(creature.getDirection() + angle) * creature.getTorso().getRadius() * distFromCenter;
double relY = Math.sin(creature.getDirection() + angle) * creature.getTorso().getRadius() * distFromCenter; double relY = Math.sin(creature.getDirection() + angle) * creature.getTorso().getRadius() * distFromCenter;
draw(s, (float) relX, (float) relY); draw(s, (float) relX, (float) relY);
} }
/**
*
* @return how many input neurons are used by this body part
*/
public int getInputNeuronsUsed() { public int getInputNeuronsUsed() {
return inputNeuronsUsed; return inputNeuronsUsed;
} }
/**
*
* @return how many output neurons are used by this body part
*/
public int getOutputNeuronsUsed() { public int getOutputNeuronsUsed() {
return outputs.length; return outputs.length;
} }
/**
*
* @return the angle of this bodypart relative to the center of the creature
*/
public float getAngle() { public float getAngle() {
return angle; return angle;
} }

View File

@ -10,11 +10,14 @@ import logic.Vegetable;
import logic.neural.Brain; 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 * @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 int brain_hidden_layers = 2, brain_hidden_neurons = 10;
public static float corpseDecayRate = 0, pointsForEatingPlants = 1f, pointsForAttacking = 2f, hpForAttacking = 1f, hpForEatingPlants = 1f; 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<BodyPart> bodyParts; private final ArrayList<BodyPart> bodyParts;
private float dir, fitness = 0; private float dir, fitness = 0;
private boolean workerDone = false, killWorker = false; private boolean workerDone = false, killWorker = false;
private Sight[] sights;
private Thread workerThread; private Thread workerThread;
/** /**
@ -39,11 +41,8 @@ public class Creature extends Element implements Runnable {
dir = (float) (Math.random() * 2 * Math.PI); dir = (float) (Math.random() * 2 * Math.PI);
bodyParts = new ArrayList<BodyPart>(); bodyParts = new ArrayList<BodyPart>();
bodyParts.add(torso = new Torso(this)); bodyParts.add(torso = new Torso(this));
bodyParts.add(new Beak(0, this)); buildBody();
bodyParts.add(new Eye(5, 0, this));
bodyParts.add(new Movement(this));
brain = new Brain(howManyInputNeurons(), howManyOutputNeurons(), brain_hidden_layers, brain_hidden_neurons); brain = new Brain(howManyInputNeurons(), howManyOutputNeurons(), brain_hidden_layers, brain_hidden_neurons);
sights = new Sight[2];
} }
@Override @Override
@ -196,13 +195,24 @@ public class Creature extends Element implements Runnable {
for (BodyPart b : bodyParts) { for (BodyPart b : bodyParts) {
if (b instanceof Beak) { if (b instanceof Beak) {
beaks++; 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; 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() { public Brain getBrain() {
return brain; return brain;
} }

View File

@ -115,7 +115,9 @@ public class Brain {
*/ */
public void input(float[] values) throws Exception { public void input(float[] values) throws Exception {
if (values.length != neurons[0].length) { 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++) { for (int i = 0; i < values.length; i++) {
neurons[0][i].setOutput(values[i]); neurons[0][i].setOutput(values[i]);

View File

@ -41,13 +41,14 @@ import logic.World;
* *
* @author fazo * @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 Game game;
private World world; private World world;
private LwjglApplication app; private LwjglApplication app;
private boolean shouldUpdateGUI = false; private boolean shouldUpdateGUI = false;
private final Thread guiUpdater; private final Thread guiUpdater;
private final Listener listener;
private Map<String, Float> options; private Map<String, Float> options;
private boolean updatingSliders = false, updatingTable = false; private boolean updatingSliders = false, updatingTable = false;
@ -59,6 +60,13 @@ public class GUI extends javax.swing.JFrame implements LogListener, Listener {
currentFpsLimit.setText("" + fpsLimitSlider.getValue()); currentFpsLimit.setText("" + fpsLimitSlider.getValue());
setLocationRelativeTo(null); // Center the window setLocationRelativeTo(null); // Center the window
Log.addListener(this); Log.addListener(this);
listener = new Listener() {
@Override
public void on(int event) {
updateGUI();
}
};
options = new HashMap<String, Float>(); options = new HashMap<String, Float>();
world = new World(options); world = new World(options);
updateSettingsUI(); updateSettingsUI();
@ -90,14 +98,10 @@ public class GUI extends javax.swing.JFrame implements LogListener, Listener {
@Override @Override
public void run() { public void run() {
for (;;) { for (;;) {
if (shouldUpdateGUI) { listener.pollAndHandleEvents();
updateGUI(); try {
shouldUpdateGUI = false; Thread.sleep(100);
} else { } catch (InterruptedException ex) {
try {
Thread.sleep(5000);
} 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); app = new LwjglApplication(game = new Game(world), config);
startButton.setText("Restart"); startButton.setText("Restart");
pauseButton.setEnabled(true); pauseButton.setEnabled(true);
world.addListener(this);
setCreatureList(); setCreatureList();
} }
updateGUI(); updateGUI();
@ -900,12 +903,6 @@ public class GUI extends javax.swing.JFrame implements LogListener, Listener {
setScrollBarToTheBottom(); setScrollBarToTheBottom();
} }
@Override
public void on(int event) {
shouldUpdateGUI = true;
guiUpdater.interrupt();
}
public void enableControlButtons(boolean yn) { public void enableControlButtons(boolean yn) {
pauseButton.setEnabled(yn); pauseButton.setEnabled(yn);
pauseMenuButton.setEnabled(yn); pauseMenuButton.setEnabled(yn);