diff --git a/core/build.gradle b/core/build.gradle index 03cd1be..13c049a 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,6 +1,6 @@ apply plugin: "java" -sourceCompatibility = 1.6 +sourceCompatibility = 1.8 [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' sourceSets.main.java.srcDirs = [ "src/" ] diff --git a/core/src/com/mygdx/game/Game.java b/core/src/com/mygdx/game/Game.java index a53c196..a68b6e3 100644 --- a/core/src/com/mygdx/game/Game.java +++ b/core/src/com/mygdx/game/Game.java @@ -9,7 +9,7 @@ import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.math.Vector3; import java.util.ConcurrentModificationException; import java.util.Map; -import logic.Creature; +import logic.creatures.Creature; import logic.Element; import logic.World; diff --git a/core/src/logic/Creature.java b/core/src/logic/Creature.java deleted file mode 100644 index d517cca..0000000 --- a/core/src/logic/Creature.java +++ /dev/null @@ -1,390 +0,0 @@ -package logic; - -import com.badlogic.gdx.graphics.glutils.ShapeRenderer; -import com.mygdx.game.Game; -import com.mygdx.game.Log; -import java.util.logging.Level; -import java.util.logging.Logger; -import logic.neural.Brain; - -/** - * A (hopefully) smart biological creature in the simulated world. - * - * @author fazo - */ -public class Creature extends Element implements Runnable { - - public static int default_radius = 20, max_hp = 100, brain_hidden_layers = 2, brain_hidden_neurons = 10; - public static float max_speed = 3, max_beak = default_radius / 4, fov, sightRange, corpseDecayRate = 0, hpDecay = 0.5f, pointsForEatingPlants = 1f, pointsForAttacking = 2f, hpForAttacking = 1f, hpForEatingPlants = 1f; - public static boolean leaveCorpses = false; - - private Brain brain; - private float dir, hp, prevHp, speed, fitness, rotSpeed, beak; - private boolean eating = false, killing = false, workerDone = false, killWorker = false; - 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); - hp = max_hp; - prevHp = hp; - speed = 0; - rotSpeed = 0; - fitness = 0; - brain = new Brain(9, 5, brain_hidden_layers, brain_hidden_neurons); - sights = new Sight[2]; - } - - @Override - public void run() { - for (;;) { - if (workerDone) { - try { - Thread.sleep(1000); - } catch (InterruptedException ex) { - } - } else { - update(); - workerDone = true; - } - if(killWorker) break; - } - } - - @Override - public void update() { - // apply hunger - hp -= hpDecay; - prevHp = hp; - if (hp < 0) { // Dead - Game.get().getWorld().getGraveyard().add(this); - if (leaveCorpses) { - Vegetable carcass = new Vegetable(getX(), getY()); - carcass.setSize(getSize()); - carcass.setDecayRate(corpseDecayRate); - Game.get().getWorld().add(carcass); - } - killWorker = true; - return; - } - if (speed > max_speed) { - speed = max_speed; - } - if (speed < -max_speed) { - speed = -max_speed; - } - // apply speed - float xMul = (float) Math.cos(dir), yMul = (float) Math.sin(dir); - move(xMul * speed, yMul * speed); - if (getX() < 0) { - setX(Game.get().getWorld().getWidth() + getX()); - } - if (getY() < 0) { - setY(Game.get().getWorld().getHeight() + getY()); - } - if (getX() > Game.get().getWorld().getWidth()) { - setX(getX() - Game.get().getWorld().getWidth()); - } - if (getY() > Game.get().getWorld().getHeight()) { - setY(getY() - Game.get().getWorld().getHeight()); - } - dir += rotSpeed; - //fitness -= 0.1; - if (dir > 2 * Math.PI) { - dir -= 2 * Math.PI; - } - if (dir < 0) { - dir += 2 * Math.PI; - } - // read from sensors and interact with world - sights = interactWithWorld(); - // feed data to brain - float[] values = new float[brain.getNeurons()[0].length]; - // VIEW: PLANTS - // 0: sight(v): see food? - // 1: sight(v): distance - // 2: sight(v): angle - // 3: sight(v): size - // VIEW: CREATS - // 4: sight(c): see food? - // 5: sight(c): distance - // 6: sight(c): angle - // 8: sight(c): hunger - // 7: sight(c): beak - // OTHER: - // 8: food sensor - int viewSensors = 4; - for (int i = 0; i < sights.length; i++) { - int mul = i * viewSensors; - if (sights[i] == null || sights[i].getElement() == null) { - // See nothing - values[0 + mul] = 0; - values[1 + mul] = 0; - values[2 + mul] = 0; - values[3 + mul] = 0; - } else { - // See something - values[1 + mul] = sights[i].getDistance() / sightRange; - values[2 + mul] = sights[i].getAngle(); - if (sights[i].getElement() instanceof Vegetable) { - values[0 + mul] = 1f; - values[3 + mul] = sights[i].getElement().getSize() / default_radius; - } else { - values[0 + mul] = 1f; - values[3 + mul] = max_hp - ((Creature) sights[i].getElement()).getHp() / max_hp; - values[3 + mul] = ((Creature) sights[i].getElement()).getBeak() / max_beak; - } - } - } - values[8] = eating || killing ? 1 : 0; - // compute behavior - float[] actions = null; - try { - actions = brain.compute(values); - } catch (Exception ex) { - // Should not happen - Logger.getLogger(Creature.class.getName()).log(Level.SEVERE, null, ex); - } - Log.log(Log.DEBUG, "Accel: " + actions[0] + " RotClock: " + actions[1] + " RotAntiClock: " + actions[2] + " Beak: " + actions[3]); - speed = (actions[0] * 2 - actions[4] / 2) * max_speed; - rotSpeed = actions[1] - actions[2]; - beak = actions[3] * max_beak; - if (beak > max_beak) { - beak = max_beak; - } else if (beak < 0) { - beak = 0; - } - } - - @Override - public void render(ShapeRenderer s) { - // Draw Body - s.setColor(1 - (hp / max_hp), hp / max_hp, 0, 1); - s.circle(getX(), getY(), getSize()); - // 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) { - c = sight.getDistance() / sightRange * 2 + sightRange; - } else { - } - if (sight != null) { - if (sight.getElement() instanceof Creature) { - s.setColor(c, 0, 0, 1); - } else if (sight.getElement() instanceof Vegetable) { - s.setColor(0, c, 0, 1); - } - s.line(eyeX + getX(), getY() + eyeY, sight.getElement().getX(), sight.getElement().getY()); - } - } - } - // 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); - // 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); - s.setColor(1, 0, 0, 1); - s.circle(getX(), getY(), 5); - } else if (killing || eating) { - // Heal mark - s.set(ShapeRenderer.ShapeType.Filled); - s.setColor(0, 1, 0, 1); - s.circle(getX(), getY(), 5); - } - s.set(ShapeRenderer.ShapeType.Line); - // 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 - Element seen = null; - float dist = 0, angle = 0, ndir = dir - (float) Math.PI; - eating = false; - for (Element e : Game.get().getWorld().getPlants()) { - float tempDist = distanceFrom(e); - if (tempDist > sightRange) { - continue; - } - //Log.log(Log.DEBUG,"TempDist "+tempDist+" SightRange "+sightRange); - float relAngle = (float) (Math.atan2(getY() - e.getY(), getX() - e.getX())); - if (tempDist < dist || seen == null) { - // Check if Visible - if (Math.abs(relAngle - ndir) < fov) { - // Visible - seen = e; - angle = relAngle - ndir; - dist = tempDist; - } - //Log.log(Log.DEBUG,"RelAngle "+relAngle+" Dir "+ndir); - } - // Check if eatable - if (tempDist < 0) { - // Eat - eating = true; - e.setSize(e.getSize() - 0.1f); - if (e.getSize() == 0) { - e.setSize(0); - } - hp += hpForEatingPlants; - fitness+=pointsForEatingPlants; - if (hp > max_hp) { - hp = max_hp; - } - } - } - if (seen != null) { - newSights[0] = new Sight(seen, dist, angle); - } - // Try to see creature - seen = null; - dist = 0; - angle = 0; - ndir = dir - (float) Math.PI; - killing = false; - for (Element e : Game.get().getWorld().getCreatures()) { - if (e == this) { - continue; - } - float tempDist = distanceFrom(e); - if (tempDist > sightRange) { - continue; - } - //Log.log(Log.DEBUG,"TempDist "+tempDist+" SightRange "+sightRange); - float relAngle = (float) (Math.atan2(getY() - e.getY(), getX() - e.getX())); - if (tempDist < dist || seen == null) { - // Check if Visible - float tempAngle = Math.abs(relAngle - ndir); - if (tempAngle < fov) { - // Visible - seen = e; - angle = relAngle - ndir; - dist = tempDist; - // Check if attackable - if (beak > beak / 2 && tempDist < beak * 1.5f && tempAngle < fov / 2) { - // Attacking! - float damage = beak * hpForAttacking / 2; - hp += damage; - fitness += pointsForAttacking; - if (hp > max_hp) { - hp = max_hp; - } - killing = true; - Creature c = (Creature) e; - c.heal(-damage); - //c.praise(-1); - } - } - //Log.log(Log.DEBUG,"RelAngle "+relAngle+" Dir "+ndir); - } - } - if (seen != null) { - newSights[1] = new Sight(seen, dist, angle); - } - return newSights; - } - - /** - * 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; - } - - /** - * Check if the Worker thread has finished its current iteration - * - * @return true if worker thread has finished its current iteration - */ - public boolean isWorkerDone() { - return workerDone; - } - - /** - * Command the Worker thread to start another iteration. - */ - 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(); - } - } - - public Brain getBrain() { - return brain; - } - - public void setDirection(float dir) { - this.dir = dir; - } - - public float getFitness() { - return fitness; - } - - public void reset() { - fitness = 0; - hp = max_hp; - } - - public float getBeak() { - return beak; - } - - public float getHp() { - return hp; - } - - public void setHp(float hp) { - this.hp = hp; - } -} diff --git a/core/src/logic/Vegetable.java b/core/src/logic/Vegetable.java index 0ffd711..cf1eaca 100644 --- a/core/src/logic/Vegetable.java +++ b/core/src/logic/Vegetable.java @@ -9,7 +9,7 @@ import com.mygdx.game.Game; */ public class Vegetable extends Element { - public static final int default_radius = 5; + public static final float default_radius = 5; private float decayRate = 0; public Vegetable(float x, float y) { diff --git a/core/src/logic/World.java b/core/src/logic/World.java index 61f9a67..356210e 100644 --- a/core/src/logic/World.java +++ b/core/src/logic/World.java @@ -1,5 +1,6 @@ package logic; +import logic.creatures.Creature; import com.mygdx.game.Game; import com.mygdx.game.Listener; import com.mygdx.game.Log; @@ -12,6 +13,9 @@ import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; +import logic.creatures.Eye; +import logic.creatures.Movement; +import logic.creatures.Torso; import logic.neural.Brain; /** @@ -279,12 +283,12 @@ public class World implements Runnable { multithreading = options.get("enable_multithreading") > 0; Creature.corpseDecayRate = options.get("corpse_decay_rate"); Creature.leaveCorpses = options.get("enable_corpses") > 0; - Creature.default_radius = Math.round(options.get("creature_radius")); - Creature.max_hp = Math.round(options.get("creature_max_hp")); - Creature.max_speed = options.get("creature_max_speed"); - Creature.fov = options.get("creature_fov"); - Creature.sightRange = options.get("creature_sight_range"); - Creature.hpDecay = options.get("creature_hp_decay"); + Torso.default_radius = Math.round(options.get("creature_radius")); + Torso.max_hp = Math.round(options.get("creature_max_hp")); + Movement.max_speed = options.get("creature_max_speed"); + Eye.fov = options.get("creature_fov"); + Eye.sightRange = options.get("creature_sight_range"); + Torso.hpDecay = options.get("creature_hp_decay"); Creature.hpForAttacking = options.get("creature_hp_for_attacking"); Creature.hpForEatingPlants = options.get("creature_hp_for_eating_plants"); Creature.pointsForAttacking = options.get("creature_points_for_attacking"); @@ -307,10 +311,11 @@ public class World implements Runnable { * @return the spawned element */ private Element spawn(boolean isCreature, float[][][] brainMap) { - int x, y, r; - boolean overlaps = false; + int x, y; + float r; + boolean overlaps; if (isCreature) { - r = Creature.default_radius; + r = Torso.default_radius; } else { r = Vegetable.default_radius; } diff --git a/core/src/logic/creatures/Beak.java b/core/src/logic/creatures/Beak.java new file mode 100644 index 0000000..576090a --- /dev/null +++ b/core/src/logic/creatures/Beak.java @@ -0,0 +1,73 @@ +/* + * 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.creatures; + +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; +import logic.Element; + +/** + * + * @author fazo + */ +public class Beak extends BodyPart { + + private float length; + private boolean attacking = false; + public static float max_length = Torso.default_radius / 4, min_length = max_length / 4; + + public Beak(float angle, Creature creature) { + super(3, 1, angle, 1, creature); + length = min_length; + } + + @Override + public float[] act() { + float r[] = new float[]{ + length / max_length, // current beak length + length > max_length / 2 ? 1f : 0f, // wether the beak is doing damage + attacking ? 1f : 0f + }; + attacking = false; + return r; + } + + @Override + public void interactWithElement(Element e, float distance, float relAngle) { + if (e instanceof Creature && distance < length && length > max_length / 2 && Math.abs(relAngle) < 0.3f) { + // Can attack + creature.praise(Creature.pointsForAttacking); + creature.getTorso().heal(length*Creature.hpForAttacking/2); + ((Creature) e).getTorso().heal(-length*Creature.hpForAttacking/2); + attacking = true; + } + } + + @Override + protected void draw(ShapeRenderer s, float relX, float relY) { + s.set(ShapeRenderer.ShapeType.Line); + // Draw Beak + s.setColor(getLength() / Beak.max_length, 1 - getLength() / Beak.max_length, 0, 1); + s.line((float) (relX + creature.getX()), (float) (relY + creature.getY()), (float) (relX * (1.5f + getLength() / Beak.max_length) + creature.getX()), (float) (relY * (1.5f + getLength() / Beak.max_length) + creature.getY())); + if (attacking) { + s.circle((float) (relX * (1.5f + getLength() / Beak.max_length) + creature.getX()), (float) (relY * (1.5f + getLength() / Beak.max_length) + creature.getY()), 0.3f); + } + } + + @Override + public void readFromBrain(float[] outputs) { + length = outputs[0] * max_length; + if (length > max_length) { + length = max_length; + } else if (length < 0) { + length = 0; + } + } + + public float getLength() { + return length; + } + +} diff --git a/core/src/logic/creatures/BodyPart.java b/core/src/logic/creatures/BodyPart.java new file mode 100644 index 0000000..3d8d547 --- /dev/null +++ b/core/src/logic/creatures/BodyPart.java @@ -0,0 +1,77 @@ +package logic.creatures; + +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; +import logic.Element; + +/** + * + * @author fazo + */ +public abstract class BodyPart { + + protected int inputNeuronsUsed; + protected float angle, distFromCenter; + protected float outputs[]; + protected Creature creature; + + public BodyPart(int inputNeuronsUsed, int outputNeuronsUsed, float angle, float distFromCenter, Creature creature) { + this.inputNeuronsUsed = inputNeuronsUsed; + this.angle = angle; + this.distFromCenter = distFromCenter; + this.creature = creature; + outputs = new float[outputNeuronsUsed]; + } + + /** + * Prepare data to be sent to the brain + * + * @return the data to send to the brain, must be inputNeuronsUsed long + */ + public abstract float[] act(); + + /** + * Interact with another element + * + * @param e the Element (creature or plant) + * @param distance the distance + * @param relAngle the relative angle + */ + public abstract void interactWithElement(Element e, float distance, float relAngle); + + /** + * Receive some data from the brain + * + * @param data the data received from the brain, will be outputNeuronsUsed + * long + */ + public abstract void readFromBrain(float data[]); + + protected abstract void draw(ShapeRenderer s, float relX, float relY); + + public 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); + } + + public int getInputNeuronsUsed() { + return inputNeuronsUsed; + } + + public int getOutputNeuronsUsed() { + return outputs.length; + } + + public float getAngle() { + return angle; + } + + public float getDistanceFromCreatureCenter() { + return distFromCenter; + } + + public Creature getCreature() { + return creature; + } + +} diff --git a/core/src/logic/creatures/Creature.java b/core/src/logic/creatures/Creature.java new file mode 100644 index 0000000..0a7a86a --- /dev/null +++ b/core/src/logic/creatures/Creature.java @@ -0,0 +1,222 @@ +package logic.creatures; + +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; +import com.mygdx.game.Game; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; +import logic.Element; +import logic.Vegetable; +import logic.neural.Brain; + +/** + * A (hopefully) smart biological creature in the simulated world. + * + * @author fazo + */ +public 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; + public static boolean leaveCorpses = false; + + private final Brain brain; + private final Torso torso; + private final Beak beak; + private final ArrayList bodyParts; + private float dir, fitness = 0; + private boolean workerDone = false, killWorker = false; + 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, Torso.default_radius); + dir = (float) (Math.random() * 2 * Math.PI); + bodyParts = new ArrayList(); + bodyParts.add(torso = new Torso(this)); + bodyParts.add(beak = new Beak(0, this)); + bodyParts.add(new Eye(5, 0, this)); + bodyParts.add(new Movement(this)); + brain = new Brain(howManyInputNeurons(), howManyOutputNeurons(), brain_hidden_layers, brain_hidden_neurons); + sights = new Sight[2]; + } + + @Override + public void run() { + for (;;) { + if (workerDone) { + try { + Thread.sleep(1000); + } catch (InterruptedException ex) { + } + } else { + update(); + workerDone = true; + } + if (killWorker) { + break; + } + } + } + + @Override + public void update() { + if (!torso.isAlive()) { // Dead + Game.get().getWorld().getGraveyard().add(this); + if (leaveCorpses) { + Vegetable carcass = new Vegetable(getX(), getY()); + carcass.setSize(getSize()); + carcass.setDecayRate(corpseDecayRate); + Game.get().getWorld().add(carcass); + } + killWorker = true; + return; + } + // collect inputs + float values[] = new float[howManyInputNeurons()]; + int i = 0; + for (BodyPart b : bodyParts) { + for (float v : b.act()) { + values[i] = v; + i++; + } + } + // read from sensors and interact with world + interactWithWorld(); + // compute behavior + float[] actions = null; + try { + actions = brain.compute(values); + } catch (Exception ex) { + // Should not happen + Logger.getLogger(Creature.class.getName()).log(Level.SEVERE, null, ex); + } + i = 0; + // Save brain outputs to body parts + for (BodyPart b : bodyParts) { + int n = 0; + float data[] = new float[b.getOutputNeuronsUsed()]; + while (n < b.getOutputNeuronsUsed()) { + data[n] = actions[i]; + i++; + n++; + } + b.readFromBrain(data); + } + } + + @Override + public void render(ShapeRenderer s) { + // Draw Body + for (BodyPart b : bodyParts) { + b.render(s); + } + } + + /** + * Make the body components interact with the world + */ + public void interactWithWorld() { + for (Element e : Game.get().getWorld().getElements()) { + float distance = distanceFrom(e); + float angle = (float) (Math.atan2(getY() - e.getY(), getX() - e.getX())) - (dir - (float) Math.PI); + for (BodyPart b : bodyParts) { + b.interactWithElement(e, distance, angle); + } + } + } + + public final int howManyInputNeurons() { + int n = 0; + for (BodyPart b : bodyParts) { + n += b.getInputNeuronsUsed(); + } + return n; + } + + public final int howManyOutputNeurons() { + int n = 0; + for (BodyPart b : bodyParts) { + n += b.getOutputNeuronsUsed(); + } + return n; + } + + public void rotate(float amount) { + dir += amount; + if (dir > 2 * Math.PI) { + dir -= 2 * Math.PI; + } else if (dir < 0) { + dir += 2 * Math.PI; + } + } + + /** + * Praise this creature by increasing fitness. Can be negative to decrease + * fitness + * + * @param amount how much + */ + public void praise(float amount) { + fitness += amount; + } + + /** + * Check if the Worker thread has finished its current iteration + * + * @return true if worker thread has finished its current iteration + */ + public boolean isWorkerDone() { + return workerDone; + } + + /** + * Command the Worker thread to start another iteration. + */ + 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(); + } + } + + public Brain getBrain() { + return brain; + } + + public void setDirection(float dir) { + this.dir = dir; + } + + public float getDirection() { + return dir; + } + + public float getFitness() { + return fitness; + } + + public float getBeak() { + return beak.getLength(); + } + + public Torso getTorso() { + return torso; + } + + public ArrayList getBodyParts() { + return bodyParts; + } + +} diff --git a/core/src/logic/creatures/Eye.java b/core/src/logic/creatures/Eye.java new file mode 100644 index 0000000..fc9fbbc --- /dev/null +++ b/core/src/logic/creatures/Eye.java @@ -0,0 +1,106 @@ +/* + * 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.creatures; + +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; +import com.mygdx.game.Game; +import com.mygdx.game.Log; +import logic.Element; +import logic.Vegetable; + +/** + * + * @author fazo + */ +public class Eye extends BodyPart { + + private Sight sights[]; + private int seen; + public static float fov = 2, sightRange = 30; + + public Eye(int nSights, float angle, Creature creature) { + super(6 * nSights, 0, angle, 0.8f, creature); + sights = new Sight[nSights]; + seen = 0; + } + + @Override + public float[] act() { + float ret[] = new float[inputNeuronsUsed]; + int j = 0; + for (int i = 0; i < sights.length; i++) { + if (i < seen) { + // Saw something + ret[j] = 1; + ret[j + 1] = sights[i].getElement() instanceof Creature ? 1 : 0; + ret[j + 2] = sights[i].getDistance() / sightRange; + ret[j + 3] = sights[i].getAngle(); + if (sights[i].getElement() instanceof Creature) { + ret[i + 4] = ((Creature) sights[i].getElement()).getBeak() / Beak.max_length; + ret[i + 5] = ((Creature) sights[i].getElement()).getTorso().getHp() / Torso.max_hp; + } else { + ret[i + 4] = ((Vegetable) sights[i].getElement()).getSize() / Vegetable.default_radius; + ret[i + 5] = 0; + } + } else { + // Saw nothing + for (int z = 0; z < 6; z++) { + ret[j + z] = 0; + } + } + j += 6; + } + seen = 0; + sights = new Sight[sights.length]; + return ret; + } + + + @Override + public void interactWithElement(Element e, float distance, float angle) { + if (e != creature && distance < sightRange && Math.abs(angle) < fov / 2) { + if (seen < sights.length) { + sights[seen] = new Sight(e, distance, angle); + Log.log(Log.DEBUG, "Saw " + e.getClass().getName() + " at " + distance + " with relative angle " + angle); + seen++; + } + } + } + + @Override + protected void draw(ShapeRenderer s, float relX, float relY) { + // Draw eye + s.setColor(1, 1, 1, 1); + s.circle(creature.getX() + relX, creature.getY() + relY, 3); + // Draw FOV cone + float degrees = fov * 360f / (float) Math.PI; + float orient = (creature.getDirection() + angle) * 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) relX + creature.getX(), (float) relY + creature.getY(), sightRange, orient, degrees); + } + // Sight Lines + float c = 0; + if (Game.get().getWorld().getOptions().getOrDefault("draw_sight_lines", 0f) > 0) { + for (Sight sight : sights) { + if (sight != null) { + c = sight.getDistance() / sightRange * 2 + sightRange; + if (sight.getElement() instanceof Creature) { + s.setColor(c, 0, 0, 1); + } else if (sight.getElement() instanceof Vegetable) { + s.setColor(0, c, 0, 1); + } + s.line(relX + creature.getX(), creature.getY() + relY, sight.getElement().getX(), sight.getElement().getY()); + } + } + } + } + + @Override + public void readFromBrain(float[] data) { + } + +} diff --git a/core/src/logic/creatures/Movement.java b/core/src/logic/creatures/Movement.java new file mode 100644 index 0000000..c197469 --- /dev/null +++ b/core/src/logic/creatures/Movement.java @@ -0,0 +1,66 @@ +/* + * 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.creatures; + +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; +import com.mygdx.game.Game; +import com.mygdx.game.Log; +import logic.Element; + +/** + * + * @author Fazo + */ +public class Movement extends BodyPart { + + private float speed = 0, rotSpeed = 0; + public static float max_speed = 3; + + public Movement(Creature creature) { + super(0, 4, 0, 0, creature); + } + + @Override + public float[] act() { + if (speed > max_speed) { + speed = max_speed; + } + if (speed < -max_speed) { + speed = -max_speed; + } + // apply speed + float xMul = (float) Math.cos(creature.getDirection()), yMul = (float) Math.sin(creature.getDirection()); + creature.move(xMul * speed, yMul * speed); + if (creature.getX() < 0) { + creature.setX(Game.get().getWorld().getWidth() + creature.getX()); + } else if (creature.getX() > Game.get().getWorld().getWidth()) { + creature.setX(creature.getX() - Game.get().getWorld().getWidth()); + } + if (creature.getY() < 0) { + creature.setY(Game.get().getWorld().getHeight() + creature.getY()); + } else if (creature.getY() > Game.get().getWorld().getHeight()) { + creature.setY(creature.getY() - Game.get().getWorld().getHeight()); + } + creature.rotate(rotSpeed); + return new float[]{}; + } + + @Override + public void interactWithElement(Element e, float distance, float relAngle) { + } + + @Override + protected void draw(ShapeRenderer s, float relX, float relY) { + } + + @Override + public void readFromBrain(float[] data) { + Log.log(Log.DEBUG, "Fowward: " + data[0] + "Back: " + data[1] + " Rot: " + data[2] + " RotAnti: " + data[3]); + speed = (data[0] * 2 - data[1] / 2) * max_speed; + rotSpeed = data[2] - data[3]; + } + +} diff --git a/core/src/logic/Sight.java b/core/src/logic/creatures/Sight.java similarity index 91% rename from core/src/logic/Sight.java rename to core/src/logic/creatures/Sight.java index 6a0c1de..27098a8 100644 --- a/core/src/logic/Sight.java +++ b/core/src/logic/creatures/Sight.java @@ -1,4 +1,6 @@ -package logic; +package logic.creatures; + +import logic.Element; /** * Stores a sight of an element, as seen from a creature's eye diff --git a/core/src/logic/creatures/Torso.java b/core/src/logic/creatures/Torso.java new file mode 100644 index 0000000..701f68a --- /dev/null +++ b/core/src/logic/creatures/Torso.java @@ -0,0 +1,120 @@ +package logic.creatures; + +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; +import logic.Element; +import logic.Vegetable; + +/** + * + * @author fazo + */ +public class Torso extends BodyPart { + + private float hp, prevHp, radius, pain = 0; + public static float default_radius = 20, max_hp = 100, hpDecay = 0.5f, eatingSpeed = 0.1f; + private boolean eating = false; + + public Torso(Creature c) { + super(3, 0, 0, 0, c); + radius = default_radius; + hp = max_hp; + prevHp = hp; + } + + @Override + public void draw(ShapeRenderer s, float x, float y) { + s.setColor(1 - (hp / max_hp), hp / max_hp, 0, 1); + s.circle(x + creature.getX(), y + creature.getY(), radius); + // Draw damage/heal marks + s.set(ShapeRenderer.ShapeType.Filled); + if (getReceivedDamage() > 0) { + // Damage mark + s.setColor(1, 0, 0, 1); + } else if (getReceivedDamage() < 0 || eating) { + // Heal mark + s.setColor(0, 1, 0, 1); + } + if (getReceivedDamage() != 0 || eating) { + s.circle(x + creature.getX(), y + creature.getY(), 5); + } + } + + @Override + public float[] act() { + // apply hunger + hp -= hpDecay; + float r[] = new float[]{ + hp/max_hp, + eating ? 1f : 0f, + pain + }; + pain = 0; + prevHp = hp; + eating = false; + return r; + } + + @Override + public void interactWithElement(Element e, float distance, float relAngle) { + if (e instanceof Vegetable && distance < 0 && hp < max_hp) { + e.setSize(e.getSize() - eatingSpeed); + if (e.getSize() == 0) { + e.setSize(0); + } + heal(Creature.hpForEatingPlants); + creature.praise(Creature.pointsForEatingPlants); + eating = true; + } + } + + @Override + public void readFromBrain(float[] data) { + if (getReceivedDamage() > 0) { + pain = -1; + } else if (getReceivedDamage() < 0) { + pain = 1; + } else { + pain = 0; + } + } + + public boolean isAlive() { + return hp > 0; + } + + /** + * Apply a modification to this creature's health. Can be negative. + * + * @param amount how much to heal/damage + */ + public void heal(float amount) { + hp += amount; + if (hp < 0) { + hp = 0; + } + if (hp > max_hp) { + hp = max_hp; + } + } + + public float getReceivedDamage() { + return prevHp - hp; + } + + public float getRadius() { + return radius; + } + + public void setRadius(float radius) { + this.radius = radius; + } + + public float getHp() { + return hp; + } + + public void setHp(float hp) { + this.hp = hp; + } + +} diff --git a/desktop/build.gradle b/desktop/build.gradle index d11233f..7266caa 100644 --- a/desktop/build.gradle +++ b/desktop/build.gradle @@ -1,6 +1,6 @@ apply plugin: "java" -sourceCompatibility = 1.6 +sourceCompatibility = 1.8 sourceSets.main.java.srcDirs = [ "src/" ] project.ext.mainClassName = "com.mygdx.game.desktop.DesktopLauncher" diff --git a/desktop/src/gui/GUI.java b/desktop/src/gui/GUI.java index f9ab4be..76f569d 100644 --- a/desktop/src/gui/GUI.java +++ b/desktop/src/gui/GUI.java @@ -33,7 +33,7 @@ import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableModel; -import logic.Creature; +import logic.creatures.Creature; import logic.World; /**