From 31d92b4cf519a8503f5b5034f50444e0a7c55c79 Mon Sep 17 00:00:00 2001 From: Enrico Fasoli Date: Sun, 9 Aug 2015 17:47:39 +0200 Subject: [PATCH] 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; /**