diff --git a/.gitignore b/.gitignore index 803467a..a3eb201 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .nb-gradle/profiles/private/* +.nb-gradle/profiles/private/aux-config ## Java diff --git a/.nb-gradle/profiles/private/aux-config b/.nb-gradle/profiles/private/aux-config index fd3bee1..cce7475 100644 --- a/.nb-gradle/profiles/private/aux-config +++ b/.nb-gradle/profiles/private/aux-config @@ -4,9 +4,14 @@ - + + file:/home/fazo/Documents/Git/AIrium/core/src/com/mygdx/game/Game.java + file:/home/fazo/Documents/Git/AIrium/core/src/logic/neural/Neuron.java + file:/home/fazo/Documents/Git/AIrium/core/src/logic/Creature.java + file:/home/fazo/Documents/Git/AIrium/core/src/logic/neural/Brain.java + file:/home/fazo/Documents/Git/AIrium/core/src/logic/World.java + - file:/home/fazo/Documents/Git/AIrium/core/src/com/mygdx/game/Game.java diff --git a/core/src/com/mygdx/game/Game.java b/core/src/com/mygdx/game/Game.java index bcfde4b..5b198ad 100644 --- a/core/src/com/mygdx/game/Game.java +++ b/core/src/com/mygdx/game/Game.java @@ -16,13 +16,14 @@ public class Game extends ApplicationAdapter { private World world; private float cameraSpeed = 15; private BitmapFont font; + private boolean paused = false; @Override public void create() { - game = this; world = new World(2500, 2500); shaper = new ShapeRenderer(); + shaper.setAutoShapeType(true); font = new BitmapFont(); } @@ -50,8 +51,13 @@ public class Game extends ApplicationAdapter { if (Gdx.input.isKeyJustPressed(Input.Keys.MINUS)) { shaper.scale(1.5f, 1.5f, 1); } + if (Gdx.input.isKeyJustPressed(Input.Keys.P)) { + paused = !paused; + } // Update - world.update(); + if (!paused) { + world.update(); + } // Draw Gdx.gl.glClearColor(0, 0, 0, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); diff --git a/core/src/logic/Creature.java b/core/src/logic/Creature.java index 4be17d2..b86064b 100644 --- a/core/src/logic/Creature.java +++ b/core/src/logic/Creature.java @@ -18,8 +18,8 @@ public class Creature extends Element { public static final float max_speed = 3; private Brain brain; - private float dir, hp, speed, sightRange, fov, fitness, rotSpeed; - private boolean eating = false; + private float dir, hp, speed, sightRange, fov, fitness, rotSpeed, beak; + private boolean eating = false, killing = false; private Sight sight; public Creature(float x, float y) { @@ -31,15 +31,20 @@ public class Creature extends Element { sightRange = 100; fov = (float) Math.PI / 2.5f; fitness = 0; - brain = new Brain(4, 3, 1, 8); + brain = new Brain(6, 5, 2, 10); } @Override public void update() { // apply hunger hp -= 0.5f; - if (hp < 0) { + if (hp < 0) { // Dead Game.get().getWorld().getGraveyard().add(this); + Vegetable carcass = new Vegetable(getX(), getY()); + carcass.setSize(getSize()); + //carcass.setDecayRate(0.01f); + Game.get().getWorld().add(carcass); + return; } if (speed > max_speed) { speed = max_speed; @@ -51,16 +56,16 @@ public class Creature extends Element { 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()); + setX(Game.get().getWorld().getWidth() + getX()); } if (getY() < 0) { - setX(Game.get().getWorld().getHeight()+getY()); + setY(Game.get().getWorld().getHeight() + getY()); } if (getX() > Game.get().getWorld().getWidth()) { - setX(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; // try eating @@ -72,51 +77,63 @@ public class Creature extends Element { if (dir < 0) { dir += 2 * Math.PI; } - sight = look(); // take a look + sight = lookAndHit(); // take a look // feed data to brain - float[] values = new float[4]; - // 0: see food - // 1: distance - // 2: angle - // 3: food sensor + float[] values = new float[brain.getNeurons()[0].length]; + // 0: sight: see food? + // 1: sight: see creat? + // 2: sight: distance + // 3: sight: angle + // 4: sight: size for vegetables, hunger for creatures + // 5: food sensor if (sight == null || sight.getElement() == null) { + // See nothing values[0] = 0; values[1] = 0; values[2] = 0; - } else if (sight.getElement() instanceof Vegetable) { - values[1] = 1; - values[1] = sight.getDistance() / sightRange; - values[2] = sight.getAngle(); + values[3] = 0; + values[4] = 0; } else { - values[0] = 0f; - values[1] = sight.getDistance() / sightRange; - values[2] = sight.getAngle(); + // See something + values[2] = sight.getDistance() / sightRange; + values[3] = sight.getAngle(); + if (sight.getElement() instanceof Vegetable) { + values[0] = 1f; + values[1] = 0; + values[4] = sight.getElement().getSize() / default_radius; + } else { + values[0] = 0f; + values[1] = 1f; + values[4] = 100 - ((Creature) sight.getElement()).getHp() / 100; + } } - values[3] = eating ? 1 : 0; + values[5] = eating ? 1 : 0; + // compute behavior + float[] actions = null; try { - brain.input(values); + actions = brain.compute(values); } catch (Exception ex) { // Should not happen Logger.getLogger(Creature.class.getName()).log(Level.SEVERE, null, ex); } - // compute behavior - float[] actions = brain.compute(); - Log.log(Log.DEBUG, "Accel: " + actions[0] + " RotClock: " + actions[1] + " RotAntiClock: " + actions[2]); - speed = actions[0] * max_speed; + 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]; - } - - public void setHp(float hp) { - this.hp = hp; + beak = actions[3]; + if (beak > 20) { + beak = 20; + } else if (beak < 0) { + beak = 0; + } } @Override public void render(ShapeRenderer s) { // Body s.setColor(1 - (hp / 100), hp / 100, 0, 1); - s.circle(getX() , getY(), getSize()); + s.circle(getX(), getY(), getSize()); // Eye - double relX = Math.cos(dir) * getSize(), relY = Math.sin(dir) * getSize(); + double relX = Math.cos(dir), relY = Math.sin(dir); s.setColor(1, 1, 1, 1); if (sight != null) { float c = sight.getDistance() / sightRange * 2 + sightRange; @@ -126,17 +143,29 @@ public class Creature extends Element { s.setColor(0, c, 0, 1); } } - s.circle((float) relX + getX() , (float) relY + getY() , 3); + // Eye + s.circle((float) relX * getSize() * 0.6f + getX(), (float) relY * getSize() * 0.6f + getY(), 3); + if (killing) { + // Evil mark + s.set(ShapeRenderer.ShapeType.Filled); + s.setColor(1, 0, 0, 1); + s.circle(getX(), getY(), 5); + } + s.set(ShapeRenderer.ShapeType.Line); + // Beak + s.setColor(1, 1, 0, beak / 20); + s.line((float) (relX * getSize() * 0.8f + getX()), (float) (relY * getSize() * 0.8f + getY()), (float) (relX * getSize() * (1.5f + beak / 20) + getX()), (float) (relY * getSize() * (1.5f + beak / 20) + getY())); //FOV float degrees = fov * 180f / (float) Math.PI; float orient = dir * 180f / (float) Math.PI - degrees / 2; s.setColor(0.3f, 0.3f, 0.3f, 1); - s.arc((float) relX + getX() , (float) relY + getY() , sightRange, orient, degrees); + s.arc((float) relX * getSize() + getX(), (float) relY * getSize() + getY(), sightRange, orient, degrees); } - public Sight look() { + public Sight lookAndHit() { Element seen = null; float dist = 0, angle = 0, ndir = dir - (float) Math.PI; + killing = false; for (Element e : Game.get().getWorld().getElements()) { if (e == this) { continue; @@ -146,18 +175,30 @@ public class Creature extends Element { continue; } //Log.log(Log.DEBUG,"TempDist "+tempDist+" SightRange "+sightRange); - if (tempDist > dist && seen != null) { - continue; - } float relAngle = (float) (Math.atan2(getY() - e.getY(), getX() - e.getX())); - //if((relAngle > dir-fov/2 && relAngle < dir+fov/2)){ - if (Math.abs(relAngle - ndir) < fov) { - // Visible - seen = e; - angle = relAngle - ndir; - dist = tempDist; + 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); } - //Log.log(Log.DEBUG,"RelAngle "+relAngle+" Dir "+ndir); + // Check if attackable + if (e instanceof Creature && beak > 5 && tempDist < beak * 1.5f && Math.abs(relAngle - ndir) < (float) Math.PI / 10f) { + // Attacking! + hp++; + fitness++; + if (hp > 100) { + hp = 100; + } + killing = true; + Creature c = (Creature) e; + c.setHp(c.getHp() - 0.2f); + } + } if (seen != null) { return new Sight(seen, dist, angle); @@ -172,7 +213,9 @@ public class Creature extends Element { if (overlaps(e)) { eating = true; e.setSize(e.getSize() - 0.1f); - if(e.getSize() == 0) e.setSize(0); + if (e.getSize() == 0) { + e.setSize(0); + } hp++; fitness++; if (hp > 100) { @@ -199,4 +242,11 @@ public class Creature extends Element { hp = 100; } + public float getHp() { + return hp; + } + + public void setHp(float hp) { + this.hp = hp; + } } diff --git a/core/src/logic/Vegetable.java b/core/src/logic/Vegetable.java index e0c6ac3..54e57e9 100644 --- a/core/src/logic/Vegetable.java +++ b/core/src/logic/Vegetable.java @@ -15,7 +15,7 @@ import com.mygdx.game.Game; public class Vegetable extends Element { public static final int default_radius = 5; - private float x, y; + private float decayRate = 0; public Vegetable(float x, float y) { super(x, y, default_radius); @@ -23,6 +23,7 @@ public class Vegetable extends Element { @Override public void update() { + setSize(getSize()-decayRate); if (getSize() <= 0) { Game.get().getWorld().getDeadPlants().add(this); } @@ -33,4 +34,12 @@ public class Vegetable extends Element { s.setColor(1, 1, 1, 1); s.circle(getX(), getY(), getSize()); } + + public float getDecayRate() { + return decayRate; + } + + public void setDecayRate(float decayRate) { + this.decayRate = decayRate; + } } diff --git a/core/src/logic/World.java b/core/src/logic/World.java index 5b9e5a5..514ee09 100644 --- a/core/src/logic/World.java +++ b/core/src/logic/World.java @@ -18,8 +18,9 @@ import java.util.logging.Logger; public class World { private final int width, height, nPlants, creatPerGen; - private int generation = 0; + private int generation = 1; public ArrayList elements; + public ArrayList toAdd; public ArrayList creatures; public ArrayList graveyard; public ArrayList plants; @@ -30,8 +31,9 @@ public class World { this.height = height; elements = new ArrayList(); creatures = new ArrayList(); + toAdd = new ArrayList(); creatPerGen = Math.min(Math.round(width * height / 20000), 50); - nPlants = Math.round(width * height / 5000); + nPlants = Math.round(width * height / 5500); plants = new ArrayList(); deadPlants = new ArrayList(); graveyard = new ArrayList(); @@ -39,6 +41,15 @@ public class World { } public void update() { + for (Element e : toAdd) { + elements.add(e); + if (e instanceof Creature) { + creatures.add((Creature) e); + } else if (e instanceof Vegetable) { + plants.add((Vegetable) e); + } + } + toAdd.clear(); elements.removeAll(graveyard); elements.removeAll(deadPlants); plants.removeAll(deadPlants); @@ -69,14 +80,15 @@ public class World { } }; if (graveyard.isEmpty() || restart) { // First gen - generation = 0; + generation = 1; + Log.log(Log.INFO, "Starting from generation 1: spawning "+creatPerGen+" creatures."); for (int i = 0; i < creatPerGen; i++) { spawnCreature(); } } else { // Evolve previous gen graveyard.sort(creatureComp); // sort by fitness // Prepare best agent list - int topSize = (int) Math.floor(graveyard.size() * 0.1f); + int topSize = (int) Math.round(graveyard.size() * 0.05f); Creature[] top = new Creature[topSize]; // Calculate avg fitness and prepare best agent list float avgFitness = 0; @@ -105,7 +117,7 @@ public class World { Logger.getLogger(World.class.getName()).log(Level.SEVERE, null, ex); } Creature ne = spawnCreature(n); - //ne.getBrain().mutate(0.1f); // mutate children + ne.getBrain().mutate(0.05f); // mutate children } graveyard.clear(); generation++; @@ -136,12 +148,14 @@ public class World { if (brainMap != null) { c.getBrain().remap(brainMap); } + //add(c); elements.add(c); creatures.add(c); return c; } else { Log.log(Log.DEBUG, "New Veg: " + x + " " + y); Vegetable v = new Vegetable(x, y); + //add(v); elements.add(v); plants.add(v); return v; @@ -168,6 +182,10 @@ public class World { return height; } + public void add(Element e) { + toAdd.add(e); + } + public ArrayList getElements() { return elements; }