diff --git a/core/src/logic/Creature.java b/core/src/logic/Creature.java index 03bea5d..4327d0e 100644 --- a/core/src/logic/Creature.java +++ b/core/src/logic/Creature.java @@ -8,7 +8,7 @@ import java.util.logging.Logger; import logic.neural.Brain; /** - * A (hopefully) smart biological creature. + * A (hopefully) smart biological creature in the simulated world. * * @author fazo */ @@ -24,6 +24,12 @@ public class Creature extends Element implements Runnable { private Sight[] sights; private Thread workerThread; + /** + * Create a creature with a random mind at given position in space + * + * @param x + * @param y + */ public Creature(float x, float y) { super(x, y, default_radius); dir = (float) (Math.random() * 2 * Math.PI); @@ -157,13 +163,14 @@ public class Creature extends Element implements Runnable { @Override public void render(ShapeRenderer s) { - // Body + // Draw Body s.setColor(1 - (hp / max_hp), hp / max_hp, 0, 1); s.circle(getX(), getY(), getSize()); - // Vision + // Prepare vision stuff double relX = Math.cos(dir), relY = Math.sin(dir); float c = 0; float eyeX = (float) (relX * getSize() * 0.6f), eyeY = (float) (relY * getSize() * 0.6f); + // Draw Sight Lines if (Game.get().getWorld().getOptions().getOrDefault("draw_sight_lines", 0f) > 0) { for (Sight sight : sights) { if (sight != null) { @@ -180,19 +187,21 @@ public class Creature extends Element implements Runnable { } } } + // Draw eye if (sights[0] == null && sights[1] == null) { s.setColor(1, 1, 1, 1); } else { s.setColor(sights[1] == null ? 0 : 1, sights[0] == null ? 0 : 1, 0, 1); } s.circle(getX() + eyeX, getY() + eyeY, 3); - //FOV + // Draw FOV cone float degrees = fov * 360f / (float) Math.PI; float orient = dir * 180f / (float) Math.PI - degrees / 2; if (Game.get().getWorld().getOptions().getOrDefault("draw_view_cones", 0f) > 0) { s.setColor(0.3f, 0.3f, 0.3f, 1); s.arc((float) eyeX + getX(), (float) eyeY + getY(), sightRange, orient, degrees); } + // Draw damage/heal marks if (hp < prevHp) { // Damage mark s.set(ShapeRenderer.ShapeType.Filled); @@ -205,11 +214,17 @@ public class Creature extends Element implements Runnable { s.circle(getX(), getY(), 5); } s.set(ShapeRenderer.ShapeType.Line); - // Beak + // Draw Beak s.setColor(beak / max_beak, 1 - beak / max_beak, 0, 1); s.line((float) (relX * getSize() * 0.8f + getX()), (float) (relY * getSize() * 0.8f + getY()), (float) (relX * getSize() * (1.5f + beak / max_beak) + getX()), (float) (relY * getSize() * (1.5f + beak / max_beak) + getY())); } + /** + * Store Sight information (what the creature sees) and eat/attack if + * applicable + * + * @return the sight information retrieved + */ public Sight[] interactWithWorld() { Sight[] newSights = new Sight[2]; // Try to see plant @@ -299,28 +314,21 @@ public class Creature extends Element implements Runnable { return newSights; } - public void eat() { - eating = false; - for (Element e : Game.get().getWorld().getPlants()) { - if (overlaps(e)) { - eating = true; - e.setSize(e.getSize() - 0.1f); - if (e.getSize() == 0) { - e.setSize(0); - } - hp++; - fitness++; - if (hp > max_hp) { - hp = max_hp; - } - } - } - } - + /** + * Apply a modification to this creature's health. Can be negative. + * + * @param amount how much to heal/damage + */ private void heal(float amount) { hp += amount; } + /** + * Praise this creature by increasing fitness. Can be negative to decrease + * fitness + * + * @param amount how much + */ private void praise(float amount) { fitness += amount; } @@ -340,9 +348,11 @@ public class Creature extends Element implements Runnable { public void startWorker() { workerDone = false; if (workerThread == null) { + // Create a new thread workerThread = new Thread(this); workerThread.start(); } else { + // Interrupt current thread, throwing it out of sleep workerThread.interrupt(); } } diff --git a/core/src/logic/Element.java b/core/src/logic/Element.java index 93d458a..a4e7773 100644 --- a/core/src/logic/Element.java +++ b/core/src/logic/Element.java @@ -1,13 +1,9 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ package logic; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; /** + * Represents a dynamic simulation element with a circular shape * * @author fazo */ @@ -15,35 +11,89 @@ public abstract class Element { private float x, y, size; + /** + * Create an element at given position with given radius. Elements have a + * circular shape. + * + * @param x the x position + * @param y the y position + * @param size the element body radius + */ public Element(float x, float y, float size) { this.x = x; this.y = y; this.size = size; } + /** + * Calculate the distance between the circumference of this element and + * another one's, taking into account the size. This means that two elements + * whose circumference is touching will have a distance of 0, and two + * overlapping elements will have a negative distance + * + * @param e the element whose distance from this one will be checked + * @return the distance from the element. It's 0 if they are just touching, + * negative if they are colliding and positive if they are not + */ public float distanceFrom(Element e) { return (float) Math.sqrt(Math.pow(e.x - x, 2) + Math.pow(e.y - y, 2)) - getSize() - e.getSize(); } + /** + * Checks if this element overlaps another one, causing a collision + * + * @param e the element which position will be checked to see if it overlaps + * this one + * @return true if the elements are overlapping, causing a collision + */ public boolean overlaps(Element e) { return distanceFrom(e) < 0; } + /** + * Checks if this element overlaps a circular object, causing a collision + * + * @param x the position of the center of the circular object + * @param y the position of the center of the circular object + * @param radius the radius of the circular object + * @return true if the object overlaps this one + */ public boolean overlaps(float x, float y, float radius) { return (float) Math.sqrt(Math.pow(x - this.x, 2) + Math.pow(y - this.y, 2)) < getSize() + radius; } + /** + * Check if a point in space occupies the same space as this object + * + * @param x the x position of the point to check + * @param y the y position of the point to check + * @return true if the point is part of this object's area + */ public boolean overlaps(float x, float y) { return overlaps(x, y, 1); } + /** + * Translate this object is space + * + * @param deltaX x translation + * @param deltaY y translation + */ public void move(float deltaX, float deltaY) { x += deltaX; y += deltaY; } + /** + * Run one iteration of this object's logic + */ public abstract void update(); + /** + * Draw this object + * + * @param s the ShapeRenderer used to draw this object + */ public abstract void render(ShapeRenderer s); public float getX() { diff --git a/core/src/logic/Sight.java b/core/src/logic/Sight.java index e2f8d43..6a0c1de 100644 --- a/core/src/logic/Sight.java +++ b/core/src/logic/Sight.java @@ -1,15 +1,12 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ package logic; /** + * Stores a sight of an element, as seen from a creature's eye * * @author fazo */ public class Sight { + private Element seen; private float distance, angle; @@ -30,5 +27,5 @@ public class Sight { public float getAngle() { return angle; } - + } diff --git a/core/src/logic/Vegetable.java b/core/src/logic/Vegetable.java index 6f6f6bb..0ffd711 100644 --- a/core/src/logic/Vegetable.java +++ b/core/src/logic/Vegetable.java @@ -1,15 +1,10 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ package logic; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.mygdx.game.Game; /** - * + * Represents a small plant-like vegetable with circular shape * @author fazo */ public class Vegetable extends Element { diff --git a/core/src/logic/World.java b/core/src/logic/World.java index 036cd49..6fe3dbe 100644 --- a/core/src/logic/World.java +++ b/core/src/logic/World.java @@ -1,8 +1,3 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ package logic; import com.mygdx.game.Game; @@ -18,6 +13,8 @@ import java.util.logging.Level; import java.util.logging.Logger; /** + * This class represents an instance of a simulation, its world and its + * configuration * * @author fazo */ @@ -40,6 +37,12 @@ public class World implements Runnable { private final ArrayList deadPlants; private final ArrayList listeners; + /** + * Create a new World. Can be customized with given options. + * + * @param options customization options. Can be null. See the + * "reloadOptions" function for possible options + */ public World(Map options) { if (options == null) { this.options = new HashMap(); @@ -240,6 +243,10 @@ public class World implements Runnable { } } + /** + * Applies current options. Uses default alternatives if options are not + * provided + */ public void reloadOptions() { width = Math.round(options.getOrDefault("world_width", 2000f)); height = Math.round(options.getOrDefault("world_height", 2000f)); @@ -262,6 +269,14 @@ public class World implements Runnable { mutationFactor = options.getOrDefault("nMutationFactor", 1f); } + /** + * Spawn a new random element in the world, at a random position. + * + * @param isCreature true if you want to spawn a creature + * @param brainMap the brain configuration. Used if spawning a creature. If + * null, a random mind will be created + * @return the spawned element + */ private Element spawn(boolean isCreature, float[][][] brainMap) { int x, y, r; boolean overlaps = false; @@ -301,6 +316,13 @@ public class World implements Runnable { } } + /** + * Sets currently select creature to the first creature that overlaps given + * coordinates + * + * @param x the x coordinate of the creature you want to select + * @param y the x coordinate of the creature you want to select + */ public void selectCreatureAt(int x, int y) { selected = null; // Clear selection try { @@ -314,6 +336,12 @@ public class World implements Runnable { } } + /** + * Fire an event + * + * @param eventCode the event code. Look at the Listener class for event + * codes. + */ public void fire(int eventCode) { for (Listener f : listeners) { f.on(eventCode); diff --git a/core/src/logic/neural/Brain.java b/core/src/logic/neural/Brain.java index 0311744..d7b12b9 100644 --- a/core/src/logic/neural/Brain.java +++ b/core/src/logic/neural/Brain.java @@ -83,7 +83,7 @@ public class Brain { } /** - * Draw this brain's status. + * Draw this brain's status to the screen. * * @param s the ShapeRenderer to use for the drawing */ @@ -224,6 +224,17 @@ public class Brain { } } + /** + * Combine this brain with another one's map to get an offspring. The brains + * must have identical neuron configuration. There are huge amounts of + * combinations, so you can call multiple times to get different children + * from the same parents + * + * @param map the brain to "breed" with this one + * @return a child brain from the two brains + * @throws Exception if the brains don't have identical neuron and layer + * numbers + */ public float[][][] breed(float[][][] map) throws Exception { float[][][] res = new float[neurons.length - 1][][]; if (map.length != neurons.length - 1) { @@ -237,9 +248,6 @@ public class Brain { } for (int j = 0; j < neurons[i].length; j++) // neurons per layer { - // j = 8 not valid for neurons[i][j]. investigate why. - //System.out.println(i+" "+j+" | "+neurons[i].length+" "+res[i-1].length+" "+neurons[i][j].getWeights().length); - //System.out.println(neurons[i].length +" has to be > "+j); res[i - 1][j] = new float[neurons[i][j].getWeights().length]; if (map[i - 1][j].length != neurons[i][j].getWeights().length) { throw new Exception("incompatible brains"); diff --git a/core/src/logic/neural/Neuron.java b/core/src/logic/neural/Neuron.java index 448a263..ce390da 100644 --- a/core/src/logic/neural/Neuron.java +++ b/core/src/logic/neural/Neuron.java @@ -1,8 +1,3 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ package logic.neural; import com.mygdx.game.Log; @@ -10,6 +5,7 @@ import java.util.logging.Level; import java.util.logging.Logger; /** + * A neuron in some brain. See Brain class. * * @author fazo */ @@ -22,10 +18,27 @@ public class Neuron { private int layer; private Brain brain; + /** + * Create a neuron with given brain, bias, and at the given layer with 0 + * being the input layer, with random weights + * + * @param layer the layer in which this neuron is positioned + * @param bias the bias of this neuron + * @param brain the brain which contains this neuron + */ public Neuron(int layer, float bias, Brain brain) { this(layer, bias, brain, null); } + /** + * Create a neuron with given brain, bias, and at the given layer with 0 + * being the input layer, with given weights + * + * @param layer the layer in which this neuron is positioned + * @param bias the bias of this neuron + * @param brain the brain which contains this neuron + * @param weights the weights to use to configure this neuron + */ public Neuron(int layer, float bias, Brain brain, float[] weights) { this.brain = brain; this.layer = layer; @@ -37,6 +50,9 @@ public class Neuron { cache = new NeuronCache(this.weights.length); } + /** + * Randomize the weights of this neuron + */ private void scramble() { // init weights if (layer > 0) { @@ -51,6 +67,14 @@ public class Neuron { } } + /** + * Compute the output of this neuron using the previous layer. Does nothing + * with input neurons. Uses a cache to store the output until it is + * invalidated by using the clearCache function. This function is recursive, + * meaning it will calculate all necessary neuron outputs to get this one. + * + * @return the output of this neuron. + */ public float compute() { if (isInputNeuron) { return output; @@ -67,16 +91,27 @@ public class Neuron { // This should never happen Logger.getLogger(Neuron.class.getName()).log(Level.SEVERE, null, ex); } + } else { + Neuron n = brain.getNeurons()[layer - 1][i]; + float v = n.compute() * weights[i]; + a += v; + cache.put(i, v); } - Neuron n = brain.getNeurons()[layer - 1][i]; - a += n.compute() * weights[i]; } // sigmoid function float res = (float) (1 / (1 + Math.pow(Math.E, a * -1))); + cache.setCachedOutput(res); Log.log(Log.DEBUG, "Computed Value " + res + " for neuron"); return res; } + /** + * Get a copy of the weights, with a mutation + * + * @param mutationProbability controls how many weights actually mutates + * @param mutationFactor controls how much weights mutate + * @return the new weights + */ public float[] getMutatedWeights(float mutationProbability, float mutationFactor) { float[] mutatedWeights = new float[weights.length]; for (int i = 0; i < weights.length; i++) { @@ -90,27 +125,10 @@ public class Neuron { } /** - * Broken, doesn't work for some reason. + * Use this to manually set the output of input neurons * - * @return + * @param output the output you want to set */ - public float[] getInputs() { - float inputs[] = new float[weights.length]; - for (int i = 0; i < inputs.length; i++) { - if (cache.has(i)) { - try { - inputs[i] = cache.get(i); - } catch (Exception ex) { - // Shouldnt happen - Logger.getLogger(Neuron.class.getName()).log(Level.SEVERE, null, ex); - } - } else { - inputs[i] = 0; - } - } - return inputs; - } - public void setOutput(float output) { isInputNeuron = true; this.output = output; @@ -140,6 +158,11 @@ public class Neuron { return weights; } + /** + * Change the neuron weights + * + * @param weights the new weights to put + */ public void setWeights(float[] weights) { this.weights = weights; // Changing the neuron makes the cache invalid