From 31d92b4cf519a8503f5b5034f50444e0a7c55c79 Mon Sep 17 00:00:00 2001 From: Enrico Fasoli Date: Sun, 9 Aug 2015 17:47:39 +0200 Subject: [PATCH 1/3] working on modular body parts --- core/src/com/mygdx/game/Game.java | 2 +- core/src/logic/Vegetable.java | 2 +- core/src/logic/World.java | 15 +- core/src/logic/creatures/Beak.java | 54 ++++++ core/src/logic/creatures/BodyPart.java | 57 ++++++ core/src/logic/{ => creatures}/Creature.java | 179 +++++++++---------- core/src/logic/creatures/Eye.java | 14 ++ core/src/logic/{ => creatures}/Sight.java | 4 +- core/src/logic/creatures/Torso.java | 90 ++++++++++ desktop/src/gui/GUI.java | 2 +- 10 files changed, 316 insertions(+), 103 deletions(-) create mode 100644 core/src/logic/creatures/Beak.java create mode 100644 core/src/logic/creatures/BodyPart.java rename core/src/logic/{ => creatures}/Creature.java (70%) create mode 100644 core/src/logic/creatures/Eye.java rename core/src/logic/{ => creatures}/Sight.java (91%) create mode 100644 core/src/logic/creatures/Torso.java 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/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..1d8e442 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,7 @@ import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; +import logic.creatures.Torso; import logic.neural.Brain; /** @@ -279,12 +281,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")); + Torso.default_radius = Math.round(options.get("creature_radius")); + Torso.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.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 +309,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..50b00fa --- /dev/null +++ b/core/src/logic/creatures/Beak.java @@ -0,0 +1,54 @@ +/* + * 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 static logic.creatures.Creature.fov; +import static logic.creatures.Creature.hpForAttacking; +import static logic.creatures.Creature.pointsForAttacking; + +/** + * + * @author fazo + */ +public class Beak extends BodyPart { + + private float length; + public static float max_length = Torso.default_radius / 4, min_length = max_length / 4; + + public Beak(float angle, Creature creature) { + super(1, 1, angle, 1, creature); + length = min_length; + } + + @Override + public float[] update() { + return new float[]{}; + } + + @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())); + } + + @Override + protected void useOutputs(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..3a9fbe1 --- /dev/null +++ b/core/src/logic/creatures/BodyPart.java @@ -0,0 +1,57 @@ +package logic.creatures; + +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; +import com.mygdx.game.Log; + +/** + * + * @author fazo + */ +public abstract class BodyPart { + + protected int inputNeuronsUsed, outputNeuronsUsed; + 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]; + } + + public abstract float[] update(); + + 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); + } + + protected abstract void useOutputs(float outputs[]); + + public int getInputNeuronsUsed() { + return inputNeuronsUsed; + } + + public int getOutputNeuronsUsed() { + return outputNeuronsUsed; + } + + public float getAngle() { + return angle; + } + + public float getDistanceFromCreatureCenter() { + return distFromCenter; + } + + public Creature getCreature() { + return creature; + } + +} diff --git a/core/src/logic/Creature.java b/core/src/logic/creatures/Creature.java similarity index 70% rename from core/src/logic/Creature.java rename to core/src/logic/creatures/Creature.java index d517cca..a7a0aaa 100644 --- a/core/src/logic/Creature.java +++ b/core/src/logic/creatures/Creature.java @@ -1,10 +1,13 @@ -package logic; +package logic.creatures; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.mygdx.game.Game; import com.mygdx.game.Log; +import java.util.ArrayList; import java.util.logging.Level; import java.util.logging.Logger; +import logic.Element; +import logic.Vegetable; import logic.neural.Brain; /** @@ -14,12 +17,15 @@ import logic.neural.Brain; */ 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 int brain_hidden_layers = 2, brain_hidden_neurons = 10; + public static float max_speed = 3, fov, sightRange, corpseDecayRate = 0, 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 final Brain brain; + private final Torso torso; + private final Beak beak; + private final ArrayList bodyParts; + private float dir, speed, fitness, rotSpeed; private boolean eating = false, killing = false, workerDone = false, killWorker = false; private Sight[] sights; private Thread workerThread; @@ -31,13 +37,14 @@ public class Creature extends Element implements Runnable { * @param y */ public Creature(float x, float y) { - super(x, y, default_radius); + super(x, y, Torso.default_radius); dir = (float) (Math.random() * 2 * Math.PI); - hp = max_hp; - prevHp = hp; speed = 0; rotSpeed = 0; fitness = 0; + bodyParts = new ArrayList(); + bodyParts.add(torso = new Torso(this)); + bodyParts.add(beak = new Beak(0, this)); brain = new Brain(9, 5, brain_hidden_layers, brain_hidden_neurons); sights = new Sight[2]; } @@ -54,16 +61,15 @@ public class Creature extends Element implements Runnable { update(); workerDone = true; } - if(killWorker) break; + if (killWorker) { + break; + } } } @Override public void update() { - // apply hunger - hp -= hpDecay; - prevHp = hp; - if (hp < 0) { // Dead + if (!torso.isAlive()) { // Dead Game.get().getWorld().getGraveyard().add(this); if (leaveCorpses) { Vegetable carcass = new Vegetable(getX(), getY()); @@ -85,28 +91,32 @@ public class Creature extends Element implements Runnable { move(xMul * speed, yMul * speed); if (getX() < 0) { setX(Game.get().getWorld().getWidth() + getX()); + } else if (getX() > Game.get().getWorld().getWidth()) { + setX(getX() - Game.get().getWorld().getWidth()); } 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()) { + } else 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) { + } else 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]; + int valueCounter = 0; + for (BodyPart b : bodyParts) { + for (float v : b.update()) { + values[valueCounter] = v; + valueCounter++; + } + } // VIEW: PLANTS // 0: sight(v): see food? // 1: sight(v): distance @@ -117,29 +127,29 @@ public class Creature extends Element implements Runnable { // 5: sight(c): distance // 6: sight(c): angle // 8: sight(c): hunger - // 7: sight(c): beak + // 7: sight(c): beak.getLength() // OTHER: // 8: food sensor int viewSensors = 4; for (int i = 0; i < sights.length; i++) { - int mul = i * viewSensors; + int offset = i * viewSensors + valueCounter; 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; + values[0 + offset] = 0; + values[1 + offset] = 0; + values[2 + offset] = 0; + values[3 + offset] = 0; } else { // See something - values[1 + mul] = sights[i].getDistance() / sightRange; - values[2 + mul] = sights[i].getAngle(); + values[1 + offset] = sights[i].getDistance() / sightRange; + values[2 + offset] = sights[i].getAngle(); if (sights[i].getElement() instanceof Vegetable) { - values[0 + mul] = 1f; - values[3 + mul] = sights[i].getElement().getSize() / default_radius; + values[0 + offset] = 1f; + values[3 + offset] = sights[i].getElement().getSize() / torso.getRadius(); } 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[0 + offset] = 1f; + values[3 + offset] = Torso.max_hp - ((Creature) sights[i].getElement()).getTorso().getHp() / Torso.max_hp; + values[3 + offset] = ((Creature) sights[i].getElement()).getBeak() / Beak.max_length; } } } @@ -152,27 +162,35 @@ public class Creature extends Element implements Runnable { // Should not happen Logger.getLogger(Creature.class.getName()).log(Level.SEVERE, null, ex); } + int 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.useOutputs(data); + } 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()); + for (BodyPart b : bodyParts) { + b.render(s); + } // 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 + s.set(ShapeRenderer.ShapeType.Line); if (Game.get().getWorld().getOptions().getOrDefault("draw_sight_lines", 0f) > 0) { for (Sight sight : sights) { if (sight != null) { @@ -203,22 +221,6 @@ public class Creature extends Element implements Runnable { 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())); } /** @@ -258,11 +260,8 @@ public class Creature extends Element implements Runnable { if (e.getSize() == 0) { e.setSize(0); } - hp += hpForEatingPlants; - fitness+=pointsForEatingPlants; - if (hp > max_hp) { - hp = max_hp; - } + torso.heal(hpForEatingPlants); + praise(pointsForEatingPlants); } } if (seen != null) { @@ -292,20 +291,7 @@ public class Creature extends Element implements Runnable { 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); } @@ -316,13 +302,20 @@ public class Creature extends Element implements Runnable { 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; + public int howManyInputNeurons() { + int n = 0; + for (BodyPart b : bodyParts) { + n += b.getInputNeuronsUsed(); + } + return n; + } + + public int howManyOutputNeurons() { + int n = 0; + for (BodyPart b : bodyParts) { + n += b.getOutputNeuronsUsed(); + } + return n; } /** @@ -367,24 +360,24 @@ public class Creature extends Element implements Runnable { this.dir = dir; } + public float getDirection() { + return dir; + } + public float getFitness() { return fitness; } - public void reset() { - fitness = 0; - hp = max_hp; - } - public float getBeak() { - return beak; + return beak.getLength(); } - public float getHp() { - return hp; + public Torso getTorso() { + return torso; } - public void setHp(float hp) { - this.hp = hp; + 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..64e9c06 --- /dev/null +++ b/core/src/logic/creatures/Eye.java @@ -0,0 +1,14 @@ +/* + * 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; + +/** + * + * @author fazo + */ +public class Eye { + +} 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..028030f --- /dev/null +++ b/core/src/logic/creatures/Torso.java @@ -0,0 +1,90 @@ +package logic.creatures; + +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +/** + * + * @author fazo + */ +public class Torso extends BodyPart { + + private float hp, prevHp, radius; + public static float default_radius = 20, max_hp = 100, hpDecay = 0.5f; + + public Torso(Creature c) { + super(2, 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) { + // Heal mark + s.setColor(0, 1, 0, 1); + } + if (getReceivedDamage() != 0) { + s.circle(x, y, 5); + } + } + + @Override + protected void useOutputs(float[] outputs) { + } + + @Override + public float[] update() { + // apply hunger + hp -= hpDecay; + prevHp = hp; + return new float[]{}; + } + + 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/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; /** From dd6b5d6b1b194639a91955f4cb634022aca3bfab Mon Sep 17 00:00:00 2001 From: Enrico Fasoli Date: Sun, 9 Aug 2015 22:52:02 +0200 Subject: [PATCH 2/3] completed the move to modular body system --- core/src/logic/World.java | 8 +- core/src/logic/creatures/Beak.java | 35 +++- core/src/logic/creatures/BodyPart.java | 32 +++- core/src/logic/creatures/Creature.java | 233 ++++--------------------- core/src/logic/creatures/Eye.java | 96 +++++++++- core/src/logic/creatures/Movement.java | 66 +++++++ core/src/logic/creatures/Torso.java | 56 ++++-- 7 files changed, 297 insertions(+), 229 deletions(-) create mode 100644 core/src/logic/creatures/Movement.java diff --git a/core/src/logic/World.java b/core/src/logic/World.java index 1d8e442..356210e 100644 --- a/core/src/logic/World.java +++ b/core/src/logic/World.java @@ -13,6 +13,8 @@ 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; @@ -283,9 +285,9 @@ public class World implements Runnable { Creature.leaveCorpses = options.get("enable_corpses") > 0; Torso.default_radius = Math.round(options.get("creature_radius")); Torso.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"); + 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"); diff --git a/core/src/logic/creatures/Beak.java b/core/src/logic/creatures/Beak.java index 50b00fa..576090a 100644 --- a/core/src/logic/creatures/Beak.java +++ b/core/src/logic/creatures/Beak.java @@ -6,9 +6,7 @@ package logic.creatures; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; -import static logic.creatures.Creature.fov; -import static logic.creatures.Creature.hpForAttacking; -import static logic.creatures.Creature.pointsForAttacking; +import logic.Element; /** * @@ -17,28 +15,49 @@ import static logic.creatures.Creature.pointsForAttacking; 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(1, 1, angle, 1, creature); + super(3, 1, angle, 1, creature); length = min_length; } @Override - public float[] update() { - return new float[]{}; + 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 - protected void useOutputs(float[] outputs) { + public void readFromBrain(float[] outputs) { length = outputs[0] * max_length; if (length > max_length) { length = max_length; diff --git a/core/src/logic/creatures/BodyPart.java b/core/src/logic/creatures/BodyPart.java index 3a9fbe1..3d8d547 100644 --- a/core/src/logic/creatures/BodyPart.java +++ b/core/src/logic/creatures/BodyPart.java @@ -1,7 +1,7 @@ package logic.creatures; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; -import com.mygdx.game.Log; +import logic.Element; /** * @@ -9,7 +9,7 @@ import com.mygdx.game.Log; */ public abstract class BodyPart { - protected int inputNeuronsUsed, outputNeuronsUsed; + protected int inputNeuronsUsed; protected float angle, distFromCenter; protected float outputs[]; protected Creature creature; @@ -22,7 +22,29 @@ public abstract class BodyPart { outputs = new float[outputNeuronsUsed]; } - public abstract float[] update(); + /** + * 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); @@ -31,15 +53,13 @@ public abstract class BodyPart { double relY = Math.sin(creature.getDirection() + angle) * creature.getTorso().getRadius() * distFromCenter; draw(s, (float) relX, (float) relY); } - - protected abstract void useOutputs(float outputs[]); public int getInputNeuronsUsed() { return inputNeuronsUsed; } public int getOutputNeuronsUsed() { - return outputNeuronsUsed; + return outputs.length; } public float getAngle() { diff --git a/core/src/logic/creatures/Creature.java b/core/src/logic/creatures/Creature.java index a7a0aaa..0a7a86a 100644 --- a/core/src/logic/creatures/Creature.java +++ b/core/src/logic/creatures/Creature.java @@ -2,7 +2,6 @@ package logic.creatures; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.mygdx.game.Game; -import com.mygdx.game.Log; import java.util.ArrayList; import java.util.logging.Level; import java.util.logging.Logger; @@ -18,15 +17,15 @@ import logic.neural.Brain; public class Creature extends Element implements Runnable { public static int brain_hidden_layers = 2, brain_hidden_neurons = 10; - public static float max_speed = 3, fov, sightRange, corpseDecayRate = 0, pointsForEatingPlants = 1f, pointsForAttacking = 2f, hpForAttacking = 1f, hpForEatingPlants = 1f; + 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, speed, fitness, rotSpeed; - private boolean eating = false, killing = false, workerDone = false, killWorker = false; + private float dir, fitness = 0; + private boolean workerDone = false, killWorker = false; private Sight[] sights; private Thread workerThread; @@ -39,13 +38,12 @@ public class Creature extends Element implements Runnable { public Creature(float x, float y) { super(x, y, Torso.default_radius); dir = (float) (Math.random() * 2 * Math.PI); - speed = 0; - rotSpeed = 0; - fitness = 0; bodyParts = new ArrayList(); bodyParts.add(torso = new Torso(this)); bodyParts.add(beak = new Beak(0, this)); - brain = new Brain(9, 5, brain_hidden_layers, brain_hidden_neurons); + 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]; } @@ -80,80 +78,17 @@ public class Creature extends Element implements Runnable { 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()); - } else if (getX() > Game.get().getWorld().getWidth()) { - setX(getX() - Game.get().getWorld().getWidth()); - } - if (getY() < 0) { - setY(Game.get().getWorld().getHeight() + getY()); - } else 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; - } else if (dir < 0) { - dir += 2 * Math.PI; + // 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 - sights = interactWithWorld(); - // feed data to brain - float[] values = new float[brain.getNeurons()[0].length]; - int valueCounter = 0; - for (BodyPart b : bodyParts) { - for (float v : b.update()) { - values[valueCounter] = v; - valueCounter++; - } - } - // 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.getLength() - // OTHER: - // 8: food sensor - int viewSensors = 4; - for (int i = 0; i < sights.length; i++) { - int offset = i * viewSensors + valueCounter; - if (sights[i] == null || sights[i].getElement() == null) { - // See nothing - values[0 + offset] = 0; - values[1 + offset] = 0; - values[2 + offset] = 0; - values[3 + offset] = 0; - } else { - // See something - values[1 + offset] = sights[i].getDistance() / sightRange; - values[2 + offset] = sights[i].getAngle(); - if (sights[i].getElement() instanceof Vegetable) { - values[0 + offset] = 1f; - values[3 + offset] = sights[i].getElement().getSize() / torso.getRadius(); - } else { - values[0 + offset] = 1f; - values[3 + offset] = Torso.max_hp - ((Creature) sights[i].getElement()).getTorso().getHp() / Torso.max_hp; - values[3 + offset] = ((Creature) sights[i].getElement()).getBeak() / Beak.max_length; - } - } - } - values[8] = eating || killing ? 1 : 0; + interactWithWorld(); // compute behavior float[] actions = null; try { @@ -162,7 +97,7 @@ public class Creature extends Element implements Runnable { // Should not happen Logger.getLogger(Creature.class.getName()).log(Level.SEVERE, null, ex); } - int i = 0; + i = 0; // Save brain outputs to body parts for (BodyPart b : bodyParts) { int n = 0; @@ -172,11 +107,8 @@ public class Creature extends Element implements Runnable { i++; n++; } - b.useOutputs(data); + b.readFromBrain(data); } - 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]; } @Override @@ -185,124 +117,22 @@ public class Creature extends Element implements Runnable { for (BodyPart b : bodyParts) { b.render(s); } - // 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 - s.set(ShapeRenderer.ShapeType.Line); - 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); - } } /** - * Store Sight information (what the creature sees) and eat/attack if - * applicable - * - * @return the sight information retrieved + * Make the body components interact with the world */ - 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); - } - torso.heal(hpForEatingPlants); - praise(pointsForEatingPlants); + 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); } } - 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; - - } - //Log.log(Log.DEBUG,"RelAngle "+relAngle+" Dir "+ndir); - } - } - if (seen != null) { - newSights[1] = new Sight(seen, dist, angle); - } - return newSights; } - public int howManyInputNeurons() { + public final int howManyInputNeurons() { int n = 0; for (BodyPart b : bodyParts) { n += b.getInputNeuronsUsed(); @@ -310,7 +140,7 @@ public class Creature extends Element implements Runnable { return n; } - public int howManyOutputNeurons() { + public final int howManyOutputNeurons() { int n = 0; for (BodyPart b : bodyParts) { n += b.getOutputNeuronsUsed(); @@ -318,13 +148,22 @@ public class Creature extends Element implements Runnable { 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 */ - private void praise(float amount) { + public void praise(float amount) { fitness += amount; } diff --git a/core/src/logic/creatures/Eye.java b/core/src/logic/creatures/Eye.java index 64e9c06..fc9fbbc 100644 --- a/core/src/logic/creatures/Eye.java +++ b/core/src/logic/creatures/Eye.java @@ -5,10 +5,102 @@ */ 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 { - +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/creatures/Torso.java b/core/src/logic/creatures/Torso.java index 028030f..701f68a 100644 --- a/core/src/logic/creatures/Torso.java +++ b/core/src/logic/creatures/Torso.java @@ -1,6 +1,8 @@ package logic.creatures; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; +import logic.Element; +import logic.Vegetable; /** * @@ -8,11 +10,12 @@ import com.badlogic.gdx.graphics.glutils.ShapeRenderer; */ public class Torso extends BodyPart { - private float hp, prevHp, radius; - public static float default_radius = 20, max_hp = 100, hpDecay = 0.5f; + 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(2, 0, 0, 0, c); + super(3, 0, 0, 0, c); radius = default_radius; hp = max_hp; prevHp = hp; @@ -21,31 +24,58 @@ public class Torso extends BodyPart { @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); + 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) { + } else if (getReceivedDamage() < 0 || eating) { // Heal mark s.setColor(0, 1, 0, 1); } - if (getReceivedDamage() != 0) { - s.circle(x, y, 5); + if (getReceivedDamage() != 0 || eating) { + s.circle(x + creature.getX(), y + creature.getY(), 5); } } @Override - protected void useOutputs(float[] outputs) { + 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 float[] update() { - // apply hunger - hp -= hpDecay; - prevHp = hp; - return new float[]{}; + 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() { From d070bfcae9561ced6b44b7e793cc3f71ac191420 Mon Sep 17 00:00:00 2001 From: Enrico Fasoli Date: Sun, 9 Aug 2015 22:52:35 +0200 Subject: [PATCH 3/3] ensure the gradle build system uses Java 8 --- core/build.gradle | 2 +- desktop/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/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"