mirror of
https://github.com/fazo96/AIrium.git
synced 2025-01-25 11:54:20 +01:00
Merge branch 'modularbodies'
This commit is contained in:
commit
c56c24e99d
@ -1,6 +1,6 @@
|
|||||||
apply plugin: "java"
|
apply plugin: "java"
|
||||||
|
|
||||||
sourceCompatibility = 1.6
|
sourceCompatibility = 1.8
|
||||||
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
|
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
|
||||||
|
|
||||||
sourceSets.main.java.srcDirs = [ "src/" ]
|
sourceSets.main.java.srcDirs = [ "src/" ]
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -1,390 +0,0 @@
|
|||||||
package logic;
|
|
||||||
|
|
||||||
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
|
|
||||||
import com.mygdx.game.Game;
|
|
||||||
import com.mygdx.game.Log;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import logic.neural.Brain;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A (hopefully) smart biological creature in the simulated world.
|
|
||||||
*
|
|
||||||
* @author fazo
|
|
||||||
*/
|
|
||||||
public class Creature extends Element implements Runnable {
|
|
||||||
|
|
||||||
public static int default_radius = 20, max_hp = 100, brain_hidden_layers = 2, brain_hidden_neurons = 10;
|
|
||||||
public static float max_speed = 3, max_beak = default_radius / 4, fov, sightRange, corpseDecayRate = 0, hpDecay = 0.5f, pointsForEatingPlants = 1f, pointsForAttacking = 2f, hpForAttacking = 1f, hpForEatingPlants = 1f;
|
|
||||||
public static boolean leaveCorpses = false;
|
|
||||||
|
|
||||||
private Brain brain;
|
|
||||||
private float dir, hp, prevHp, speed, fitness, rotSpeed, beak;
|
|
||||||
private boolean eating = false, killing = false, workerDone = false, killWorker = false;
|
|
||||||
private Sight[] sights;
|
|
||||||
private Thread workerThread;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a creature with a random mind at given position in space
|
|
||||||
*
|
|
||||||
* @param x
|
|
||||||
* @param y
|
|
||||||
*/
|
|
||||||
public Creature(float x, float y) {
|
|
||||||
super(x, y, default_radius);
|
|
||||||
dir = (float) (Math.random() * 2 * Math.PI);
|
|
||||||
hp = max_hp;
|
|
||||||
prevHp = hp;
|
|
||||||
speed = 0;
|
|
||||||
rotSpeed = 0;
|
|
||||||
fitness = 0;
|
|
||||||
brain = new Brain(9, 5, brain_hidden_layers, brain_hidden_neurons);
|
|
||||||
sights = new Sight[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
for (;;) {
|
|
||||||
if (workerDone) {
|
|
||||||
try {
|
|
||||||
Thread.sleep(1000);
|
|
||||||
} catch (InterruptedException ex) {
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
update();
|
|
||||||
workerDone = true;
|
|
||||||
}
|
|
||||||
if(killWorker) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update() {
|
|
||||||
// apply hunger
|
|
||||||
hp -= hpDecay;
|
|
||||||
prevHp = hp;
|
|
||||||
if (hp < 0) { // Dead
|
|
||||||
Game.get().getWorld().getGraveyard().add(this);
|
|
||||||
if (leaveCorpses) {
|
|
||||||
Vegetable carcass = new Vegetable(getX(), getY());
|
|
||||||
carcass.setSize(getSize());
|
|
||||||
carcass.setDecayRate(corpseDecayRate);
|
|
||||||
Game.get().getWorld().add(carcass);
|
|
||||||
}
|
|
||||||
killWorker = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (speed > max_speed) {
|
|
||||||
speed = max_speed;
|
|
||||||
}
|
|
||||||
if (speed < -max_speed) {
|
|
||||||
speed = -max_speed;
|
|
||||||
}
|
|
||||||
// apply speed
|
|
||||||
float xMul = (float) Math.cos(dir), yMul = (float) Math.sin(dir);
|
|
||||||
move(xMul * speed, yMul * speed);
|
|
||||||
if (getX() < 0) {
|
|
||||||
setX(Game.get().getWorld().getWidth() + getX());
|
|
||||||
}
|
|
||||||
if (getY() < 0) {
|
|
||||||
setY(Game.get().getWorld().getHeight() + getY());
|
|
||||||
}
|
|
||||||
if (getX() > Game.get().getWorld().getWidth()) {
|
|
||||||
setX(getX() - Game.get().getWorld().getWidth());
|
|
||||||
}
|
|
||||||
if (getY() > Game.get().getWorld().getHeight()) {
|
|
||||||
setY(getY() - Game.get().getWorld().getHeight());
|
|
||||||
}
|
|
||||||
dir += rotSpeed;
|
|
||||||
//fitness -= 0.1;
|
|
||||||
if (dir > 2 * Math.PI) {
|
|
||||||
dir -= 2 * Math.PI;
|
|
||||||
}
|
|
||||||
if (dir < 0) {
|
|
||||||
dir += 2 * Math.PI;
|
|
||||||
}
|
|
||||||
// read from sensors and interact with world
|
|
||||||
sights = interactWithWorld();
|
|
||||||
// feed data to brain
|
|
||||||
float[] values = new float[brain.getNeurons()[0].length];
|
|
||||||
// VIEW: PLANTS
|
|
||||||
// 0: sight(v): see food?
|
|
||||||
// 1: sight(v): distance
|
|
||||||
// 2: sight(v): angle
|
|
||||||
// 3: sight(v): size
|
|
||||||
// VIEW: CREATS
|
|
||||||
// 4: sight(c): see food?
|
|
||||||
// 5: sight(c): distance
|
|
||||||
// 6: sight(c): angle
|
|
||||||
// 8: sight(c): hunger
|
|
||||||
// 7: sight(c): beak
|
|
||||||
// OTHER:
|
|
||||||
// 8: food sensor
|
|
||||||
int viewSensors = 4;
|
|
||||||
for (int i = 0; i < sights.length; i++) {
|
|
||||||
int mul = i * viewSensors;
|
|
||||||
if (sights[i] == null || sights[i].getElement() == null) {
|
|
||||||
// See nothing
|
|
||||||
values[0 + mul] = 0;
|
|
||||||
values[1 + mul] = 0;
|
|
||||||
values[2 + mul] = 0;
|
|
||||||
values[3 + mul] = 0;
|
|
||||||
} else {
|
|
||||||
// See something
|
|
||||||
values[1 + mul] = sights[i].getDistance() / sightRange;
|
|
||||||
values[2 + mul] = sights[i].getAngle();
|
|
||||||
if (sights[i].getElement() instanceof Vegetable) {
|
|
||||||
values[0 + mul] = 1f;
|
|
||||||
values[3 + mul] = sights[i].getElement().getSize() / default_radius;
|
|
||||||
} else {
|
|
||||||
values[0 + mul] = 1f;
|
|
||||||
values[3 + mul] = max_hp - ((Creature) sights[i].getElement()).getHp() / max_hp;
|
|
||||||
values[3 + mul] = ((Creature) sights[i].getElement()).getBeak() / max_beak;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
values[8] = eating || killing ? 1 : 0;
|
|
||||||
// compute behavior
|
|
||||||
float[] actions = null;
|
|
||||||
try {
|
|
||||||
actions = brain.compute(values);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
// Should not happen
|
|
||||||
Logger.getLogger(Creature.class.getName()).log(Level.SEVERE, null, ex);
|
|
||||||
}
|
|
||||||
Log.log(Log.DEBUG, "Accel: " + actions[0] + " RotClock: " + actions[1] + " RotAntiClock: " + actions[2] + " Beak: " + actions[3]);
|
|
||||||
speed = (actions[0] * 2 - actions[4] / 2) * max_speed;
|
|
||||||
rotSpeed = actions[1] - actions[2];
|
|
||||||
beak = actions[3] * max_beak;
|
|
||||||
if (beak > max_beak) {
|
|
||||||
beak = max_beak;
|
|
||||||
} else if (beak < 0) {
|
|
||||||
beak = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void render(ShapeRenderer s) {
|
|
||||||
// Draw Body
|
|
||||||
s.setColor(1 - (hp / max_hp), hp / max_hp, 0, 1);
|
|
||||||
s.circle(getX(), getY(), getSize());
|
|
||||||
// Prepare vision stuff
|
|
||||||
double relX = Math.cos(dir), relY = Math.sin(dir);
|
|
||||||
float c = 0;
|
|
||||||
float eyeX = (float) (relX * getSize() * 0.6f), eyeY = (float) (relY * getSize() * 0.6f);
|
|
||||||
// Draw Sight Lines
|
|
||||||
if (Game.get().getWorld().getOptions().getOrDefault("draw_sight_lines", 0f) > 0) {
|
|
||||||
for (Sight sight : sights) {
|
|
||||||
if (sight != null) {
|
|
||||||
c = sight.getDistance() / sightRange * 2 + sightRange;
|
|
||||||
} else {
|
|
||||||
}
|
|
||||||
if (sight != null) {
|
|
||||||
if (sight.getElement() instanceof Creature) {
|
|
||||||
s.setColor(c, 0, 0, 1);
|
|
||||||
} else if (sight.getElement() instanceof Vegetable) {
|
|
||||||
s.setColor(0, c, 0, 1);
|
|
||||||
}
|
|
||||||
s.line(eyeX + getX(), getY() + eyeY, sight.getElement().getX(), sight.getElement().getY());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Draw eye
|
|
||||||
if (sights[0] == null && sights[1] == null) {
|
|
||||||
s.setColor(1, 1, 1, 1);
|
|
||||||
} else {
|
|
||||||
s.setColor(sights[1] == null ? 0 : 1, sights[0] == null ? 0 : 1, 0, 1);
|
|
||||||
}
|
|
||||||
s.circle(getX() + eyeX, getY() + eyeY, 3);
|
|
||||||
// Draw FOV cone
|
|
||||||
float degrees = fov * 360f / (float) Math.PI;
|
|
||||||
float orient = dir * 180f / (float) Math.PI - degrees / 2;
|
|
||||||
if (Game.get().getWorld().getOptions().getOrDefault("draw_view_cones", 0f) > 0) {
|
|
||||||
s.setColor(0.3f, 0.3f, 0.3f, 1);
|
|
||||||
s.arc((float) eyeX + getX(), (float) eyeY + getY(), sightRange, orient, degrees);
|
|
||||||
}
|
|
||||||
// Draw damage/heal marks
|
|
||||||
if (hp < prevHp) {
|
|
||||||
// Damage mark
|
|
||||||
s.set(ShapeRenderer.ShapeType.Filled);
|
|
||||||
s.setColor(1, 0, 0, 1);
|
|
||||||
s.circle(getX(), getY(), 5);
|
|
||||||
} else if (killing || eating) {
|
|
||||||
// Heal mark
|
|
||||||
s.set(ShapeRenderer.ShapeType.Filled);
|
|
||||||
s.setColor(0, 1, 0, 1);
|
|
||||||
s.circle(getX(), getY(), 5);
|
|
||||||
}
|
|
||||||
s.set(ShapeRenderer.ShapeType.Line);
|
|
||||||
// Draw Beak
|
|
||||||
s.setColor(beak / max_beak, 1 - beak / max_beak, 0, 1);
|
|
||||||
s.line((float) (relX * getSize() * 0.8f + getX()), (float) (relY * getSize() * 0.8f + getY()), (float) (relX * getSize() * (1.5f + beak / max_beak) + getX()), (float) (relY * getSize() * (1.5f + beak / max_beak) + getY()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store Sight information (what the creature sees) and eat/attack if
|
|
||||||
* applicable
|
|
||||||
*
|
|
||||||
* @return the sight information retrieved
|
|
||||||
*/
|
|
||||||
public Sight[] interactWithWorld() {
|
|
||||||
Sight[] newSights = new Sight[2];
|
|
||||||
// Try to see plant
|
|
||||||
Element seen = null;
|
|
||||||
float dist = 0, angle = 0, ndir = dir - (float) Math.PI;
|
|
||||||
eating = false;
|
|
||||||
for (Element e : Game.get().getWorld().getPlants()) {
|
|
||||||
float tempDist = distanceFrom(e);
|
|
||||||
if (tempDist > sightRange) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
//Log.log(Log.DEBUG,"TempDist "+tempDist+" SightRange "+sightRange);
|
|
||||||
float relAngle = (float) (Math.atan2(getY() - e.getY(), getX() - e.getX()));
|
|
||||||
if (tempDist < dist || seen == null) {
|
|
||||||
// Check if Visible
|
|
||||||
if (Math.abs(relAngle - ndir) < fov) {
|
|
||||||
// Visible
|
|
||||||
seen = e;
|
|
||||||
angle = relAngle - ndir;
|
|
||||||
dist = tempDist;
|
|
||||||
}
|
|
||||||
//Log.log(Log.DEBUG,"RelAngle "+relAngle+" Dir "+ndir);
|
|
||||||
}
|
|
||||||
// Check if eatable
|
|
||||||
if (tempDist < 0) {
|
|
||||||
// Eat
|
|
||||||
eating = true;
|
|
||||||
e.setSize(e.getSize() - 0.1f);
|
|
||||||
if (e.getSize() == 0) {
|
|
||||||
e.setSize(0);
|
|
||||||
}
|
|
||||||
hp += hpForEatingPlants;
|
|
||||||
fitness+=pointsForEatingPlants;
|
|
||||||
if (hp > max_hp) {
|
|
||||||
hp = max_hp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (seen != null) {
|
|
||||||
newSights[0] = new Sight(seen, dist, angle);
|
|
||||||
}
|
|
||||||
// Try to see creature
|
|
||||||
seen = null;
|
|
||||||
dist = 0;
|
|
||||||
angle = 0;
|
|
||||||
ndir = dir - (float) Math.PI;
|
|
||||||
killing = false;
|
|
||||||
for (Element e : Game.get().getWorld().getCreatures()) {
|
|
||||||
if (e == this) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
float tempDist = distanceFrom(e);
|
|
||||||
if (tempDist > sightRange) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
//Log.log(Log.DEBUG,"TempDist "+tempDist+" SightRange "+sightRange);
|
|
||||||
float relAngle = (float) (Math.atan2(getY() - e.getY(), getX() - e.getX()));
|
|
||||||
if (tempDist < dist || seen == null) {
|
|
||||||
// Check if Visible
|
|
||||||
float tempAngle = Math.abs(relAngle - ndir);
|
|
||||||
if (tempAngle < fov) {
|
|
||||||
// Visible
|
|
||||||
seen = e;
|
|
||||||
angle = relAngle - ndir;
|
|
||||||
dist = tempDist;
|
|
||||||
// Check if attackable
|
|
||||||
if (beak > beak / 2 && tempDist < beak * 1.5f && tempAngle < fov / 2) {
|
|
||||||
// Attacking!
|
|
||||||
float damage = beak * hpForAttacking / 2;
|
|
||||||
hp += damage;
|
|
||||||
fitness += pointsForAttacking;
|
|
||||||
if (hp > max_hp) {
|
|
||||||
hp = max_hp;
|
|
||||||
}
|
|
||||||
killing = true;
|
|
||||||
Creature c = (Creature) e;
|
|
||||||
c.heal(-damage);
|
|
||||||
//c.praise(-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Log.log(Log.DEBUG,"RelAngle "+relAngle+" Dir "+ndir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (seen != null) {
|
|
||||||
newSights[1] = new Sight(seen, dist, angle);
|
|
||||||
}
|
|
||||||
return newSights;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply a modification to this creature's health. Can be negative.
|
|
||||||
*
|
|
||||||
* @param amount how much to heal/damage
|
|
||||||
*/
|
|
||||||
private void heal(float amount) {
|
|
||||||
hp += amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Praise this creature by increasing fitness. Can be negative to decrease
|
|
||||||
* fitness
|
|
||||||
*
|
|
||||||
* @param amount how much
|
|
||||||
*/
|
|
||||||
private void praise(float amount) {
|
|
||||||
fitness += amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the Worker thread has finished its current iteration
|
|
||||||
*
|
|
||||||
* @return true if worker thread has finished its current iteration
|
|
||||||
*/
|
|
||||||
public boolean isWorkerDone() {
|
|
||||||
return workerDone;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Command the Worker thread to start another iteration.
|
|
||||||
*/
|
|
||||||
public void startWorker() {
|
|
||||||
workerDone = false;
|
|
||||||
if (workerThread == null) {
|
|
||||||
// Create a new thread
|
|
||||||
workerThread = new Thread(this);
|
|
||||||
workerThread.start();
|
|
||||||
} else {
|
|
||||||
// Interrupt current thread, throwing it out of sleep
|
|
||||||
workerThread.interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Brain getBrain() {
|
|
||||||
return brain;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDirection(float dir) {
|
|
||||||
this.dir = dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float getFitness() {
|
|
||||||
return fitness;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reset() {
|
|
||||||
fitness = 0;
|
|
||||||
hp = max_hp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float getBeak() {
|
|
||||||
return beak;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float getHp() {
|
|
||||||
return hp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHp(float hp) {
|
|
||||||
this.hp = hp;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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) {
|
||||||
|
@ -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,9 @@ 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.Eye;
|
||||||
|
import logic.creatures.Movement;
|
||||||
|
import logic.creatures.Torso;
|
||||||
import logic.neural.Brain;
|
import logic.neural.Brain;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -279,12 +283,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");
|
Movement.max_speed = options.get("creature_max_speed");
|
||||||
Creature.fov = options.get("creature_fov");
|
Eye.fov = options.get("creature_fov");
|
||||||
Creature.sightRange = options.get("creature_sight_range");
|
Eye.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 +311,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;
|
||||||
}
|
}
|
||||||
|
73
core/src/logic/creatures/Beak.java
Normal file
73
core/src/logic/creatures/Beak.java
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* To change this license header, choose License Headers in Project Properties.
|
||||||
|
* To change this template file, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
package logic.creatures;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
|
||||||
|
import logic.Element;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author fazo
|
||||||
|
*/
|
||||||
|
public class Beak extends BodyPart {
|
||||||
|
|
||||||
|
private float length;
|
||||||
|
private boolean attacking = false;
|
||||||
|
public static float max_length = Torso.default_radius / 4, min_length = max_length / 4;
|
||||||
|
|
||||||
|
public Beak(float angle, Creature creature) {
|
||||||
|
super(3, 1, angle, 1, creature);
|
||||||
|
length = min_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float[] act() {
|
||||||
|
float r[] = new float[]{
|
||||||
|
length / max_length, // current beak length
|
||||||
|
length > max_length / 2 ? 1f : 0f, // wether the beak is doing damage
|
||||||
|
attacking ? 1f : 0f
|
||||||
|
};
|
||||||
|
attacking = false;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void interactWithElement(Element e, float distance, float relAngle) {
|
||||||
|
if (e instanceof Creature && distance < length && length > max_length / 2 && Math.abs(relAngle) < 0.3f) {
|
||||||
|
// Can attack
|
||||||
|
creature.praise(Creature.pointsForAttacking);
|
||||||
|
creature.getTorso().heal(length*Creature.hpForAttacking/2);
|
||||||
|
((Creature) e).getTorso().heal(-length*Creature.hpForAttacking/2);
|
||||||
|
attacking = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void draw(ShapeRenderer s, float relX, float relY) {
|
||||||
|
s.set(ShapeRenderer.ShapeType.Line);
|
||||||
|
// Draw Beak
|
||||||
|
s.setColor(getLength() / Beak.max_length, 1 - getLength() / Beak.max_length, 0, 1);
|
||||||
|
s.line((float) (relX + creature.getX()), (float) (relY + creature.getY()), (float) (relX * (1.5f + getLength() / Beak.max_length) + creature.getX()), (float) (relY * (1.5f + getLength() / Beak.max_length) + creature.getY()));
|
||||||
|
if (attacking) {
|
||||||
|
s.circle((float) (relX * (1.5f + getLength() / Beak.max_length) + creature.getX()), (float) (relY * (1.5f + getLength() / Beak.max_length) + creature.getY()), 0.3f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readFromBrain(float[] outputs) {
|
||||||
|
length = outputs[0] * max_length;
|
||||||
|
if (length > max_length) {
|
||||||
|
length = max_length;
|
||||||
|
} else if (length < 0) {
|
||||||
|
length = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getLength() {
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
77
core/src/logic/creatures/BodyPart.java
Normal file
77
core/src/logic/creatures/BodyPart.java
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package logic.creatures;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
|
||||||
|
import logic.Element;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author fazo
|
||||||
|
*/
|
||||||
|
public abstract class BodyPart {
|
||||||
|
|
||||||
|
protected int inputNeuronsUsed;
|
||||||
|
protected float angle, distFromCenter;
|
||||||
|
protected float outputs[];
|
||||||
|
protected Creature creature;
|
||||||
|
|
||||||
|
public BodyPart(int inputNeuronsUsed, int outputNeuronsUsed, float angle, float distFromCenter, Creature creature) {
|
||||||
|
this.inputNeuronsUsed = inputNeuronsUsed;
|
||||||
|
this.angle = angle;
|
||||||
|
this.distFromCenter = distFromCenter;
|
||||||
|
this.creature = creature;
|
||||||
|
outputs = new float[outputNeuronsUsed];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare data to be sent to the brain
|
||||||
|
*
|
||||||
|
* @return the data to send to the brain, must be inputNeuronsUsed long
|
||||||
|
*/
|
||||||
|
public abstract float[] act();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interact with another element
|
||||||
|
*
|
||||||
|
* @param e the Element (creature or plant)
|
||||||
|
* @param distance the distance
|
||||||
|
* @param relAngle the relative angle
|
||||||
|
*/
|
||||||
|
public abstract void interactWithElement(Element e, float distance, float relAngle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receive some data from the brain
|
||||||
|
*
|
||||||
|
* @param data the data received from the brain, will be outputNeuronsUsed
|
||||||
|
* long
|
||||||
|
*/
|
||||||
|
public abstract void readFromBrain(float data[]);
|
||||||
|
|
||||||
|
protected abstract void draw(ShapeRenderer s, float relX, float relY);
|
||||||
|
|
||||||
|
public void render(ShapeRenderer s) {
|
||||||
|
double relX = Math.cos(creature.getDirection() + angle) * creature.getTorso().getRadius() * distFromCenter;
|
||||||
|
double relY = Math.sin(creature.getDirection() + angle) * creature.getTorso().getRadius() * distFromCenter;
|
||||||
|
draw(s, (float) relX, (float) relY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInputNeuronsUsed() {
|
||||||
|
return inputNeuronsUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOutputNeuronsUsed() {
|
||||||
|
return outputs.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getAngle() {
|
||||||
|
return angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getDistanceFromCreatureCenter() {
|
||||||
|
return distFromCenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Creature getCreature() {
|
||||||
|
return creature;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
222
core/src/logic/creatures/Creature.java
Normal file
222
core/src/logic/creatures/Creature.java
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
package logic.creatures;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
|
||||||
|
import com.mygdx.game.Game;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import logic.Element;
|
||||||
|
import logic.Vegetable;
|
||||||
|
import logic.neural.Brain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A (hopefully) smart biological creature in the simulated world.
|
||||||
|
*
|
||||||
|
* @author fazo
|
||||||
|
*/
|
||||||
|
public class Creature extends Element implements Runnable {
|
||||||
|
|
||||||
|
public static int brain_hidden_layers = 2, brain_hidden_neurons = 10;
|
||||||
|
public static float corpseDecayRate = 0, pointsForEatingPlants = 1f, pointsForAttacking = 2f, hpForAttacking = 1f, hpForEatingPlants = 1f;
|
||||||
|
public static boolean leaveCorpses = false;
|
||||||
|
|
||||||
|
private final Brain brain;
|
||||||
|
private final Torso torso;
|
||||||
|
private final Beak beak;
|
||||||
|
private final ArrayList<BodyPart> bodyParts;
|
||||||
|
private float dir, fitness = 0;
|
||||||
|
private boolean workerDone = false, killWorker = false;
|
||||||
|
private Sight[] sights;
|
||||||
|
private Thread workerThread;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a creature with a random mind at given position in space
|
||||||
|
*
|
||||||
|
* @param x
|
||||||
|
* @param y
|
||||||
|
*/
|
||||||
|
public Creature(float x, float y) {
|
||||||
|
super(x, y, Torso.default_radius);
|
||||||
|
dir = (float) (Math.random() * 2 * Math.PI);
|
||||||
|
bodyParts = new ArrayList<BodyPart>();
|
||||||
|
bodyParts.add(torso = new Torso(this));
|
||||||
|
bodyParts.add(beak = new Beak(0, this));
|
||||||
|
bodyParts.add(new Eye(5, 0, this));
|
||||||
|
bodyParts.add(new Movement(this));
|
||||||
|
brain = new Brain(howManyInputNeurons(), howManyOutputNeurons(), brain_hidden_layers, brain_hidden_neurons);
|
||||||
|
sights = new Sight[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
for (;;) {
|
||||||
|
if (workerDone) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
update();
|
||||||
|
workerDone = true;
|
||||||
|
}
|
||||||
|
if (killWorker) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update() {
|
||||||
|
if (!torso.isAlive()) { // Dead
|
||||||
|
Game.get().getWorld().getGraveyard().add(this);
|
||||||
|
if (leaveCorpses) {
|
||||||
|
Vegetable carcass = new Vegetable(getX(), getY());
|
||||||
|
carcass.setSize(getSize());
|
||||||
|
carcass.setDecayRate(corpseDecayRate);
|
||||||
|
Game.get().getWorld().add(carcass);
|
||||||
|
}
|
||||||
|
killWorker = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// collect inputs
|
||||||
|
float values[] = new float[howManyInputNeurons()];
|
||||||
|
int i = 0;
|
||||||
|
for (BodyPart b : bodyParts) {
|
||||||
|
for (float v : b.act()) {
|
||||||
|
values[i] = v;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// read from sensors and interact with world
|
||||||
|
interactWithWorld();
|
||||||
|
// compute behavior
|
||||||
|
float[] actions = null;
|
||||||
|
try {
|
||||||
|
actions = brain.compute(values);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// Should not happen
|
||||||
|
Logger.getLogger(Creature.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
|
}
|
||||||
|
i = 0;
|
||||||
|
// Save brain outputs to body parts
|
||||||
|
for (BodyPart b : bodyParts) {
|
||||||
|
int n = 0;
|
||||||
|
float data[] = new float[b.getOutputNeuronsUsed()];
|
||||||
|
while (n < b.getOutputNeuronsUsed()) {
|
||||||
|
data[n] = actions[i];
|
||||||
|
i++;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
b.readFromBrain(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(ShapeRenderer s) {
|
||||||
|
// Draw Body
|
||||||
|
for (BodyPart b : bodyParts) {
|
||||||
|
b.render(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make the body components interact with the world
|
||||||
|
*/
|
||||||
|
public void interactWithWorld() {
|
||||||
|
for (Element e : Game.get().getWorld().getElements()) {
|
||||||
|
float distance = distanceFrom(e);
|
||||||
|
float angle = (float) (Math.atan2(getY() - e.getY(), getX() - e.getX())) - (dir - (float) Math.PI);
|
||||||
|
for (BodyPart b : bodyParts) {
|
||||||
|
b.interactWithElement(e, distance, angle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int howManyInputNeurons() {
|
||||||
|
int n = 0;
|
||||||
|
for (BodyPart b : bodyParts) {
|
||||||
|
n += b.getInputNeuronsUsed();
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int howManyOutputNeurons() {
|
||||||
|
int n = 0;
|
||||||
|
for (BodyPart b : bodyParts) {
|
||||||
|
n += b.getOutputNeuronsUsed();
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void rotate(float amount) {
|
||||||
|
dir += amount;
|
||||||
|
if (dir > 2 * Math.PI) {
|
||||||
|
dir -= 2 * Math.PI;
|
||||||
|
} else if (dir < 0) {
|
||||||
|
dir += 2 * Math.PI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Praise this creature by increasing fitness. Can be negative to decrease
|
||||||
|
* fitness
|
||||||
|
*
|
||||||
|
* @param amount how much
|
||||||
|
*/
|
||||||
|
public void praise(float amount) {
|
||||||
|
fitness += amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the Worker thread has finished its current iteration
|
||||||
|
*
|
||||||
|
* @return true if worker thread has finished its current iteration
|
||||||
|
*/
|
||||||
|
public boolean isWorkerDone() {
|
||||||
|
return workerDone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command the Worker thread to start another iteration.
|
||||||
|
*/
|
||||||
|
public void startWorker() {
|
||||||
|
workerDone = false;
|
||||||
|
if (workerThread == null) {
|
||||||
|
// Create a new thread
|
||||||
|
workerThread = new Thread(this);
|
||||||
|
workerThread.start();
|
||||||
|
} else {
|
||||||
|
// Interrupt current thread, throwing it out of sleep
|
||||||
|
workerThread.interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Brain getBrain() {
|
||||||
|
return brain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDirection(float dir) {
|
||||||
|
this.dir = dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getDirection() {
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getFitness() {
|
||||||
|
return fitness;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getBeak() {
|
||||||
|
return beak.getLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Torso getTorso() {
|
||||||
|
return torso;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<BodyPart> getBodyParts() {
|
||||||
|
return bodyParts;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
106
core/src/logic/creatures/Eye.java
Normal file
106
core/src/logic/creatures/Eye.java
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* To change this license header, choose License Headers in Project Properties.
|
||||||
|
* To change this template file, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
package logic.creatures;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
|
||||||
|
import com.mygdx.game.Game;
|
||||||
|
import com.mygdx.game.Log;
|
||||||
|
import logic.Element;
|
||||||
|
import logic.Vegetable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author fazo
|
||||||
|
*/
|
||||||
|
public class Eye extends BodyPart {
|
||||||
|
|
||||||
|
private Sight sights[];
|
||||||
|
private int seen;
|
||||||
|
public static float fov = 2, sightRange = 30;
|
||||||
|
|
||||||
|
public Eye(int nSights, float angle, Creature creature) {
|
||||||
|
super(6 * nSights, 0, angle, 0.8f, creature);
|
||||||
|
sights = new Sight[nSights];
|
||||||
|
seen = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float[] act() {
|
||||||
|
float ret[] = new float[inputNeuronsUsed];
|
||||||
|
int j = 0;
|
||||||
|
for (int i = 0; i < sights.length; i++) {
|
||||||
|
if (i < seen) {
|
||||||
|
// Saw something
|
||||||
|
ret[j] = 1;
|
||||||
|
ret[j + 1] = sights[i].getElement() instanceof Creature ? 1 : 0;
|
||||||
|
ret[j + 2] = sights[i].getDistance() / sightRange;
|
||||||
|
ret[j + 3] = sights[i].getAngle();
|
||||||
|
if (sights[i].getElement() instanceof Creature) {
|
||||||
|
ret[i + 4] = ((Creature) sights[i].getElement()).getBeak() / Beak.max_length;
|
||||||
|
ret[i + 5] = ((Creature) sights[i].getElement()).getTorso().getHp() / Torso.max_hp;
|
||||||
|
} else {
|
||||||
|
ret[i + 4] = ((Vegetable) sights[i].getElement()).getSize() / Vegetable.default_radius;
|
||||||
|
ret[i + 5] = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Saw nothing
|
||||||
|
for (int z = 0; z < 6; z++) {
|
||||||
|
ret[j + z] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j += 6;
|
||||||
|
}
|
||||||
|
seen = 0;
|
||||||
|
sights = new Sight[sights.length];
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void interactWithElement(Element e, float distance, float angle) {
|
||||||
|
if (e != creature && distance < sightRange && Math.abs(angle) < fov / 2) {
|
||||||
|
if (seen < sights.length) {
|
||||||
|
sights[seen] = new Sight(e, distance, angle);
|
||||||
|
Log.log(Log.DEBUG, "Saw " + e.getClass().getName() + " at " + distance + " with relative angle " + angle);
|
||||||
|
seen++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void draw(ShapeRenderer s, float relX, float relY) {
|
||||||
|
// Draw eye
|
||||||
|
s.setColor(1, 1, 1, 1);
|
||||||
|
s.circle(creature.getX() + relX, creature.getY() + relY, 3);
|
||||||
|
// Draw FOV cone
|
||||||
|
float degrees = fov * 360f / (float) Math.PI;
|
||||||
|
float orient = (creature.getDirection() + angle) * 180f / (float) Math.PI - degrees / 2;
|
||||||
|
if (Game.get().getWorld().getOptions().getOrDefault("draw_view_cones", 0f) > 0) {
|
||||||
|
s.setColor(0.3f, 0.3f, 0.3f, 1);
|
||||||
|
s.arc((float) relX + creature.getX(), (float) relY + creature.getY(), sightRange, orient, degrees);
|
||||||
|
}
|
||||||
|
// Sight Lines
|
||||||
|
float c = 0;
|
||||||
|
if (Game.get().getWorld().getOptions().getOrDefault("draw_sight_lines", 0f) > 0) {
|
||||||
|
for (Sight sight : sights) {
|
||||||
|
if (sight != null) {
|
||||||
|
c = sight.getDistance() / sightRange * 2 + sightRange;
|
||||||
|
if (sight.getElement() instanceof Creature) {
|
||||||
|
s.setColor(c, 0, 0, 1);
|
||||||
|
} else if (sight.getElement() instanceof Vegetable) {
|
||||||
|
s.setColor(0, c, 0, 1);
|
||||||
|
}
|
||||||
|
s.line(relX + creature.getX(), creature.getY() + relY, sight.getElement().getX(), sight.getElement().getY());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readFromBrain(float[] data) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
66
core/src/logic/creatures/Movement.java
Normal file
66
core/src/logic/creatures/Movement.java
Normal 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];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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
|
120
core/src/logic/creatures/Torso.java
Normal file
120
core/src/logic/creatures/Torso.java
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
package logic.creatures;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
|
||||||
|
import logic.Element;
|
||||||
|
import logic.Vegetable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author fazo
|
||||||
|
*/
|
||||||
|
public class Torso extends BodyPart {
|
||||||
|
|
||||||
|
private float hp, prevHp, radius, pain = 0;
|
||||||
|
public static float default_radius = 20, max_hp = 100, hpDecay = 0.5f, eatingSpeed = 0.1f;
|
||||||
|
private boolean eating = false;
|
||||||
|
|
||||||
|
public Torso(Creature c) {
|
||||||
|
super(3, 0, 0, 0, c);
|
||||||
|
radius = default_radius;
|
||||||
|
hp = max_hp;
|
||||||
|
prevHp = hp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(ShapeRenderer s, float x, float y) {
|
||||||
|
s.setColor(1 - (hp / max_hp), hp / max_hp, 0, 1);
|
||||||
|
s.circle(x + creature.getX(), y + creature.getY(), radius);
|
||||||
|
// Draw damage/heal marks
|
||||||
|
s.set(ShapeRenderer.ShapeType.Filled);
|
||||||
|
if (getReceivedDamage() > 0) {
|
||||||
|
// Damage mark
|
||||||
|
s.setColor(1, 0, 0, 1);
|
||||||
|
} else if (getReceivedDamage() < 0 || eating) {
|
||||||
|
// Heal mark
|
||||||
|
s.setColor(0, 1, 0, 1);
|
||||||
|
}
|
||||||
|
if (getReceivedDamage() != 0 || eating) {
|
||||||
|
s.circle(x + creature.getX(), y + creature.getY(), 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float[] act() {
|
||||||
|
// apply hunger
|
||||||
|
hp -= hpDecay;
|
||||||
|
float r[] = new float[]{
|
||||||
|
hp/max_hp,
|
||||||
|
eating ? 1f : 0f,
|
||||||
|
pain
|
||||||
|
};
|
||||||
|
pain = 0;
|
||||||
|
prevHp = hp;
|
||||||
|
eating = false;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void interactWithElement(Element e, float distance, float relAngle) {
|
||||||
|
if (e instanceof Vegetable && distance < 0 && hp < max_hp) {
|
||||||
|
e.setSize(e.getSize() - eatingSpeed);
|
||||||
|
if (e.getSize() == 0) {
|
||||||
|
e.setSize(0);
|
||||||
|
}
|
||||||
|
heal(Creature.hpForEatingPlants);
|
||||||
|
creature.praise(Creature.pointsForEatingPlants);
|
||||||
|
eating = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readFromBrain(float[] data) {
|
||||||
|
if (getReceivedDamage() > 0) {
|
||||||
|
pain = -1;
|
||||||
|
} else if (getReceivedDamage() < 0) {
|
||||||
|
pain = 1;
|
||||||
|
} else {
|
||||||
|
pain = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAlive() {
|
||||||
|
return hp > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a modification to this creature's health. Can be negative.
|
||||||
|
*
|
||||||
|
* @param amount how much to heal/damage
|
||||||
|
*/
|
||||||
|
public void heal(float amount) {
|
||||||
|
hp += amount;
|
||||||
|
if (hp < 0) {
|
||||||
|
hp = 0;
|
||||||
|
}
|
||||||
|
if (hp > max_hp) {
|
||||||
|
hp = max_hp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getReceivedDamage() {
|
||||||
|
return prevHp - hp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getRadius() {
|
||||||
|
return radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRadius(float radius) {
|
||||||
|
this.radius = radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getHp() {
|
||||||
|
return hp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHp(float hp) {
|
||||||
|
this.hp = hp;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
apply plugin: "java"
|
apply plugin: "java"
|
||||||
|
|
||||||
sourceCompatibility = 1.6
|
sourceCompatibility = 1.8
|
||||||
sourceSets.main.java.srcDirs = [ "src/" ]
|
sourceSets.main.java.srcDirs = [ "src/" ]
|
||||||
|
|
||||||
project.ext.mainClassName = "com.mygdx.game.desktop.DesktopLauncher"
|
project.ext.mainClassName = "com.mygdx.game.desktop.DesktopLauncher"
|
||||||
|
@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user