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

working on modular body parts

This commit is contained in:
Enrico Fasoli 2015-08-09 17:47:39 +02:00
parent 2dcb9e9447
commit 31d92b4cf5
10 changed files with 316 additions and 103 deletions

View File

@ -9,7 +9,7 @@ import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.math.Vector3;
import java.util.ConcurrentModificationException; import java.util.ConcurrentModificationException;
import java.util.Map; import java.util.Map;
import logic.Creature; import logic.creatures.Creature;
import logic.Element; import logic.Element;
import logic.World; import logic.World;

View File

@ -9,7 +9,7 @@ import com.mygdx.game.Game;
*/ */
public class Vegetable extends Element { public class Vegetable extends Element {
public static final int default_radius = 5; public static final float default_radius = 5;
private float decayRate = 0; private float decayRate = 0;
public Vegetable(float x, float y) { public Vegetable(float x, float y) {

View File

@ -1,5 +1,6 @@
package logic; package logic;
import logic.creatures.Creature;
import com.mygdx.game.Game; import com.mygdx.game.Game;
import com.mygdx.game.Listener; import com.mygdx.game.Listener;
import com.mygdx.game.Log; import com.mygdx.game.Log;
@ -12,6 +13,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import logic.creatures.Torso;
import logic.neural.Brain; import logic.neural.Brain;
/** /**
@ -279,12 +281,12 @@ public class World implements Runnable {
multithreading = options.get("enable_multithreading") > 0; multithreading = options.get("enable_multithreading") > 0;
Creature.corpseDecayRate = options.get("corpse_decay_rate"); Creature.corpseDecayRate = options.get("corpse_decay_rate");
Creature.leaveCorpses = options.get("enable_corpses") > 0; Creature.leaveCorpses = options.get("enable_corpses") > 0;
Creature.default_radius = Math.round(options.get("creature_radius")); Torso.default_radius = Math.round(options.get("creature_radius"));
Creature.max_hp = Math.round(options.get("creature_max_hp")); Torso.max_hp = Math.round(options.get("creature_max_hp"));
Creature.max_speed = options.get("creature_max_speed"); Creature.max_speed = options.get("creature_max_speed");
Creature.fov = options.get("creature_fov"); Creature.fov = options.get("creature_fov");
Creature.sightRange = options.get("creature_sight_range"); 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.hpForAttacking = options.get("creature_hp_for_attacking");
Creature.hpForEatingPlants = options.get("creature_hp_for_eating_plants"); Creature.hpForEatingPlants = options.get("creature_hp_for_eating_plants");
Creature.pointsForAttacking = options.get("creature_points_for_attacking"); Creature.pointsForAttacking = options.get("creature_points_for_attacking");
@ -307,10 +309,11 @@ public class World implements Runnable {
* @return the spawned element * @return the spawned element
*/ */
private Element spawn(boolean isCreature, float[][][] brainMap) { private Element spawn(boolean isCreature, float[][][] brainMap) {
int x, y, r; int x, y;
boolean overlaps = false; float r;
boolean overlaps;
if (isCreature) { if (isCreature) {
r = Creature.default_radius; r = Torso.default_radius;
} else { } else {
r = Vegetable.default_radius; r = Vegetable.default_radius;
} }

View File

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

View File

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

View File

@ -1,10 +1,13 @@
package logic; package logic.creatures;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.mygdx.game.Game; import com.mygdx.game.Game;
import com.mygdx.game.Log; import com.mygdx.game.Log;
import java.util.ArrayList;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import logic.Element;
import logic.Vegetable;
import logic.neural.Brain; import logic.neural.Brain;
/** /**
@ -14,12 +17,15 @@ import logic.neural.Brain;
*/ */
public class Creature extends Element implements Runnable { 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 int 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 float max_speed = 3, fov, sightRange, corpseDecayRate = 0, pointsForEatingPlants = 1f, pointsForAttacking = 2f, hpForAttacking = 1f, hpForEatingPlants = 1f;
public static boolean leaveCorpses = false; public static boolean leaveCorpses = false;
private Brain brain; private final Brain brain;
private float dir, hp, prevHp, speed, fitness, rotSpeed, beak; 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 boolean eating = false, killing = false, workerDone = false, killWorker = false;
private Sight[] sights; private Sight[] sights;
private Thread workerThread; private Thread workerThread;
@ -31,13 +37,14 @@ public class Creature extends Element implements Runnable {
* @param y * @param y
*/ */
public Creature(float x, float 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); dir = (float) (Math.random() * 2 * Math.PI);
hp = max_hp;
prevHp = hp;
speed = 0; speed = 0;
rotSpeed = 0; rotSpeed = 0;
fitness = 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); brain = new Brain(9, 5, brain_hidden_layers, brain_hidden_neurons);
sights = new Sight[2]; sights = new Sight[2];
} }
@ -54,16 +61,15 @@ public class Creature extends Element implements Runnable {
update(); update();
workerDone = true; workerDone = true;
} }
if(killWorker) break; if (killWorker) {
break;
}
} }
} }
@Override @Override
public void update() { public void update() {
// apply hunger if (!torso.isAlive()) { // Dead
hp -= hpDecay;
prevHp = hp;
if (hp < 0) { // Dead
Game.get().getWorld().getGraveyard().add(this); Game.get().getWorld().getGraveyard().add(this);
if (leaveCorpses) { if (leaveCorpses) {
Vegetable carcass = new Vegetable(getX(), getY()); Vegetable carcass = new Vegetable(getX(), getY());
@ -85,28 +91,32 @@ public class Creature extends Element implements Runnable {
move(xMul * speed, yMul * speed); move(xMul * speed, yMul * speed);
if (getX() < 0) { if (getX() < 0) {
setX(Game.get().getWorld().getWidth() + getX()); setX(Game.get().getWorld().getWidth() + getX());
} else if (getX() > Game.get().getWorld().getWidth()) {
setX(getX() - Game.get().getWorld().getWidth());
} }
if (getY() < 0) { if (getY() < 0) {
setY(Game.get().getWorld().getHeight() + getY()); setY(Game.get().getWorld().getHeight() + getY());
} } else if (getY() > Game.get().getWorld().getHeight()) {
if (getX() > Game.get().getWorld().getWidth()) {
setX(getX() - Game.get().getWorld().getWidth());
}
if (getY() > Game.get().getWorld().getHeight()) {
setY(getY() - Game.get().getWorld().getHeight()); setY(getY() - Game.get().getWorld().getHeight());
} }
dir += rotSpeed; dir += rotSpeed;
//fitness -= 0.1; //fitness -= 0.1;
if (dir > 2 * Math.PI) { if (dir > 2 * Math.PI) {
dir -= 2 * Math.PI; dir -= 2 * Math.PI;
} } else if (dir < 0) {
if (dir < 0) {
dir += 2 * Math.PI; dir += 2 * Math.PI;
} }
// read from sensors and interact with world // read from sensors and interact with world
sights = interactWithWorld(); sights = interactWithWorld();
// feed data to brain // feed data to brain
float[] values = new float[brain.getNeurons()[0].length]; 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 // VIEW: PLANTS
// 0: sight(v): see food? // 0: sight(v): see food?
// 1: sight(v): distance // 1: sight(v): distance
@ -117,29 +127,29 @@ public class Creature extends Element implements Runnable {
// 5: sight(c): distance // 5: sight(c): distance
// 6: sight(c): angle // 6: sight(c): angle
// 8: sight(c): hunger // 8: sight(c): hunger
// 7: sight(c): beak // 7: sight(c): beak.getLength()
// OTHER: // OTHER:
// 8: food sensor // 8: food sensor
int viewSensors = 4; int viewSensors = 4;
for (int i = 0; i < sights.length; i++) { 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) { if (sights[i] == null || sights[i].getElement() == null) {
// See nothing // See nothing
values[0 + mul] = 0; values[0 + offset] = 0;
values[1 + mul] = 0; values[1 + offset] = 0;
values[2 + mul] = 0; values[2 + offset] = 0;
values[3 + mul] = 0; values[3 + offset] = 0;
} else { } else {
// See something // See something
values[1 + mul] = sights[i].getDistance() / sightRange; values[1 + offset] = sights[i].getDistance() / sightRange;
values[2 + mul] = sights[i].getAngle(); values[2 + offset] = sights[i].getAngle();
if (sights[i].getElement() instanceof Vegetable) { if (sights[i].getElement() instanceof Vegetable) {
values[0 + mul] = 1f; values[0 + offset] = 1f;
values[3 + mul] = sights[i].getElement().getSize() / default_radius; values[3 + offset] = sights[i].getElement().getSize() / torso.getRadius();
} else { } else {
values[0 + mul] = 1f; values[0 + offset] = 1f;
values[3 + mul] = max_hp - ((Creature) sights[i].getElement()).getHp() / max_hp; values[3 + offset] = Torso.max_hp - ((Creature) sights[i].getElement()).getTorso().getHp() / Torso.max_hp;
values[3 + mul] = ((Creature) sights[i].getElement()).getBeak() / max_beak; 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 // Should not happen
Logger.getLogger(Creature.class.getName()).log(Level.SEVERE, null, ex); 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]); 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; speed = (actions[0] * 2 - actions[4] / 2) * max_speed;
rotSpeed = actions[1] - actions[2]; rotSpeed = actions[1] - actions[2];
beak = actions[3] * max_beak;
if (beak > max_beak) {
beak = max_beak;
} else if (beak < 0) {
beak = 0;
}
} }
@Override @Override
public void render(ShapeRenderer s) { public void render(ShapeRenderer s) {
// Draw Body // Draw Body
s.setColor(1 - (hp / max_hp), hp / max_hp, 0, 1); for (BodyPart b : bodyParts) {
s.circle(getX(), getY(), getSize()); b.render(s);
}
// Prepare vision stuff // Prepare vision stuff
double relX = Math.cos(dir), relY = Math.sin(dir); double relX = Math.cos(dir), relY = Math.sin(dir);
float c = 0; float c = 0;
float eyeX = (float) (relX * getSize() * 0.6f), eyeY = (float) (relY * getSize() * 0.6f); float eyeX = (float) (relX * getSize() * 0.6f), eyeY = (float) (relY * getSize() * 0.6f);
// Draw Sight Lines // Draw Sight Lines
s.set(ShapeRenderer.ShapeType.Line);
if (Game.get().getWorld().getOptions().getOrDefault("draw_sight_lines", 0f) > 0) { if (Game.get().getWorld().getOptions().getOrDefault("draw_sight_lines", 0f) > 0) {
for (Sight sight : sights) { for (Sight sight : sights) {
if (sight != null) { if (sight != null) {
@ -203,22 +221,6 @@ public class Creature extends Element implements Runnable {
s.setColor(0.3f, 0.3f, 0.3f, 1); s.setColor(0.3f, 0.3f, 0.3f, 1);
s.arc((float) eyeX + getX(), (float) eyeY + getY(), sightRange, orient, degrees); 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) { if (e.getSize() == 0) {
e.setSize(0); e.setSize(0);
} }
hp += hpForEatingPlants; torso.heal(hpForEatingPlants);
fitness+=pointsForEatingPlants; praise(pointsForEatingPlants);
if (hp > max_hp) {
hp = max_hp;
}
} }
} }
if (seen != null) { if (seen != null) {
@ -292,20 +291,7 @@ public class Creature extends Element implements Runnable {
seen = e; seen = e;
angle = relAngle - ndir; angle = relAngle - ndir;
dist = tempDist; 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); //Log.log(Log.DEBUG,"RelAngle "+relAngle+" Dir "+ndir);
} }
@ -316,13 +302,20 @@ public class Creature extends Element implements Runnable {
return newSights; return newSights;
} }
/** public int howManyInputNeurons() {
* Apply a modification to this creature's health. Can be negative. int n = 0;
* for (BodyPart b : bodyParts) {
* @param amount how much to heal/damage n += b.getInputNeuronsUsed();
*/ }
private void heal(float amount) { return n;
hp += amount; }
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; this.dir = dir;
} }
public float getDirection() {
return dir;
}
public float getFitness() { public float getFitness() {
return fitness; return fitness;
} }
public void reset() {
fitness = 0;
hp = max_hp;
}
public float getBeak() { public float getBeak() {
return beak; return beak.getLength();
} }
public float getHp() { public Torso getTorso() {
return hp; return torso;
} }
public void setHp(float hp) { public ArrayList<BodyPart> getBodyParts() {
this.hp = hp; return bodyParts;
} }
} }

View File

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

View File

@ -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 * Stores a sight of an element, as seen from a creature's eye

View File

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

View File

@ -33,7 +33,7 @@ import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener; import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableModel; import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel; import javax.swing.table.TableModel;
import logic.Creature; import logic.creatures.Creature;
import logic.World; import logic.World;
/** /**