mirror of
https://github.com/fazo96/AIrium.git
synced 2025-01-09 09:29:53 +01:00
fixed bugs, implemented brain rendering and creature selection (broken for now)
This commit is contained in:
parent
75146638bf
commit
124087d381
@ -1,105 +1,127 @@
|
||||
package com.mygdx.game;
|
||||
|
||||
import com.badlogic.gdx.ApplicationAdapter;
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.Input;
|
||||
import com.badlogic.gdx.graphics.GL20;
|
||||
import com.badlogic.gdx.graphics.g2d.BitmapFont;
|
||||
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
|
||||
import java.util.ConcurrentModificationException;
|
||||
import logic.Element;
|
||||
import logic.World;
|
||||
|
||||
public class Game extends ApplicationAdapter {
|
||||
|
||||
private static Game game;
|
||||
ShapeRenderer shaper;
|
||||
private World world;
|
||||
private float cameraSpeed = 15;
|
||||
private BitmapFont font;
|
||||
private boolean paused = false;
|
||||
|
||||
@Override
|
||||
public void create() {
|
||||
game = this;
|
||||
shaper = new ShapeRenderer();
|
||||
shaper.setAutoShapeType(true);
|
||||
font = new BitmapFont();
|
||||
Thread worldThread = new Thread(world);
|
||||
worldThread.setName("Worker");
|
||||
worldThread.setPriority(Thread.MAX_PRIORITY);
|
||||
worldThread.start();
|
||||
}
|
||||
|
||||
public Game() {
|
||||
world = new World(2500, 2500);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render() {
|
||||
// Controls
|
||||
if (Gdx.input.isKeyJustPressed(Input.Keys.SPACE)) {
|
||||
world.newGen(false);
|
||||
}
|
||||
if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) {
|
||||
shaper.translate(-cameraSpeed, 0, 0);
|
||||
}
|
||||
if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) {
|
||||
shaper.translate(cameraSpeed, 0, 0);
|
||||
}
|
||||
if (Gdx.input.isKeyPressed(Input.Keys.UP)) {
|
||||
shaper.translate(0, -cameraSpeed, 0);
|
||||
}
|
||||
if (Gdx.input.isKeyPressed(Input.Keys.DOWN)) {
|
||||
shaper.translate(0, cameraSpeed, 0);
|
||||
}
|
||||
if (Gdx.input.isKeyJustPressed(Input.Keys.PLUS)) {
|
||||
shaper.scale(0.5f, 0.5f, 1);
|
||||
}
|
||||
if (Gdx.input.isKeyJustPressed(Input.Keys.MINUS)) {
|
||||
shaper.scale(1.5f, 1.5f, 1);
|
||||
}
|
||||
if (Gdx.input.isKeyJustPressed(Input.Keys.P)) {
|
||||
paused = !paused;
|
||||
}
|
||||
if (Gdx.input.isKeyJustPressed(Input.Keys.L)) {
|
||||
if (world.getFpsLimit() == 60) {
|
||||
world.setFpsLimit(0);
|
||||
} else {
|
||||
world.setFpsLimit(60);
|
||||
}
|
||||
}
|
||||
// Draw
|
||||
Gdx.gl.glClearColor(0, 0, 0, 1);
|
||||
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
|
||||
shaper.setColor(1, 1, 1, 1);
|
||||
shaper.begin(ShapeRenderer.ShapeType.Line);
|
||||
try {
|
||||
for (Element e : world.getElements()) {
|
||||
try {
|
||||
e.render(shaper);
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
// No idea why it happens, but it's rendering so meh
|
||||
//Log.log(Log.ERROR, ex+"");
|
||||
}
|
||||
}
|
||||
} catch (ConcurrentModificationException ex) {
|
||||
}
|
||||
shaper.setColor(0.3f, 0.3f, 0.3f, 1);
|
||||
// draw borders
|
||||
shaper.rect(0, 0, world.getWidth(), world.getHeight());
|
||||
shaper.end();
|
||||
}
|
||||
|
||||
public World getWorld() {
|
||||
return world;
|
||||
}
|
||||
|
||||
public static Game get() {
|
||||
return game;
|
||||
}
|
||||
|
||||
public boolean isPaused() {
|
||||
return paused;
|
||||
}
|
||||
}
|
||||
package com.mygdx.game;
|
||||
|
||||
import com.badlogic.gdx.ApplicationAdapter;
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.Input;
|
||||
import com.badlogic.gdx.graphics.GL20;
|
||||
import com.badlogic.gdx.graphics.g2d.BitmapFont;
|
||||
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
|
||||
import java.util.ConcurrentModificationException;
|
||||
import logic.Creature;
|
||||
import logic.Element;
|
||||
import logic.World;
|
||||
|
||||
public class Game extends ApplicationAdapter {
|
||||
|
||||
private static Game game;
|
||||
ShapeRenderer renderer, overlayRenderer;
|
||||
private World world;
|
||||
private float cameraSpeed = 15;
|
||||
private BitmapFont font;
|
||||
private boolean paused = false;
|
||||
|
||||
@Override
|
||||
public void create() {
|
||||
game = this;
|
||||
renderer = new ShapeRenderer();
|
||||
renderer.setAutoShapeType(true);
|
||||
overlayRenderer = new ShapeRenderer();
|
||||
overlayRenderer.setAutoShapeType(true);
|
||||
font = new BitmapFont();
|
||||
Thread worldThread = new Thread(world);
|
||||
worldThread.setName("Worker");
|
||||
worldThread.setPriority(Thread.MAX_PRIORITY);
|
||||
worldThread.start();
|
||||
}
|
||||
|
||||
public Game() {
|
||||
world = new World(2500, 2500);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render() {
|
||||
// Controls
|
||||
if (Gdx.input.isKeyJustPressed(Input.Keys.SPACE)) {
|
||||
world.newGen(false);
|
||||
}
|
||||
if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) {
|
||||
renderer.translate(-cameraSpeed, 0, 0);
|
||||
}
|
||||
if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) {
|
||||
renderer.translate(cameraSpeed, 0, 0);
|
||||
}
|
||||
if (Gdx.input.isKeyPressed(Input.Keys.UP)) {
|
||||
renderer.translate(0, -cameraSpeed, 0);
|
||||
}
|
||||
if (Gdx.input.isKeyPressed(Input.Keys.DOWN)) {
|
||||
renderer.translate(0, cameraSpeed, 0);
|
||||
}
|
||||
if (Gdx.input.isKeyJustPressed(Input.Keys.PLUS)) {
|
||||
renderer.scale(0.5f, 0.5f, 1);
|
||||
}
|
||||
if (Gdx.input.isKeyJustPressed(Input.Keys.MINUS)) {
|
||||
renderer.scale(1.5f, 1.5f, 1);
|
||||
}
|
||||
if (Gdx.input.isKeyJustPressed(Input.Keys.P)) {
|
||||
paused = !paused;
|
||||
}
|
||||
if (Gdx.input.isKeyJustPressed(Input.Keys.L)) {
|
||||
if (world.getFpsLimit() == 60) {
|
||||
world.setFpsLimit(0);
|
||||
} else {
|
||||
world.setFpsLimit(60);
|
||||
}
|
||||
}
|
||||
if (Gdx.input.isButtonPressed(Input.Buttons.RIGHT)) {
|
||||
renderer.translate(Gdx.input.getDeltaX(), Gdx.input.getDeltaY() * -1, 0);
|
||||
}
|
||||
/*
|
||||
// Broken for now
|
||||
if(Gdx.input.isButtonPressed(Input.Buttons.LEFT)){
|
||||
// TODO: project coordinates to world
|
||||
world.selectCreatureAt(Gdx.input.getX(), Gdx.input.getY());
|
||||
}*/
|
||||
// Draw
|
||||
Gdx.gl.glClearColor(0, 0, 0, 1);
|
||||
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
|
||||
renderer.begin(ShapeRenderer.ShapeType.Line);
|
||||
try {
|
||||
for (Element e : world.getElements()) {
|
||||
try {
|
||||
e.render(renderer);
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
// No idea why it happens, but it's rendering so meh
|
||||
//Log.log(Log.ERROR, ex+"");
|
||||
}
|
||||
}
|
||||
} catch (ConcurrentModificationException ex) {
|
||||
}
|
||||
if (world.getSelectedCreature() != null) {
|
||||
// There is a selection
|
||||
Creature c = world.getSelectedCreature();
|
||||
renderer.setColor(1, 1, 1, 1);
|
||||
// Draw selection rectangle
|
||||
renderer.rect(c.getX() - c.getSize() / 2, c.getY() - c.getSize() / 2, c.getX() + c.getSize() / 2, c.getY() + c.getSize() / 2);
|
||||
// Draw brain
|
||||
overlayRenderer.begin();
|
||||
c.getBrain().render(overlayRenderer);
|
||||
overlayRenderer.end();
|
||||
}
|
||||
renderer.setColor(0.3f, 0.3f, 0.3f, 1);
|
||||
// draw borders
|
||||
renderer.rect(0, 0, world.getWidth(), world.getHeight());
|
||||
renderer.end();
|
||||
}
|
||||
|
||||
public World getWorld() {
|
||||
return world;
|
||||
}
|
||||
|
||||
public static Game get() {
|
||||
return game;
|
||||
}
|
||||
|
||||
public boolean isPaused() {
|
||||
return paused;
|
||||
}
|
||||
}
|
||||
|
@ -1,45 +1,45 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
|
||||
import com.mygdx.game.Game;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author fazo
|
||||
*/
|
||||
public class Vegetable extends Element {
|
||||
|
||||
public static final int default_radius = 5;
|
||||
private float decayRate = 0;
|
||||
|
||||
public Vegetable(float x, float y) {
|
||||
super(x, y, default_radius);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
setSize(getSize()-decayRate);
|
||||
if (getSize() <= 0) {
|
||||
Game.get().getWorld().getDeadPlants().add(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(ShapeRenderer s) {
|
||||
s.setColor(1, 1, 1, 1);
|
||||
s.circle(getX(), getY(), getSize());
|
||||
}
|
||||
|
||||
public float getDecayRate() {
|
||||
return decayRate;
|
||||
}
|
||||
|
||||
public void setDecayRate(float decayRate) {
|
||||
this.decayRate = decayRate;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
|
||||
import com.mygdx.game.Game;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author fazo
|
||||
*/
|
||||
public class Vegetable extends Element {
|
||||
|
||||
public static final int default_radius = 5;
|
||||
private float decayRate = 0;
|
||||
|
||||
public Vegetable(float x, float y) {
|
||||
super(x, y, default_radius);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
setSize(getSize()-decayRate);
|
||||
if (getSize() <= 2) {
|
||||
Game.get().getWorld().getDeadPlants().add(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(ShapeRenderer s) {
|
||||
s.setColor(1, 1, 1, 1);
|
||||
s.circle(getX(), getY(), getSize());
|
||||
}
|
||||
|
||||
public float getDecayRate() {
|
||||
return decayRate;
|
||||
}
|
||||
|
||||
public void setDecayRate(float decayRate) {
|
||||
this.decayRate = decayRate;
|
||||
}
|
||||
}
|
||||
|
@ -1,279 +1,311 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.mygdx.game.Game;
|
||||
import com.mygdx.game.Log;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author fazo
|
||||
*/
|
||||
public class World implements Runnable {
|
||||
|
||||
private final int width, height, nPlants, creatPerGen;
|
||||
private int generation = 1;
|
||||
private int fpsLimit = 60, fps = 0;
|
||||
private final ArrayList<Element> elements;
|
||||
private final ArrayList<Element> toAdd;
|
||||
private final ArrayList<Creature> creatures;
|
||||
private final ArrayList<Creature> graveyard;
|
||||
private final ArrayList<Vegetable> plants;
|
||||
private final ArrayList<Vegetable> deadPlants;
|
||||
private final ArrayList<FpsListener> fpsListeners;
|
||||
|
||||
public World(int width, int height) {
|
||||
this.width = width;
|
||||
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 / 5500);
|
||||
plants = new ArrayList();
|
||||
deadPlants = new ArrayList();
|
||||
graveyard = new ArrayList();
|
||||
fpsListeners = new ArrayList();
|
||||
newGen(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Date d, timekeeper = new Date();
|
||||
long time;
|
||||
int target, frames = 0;
|
||||
for (;;) {
|
||||
if (!Game.get().isPaused()) {
|
||||
d = new Date();
|
||||
update();
|
||||
frames++;
|
||||
Date now = new Date();
|
||||
if (now.getTime() - timekeeper.getTime() > 1000) {
|
||||
fps = frames;
|
||||
frames = 0;
|
||||
for (FpsListener f : fpsListeners) {
|
||||
f.fpsChanged(fps);
|
||||
}
|
||||
timekeeper = new Date();
|
||||
}
|
||||
if (fpsLimit > 0) {
|
||||
time = now.getTime() - d.getTime();
|
||||
target = 1000 / fpsLimit;
|
||||
if (time < target) {
|
||||
try {
|
||||
Thread.sleep((long) (target - time));
|
||||
} catch (InterruptedException ex) {
|
||||
Logger.getLogger(World.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException ex) {
|
||||
Logger.getLogger(World.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
deadPlants.clear();
|
||||
creatures.removeAll(graveyard);
|
||||
if (creatures.isEmpty()) {
|
||||
// All dead, next gen
|
||||
newGen(false);
|
||||
}
|
||||
while (plants.size() < nPlants) {
|
||||
spawnVegetable();
|
||||
}
|
||||
for (Element e : elements) {
|
||||
e.update();
|
||||
}
|
||||
}
|
||||
|
||||
public void newGen(boolean restart) {
|
||||
elements.removeAll(creatures);
|
||||
graveyard.addAll(creatures);
|
||||
creatures.clear();
|
||||
Comparator creatureComp = new Comparator<Creature>() {
|
||||
|
||||
@Override
|
||||
public int compare(Creature t, Creature t1) {
|
||||
// put the highest fitness first (sort in reverse)
|
||||
return (int) (t1.getFitness() - t.getFitness());
|
||||
}
|
||||
};
|
||||
if (graveyard.isEmpty() || restart) { // First gen
|
||||
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.round(graveyard.size() * 0.05f);
|
||||
Creature[] top = new Creature[topSize];
|
||||
// Calculate avg fitness and prepare best agent list
|
||||
float avgFitness = 0;
|
||||
for (int i = 0; i < graveyard.size(); i++) {
|
||||
Creature c = graveyard.get(i);
|
||||
if (i < topSize) {
|
||||
top[i] = graveyard.get(i);
|
||||
Log.log(Log.INFO, "Gen " + generation + " Top " + (i + 1) + ": " + c.getFitness());
|
||||
}
|
||||
avgFitness += c.getFitness();
|
||||
}
|
||||
avgFitness = avgFitness / graveyard.size();
|
||||
Log.log(Log.INFO, "Gen " + generation + " done. Avg fitness: " + avgFitness);
|
||||
// Generate children
|
||||
for (Creature c : graveyard) {
|
||||
int first = (int) Math.floor(Math.random() * topSize);
|
||||
int sec = first;
|
||||
while (sec == first) {
|
||||
sec = (int) Math.floor(Math.random() * topSize);
|
||||
}
|
||||
float[][][] n = null;
|
||||
try {
|
||||
n = top[first].getBrain().breed(top[sec].getBrain().getMap());
|
||||
} catch (Exception ex) {
|
||||
// Should not happen
|
||||
Logger.getLogger(World.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
Creature ne = spawnCreature(n);
|
||||
ne.getBrain().mutate(0.05f); // mutate children
|
||||
}
|
||||
graveyard.clear();
|
||||
generation++;
|
||||
}
|
||||
}
|
||||
|
||||
private Element spawn(boolean isCreature, float[][][] brainMap) {
|
||||
int x, y, r;
|
||||
boolean overlaps = false;
|
||||
if (isCreature) {
|
||||
r = Creature.default_radius;
|
||||
} else {
|
||||
r = Vegetable.default_radius;
|
||||
}
|
||||
do {
|
||||
overlaps = false;
|
||||
x = (int) (Math.random() * width);
|
||||
y = (int) (Math.random() * height);
|
||||
for (Element e : elements) {
|
||||
if (e.overlaps(x, y, r)) {
|
||||
overlaps = true;
|
||||
}
|
||||
}
|
||||
} while (overlaps);
|
||||
if (isCreature) {
|
||||
Log.log(Log.DEBUG, "New Creat: " + x + " " + y);
|
||||
Creature c = new Creature(x, y);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public interface FpsListener {
|
||||
|
||||
public abstract void fpsChanged(int newValue);
|
||||
}
|
||||
|
||||
private void spawnVegetable() {
|
||||
spawn(false, null);
|
||||
}
|
||||
|
||||
private Creature spawnCreature() {
|
||||
return (Creature) spawn(true, null);
|
||||
}
|
||||
|
||||
private Creature spawnCreature(float[][][] b) {
|
||||
return (Creature) spawn(true, b);
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public int getGeneration() {
|
||||
return generation;
|
||||
}
|
||||
|
||||
public void addFpsListener(FpsListener f) {
|
||||
fpsListeners.add(f);
|
||||
}
|
||||
|
||||
public void add(Element e) {
|
||||
toAdd.add(e);
|
||||
}
|
||||
|
||||
public ArrayList<Element> getElements() {
|
||||
return elements;
|
||||
}
|
||||
|
||||
public ArrayList<Creature> getGraveyard() {
|
||||
return graveyard;
|
||||
}
|
||||
|
||||
public ArrayList<Vegetable> getDeadPlants() {
|
||||
return deadPlants;
|
||||
}
|
||||
|
||||
public ArrayList<Creature> getCreatures() {
|
||||
return creatures;
|
||||
}
|
||||
|
||||
public ArrayList<Vegetable> getPlants() {
|
||||
return plants;
|
||||
}
|
||||
|
||||
public float getFpsLimit() {
|
||||
return fpsLimit;
|
||||
}
|
||||
|
||||
public void setFpsLimit(int fpsLimit) {
|
||||
this.fpsLimit = fpsLimit;
|
||||
}
|
||||
|
||||
public float getFps() {
|
||||
return fps;
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.mygdx.game.Game;
|
||||
import com.mygdx.game.Log;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.Date;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author fazo
|
||||
*/
|
||||
public class World implements Runnable {
|
||||
|
||||
private final int width, height, nPlants, creatPerGen;
|
||||
private int generation = 1;
|
||||
private int fpsLimit = 60, fps = 0;
|
||||
private Creature selected;
|
||||
private final ArrayList<Element> elements;
|
||||
private final ArrayList<Element> toAdd;
|
||||
private final ArrayList<Creature> creatures;
|
||||
private final ArrayList<Creature> graveyard;
|
||||
private final ArrayList<Vegetable> plants;
|
||||
private final ArrayList<Vegetable> deadPlants;
|
||||
private final ArrayList<FpsListener> fpsListeners;
|
||||
|
||||
public World(int width, int height) {
|
||||
this.width = width;
|
||||
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 / 5500);
|
||||
plants = new ArrayList();
|
||||
deadPlants = new ArrayList();
|
||||
graveyard = new ArrayList();
|
||||
fpsListeners = new ArrayList();
|
||||
selected = null;
|
||||
newGen(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Date d, timekeeper = new Date();
|
||||
long time;
|
||||
int target, frames = 0;
|
||||
for (;;) {
|
||||
if (!Game.get().isPaused()) {
|
||||
d = new Date();
|
||||
update();
|
||||
frames++;
|
||||
Date now = new Date();
|
||||
if (now.getTime() - timekeeper.getTime() > 1000) {
|
||||
fps = frames;
|
||||
frames = 0;
|
||||
for (FpsListener f : fpsListeners) {
|
||||
f.fpsChanged(fps);
|
||||
}
|
||||
timekeeper = new Date();
|
||||
}
|
||||
if (fpsLimit > 0) {
|
||||
time = now.getTime() - d.getTime();
|
||||
target = 1000 / fpsLimit;
|
||||
if (time < target) {
|
||||
try {
|
||||
Thread.sleep((long) (target - time));
|
||||
} catch (InterruptedException ex) {
|
||||
Logger.getLogger(World.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException ex) {
|
||||
Logger.getLogger(World.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (selected != null && graveyard.contains(selected)) {
|
||||
selected = null;
|
||||
Log.log(Log.INFO, "Cleared selection");
|
||||
}
|
||||
elements.removeAll(deadPlants);
|
||||
plants.removeAll(deadPlants);
|
||||
deadPlants.clear();
|
||||
creatures.removeAll(graveyard);
|
||||
if (creatures.isEmpty()) {
|
||||
// All dead, next gen
|
||||
newGen(false);
|
||||
}
|
||||
while (plants.size() < nPlants) {
|
||||
spawnVegetable();
|
||||
}
|
||||
for (Element e : elements) {
|
||||
e.update();
|
||||
}
|
||||
}
|
||||
|
||||
public void newGen(boolean restart) {
|
||||
elements.removeAll(creatures);
|
||||
graveyard.addAll(creatures);
|
||||
creatures.clear();
|
||||
if (selected != null) {
|
||||
selected = null;
|
||||
Log.log(Log.INFO, "Cleared selection");
|
||||
}
|
||||
Comparator creatureComp = new Comparator<Creature>() {
|
||||
|
||||
@Override
|
||||
public int compare(Creature t, Creature t1) {
|
||||
// put the highest fitness first (sort in reverse)
|
||||
return (int) (t1.getFitness() - t.getFitness());
|
||||
}
|
||||
};
|
||||
if (graveyard.isEmpty() || restart) { // First gen
|
||||
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.round(graveyard.size() * 0.05f);
|
||||
Creature[] top = new Creature[topSize];
|
||||
// Calculate avg fitness and prepare best agent list
|
||||
float avgFitness = 0;
|
||||
for (int i = 0; i < graveyard.size(); i++) {
|
||||
Creature c = graveyard.get(i);
|
||||
if (i < topSize) {
|
||||
top[i] = graveyard.get(i);
|
||||
Log.log(Log.INFO, "Gen " + generation + " Top " + (i + 1) + ": " + c.getFitness());
|
||||
}
|
||||
avgFitness += c.getFitness();
|
||||
}
|
||||
avgFitness = avgFitness / graveyard.size();
|
||||
Log.log(Log.INFO, "Gen " + generation + " done. Avg fitness: " + avgFitness);
|
||||
// Generate children
|
||||
for (Creature c : graveyard) {
|
||||
int first = (int) Math.floor(Math.random() * topSize);
|
||||
int sec = first;
|
||||
while (sec == first) {
|
||||
sec = (int) Math.floor(Math.random() * topSize);
|
||||
}
|
||||
float[][][] n = null;
|
||||
try {
|
||||
n = top[first].getBrain().breed(top[sec].getBrain().getMap());
|
||||
} catch (Exception ex) {
|
||||
// Should not happen
|
||||
Logger.getLogger(World.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
Creature ne = spawnCreature(n);
|
||||
ne.getBrain().mutate(0.05f); // mutate children
|
||||
}
|
||||
graveyard.clear();
|
||||
generation++;
|
||||
}
|
||||
}
|
||||
|
||||
private Element spawn(boolean isCreature, float[][][] brainMap) {
|
||||
int x, y, r;
|
||||
boolean overlaps = false;
|
||||
if (isCreature) {
|
||||
r = Creature.default_radius;
|
||||
} else {
|
||||
r = Vegetable.default_radius;
|
||||
}
|
||||
do {
|
||||
overlaps = false;
|
||||
x = (int) (Math.random() * width);
|
||||
y = (int) (Math.random() * height);
|
||||
for (Element e : elements) {
|
||||
if (e.overlaps(x, y, r)) {
|
||||
overlaps = true;
|
||||
}
|
||||
}
|
||||
} while (overlaps);
|
||||
if (isCreature) {
|
||||
Log.log(Log.DEBUG, "New Creat: " + x + " " + y);
|
||||
Creature c = new Creature(x, y);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public interface FpsListener {
|
||||
|
||||
public abstract void fpsChanged(int newValue);
|
||||
}
|
||||
|
||||
public void selectCreatureAt(int x, int y) {
|
||||
selected = null; // Clear selection
|
||||
try {
|
||||
for (Creature c : creatures) {
|
||||
if (c.overlaps(x, y)) {
|
||||
selected = c;
|
||||
Log.log(Log.INFO, "Selected a creature");
|
||||
}
|
||||
}
|
||||
} catch (ConcurrentModificationException ex) {
|
||||
}
|
||||
}
|
||||
|
||||
private void spawnVegetable() {
|
||||
spawn(false, null);
|
||||
}
|
||||
|
||||
private Creature spawnCreature() {
|
||||
return (Creature) spawn(true, null);
|
||||
}
|
||||
|
||||
private Creature spawnCreature(float[][][] b) {
|
||||
return (Creature) spawn(true, b);
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public int getGeneration() {
|
||||
return generation;
|
||||
}
|
||||
|
||||
public void addFpsListener(FpsListener f) {
|
||||
fpsListeners.add(f);
|
||||
}
|
||||
|
||||
public void add(Element e) {
|
||||
toAdd.add(e);
|
||||
}
|
||||
|
||||
public ArrayList<Element> getElements() {
|
||||
return elements;
|
||||
}
|
||||
|
||||
public ArrayList<Creature> getGraveyard() {
|
||||
return graveyard;
|
||||
}
|
||||
|
||||
public ArrayList<Vegetable> getDeadPlants() {
|
||||
return deadPlants;
|
||||
}
|
||||
|
||||
public ArrayList<Creature> getCreatures() {
|
||||
return creatures;
|
||||
}
|
||||
|
||||
public ArrayList<Vegetable> getPlants() {
|
||||
return plants;
|
||||
}
|
||||
|
||||
public float getFpsLimit() {
|
||||
return fpsLimit;
|
||||
}
|
||||
|
||||
public void setFpsLimit(int fpsLimit) {
|
||||
this.fpsLimit = fpsLimit;
|
||||
}
|
||||
|
||||
public float getFps() {
|
||||
return fps;
|
||||
}
|
||||
|
||||
public Creature getSelectedCreature() {
|
||||
return selected;
|
||||
}
|
||||
|
||||
public void selectCreature(Creature selected) {
|
||||
this.selected = selected;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,237 +1,271 @@
|
||||
package logic.neural;
|
||||
|
||||
import com.mygdx.game.Log;
|
||||
|
||||
/**
|
||||
* Represents a virtual brain
|
||||
*
|
||||
* @author fazo
|
||||
*/
|
||||
public class Brain {
|
||||
|
||||
public static final float bias = 0.5f;
|
||||
private Neuron[][] neurons;
|
||||
|
||||
/**
|
||||
* Create a new brain with a random map (mind) with given number of neurons
|
||||
*
|
||||
* @param nInputs the number of input neurons (at least 1)
|
||||
* @param nOutputs the number of output neurons (at least 1)
|
||||
* @param hiddenLayers how many hidden layers of neurons (at least 1)
|
||||
* @param neuronsPerHiddenLayer how many neurons per hidden layer (at least
|
||||
* 1)
|
||||
*/
|
||||
public Brain(int nInputs, int nOutputs, int hiddenLayers, int neuronsPerHiddenLayer) {
|
||||
// Prepare brain map
|
||||
neurons = new Neuron[hiddenLayers + 2][];
|
||||
neurons[0] = new Neuron[nInputs];
|
||||
neurons[hiddenLayers + 1] = new Neuron[nOutputs];
|
||||
for (int i = 0; i < hiddenLayers; i++) {
|
||||
neurons[i + 1] = new Neuron[neuronsPerHiddenLayer];
|
||||
}
|
||||
// Randomize brain
|
||||
initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new brain using given brain map (mind)
|
||||
*
|
||||
* @param brainMap the brain map (mind) to use
|
||||
*/
|
||||
public Brain(float[][][] brainMap) {
|
||||
neurons = new Neuron[brainMap.length][];
|
||||
for (int i = 0; i < brainMap.length; i++) { // for each layer
|
||||
neurons[i] = new Neuron[brainMap[i].length];
|
||||
for (int j = 0; j < brainMap[i].length; j++) { // for each neuron
|
||||
neurons[i][j] = new Neuron(i, bias, this, brainMap[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a new brain map (mind) to this brain
|
||||
*
|
||||
* @param brainMap the new brain map to apply
|
||||
*/
|
||||
public void remap(float[][][] brainMap) {
|
||||
for (int i = 0; i < brainMap.length; i++) { // for each layer
|
||||
for (int j = 0; j < brainMap[i].length; j++) { // for each neuron
|
||||
// skip input layer
|
||||
if (neurons[i + 1][j] == null) {
|
||||
neurons[i + 1][j] = new Neuron(j, bias, this, brainMap[i][j]);
|
||||
} else {
|
||||
neurons[i + 1][j].setWeights(brainMap[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the brain with brand new random neurons
|
||||
*/
|
||||
private void initialize() {
|
||||
// init hidden layers
|
||||
for (int i = 0; i < neurons.length; i++) {
|
||||
for (int j = 0; j < neurons[i].length; j++) {
|
||||
// create neuron
|
||||
Neuron n = new Neuron(i, bias, this);
|
||||
neurons[i][j] = n;
|
||||
Log.log(Log.DEBUG, "Adding Layer " + (i + 1) + " Neuron " + (j + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Give some input to the brain
|
||||
*
|
||||
* @param values the array of input. Its length must match the number of
|
||||
* input neurons of this brain
|
||||
* @throws Exception if the number of inputs given differs from the number
|
||||
* of input neurons of this brain
|
||||
*/
|
||||
public void input(float[] values) throws Exception {
|
||||
if (values.length != neurons[0].length) {
|
||||
throw new Exception("Not enough or too many inputs");
|
||||
}
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
neurons[0][i].setOutput(values[i]);
|
||||
}
|
||||
clearCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute output of the brain starting from given input
|
||||
*
|
||||
* @return an array as long as the number of output neurons, containing the
|
||||
* result
|
||||
*/
|
||||
public float[] compute() {
|
||||
//clearCache(); // unnecessary if already called when changing inputs
|
||||
float[] res = new float[neurons[neurons.length - 1].length];
|
||||
for (int i = 0; i < neurons[neurons.length - 1].length; i++) {
|
||||
res[i] = neurons[neurons.length - 1][i].compute();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Input some values (see input function) and then compute the results.
|
||||
*
|
||||
* @param values
|
||||
* @return the results of the neural network
|
||||
* @throws Exception if the number of inputs given differs from the number
|
||||
* of input neurons of this brain
|
||||
*/
|
||||
public float[] compute(float[] values) throws Exception {
|
||||
input(values);
|
||||
return compute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a brainMap that represents this brain's mind
|
||||
*
|
||||
* @return a tridimensional floating point number array representing a full
|
||||
* mind
|
||||
*/
|
||||
public float[][][] getMap() {
|
||||
float[][][] res = new float[neurons.length - 1][][];
|
||||
for (int i = 1; i < neurons.length; i++) // layers (skip input layer)
|
||||
{
|
||||
res[i - 1] = new float[neurons[i].length][];
|
||||
for (int j = 0; j < neurons[i].length; j++) // neurons per layer
|
||||
{
|
||||
res[i - 1][j] = neurons[i][j].getWeights();
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a map of this brain's mind.. with a mutation
|
||||
*
|
||||
* @param mutationFactor the higher this number, the bigger the mutation
|
||||
* @return a mutated brain map of this brain's mind
|
||||
*/
|
||||
public float[][][] getMutatedMap(float mutationFactor) {
|
||||
float[][][] res = new float[neurons.length - 1][][];
|
||||
for (int i = 1; i < neurons.length; i++) // layers (skip input layer)
|
||||
{
|
||||
res[i - 1] = new float[neurons[i].length][];
|
||||
for (int j = 0; j < neurons[i].length; j++) // neurons per layer
|
||||
{
|
||||
res[i - 1][j] = neurons[i][j].mutate(mutationFactor);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a mutation to this brain
|
||||
*
|
||||
* @param mutationFactor the higher this number, the bigger the mutation
|
||||
*/
|
||||
public void mutate(float mutationFactor) {
|
||||
for (int i = 1; i < neurons.length; i++) // layers (skip input layer)
|
||||
{
|
||||
for (int j = 0; j < neurons[i].length; j++) // neurons per layer
|
||||
{
|
||||
neurons[i][j].setWeights(neurons[i][j].mutate(mutationFactor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float[][][] breed(float[][][] map) throws Exception {
|
||||
float[][][] res = new float[neurons.length - 1][][];
|
||||
if (map.length != neurons.length - 1) {
|
||||
throw new Exception("incompatible brains");
|
||||
}
|
||||
for (int i = 1; i < neurons.length; i++) // layers (skip input layer)
|
||||
{
|
||||
res[i - 1] = new float[neurons[i].length][];
|
||||
if (map[i - 1].length != neurons[i].length) {
|
||||
throw new Exception("incompatible brains");
|
||||
}
|
||||
for (int j = 0; j < neurons[i].length; j++) // neurons per layer
|
||||
{
|
||||
// j = 8 not valid for neurons[i][j]. investigate why.
|
||||
//System.out.println(i+" "+j+" | "+neurons[i].length+" "+res[i-1].length+" "+neurons[i][j].getWeights().length);
|
||||
//System.out.println(neurons[i].length +" has to be > "+j);
|
||||
res[i - 1][j] = new float[neurons[i][j].getWeights().length];
|
||||
if (map[i - 1][j].length != neurons[i][j].getWeights().length) {
|
||||
throw new Exception("incompatible brains");
|
||||
}
|
||||
for (int z = 0; z < neurons[i][j].getWeights().length; z++) // each weight
|
||||
{
|
||||
// Combine the two weights
|
||||
if (Math.random() < 0.5) {
|
||||
res[i - 1][j][z] = map[i - 1][j][z];
|
||||
} else {
|
||||
res[i - 1][j][z] = neurons[i][j].getWeights()[z];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empties the neurons' cache. Needs to be called after changing brain
|
||||
* inputs or before computing the result.
|
||||
*/
|
||||
private void clearCache() {
|
||||
for (int i = 1; i < neurons.length; i++) {
|
||||
for (int j = 0; j < neurons[i].length; j++) {
|
||||
neurons[i][j].clearCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with pointers to all this brain's neurons.
|
||||
*
|
||||
* @return bidimensional array with first index representing the layer.
|
||||
*/
|
||||
public Neuron[][] getNeurons() {
|
||||
return neurons;
|
||||
}
|
||||
}
|
||||
package logic.neural;
|
||||
|
||||
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
|
||||
import com.mygdx.game.Log;
|
||||
|
||||
/**
|
||||
* Represents a virtual brain
|
||||
*
|
||||
* @author fazo
|
||||
*/
|
||||
public class Brain {
|
||||
|
||||
public static final float bias = 0.5f;
|
||||
private Neuron[][] neurons;
|
||||
|
||||
/**
|
||||
* Create a new brain with a random map (mind) with given number of neurons
|
||||
*
|
||||
* @param nInputs the number of input neurons (at least 1)
|
||||
* @param nOutputs the number of output neurons (at least 1)
|
||||
* @param hiddenLayers how many hidden layers of neurons (at least 1)
|
||||
* @param neuronsPerHiddenLayer how many neurons per hidden layer (at least
|
||||
* 1)
|
||||
*/
|
||||
public Brain(int nInputs, int nOutputs, int hiddenLayers, int neuronsPerHiddenLayer) {
|
||||
// Prepare brain map
|
||||
neurons = new Neuron[hiddenLayers + 2][];
|
||||
neurons[0] = new Neuron[nInputs];
|
||||
neurons[hiddenLayers + 1] = new Neuron[nOutputs];
|
||||
for (int i = 0; i < hiddenLayers; i++) {
|
||||
neurons[i + 1] = new Neuron[neuronsPerHiddenLayer];
|
||||
}
|
||||
// Randomize brain
|
||||
initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new brain using given brain map (mind)
|
||||
*
|
||||
* @param brainMap the brain map (mind) to use
|
||||
*/
|
||||
public Brain(float[][][] brainMap) {
|
||||
neurons = new Neuron[brainMap.length][];
|
||||
for (int i = 0; i < brainMap.length; i++) { // for each layer
|
||||
neurons[i] = new Neuron[brainMap[i].length];
|
||||
for (int j = 0; j < brainMap[i].length; j++) { // for each neuron
|
||||
neurons[i][j] = new Neuron(i, bias, this, brainMap[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a new brain map (mind) to this brain
|
||||
*
|
||||
* @param brainMap the new brain map to apply
|
||||
*/
|
||||
public void remap(float[][][] brainMap) {
|
||||
for (int i = 0; i < brainMap.length; i++) { // for each layer
|
||||
for (int j = 0; j < brainMap[i].length; j++) { // for each neuron
|
||||
// skip input layer
|
||||
if (neurons[i + 1][j] == null) {
|
||||
neurons[i + 1][j] = new Neuron(j, bias, this, brainMap[i][j]);
|
||||
} else {
|
||||
neurons[i + 1][j].setWeights(brainMap[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the brain with brand new random neurons
|
||||
*/
|
||||
private void initialize() {
|
||||
// init hidden layers
|
||||
for (int i = 0; i < neurons.length; i++) {
|
||||
for (int j = 0; j < neurons[i].length; j++) {
|
||||
// create neuron
|
||||
Neuron n = new Neuron(i, bias, this);
|
||||
neurons[i][j] = n;
|
||||
Log.log(Log.DEBUG, "Adding Layer " + (i + 1) + " Neuron " + (j + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw this brain's status.
|
||||
*
|
||||
* @param s the ShapeRenderer to use for the drawing
|
||||
*/
|
||||
public void render(ShapeRenderer s) {
|
||||
s.set(ShapeRenderer.ShapeType.Filled);
|
||||
int neuronHeight = 0;
|
||||
for (Neuron[] ns : neurons) {
|
||||
if (ns.length > neuronHeight) {
|
||||
neuronHeight = ns.length;
|
||||
}
|
||||
}
|
||||
s.rect(0, 0, neurons.length * 50, neuronHeight * 30);
|
||||
for (int i = 0; i < neurons.length; i++) {
|
||||
//s.set(ShapeRenderer.ShapeType.Line);
|
||||
for (int j = 0; j < neurons[i].length; j++) {
|
||||
// get neuron result first so cache system can kick in and save some calculations
|
||||
float nr = neurons[i][j].compute();
|
||||
// Draw neuron links
|
||||
float[] links = neurons[i][j].getInputs();
|
||||
for (int f = 0; f < links.length; f++) {
|
||||
s.setColor(links[f], links[f], links[f], 1);
|
||||
s.line(i * 50, j * 30, (i - 1) * 50, f * 30);
|
||||
}
|
||||
// Draw neuron
|
||||
s.setColor(1 - nr, nr, 0, 1);
|
||||
s.set(ShapeRenderer.ShapeType.Filled);
|
||||
s.circle(i * 50, j * 30, 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Give some input to the brain
|
||||
*
|
||||
* @param values the array of input. Its length must match the number of
|
||||
* input neurons of this brain
|
||||
* @throws Exception if the number of inputs given differs from the number
|
||||
* of input neurons of this brain
|
||||
*/
|
||||
public void input(float[] values) throws Exception {
|
||||
if (values.length != neurons[0].length) {
|
||||
throw new Exception("Not enough or too many inputs");
|
||||
}
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
neurons[0][i].setOutput(values[i]);
|
||||
}
|
||||
clearCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute output of the brain starting from given input
|
||||
*
|
||||
* @return an array as long as the number of output neurons, containing the
|
||||
* result
|
||||
*/
|
||||
public float[] compute() {
|
||||
//clearCache(); // unnecessary if already called when changing inputs
|
||||
float[] res = new float[neurons[neurons.length - 1].length];
|
||||
for (int i = 0; i < neurons[neurons.length - 1].length; i++) {
|
||||
res[i] = neurons[neurons.length - 1][i].compute();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Input some values (see input function) and then compute the results.
|
||||
*
|
||||
* @param values
|
||||
* @return the results of the neural network
|
||||
* @throws Exception if the number of inputs given differs from the number
|
||||
* of input neurons of this brain
|
||||
*/
|
||||
public float[] compute(float[] values) throws Exception {
|
||||
input(values);
|
||||
return compute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a brainMap that represents this brain's mind
|
||||
*
|
||||
* @return a tridimensional floating point number array representing a full
|
||||
* mind
|
||||
*/
|
||||
public float[][][] getMap() {
|
||||
float[][][] res = new float[neurons.length - 1][][];
|
||||
for (int i = 1; i < neurons.length; i++) // layers (skip input layer)
|
||||
{
|
||||
res[i - 1] = new float[neurons[i].length][];
|
||||
for (int j = 0; j < neurons[i].length; j++) // neurons per layer
|
||||
{
|
||||
res[i - 1][j] = neurons[i][j].getWeights();
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a map of this brain's mind.. with a mutation
|
||||
*
|
||||
* @param mutationFactor the higher this number, the bigger the mutation
|
||||
* @return a mutated brain map of this brain's mind
|
||||
*/
|
||||
public float[][][] getMutatedMap(float mutationFactor) {
|
||||
float[][][] res = new float[neurons.length - 1][][];
|
||||
for (int i = 1; i < neurons.length; i++) // layers (skip input layer)
|
||||
{
|
||||
res[i - 1] = new float[neurons[i].length][];
|
||||
for (int j = 0; j < neurons[i].length; j++) // neurons per layer
|
||||
{
|
||||
res[i - 1][j] = neurons[i][j].mutate(mutationFactor);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a mutation to this brain
|
||||
*
|
||||
* @param mutationFactor the higher this number, the bigger the mutation
|
||||
*/
|
||||
public void mutate(float mutationFactor) {
|
||||
for (int i = 1; i < neurons.length; i++) // layers (skip input layer)
|
||||
{
|
||||
for (int j = 0; j < neurons[i].length; j++) // neurons per layer
|
||||
{
|
||||
neurons[i][j].setWeights(neurons[i][j].mutate(mutationFactor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float[][][] breed(float[][][] map) throws Exception {
|
||||
float[][][] res = new float[neurons.length - 1][][];
|
||||
if (map.length != neurons.length - 1) {
|
||||
throw new Exception("incompatible brains");
|
||||
}
|
||||
for (int i = 1; i < neurons.length; i++) // layers (skip input layer)
|
||||
{
|
||||
res[i - 1] = new float[neurons[i].length][];
|
||||
if (map[i - 1].length != neurons[i].length) {
|
||||
throw new Exception("incompatible brains");
|
||||
}
|
||||
for (int j = 0; j < neurons[i].length; j++) // neurons per layer
|
||||
{
|
||||
// j = 8 not valid for neurons[i][j]. investigate why.
|
||||
//System.out.println(i+" "+j+" | "+neurons[i].length+" "+res[i-1].length+" "+neurons[i][j].getWeights().length);
|
||||
//System.out.println(neurons[i].length +" has to be > "+j);
|
||||
res[i - 1][j] = new float[neurons[i][j].getWeights().length];
|
||||
if (map[i - 1][j].length != neurons[i][j].getWeights().length) {
|
||||
throw new Exception("incompatible brains");
|
||||
}
|
||||
for (int z = 0; z < neurons[i][j].getWeights().length; z++) // each weight
|
||||
{
|
||||
// Combine the two weights
|
||||
if (Math.random() < 0.5) {
|
||||
res[i - 1][j][z] = map[i - 1][j][z];
|
||||
} else {
|
||||
res[i - 1][j][z] = neurons[i][j].getWeights()[z];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empties the neurons' cache. Needs to be called after changing brain
|
||||
* inputs or before computing the result.
|
||||
*/
|
||||
private void clearCache() {
|
||||
for (int i = 1; i < neurons.length; i++) {
|
||||
for (int j = 0; j < neurons[i].length; j++) {
|
||||
neurons[i][j].clearCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with pointers to all this brain's neurons.
|
||||
*
|
||||
* @return bidimensional array with first index representing the layer.
|
||||
*/
|
||||
public Neuron[][] getNeurons() {
|
||||
return neurons;
|
||||
}
|
||||
}
|
||||
|
@ -1,124 +1,142 @@
|
||||
/*
|
||||
* 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.neural;
|
||||
|
||||
import com.mygdx.game.Log;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author fazo
|
||||
*/
|
||||
public class Neuron {
|
||||
|
||||
private float[] weights;
|
||||
private NeuronCache cache;
|
||||
private float bias, output;
|
||||
private boolean isInputNeuron;
|
||||
private int layer;
|
||||
private Brain brain;
|
||||
|
||||
public Neuron(int layer, float bias, Brain brain) {
|
||||
this(layer, bias, brain, null);
|
||||
}
|
||||
|
||||
public Neuron(int layer, float bias, Brain brain, float[] weights) {
|
||||
this.brain = brain;
|
||||
this.layer = layer;
|
||||
if (weights == null) {
|
||||
scramble();
|
||||
} else {
|
||||
this.weights = weights;
|
||||
}
|
||||
cache = new NeuronCache(this.weights.length);
|
||||
}
|
||||
|
||||
private void scramble() {
|
||||
// init weights
|
||||
if (layer > 0) {
|
||||
weights = new float[brain.getNeurons()[layer - 1].length];
|
||||
} else { // layer 0
|
||||
isInputNeuron = true;
|
||||
weights = new float[0];
|
||||
}
|
||||
// Put random weights
|
||||
for (int i = 0; i < weights.length; i++) {
|
||||
weights[i] = (float) (Math.random() * 5 - 2.5f);
|
||||
}
|
||||
}
|
||||
|
||||
public float compute() {
|
||||
if (isInputNeuron) {
|
||||
return output;
|
||||
}
|
||||
float a = bias * -1; // activation
|
||||
for (int i = 0; i < weights.length; i++) {
|
||||
if (cache.has(i)) {
|
||||
try {
|
||||
return cache.get(i);
|
||||
} catch (Exception ex) {
|
||||
// This should never happen
|
||||
Logger.getLogger(Neuron.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
Neuron n = brain.getNeurons()[layer - 1][i];
|
||||
a += n.compute() * weights[i];
|
||||
}
|
||||
// sigmoid function
|
||||
float res = (float) (1 / (1 + Math.pow(Math.E, a * -1)));
|
||||
Log.log(Log.DEBUG, "Computed Value " + res + " for neuron");
|
||||
return res;
|
||||
}
|
||||
|
||||
public float[] mutate(float mutationFactor) {
|
||||
float[] mutatedWeights = new float[weights.length];
|
||||
for (int i = 0; i < weights.length; i++) {
|
||||
mutatedWeights[i] = weights[i] + mutationFactor - mutationFactor / 2;
|
||||
}
|
||||
return mutatedWeights;
|
||||
}
|
||||
|
||||
public void setOutput(float output) {
|
||||
isInputNeuron = true;
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
public float getBias() {
|
||||
return bias;
|
||||
}
|
||||
|
||||
public void setBias(float bias) {
|
||||
this.bias = bias;
|
||||
}
|
||||
|
||||
public boolean isInputNeuron() {
|
||||
return isInputNeuron;
|
||||
}
|
||||
|
||||
public int getLayer() {
|
||||
return layer;
|
||||
}
|
||||
|
||||
public void setLayer(int layer) {
|
||||
this.layer = layer;
|
||||
}
|
||||
|
||||
public float[] getWeights() {
|
||||
return weights;
|
||||
}
|
||||
|
||||
public void setWeights(float[] weights) {
|
||||
this.weights = weights;
|
||||
// Changing the neuron makes the cache invalid
|
||||
clearCache();
|
||||
}
|
||||
|
||||
public void clearCache() {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* 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.neural;
|
||||
|
||||
import com.mygdx.game.Log;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author fazo
|
||||
*/
|
||||
public class Neuron {
|
||||
|
||||
private float[] weights;
|
||||
private NeuronCache cache;
|
||||
private float bias, output;
|
||||
private boolean isInputNeuron;
|
||||
private int layer;
|
||||
private Brain brain;
|
||||
|
||||
public Neuron(int layer, float bias, Brain brain) {
|
||||
this(layer, bias, brain, null);
|
||||
}
|
||||
|
||||
public Neuron(int layer, float bias, Brain brain, float[] weights) {
|
||||
this.brain = brain;
|
||||
this.layer = layer;
|
||||
if (weights == null) {
|
||||
scramble();
|
||||
} else {
|
||||
this.weights = weights;
|
||||
}
|
||||
cache = new NeuronCache(this.weights.length);
|
||||
}
|
||||
|
||||
private void scramble() {
|
||||
// init weights
|
||||
if (layer > 0) {
|
||||
weights = new float[brain.getNeurons()[layer - 1].length];
|
||||
} else { // layer 0
|
||||
isInputNeuron = true;
|
||||
weights = new float[0];
|
||||
}
|
||||
// Put random weights
|
||||
for (int i = 0; i < weights.length; i++) {
|
||||
weights[i] = (float) (Math.random() * 5 - 2.5f);
|
||||
}
|
||||
}
|
||||
|
||||
public float compute() {
|
||||
if (isInputNeuron) {
|
||||
return output;
|
||||
}
|
||||
if(cache.hasCachedOutput()) return cache.getCachedOutput();
|
||||
float a = bias * -1; // activation
|
||||
for (int i = 0; i < weights.length; i++) {
|
||||
if (cache.has(i)) {
|
||||
try {
|
||||
a += cache.get(i);
|
||||
} catch (Exception ex) {
|
||||
// This should never happen
|
||||
Logger.getLogger(Neuron.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
Neuron n = brain.getNeurons()[layer - 1][i];
|
||||
a += n.compute() * weights[i];
|
||||
}
|
||||
// sigmoid function
|
||||
float res = (float) (1 / (1 + Math.pow(Math.E, a * -1)));
|
||||
Log.log(Log.DEBUG, "Computed Value " + res + " for neuron");
|
||||
return res;
|
||||
}
|
||||
|
||||
public float[] mutate(float mutationFactor) {
|
||||
float[] mutatedWeights = new float[weights.length];
|
||||
for (int i = 0; i < weights.length; i++) {
|
||||
mutatedWeights[i] = weights[i] + mutationFactor - mutationFactor / 2;
|
||||
}
|
||||
return mutatedWeights;
|
||||
}
|
||||
|
||||
public float[] getInputs() {
|
||||
float inputs[] = new float[weights.length];
|
||||
for (int i = 0; i < inputs.length; i++) {
|
||||
if (cache.has(i)) {
|
||||
try {
|
||||
inputs[i] = cache.get(i);
|
||||
} catch (Exception ex) {
|
||||
// Shouldnt happen
|
||||
Logger.getLogger(Neuron.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
} else {
|
||||
inputs[i] = 0;
|
||||
}
|
||||
}
|
||||
return inputs;
|
||||
}
|
||||
|
||||
public void setOutput(float output) {
|
||||
isInputNeuron = true;
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
public float getBias() {
|
||||
return bias;
|
||||
}
|
||||
|
||||
public void setBias(float bias) {
|
||||
this.bias = bias;
|
||||
}
|
||||
|
||||
public boolean isInputNeuron() {
|
||||
return isInputNeuron;
|
||||
}
|
||||
|
||||
public int getLayer() {
|
||||
return layer;
|
||||
}
|
||||
|
||||
public void setLayer(int layer) {
|
||||
this.layer = layer;
|
||||
}
|
||||
|
||||
public float[] getWeights() {
|
||||
return weights;
|
||||
}
|
||||
|
||||
public void setWeights(float[] weights) {
|
||||
this.weights = weights;
|
||||
// Changing the neuron makes the cache invalid
|
||||
clearCache();
|
||||
}
|
||||
|
||||
public void clearCache() {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,68 +1,84 @@
|
||||
package logic.neural;
|
||||
|
||||
/**
|
||||
* Used by neurons to cache inputs for faster NN evaluation performance.
|
||||
*
|
||||
* @author fazo
|
||||
*/
|
||||
public class NeuronCache {
|
||||
|
||||
private float[] cache;
|
||||
private boolean[] validity;
|
||||
|
||||
/**
|
||||
* Create a new empty input cache with given size.
|
||||
*
|
||||
* @param size how many inputs the requiring neuron has.
|
||||
*/
|
||||
public NeuronCache(int size) {
|
||||
cache = new float[size];
|
||||
validity = new boolean[size];
|
||||
clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a value in the cache.
|
||||
*
|
||||
* @param index the index of the value
|
||||
* @param value the value itself
|
||||
*/
|
||||
public void put(int index, float value) {
|
||||
validity[index] = true;
|
||||
cache[index] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a value from the cache.
|
||||
*
|
||||
* @param index the index of the value
|
||||
* @return the value required
|
||||
* @throws Exception if value not stored or declared invalid
|
||||
*/
|
||||
public float get(int index) throws Exception {
|
||||
if (validity[index]) {
|
||||
return cache[index];
|
||||
} else {
|
||||
throw new Exception("Value not present");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if required value is present and valid in the cache.
|
||||
*
|
||||
* @param index which value to check
|
||||
* @return true if has given value
|
||||
*/
|
||||
public boolean has(int index) {
|
||||
return validity[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears cache.
|
||||
*/
|
||||
public void clear() {
|
||||
for (int i = 0; i < cache.length; i++) {
|
||||
validity[i] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
package logic.neural;
|
||||
|
||||
/**
|
||||
* Used by neurons to cache inputs for faster NN evaluation performance.
|
||||
*
|
||||
* @author fazo
|
||||
*/
|
||||
public class NeuronCache {
|
||||
|
||||
private float[] cache;
|
||||
private float cachedOutput;
|
||||
private boolean cachedOutputValid;
|
||||
private boolean[] validity;
|
||||
|
||||
/**
|
||||
* Create a new empty input cache with given size.
|
||||
*
|
||||
* @param size how many inputs the requiring neuron has.
|
||||
*/
|
||||
public NeuronCache(int size) {
|
||||
cache = new float[size];
|
||||
validity = new boolean[size];
|
||||
clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a value in the cache.
|
||||
*
|
||||
* @param index the index of the value
|
||||
* @param value the value itself
|
||||
*/
|
||||
public void put(int index, float value) {
|
||||
validity[index] = true;
|
||||
cache[index] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a value from the cache.
|
||||
*
|
||||
* @param index the index of the value
|
||||
* @return the value required
|
||||
* @throws Exception if value not stored or declared invalid
|
||||
*/
|
||||
public float get(int index) throws Exception {
|
||||
if (validity[index]) {
|
||||
return cache[index];
|
||||
} else {
|
||||
throw new Exception("Value not present");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if required value is present and valid in the cache.
|
||||
*
|
||||
* @param index which value to check
|
||||
* @return true if has given value
|
||||
*/
|
||||
public boolean has(int index) {
|
||||
return validity[index];
|
||||
}
|
||||
|
||||
public float getCachedOutput() {
|
||||
return cachedOutput;
|
||||
}
|
||||
|
||||
public boolean hasCachedOutput() {
|
||||
return cachedOutputValid;
|
||||
}
|
||||
|
||||
public void setCachedOutput(float cachedOutput) {
|
||||
this.cachedOutput = cachedOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears cache.
|
||||
*/
|
||||
public void clear() {
|
||||
for (int i = 0; i < cache.length; i++) {
|
||||
validity[i] = false;
|
||||
}
|
||||
cachedOutputValid = false;
|
||||
cachedOutput = 0;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user