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

completed the move to modular body system

This commit is contained in:
Enrico Fasoli 2015-08-09 22:52:02 +02:00
parent 31d92b4cf5
commit dd6b5d6b1b
7 changed files with 297 additions and 229 deletions

View File

@ -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");

View File

@ -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;

View File

@ -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() {

View File

@ -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<BodyPart> 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<BodyPart>();
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;
}

View File

@ -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) {
}
}

View File

@ -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];
}
}

View File

@ -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() {