diff --git a/core/src/com/mygdx/game/Game.java b/core/src/com/mygdx/game/Game.java index 486dbcb..5466d76 100644 --- a/core/src/com/mygdx/game/Game.java +++ b/core/src/com/mygdx/game/Game.java @@ -42,7 +42,7 @@ public class Game extends ApplicationAdapter { public void render() { // Controls if (Gdx.input.isKeyJustPressed(Input.Keys.SPACE)) { - world.newGen(false); + world.launchNewGen(); } if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) { renderer.translate(-cameraSpeed, 0, 0); diff --git a/core/src/logic/Creature.java b/core/src/logic/Creature.java index 54bc894..ec38460 100644 --- a/core/src/logic/Creature.java +++ b/core/src/logic/Creature.java @@ -1,326 +1,361 @@ -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. - * - * @author fazo - */ -public class Creature extends Element { - - public static final int default_radius = 20, maxHp = 100; - public static final float max_speed = 3, max_beak = default_radius / 4; - - private Brain brain; - private float dir, hp, prevHp, speed, sightRange, fov, fitness, rotSpeed, beak; - private boolean eating = false, killing = false; - private Sight[] sights; - - public Creature(float x, float y) { - super(x, y, default_radius); - dir = (float) (Math.random() * 2 * Math.PI); - hp = maxHp; - prevHp = hp; - speed = 0;//(float) Math.random() * 3; - rotSpeed = 0;//(float) Math.random() - 0.5f; - sightRange = 100; - fov = (float) Math.PI / 2.5f; - fitness = 0; - brain = new Brain(10, 5, 2, 10); - sights = new Sight[2]; - } - - @Override - public void update() { - // apply hunger - hp -= 0.5f; - prevHp = hp; - 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; - } - 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: - // 9: 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] = maxHp - ((Creature) sights[i].getElement()).getHp() / maxHp; - values[3 + mul] = ((Creature) sights[i].getElement()).getBeak() / max_beak; - } - } - } - values[9] = eating ? 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) { - // Body - s.setColor(1 - (hp / maxHp), hp / maxHp, 0, 1); - s.circle(getX(), getY(), getSize()); - // Vision - 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); - 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()); - } - } - 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); - //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) eyeX + getX(), (float) eyeY + getY(), sightRange, orient, degrees); - 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); - // 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())); - } - - 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; - killing = false; - 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++; - fitness++; - if (hp > maxHp) { - hp = maxHp; - } - } - } - 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; - eating = 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 - if (Math.abs(relAngle - ndir) < fov) { - // Visible - seen = e; - angle = relAngle - ndir; - dist = tempDist; - } - //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 > maxHp) { - hp = maxHp; - } - killing = true; - Creature c = (Creature) e; - c.setHp(c.getHp() - 0.2f); - } - } - if (seen != null) { - newSights[1] = new Sight(seen, dist, angle); - } - return newSights; - } - - public void eat() { - eating = false; - for (Element e : Game.get().getWorld().getPlants()) { - if (overlaps(e)) { - eating = true; - e.setSize(e.getSize() - 0.1f); - if (e.getSize() == 0) { - e.setSize(0); - } - hp++; - fitness++; - if (hp > maxHp) { - hp = maxHp; - } - } - } - } - - public Brain getBrain() { - return brain; - } - - public void setDirection(float dir) { - this.dir = dir; - } - - public float getFitness() { - return fitness; - } - - public void reset() { - fitness = 0; - hp = maxHp; - } - - public float getBeak() { - return beak; - } - - public float getHp() { - return hp; - } - - public void setHp(float hp) { - this.hp = hp; - } -} +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. + * + * @author fazo + */ +public class Creature extends Element implements Runnable { + + public static final int default_radius = 20, maxHp = 100; + public static final float max_speed = 3, max_beak = default_radius / 4; + + private Brain brain; + private float dir, hp, prevHp, speed, sightRange, fov, fitness, rotSpeed, beak; + private boolean eating = false, killing = false, workerDone = false; + private Sight[] sights; + private Thread workerThread; + + public Creature(float x, float y) { + super(x, y, default_radius); + dir = (float) (Math.random() * 2 * Math.PI); + hp = maxHp; + prevHp = hp; + speed = 0;//(float) Math.random() * 3; + rotSpeed = 0;//(float) Math.random() - 0.5f; + sightRange = 100; + fov = (float) Math.PI / 2.5f; + fitness = 0; + brain = new Brain(9, 5, 2, 10); + sights = new Sight[2]; + } + + @Override + public void run() { + for (;;) { + if (workerDone) { + try { + Thread.sleep(1000); + } catch (InterruptedException ex) { + } + } else { + update(); + workerDone = true; + } + } + } + + @Override + public void update() { + // apply hunger + hp -= 0.5f; + prevHp = hp; + 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; + } + 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] = maxHp - ((Creature) sights[i].getElement()).getHp() / maxHp; + values[3 + mul] = ((Creature) sights[i].getElement()).getBeak() / max_beak; + } + } + } + values[8] = eating || killing ? 1 : 0; + System.out.println(values[8]); + // 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) { + // Body + s.setColor(1 - (hp / maxHp), hp / maxHp, 0, 1); + s.circle(getX(), getY(), getSize()); + // Vision + 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); + 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()); + } + } + 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); + //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) eyeX + getX(), (float) eyeY + getY(), sightRange, orient, degrees); + 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); + // 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())); + } + + 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++; + fitness++; + if (hp > maxHp) { + hp = maxHp; + } + } + } + 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 + if (Math.abs(relAngle - ndir) < fov) { + // Visible + seen = e; + angle = relAngle - ndir; + dist = tempDist; + } + //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 > maxHp) { + hp = maxHp; + } + killing = true; + Creature c = (Creature) e; + c.setHp(c.getHp() - 0.2f); + } + } + if (seen != null) { + newSights[1] = new Sight(seen, dist, angle); + } + return newSights; + } + + public void eat() { + eating = false; + for (Element e : Game.get().getWorld().getPlants()) { + if (overlaps(e)) { + eating = true; + e.setSize(e.getSize() - 0.1f); + if (e.getSize() == 0) { + e.setSize(0); + } + hp++; + fitness++; + if (hp > maxHp) { + hp = maxHp; + } + } + } + } + + /** + * 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) { + workerThread = new Thread(this); + workerThread.start(); + } + } + + public Brain getBrain() { + return brain; + } + + public void setDirection(float dir) { + this.dir = dir; + } + + public float getFitness() { + return fitness; + } + + public void reset() { + fitness = 0; + hp = maxHp; + } + + public float getBeak() { + return beak; + } + + public float getHp() { + return hp; + } + + public void setHp(float hp) { + this.hp = hp; + } +} diff --git a/core/src/logic/World.java b/core/src/logic/World.java index 2933d84..a5f2e85 100644 --- a/core/src/logic/World.java +++ b/core/src/logic/World.java @@ -23,6 +23,7 @@ public class World implements Runnable { private final int width, height, nPlants, creatPerGen; private int generation = 1; + private boolean multithreading = false, cmdLaunchNewGen = false; private int fpsLimit = 60, fps = 0; private Creature selected; private final ArrayList elements; @@ -108,6 +109,10 @@ public class World implements Runnable { if (creatures.removeAll(graveyard)) { fire(Listener.CREATURE_LIST_CHANGED); } + if (cmdLaunchNewGen) { + newGen(false); + cmdLaunchNewGen = false; + } if (creatures.isEmpty()) { // All dead, next gen newGen(false); @@ -120,7 +125,7 @@ public class World implements Runnable { } } - public void newGen(boolean restart) { + private void newGen(boolean restart) { elements.removeAll(creatures); graveyard.addAll(creatures); creatures.clear(); @@ -233,7 +238,7 @@ public class World implements Runnable { } } - public void fire(int eventCode) { + private void fire(int eventCode) { for (Listener f : listeners) { f.on(eventCode); } @@ -311,4 +316,15 @@ public class World implements Runnable { this.selected = selected; } + public boolean isMultithreading() { + return multithreading; + } + + public void setMultithreading(boolean multithreading) { + this.multithreading = multithreading; + } + + public void launchNewGen() { + cmdLaunchNewGen = true; + } } diff --git a/desktop/src/gui/GUI.java b/desktop/src/gui/GUI.java index 9362f21..f8c9883 100644 --- a/desktop/src/gui/GUI.java +++ b/desktop/src/gui/GUI.java @@ -11,10 +11,7 @@ import com.mygdx.game.Game; import com.mygdx.game.Listener; import com.mygdx.game.Log; import com.mygdx.game.Log.LogListener; -import java.util.logging.Level; -import java.util.logging.Logger; import javax.swing.JOptionPane; -import logic.World; /** *