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

fixed bugs, implemented brain rendering and creature selection (broken for now)

This commit is contained in:
Enrico Fasoli 2015-07-06 16:40:23 +02:00
parent 75146638bf
commit 124087d381
6 changed files with 980 additions and 858 deletions

View File

@ -1,105 +1,127 @@
package com.mygdx.game; package com.mygdx.game;
import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input; import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import java.util.ConcurrentModificationException; import java.util.ConcurrentModificationException;
import logic.Element; import logic.Creature;
import logic.World; import logic.Element;
import logic.World;
public class Game extends ApplicationAdapter {
public class Game extends ApplicationAdapter {
private static Game game;
ShapeRenderer shaper; private static Game game;
private World world; ShapeRenderer renderer, overlayRenderer;
private float cameraSpeed = 15; private World world;
private BitmapFont font; private float cameraSpeed = 15;
private boolean paused = false; private BitmapFont font;
private boolean paused = false;
@Override
public void create() { @Override
game = this; public void create() {
shaper = new ShapeRenderer(); game = this;
shaper.setAutoShapeType(true); renderer = new ShapeRenderer();
font = new BitmapFont(); renderer.setAutoShapeType(true);
Thread worldThread = new Thread(world); overlayRenderer = new ShapeRenderer();
worldThread.setName("Worker"); overlayRenderer.setAutoShapeType(true);
worldThread.setPriority(Thread.MAX_PRIORITY); font = new BitmapFont();
worldThread.start(); Thread worldThread = new Thread(world);
} worldThread.setName("Worker");
worldThread.setPriority(Thread.MAX_PRIORITY);
public Game() { worldThread.start();
world = new World(2500, 2500); }
}
public Game() {
@Override world = new World(2500, 2500);
public void render() { }
// Controls
if (Gdx.input.isKeyJustPressed(Input.Keys.SPACE)) { @Override
world.newGen(false); public void render() {
} // Controls
if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) { if (Gdx.input.isKeyJustPressed(Input.Keys.SPACE)) {
shaper.translate(-cameraSpeed, 0, 0); world.newGen(false);
} }
if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) { if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) {
shaper.translate(cameraSpeed, 0, 0); renderer.translate(-cameraSpeed, 0, 0);
} }
if (Gdx.input.isKeyPressed(Input.Keys.UP)) { if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) {
shaper.translate(0, -cameraSpeed, 0); renderer.translate(cameraSpeed, 0, 0);
} }
if (Gdx.input.isKeyPressed(Input.Keys.DOWN)) { if (Gdx.input.isKeyPressed(Input.Keys.UP)) {
shaper.translate(0, cameraSpeed, 0); renderer.translate(0, -cameraSpeed, 0);
} }
if (Gdx.input.isKeyJustPressed(Input.Keys.PLUS)) { if (Gdx.input.isKeyPressed(Input.Keys.DOWN)) {
shaper.scale(0.5f, 0.5f, 1); renderer.translate(0, cameraSpeed, 0);
} }
if (Gdx.input.isKeyJustPressed(Input.Keys.MINUS)) { if (Gdx.input.isKeyJustPressed(Input.Keys.PLUS)) {
shaper.scale(1.5f, 1.5f, 1); renderer.scale(0.5f, 0.5f, 1);
} }
if (Gdx.input.isKeyJustPressed(Input.Keys.P)) { if (Gdx.input.isKeyJustPressed(Input.Keys.MINUS)) {
paused = !paused; renderer.scale(1.5f, 1.5f, 1);
} }
if (Gdx.input.isKeyJustPressed(Input.Keys.L)) { if (Gdx.input.isKeyJustPressed(Input.Keys.P)) {
if (world.getFpsLimit() == 60) { paused = !paused;
world.setFpsLimit(0); }
} else { if (Gdx.input.isKeyJustPressed(Input.Keys.L)) {
world.setFpsLimit(60); if (world.getFpsLimit() == 60) {
} world.setFpsLimit(0);
} } else {
// Draw world.setFpsLimit(60);
Gdx.gl.glClearColor(0, 0, 0, 1); }
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); }
shaper.setColor(1, 1, 1, 1); if (Gdx.input.isButtonPressed(Input.Buttons.RIGHT)) {
shaper.begin(ShapeRenderer.ShapeType.Line); renderer.translate(Gdx.input.getDeltaX(), Gdx.input.getDeltaY() * -1, 0);
try { }
for (Element e : world.getElements()) { /*
try { // Broken for now
e.render(shaper); if(Gdx.input.isButtonPressed(Input.Buttons.LEFT)){
} catch (ArrayIndexOutOfBoundsException ex) { // TODO: project coordinates to world
// No idea why it happens, but it's rendering so meh world.selectCreatureAt(Gdx.input.getX(), Gdx.input.getY());
//Log.log(Log.ERROR, ex+""); }*/
} // Draw
} Gdx.gl.glClearColor(0, 0, 0, 1);
} catch (ConcurrentModificationException ex) { Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
} renderer.begin(ShapeRenderer.ShapeType.Line);
shaper.setColor(0.3f, 0.3f, 0.3f, 1); try {
// draw borders for (Element e : world.getElements()) {
shaper.rect(0, 0, world.getWidth(), world.getHeight()); try {
shaper.end(); e.render(renderer);
} } catch (ArrayIndexOutOfBoundsException ex) {
// No idea why it happens, but it's rendering so meh
public World getWorld() { //Log.log(Log.ERROR, ex+"");
return world; }
} }
} catch (ConcurrentModificationException ex) {
public static Game get() { }
return game; if (world.getSelectedCreature() != null) {
} // There is a selection
Creature c = world.getSelectedCreature();
public boolean isPaused() { renderer.setColor(1, 1, 1, 1);
return paused; // 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;
}
}

View File

@ -1,45 +1,45 @@
/* /*
* To change this license header, choose License Headers in Project Properties. * To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates * To change this template file, choose Tools | Templates
* and open the template in the editor. * and open the template in the editor.
*/ */
package logic; package logic;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.mygdx.game.Game; import com.mygdx.game.Game;
/** /**
* *
* @author fazo * @author fazo
*/ */
public class Vegetable extends Element { public class Vegetable extends Element {
public static final int default_radius = 5; public static final int default_radius = 5;
private float decayRate = 0; private float decayRate = 0;
public Vegetable(float x, float y) { public Vegetable(float x, float y) {
super(x, y, default_radius); super(x, y, default_radius);
} }
@Override @Override
public void update() { public void update() {
setSize(getSize()-decayRate); setSize(getSize()-decayRate);
if (getSize() <= 0) { if (getSize() <= 2) {
Game.get().getWorld().getDeadPlants().add(this); Game.get().getWorld().getDeadPlants().add(this);
} }
} }
@Override @Override
public void render(ShapeRenderer s) { public void render(ShapeRenderer s) {
s.setColor(1, 1, 1, 1); s.setColor(1, 1, 1, 1);
s.circle(getX(), getY(), getSize()); s.circle(getX(), getY(), getSize());
} }
public float getDecayRate() { public float getDecayRate() {
return decayRate; return decayRate;
} }
public void setDecayRate(float decayRate) { public void setDecayRate(float decayRate) {
this.decayRate = decayRate; this.decayRate = decayRate;
} }
} }

View File

@ -1,279 +1,311 @@
/* /*
* To change this license header, choose License Headers in Project Properties. * To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates * To change this template file, choose Tools | Templates
* and open the template in the editor. * and open the template in the editor.
*/ */
package logic; package logic;
import com.mygdx.game.Game; import com.mygdx.game.Game;
import com.mygdx.game.Log; import com.mygdx.game.Log;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date; import java.util.ConcurrentModificationException;
import java.util.logging.Level; import java.util.Date;
import java.util.logging.Logger; import java.util.logging.Level;
import java.util.logging.Logger;
/**
* /**
* @author fazo *
*/ * @author fazo
public class World implements Runnable { */
public class World implements Runnable {
private final int width, height, nPlants, creatPerGen;
private int generation = 1; private final int width, height, nPlants, creatPerGen;
private int fpsLimit = 60, fps = 0; private int generation = 1;
private final ArrayList<Element> elements; private int fpsLimit = 60, fps = 0;
private final ArrayList<Element> toAdd; private Creature selected;
private final ArrayList<Creature> creatures; private final ArrayList<Element> elements;
private final ArrayList<Creature> graveyard; private final ArrayList<Element> toAdd;
private final ArrayList<Vegetable> plants; private final ArrayList<Creature> creatures;
private final ArrayList<Vegetable> deadPlants; private final ArrayList<Creature> graveyard;
private final ArrayList<FpsListener> fpsListeners; private final ArrayList<Vegetable> plants;
private final ArrayList<Vegetable> deadPlants;
public World(int width, int height) { private final ArrayList<FpsListener> fpsListeners;
this.width = width;
this.height = height; public World(int width, int height) {
elements = new ArrayList(); this.width = width;
creatures = new ArrayList(); this.height = height;
toAdd = new ArrayList(); elements = new ArrayList();
creatPerGen = Math.min(Math.round(width * height / 20000), 50); creatures = new ArrayList();
nPlants = Math.round(width * height / 5500); toAdd = new ArrayList();
plants = new ArrayList(); creatPerGen = Math.min(Math.round(width * height / 20000), 50);
deadPlants = new ArrayList(); nPlants = Math.round(width * height / 5500);
graveyard = new ArrayList(); plants = new ArrayList();
fpsListeners = new ArrayList(); deadPlants = new ArrayList();
newGen(true); graveyard = new ArrayList();
} fpsListeners = new ArrayList();
selected = null;
@Override newGen(true);
public void run() { }
Date d, timekeeper = new Date();
long time; @Override
int target, frames = 0; public void run() {
for (;;) { Date d, timekeeper = new Date();
if (!Game.get().isPaused()) { long time;
d = new Date(); int target, frames = 0;
update(); for (;;) {
frames++; if (!Game.get().isPaused()) {
Date now = new Date(); d = new Date();
if (now.getTime() - timekeeper.getTime() > 1000) { update();
fps = frames; frames++;
frames = 0; Date now = new Date();
for (FpsListener f : fpsListeners) { if (now.getTime() - timekeeper.getTime() > 1000) {
f.fpsChanged(fps); fps = frames;
} frames = 0;
timekeeper = new Date(); for (FpsListener f : fpsListeners) {
} f.fpsChanged(fps);
if (fpsLimit > 0) { }
time = now.getTime() - d.getTime(); timekeeper = new Date();
target = 1000 / fpsLimit; }
if (time < target) { if (fpsLimit > 0) {
try { time = now.getTime() - d.getTime();
Thread.sleep((long) (target - time)); target = 1000 / fpsLimit;
} catch (InterruptedException ex) { if (time < target) {
Logger.getLogger(World.class.getName()).log(Level.SEVERE, null, ex); 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) { } else {
Logger.getLogger(World.class.getName()).log(Level.SEVERE, null, ex); 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) { public void update() {
creatures.add((Creature) e); for (Element e : toAdd) {
} else if (e instanceof Vegetable) { elements.add(e);
plants.add((Vegetable) e); if (e instanceof Creature) {
} creatures.add((Creature) e);
} } else if (e instanceof Vegetable) {
toAdd.clear(); plants.add((Vegetable) e);
elements.removeAll(graveyard); }
elements.removeAll(deadPlants); }
plants.removeAll(deadPlants); toAdd.clear();
deadPlants.clear(); elements.removeAll(graveyard);
creatures.removeAll(graveyard); if (selected != null && graveyard.contains(selected)) {
if (creatures.isEmpty()) { selected = null;
// All dead, next gen Log.log(Log.INFO, "Cleared selection");
newGen(false); }
} elements.removeAll(deadPlants);
while (plants.size() < nPlants) { plants.removeAll(deadPlants);
spawnVegetable(); deadPlants.clear();
} creatures.removeAll(graveyard);
for (Element e : elements) { if (creatures.isEmpty()) {
e.update(); // All dead, next gen
} newGen(false);
} }
while (plants.size() < nPlants) {
public void newGen(boolean restart) { spawnVegetable();
elements.removeAll(creatures); }
graveyard.addAll(creatures); for (Element e : elements) {
creatures.clear(); e.update();
Comparator creatureComp = new Comparator<Creature>() { }
}
@Override
public int compare(Creature t, Creature t1) { public void newGen(boolean restart) {
// put the highest fitness first (sort in reverse) elements.removeAll(creatures);
return (int) (t1.getFitness() - t.getFitness()); graveyard.addAll(creatures);
} creatures.clear();
}; if (selected != null) {
if (graveyard.isEmpty() || restart) { // First gen selected = null;
generation = 1; Log.log(Log.INFO, "Cleared selection");
Log.log(Log.INFO, "Starting from generation 1: spawning " + creatPerGen + " creatures."); }
for (int i = 0; i < creatPerGen; i++) { Comparator creatureComp = new Comparator<Creature>() {
spawnCreature();
} @Override
} else { // Evolve previous gen public int compare(Creature t, Creature t1) {
graveyard.sort(creatureComp); // sort by fitness // put the highest fitness first (sort in reverse)
// Prepare best agent list return (int) (t1.getFitness() - t.getFitness());
int topSize = (int) Math.round(graveyard.size() * 0.05f); }
Creature[] top = new Creature[topSize]; };
// Calculate avg fitness and prepare best agent list if (graveyard.isEmpty() || restart) { // First gen
float avgFitness = 0; generation = 1;
for (int i = 0; i < graveyard.size(); i++) { Log.log(Log.INFO, "Starting from generation 1: spawning " + creatPerGen + " creatures.");
Creature c = graveyard.get(i); for (int i = 0; i < creatPerGen; i++) {
if (i < topSize) { spawnCreature();
top[i] = graveyard.get(i); }
Log.log(Log.INFO, "Gen " + generation + " Top " + (i + 1) + ": " + c.getFitness()); } else { // Evolve previous gen
} graveyard.sort(creatureComp); // sort by fitness
avgFitness += c.getFitness(); // Prepare best agent list
} int topSize = (int) Math.round(graveyard.size() * 0.05f);
avgFitness = avgFitness / graveyard.size(); Creature[] top = new Creature[topSize];
Log.log(Log.INFO, "Gen " + generation + " done. Avg fitness: " + avgFitness); // Calculate avg fitness and prepare best agent list
// Generate children float avgFitness = 0;
for (Creature c : graveyard) { for (int i = 0; i < graveyard.size(); i++) {
int first = (int) Math.floor(Math.random() * topSize); Creature c = graveyard.get(i);
int sec = first; if (i < topSize) {
while (sec == first) { top[i] = graveyard.get(i);
sec = (int) Math.floor(Math.random() * topSize); Log.log(Log.INFO, "Gen " + generation + " Top " + (i + 1) + ": " + c.getFitness());
} }
float[][][] n = null; avgFitness += c.getFitness();
try { }
n = top[first].getBrain().breed(top[sec].getBrain().getMap()); avgFitness = avgFitness / graveyard.size();
} catch (Exception ex) { Log.log(Log.INFO, "Gen " + generation + " done. Avg fitness: " + avgFitness);
// Should not happen // Generate children
Logger.getLogger(World.class.getName()).log(Level.SEVERE, null, ex); for (Creature c : graveyard) {
} int first = (int) Math.floor(Math.random() * topSize);
Creature ne = spawnCreature(n); int sec = first;
ne.getBrain().mutate(0.05f); // mutate children while (sec == first) {
} sec = (int) Math.floor(Math.random() * topSize);
graveyard.clear(); }
generation++; float[][][] n = null;
} try {
} n = top[first].getBrain().breed(top[sec].getBrain().getMap());
} catch (Exception ex) {
private Element spawn(boolean isCreature, float[][][] brainMap) { // Should not happen
int x, y, r; Logger.getLogger(World.class.getName()).log(Level.SEVERE, null, ex);
boolean overlaps = false; }
if (isCreature) { Creature ne = spawnCreature(n);
r = Creature.default_radius; ne.getBrain().mutate(0.05f); // mutate children
} else { }
r = Vegetable.default_radius; graveyard.clear();
} generation++;
do { }
overlaps = false; }
x = (int) (Math.random() * width);
y = (int) (Math.random() * height); private Element spawn(boolean isCreature, float[][][] brainMap) {
for (Element e : elements) { int x, y, r;
if (e.overlaps(x, y, r)) { boolean overlaps = false;
overlaps = true; if (isCreature) {
} r = Creature.default_radius;
} } else {
} while (overlaps); r = Vegetable.default_radius;
if (isCreature) { }
Log.log(Log.DEBUG, "New Creat: " + x + " " + y); do {
Creature c = new Creature(x, y); overlaps = false;
if (brainMap != null) { x = (int) (Math.random() * width);
c.getBrain().remap(brainMap); y = (int) (Math.random() * height);
} for (Element e : elements) {
//add(c); if (e.overlaps(x, y, r)) {
elements.add(c); overlaps = true;
creatures.add(c); }
return c; }
} else { } while (overlaps);
Log.log(Log.DEBUG, "New Veg: " + x + " " + y); if (isCreature) {
Vegetable v = new Vegetable(x, y); Log.log(Log.DEBUG, "New Creat: " + x + " " + y);
//add(v); Creature c = new Creature(x, y);
elements.add(v); if (brainMap != null) {
plants.add(v); c.getBrain().remap(brainMap);
return v; }
} //add(c);
} elements.add(c);
creatures.add(c);
public interface FpsListener { return c;
} else {
public abstract void fpsChanged(int newValue); Log.log(Log.DEBUG, "New Veg: " + x + " " + y);
} Vegetable v = new Vegetable(x, y);
//add(v);
private void spawnVegetable() { elements.add(v);
spawn(false, null); plants.add(v);
} return v;
}
private Creature spawnCreature() { }
return (Creature) spawn(true, null);
} public interface FpsListener {
private Creature spawnCreature(float[][][] b) { public abstract void fpsChanged(int newValue);
return (Creature) spawn(true, b); }
}
public void selectCreatureAt(int x, int y) {
public int getWidth() { selected = null; // Clear selection
return width; try {
} for (Creature c : creatures) {
if (c.overlaps(x, y)) {
public int getHeight() { selected = c;
return height; Log.log(Log.INFO, "Selected a creature");
} }
}
public int getGeneration() { } catch (ConcurrentModificationException ex) {
return generation; }
} }
public void addFpsListener(FpsListener f) { private void spawnVegetable() {
fpsListeners.add(f); spawn(false, null);
} }
public void add(Element e) { private Creature spawnCreature() {
toAdd.add(e); return (Creature) spawn(true, null);
} }
public ArrayList<Element> getElements() { private Creature spawnCreature(float[][][] b) {
return elements; return (Creature) spawn(true, b);
} }
public ArrayList<Creature> getGraveyard() { public int getWidth() {
return graveyard; return width;
} }
public ArrayList<Vegetable> getDeadPlants() { public int getHeight() {
return deadPlants; return height;
} }
public ArrayList<Creature> getCreatures() { public int getGeneration() {
return creatures; return generation;
} }
public ArrayList<Vegetable> getPlants() { public void addFpsListener(FpsListener f) {
return plants; fpsListeners.add(f);
} }
public float getFpsLimit() { public void add(Element e) {
return fpsLimit; toAdd.add(e);
} }
public void setFpsLimit(int fpsLimit) { public ArrayList<Element> getElements() {
this.fpsLimit = fpsLimit; return elements;
} }
public float getFps() { public ArrayList<Creature> getGraveyard() {
return fps; 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;
}
}

View File

@ -1,237 +1,271 @@
package logic.neural; package logic.neural;
import com.mygdx.game.Log; import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.mygdx.game.Log;
/**
* Represents a virtual brain /**
* * Represents a virtual brain
* @author fazo *
*/ * @author fazo
public class Brain { */
public class Brain {
public static final float bias = 0.5f;
private Neuron[][] neurons; public static final float bias = 0.5f;
private Neuron[][] neurons;
/**
* Create a new brain with a random map (mind) with given number of 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 nInputs the number of input neurons (at least 1)
* @param hiddenLayers how many hidden layers of neurons (at least 1) * @param nOutputs the number of output neurons (at least 1)
* @param neuronsPerHiddenLayer how many neurons per hidden layer (at least * @param hiddenLayers how many hidden layers of neurons (at least 1)
* 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 public Brain(int nInputs, int nOutputs, int hiddenLayers, int neuronsPerHiddenLayer) {
neurons = new Neuron[hiddenLayers + 2][]; // Prepare brain map
neurons[0] = new Neuron[nInputs]; neurons = new Neuron[hiddenLayers + 2][];
neurons[hiddenLayers + 1] = new Neuron[nOutputs]; neurons[0] = new Neuron[nInputs];
for (int i = 0; i < hiddenLayers; i++) { neurons[hiddenLayers + 1] = new Neuron[nOutputs];
neurons[i + 1] = new Neuron[neuronsPerHiddenLayer]; for (int i = 0; i < hiddenLayers; i++) {
} neurons[i + 1] = new Neuron[neuronsPerHiddenLayer];
// Randomize brain }
initialize(); // Randomize brain
} initialize();
}
/**
* Create a new brain using given brain map (mind) /**
* * Create a new brain using given brain map (mind)
* @param brainMap the brain map (mind) to use *
*/ * @param brainMap the brain map (mind) to use
public Brain(float[][][] brainMap) { */
neurons = new Neuron[brainMap.length][]; public Brain(float[][][] brainMap) {
for (int i = 0; i < brainMap.length; i++) { // for each layer neurons = new Neuron[brainMap.length][];
neurons[i] = new Neuron[brainMap[i].length]; for (int i = 0; i < brainMap.length; i++) { // for each layer
for (int j = 0; j < brainMap[i].length; j++) { // for each neuron neurons[i] = new Neuron[brainMap[i].length];
neurons[i][j] = new Neuron(i, bias, this, brainMap[i][j]); 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 /**
* * Apply a new brain map (mind) to this brain
* @param brainMap the new brain map to apply *
*/ * @param brainMap the new brain map to apply
public void remap(float[][][] brainMap) { */
for (int i = 0; i < brainMap.length; i++) { // for each layer public void remap(float[][][] brainMap) {
for (int j = 0; j < brainMap[i].length; j++) { // for each neuron for (int i = 0; i < brainMap.length; i++) { // for each layer
// skip input layer for (int j = 0; j < brainMap[i].length; j++) { // for each neuron
if (neurons[i + 1][j] == null) { // skip input layer
neurons[i + 1][j] = new Neuron(j, bias, this, brainMap[i][j]); if (neurons[i + 1][j] == null) {
} else { neurons[i + 1][j] = new Neuron(j, bias, this, brainMap[i][j]);
neurons[i + 1][j].setWeights(brainMap[i][j]); } else {
} neurons[i + 1][j].setWeights(brainMap[i][j]);
} }
} }
} }
}
/**
* Populate the brain with brand new random neurons /**
*/ * Populate the brain with brand new random neurons
private void initialize() { */
// init hidden layers private void initialize() {
for (int i = 0; i < neurons.length; i++) { // init hidden layers
for (int j = 0; j < neurons[i].length; j++) { for (int i = 0; i < neurons.length; i++) {
// create neuron for (int j = 0; j < neurons[i].length; j++) {
Neuron n = new Neuron(i, bias, this); // create neuron
neurons[i][j] = n; Neuron n = new Neuron(i, bias, this);
Log.log(Log.DEBUG, "Adding Layer " + (i + 1) + " Neuron " + (j + 1)); neurons[i][j] = n;
} Log.log(Log.DEBUG, "Adding Layer " + (i + 1) + " Neuron " + (j + 1));
} }
} }
}
/**
* Give some input to the brain /**
* * Draw this brain's status.
* @param values the array of input. Its length must match the number of *
* input neurons of this brain * @param s the ShapeRenderer to use for the drawing
* @throws Exception if the number of inputs given differs from the number */
* of input neurons of this brain public void render(ShapeRenderer s) {
*/ s.set(ShapeRenderer.ShapeType.Filled);
public void input(float[] values) throws Exception { int neuronHeight = 0;
if (values.length != neurons[0].length) { for (Neuron[] ns : neurons) {
throw new Exception("Not enough or too many inputs"); if (ns.length > neuronHeight) {
} neuronHeight = ns.length;
for (int i = 0; i < values.length; i++) { }
neurons[0][i].setOutput(values[i]); }
} s.rect(0, 0, neurons.length * 50, neuronHeight * 30);
clearCache(); 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
* Compute output of the brain starting from given input float nr = neurons[i][j].compute();
* // Draw neuron links
* @return an array as long as the number of output neurons, containing the float[] links = neurons[i][j].getInputs();
* result for (int f = 0; f < links.length; f++) {
*/ s.setColor(links[f], links[f], links[f], 1);
public float[] compute() { s.line(i * 50, j * 30, (i - 1) * 50, f * 30);
//clearCache(); // unnecessary if already called when changing inputs }
float[] res = new float[neurons[neurons.length - 1].length]; // Draw neuron
for (int i = 0; i < neurons[neurons.length - 1].length; i++) { s.setColor(1 - nr, nr, 0, 1);
res[i] = neurons[neurons.length - 1][i].compute(); s.set(ShapeRenderer.ShapeType.Filled);
} s.circle(i * 50, j * 30, 15);
return res; }
} }
}
/**
* Input some values (see input function) and then compute the results. /**
* * Give some input to the brain
* @param values *
* @return the results of the neural network * @param values the array of input. Its length must match the number of
* @throws Exception if the number of inputs given differs from the number * input neurons of this brain
* 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 float[] compute(float[] values) throws Exception { */
input(values); public void input(float[] values) throws Exception {
return compute(); if (values.length != neurons[0].length) {
} throw new Exception("Not enough or too many inputs");
}
/** for (int i = 0; i < values.length; i++) {
* Get a brainMap that represents this brain's mind neurons[0][i].setOutput(values[i]);
* }
* @return a tridimensional floating point number array representing a full clearCache();
* mind }
*/
public float[][][] getMap() { /**
float[][][] res = new float[neurons.length - 1][][]; * Compute output of the brain starting from given input
for (int i = 1; i < neurons.length; i++) // layers (skip input layer) *
{ * @return an array as long as the number of output neurons, containing the
res[i - 1] = new float[neurons[i].length][]; * result
for (int j = 0; j < neurons[i].length; j++) // neurons per layer */
{ public float[] compute() {
res[i - 1][j] = neurons[i][j].getWeights(); //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++) {
return res; res[i] = neurons[neurons.length - 1][i].compute();
} }
return res;
/** }
* Get a map of this brain's mind.. with a mutation
* /**
* @param mutationFactor the higher this number, the bigger the mutation * Input some values (see input function) and then compute the results.
* @return a mutated brain map of this brain's mind *
*/ * @param values
public float[][][] getMutatedMap(float mutationFactor) { * @return the results of the neural network
float[][][] res = new float[neurons.length - 1][][]; * @throws Exception if the number of inputs given differs from the number
for (int i = 1; i < neurons.length; i++) // layers (skip input layer) * of input neurons of this brain
{ */
res[i - 1] = new float[neurons[i].length][]; public float[] compute(float[] values) throws Exception {
for (int j = 0; j < neurons[i].length; j++) // neurons per layer input(values);
{ return compute();
res[i - 1][j] = neurons[i][j].mutate(mutationFactor); }
}
} /**
return res; * Get a brainMap that represents this brain's mind
} *
* @return a tridimensional floating point number array representing a full
/** * mind
* Apply a mutation to this brain */
* public float[][][] getMap() {
* @param mutationFactor the higher this number, the bigger the mutation float[][][] res = new float[neurons.length - 1][][];
*/ for (int i = 1; i < neurons.length; i++) // layers (skip input layer)
public void mutate(float mutationFactor) { {
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
for (int j = 0; j < neurons[i].length; j++) // neurons per layer {
{ res[i - 1][j] = neurons[i][j].getWeights();
neurons[i][j].setWeights(neurons[i][j].mutate(mutationFactor)); }
} }
} return res;
} }
public float[][][] breed(float[][][] map) throws Exception { /**
float[][][] res = new float[neurons.length - 1][][]; * Get a map of this brain's mind.. with a mutation
if (map.length != neurons.length - 1) { *
throw new Exception("incompatible brains"); * @param mutationFactor the higher this number, the bigger the mutation
} * @return a mutated brain map of this brain's mind
for (int i = 1; i < neurons.length; i++) // layers (skip input layer) */
{ public float[][][] getMutatedMap(float mutationFactor) {
res[i - 1] = new float[neurons[i].length][]; float[][][] res = new float[neurons.length - 1][][];
if (map[i - 1].length != neurons[i].length) { for (int i = 1; i < neurons.length; i++) // layers (skip input layer)
throw new Exception("incompatible brains"); {
} res[i - 1] = new float[neurons[i].length][];
for (int j = 0; j < neurons[i].length; j++) // neurons per layer for (int j = 0; j < neurons[i].length; j++) // neurons per layer
{ {
// j = 8 not valid for neurons[i][j]. investigate why. res[i - 1][j] = neurons[i][j].mutate(mutationFactor);
//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]; return res;
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 * Apply a mutation to this brain
{ *
// Combine the two weights * @param mutationFactor the higher this number, the bigger the mutation
if (Math.random() < 0.5) { */
res[i - 1][j][z] = map[i - 1][j][z]; public void mutate(float mutationFactor) {
} else { for (int i = 1; i < neurons.length; i++) // layers (skip input layer)
res[i - 1][j][z] = neurons[i][j].getWeights()[z]; {
} for (int j = 0; j < neurons[i].length; j++) // neurons per layer
} {
} neurons[i][j].setWeights(neurons[i][j].mutate(mutationFactor));
} }
return res; }
} }
/** public float[][][] breed(float[][][] map) throws Exception {
* Empties the neurons' cache. Needs to be called after changing brain float[][][] res = new float[neurons.length - 1][][];
* inputs or before computing the result. if (map.length != neurons.length - 1) {
*/ throw new Exception("incompatible brains");
private void clearCache() { }
for (int i = 1; i < neurons.length; i++) { for (int i = 1; i < neurons.length; i++) // layers (skip input layer)
for (int j = 0; j < neurons[i].length; j++) { {
neurons[i][j].clearCache(); 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
/** {
* Returns an array with pointers to all this brain's neurons. // 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);
* @return bidimensional array with first index representing the layer. //System.out.println(neurons[i].length +" has to be > "+j);
*/ res[i - 1][j] = new float[neurons[i][j].getWeights().length];
public Neuron[][] getNeurons() { if (map[i - 1][j].length != neurons[i][j].getWeights().length) {
return neurons; 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;
}
}

View File

@ -1,124 +1,142 @@
/* /*
* To change this license header, choose License Headers in Project Properties. * To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates * To change this template file, choose Tools | Templates
* and open the template in the editor. * and open the template in the editor.
*/ */
package logic.neural; package logic.neural;
import com.mygdx.game.Log; import com.mygdx.game.Log;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
/** /**
* *
* @author fazo * @author fazo
*/ */
public class Neuron { public class Neuron {
private float[] weights; private float[] weights;
private NeuronCache cache; private NeuronCache cache;
private float bias, output; private float bias, output;
private boolean isInputNeuron; private boolean isInputNeuron;
private int layer; private int layer;
private Brain brain; private Brain brain;
public Neuron(int layer, float bias, Brain brain) { public Neuron(int layer, float bias, Brain brain) {
this(layer, bias, brain, null); this(layer, bias, brain, null);
} }
public Neuron(int layer, float bias, Brain brain, float[] weights) { public Neuron(int layer, float bias, Brain brain, float[] weights) {
this.brain = brain; this.brain = brain;
this.layer = layer; this.layer = layer;
if (weights == null) { if (weights == null) {
scramble(); scramble();
} else { } else {
this.weights = weights; this.weights = weights;
} }
cache = new NeuronCache(this.weights.length); cache = new NeuronCache(this.weights.length);
} }
private void scramble() { private void scramble() {
// init weights // init weights
if (layer > 0) { if (layer > 0) {
weights = new float[brain.getNeurons()[layer - 1].length]; weights = new float[brain.getNeurons()[layer - 1].length];
} else { // layer 0 } else { // layer 0
isInputNeuron = true; isInputNeuron = true;
weights = new float[0]; weights = new float[0];
} }
// Put random weights // Put random weights
for (int i = 0; i < weights.length; i++) { for (int i = 0; i < weights.length; i++) {
weights[i] = (float) (Math.random() * 5 - 2.5f); weights[i] = (float) (Math.random() * 5 - 2.5f);
} }
} }
public float compute() { public float compute() {
if (isInputNeuron) { if (isInputNeuron) {
return output; return output;
} }
float a = bias * -1; // activation if(cache.hasCachedOutput()) return cache.getCachedOutput();
for (int i = 0; i < weights.length; i++) { float a = bias * -1; // activation
if (cache.has(i)) { for (int i = 0; i < weights.length; i++) {
try { if (cache.has(i)) {
return cache.get(i); try {
} catch (Exception ex) { a += cache.get(i);
// This should never happen } catch (Exception ex) {
Logger.getLogger(Neuron.class.getName()).log(Level.SEVERE, null, 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]; 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))); // sigmoid function
Log.log(Log.DEBUG, "Computed Value " + res + " for neuron"); float res = (float) (1 / (1 + Math.pow(Math.E, a * -1)));
return res; Log.log(Log.DEBUG, "Computed Value " + res + " for neuron");
} return res;
}
public float[] mutate(float mutationFactor) {
float[] mutatedWeights = new float[weights.length]; public float[] mutate(float mutationFactor) {
for (int i = 0; i < weights.length; i++) { float[] mutatedWeights = new float[weights.length];
mutatedWeights[i] = weights[i] + mutationFactor - mutationFactor / 2; for (int i = 0; i < weights.length; i++) {
} mutatedWeights[i] = weights[i] + mutationFactor - mutationFactor / 2;
return mutatedWeights; }
} return mutatedWeights;
}
public void setOutput(float output) {
isInputNeuron = true; public float[] getInputs() {
this.output = output; float inputs[] = new float[weights.length];
} for (int i = 0; i < inputs.length; i++) {
if (cache.has(i)) {
public float getBias() { try {
return bias; inputs[i] = cache.get(i);
} } catch (Exception ex) {
// Shouldnt happen
public void setBias(float bias) { Logger.getLogger(Neuron.class.getName()).log(Level.SEVERE, null, ex);
this.bias = bias; }
} } else {
inputs[i] = 0;
public boolean isInputNeuron() { }
return isInputNeuron; }
} return inputs;
}
public int getLayer() {
return layer; public void setOutput(float output) {
} isInputNeuron = true;
this.output = output;
public void setLayer(int layer) { }
this.layer = layer;
} public float getBias() {
return bias;
public float[] getWeights() { }
return weights;
} public void setBias(float bias) {
this.bias = bias;
public void setWeights(float[] weights) { }
this.weights = weights;
// Changing the neuron makes the cache invalid public boolean isInputNeuron() {
clearCache(); return isInputNeuron;
} }
public void clearCache() { public int getLayer() {
cache.clear(); 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();
}
}

View File

@ -1,68 +1,84 @@
package logic.neural; package logic.neural;
/** /**
* Used by neurons to cache inputs for faster NN evaluation performance. * Used by neurons to cache inputs for faster NN evaluation performance.
* *
* @author fazo * @author fazo
*/ */
public class NeuronCache { public class NeuronCache {
private float[] cache; private float[] cache;
private boolean[] validity; 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. * Create a new empty input cache with given size.
*/ *
public NeuronCache(int size) { * @param size how many inputs the requiring neuron has.
cache = new float[size]; */
validity = new boolean[size]; public NeuronCache(int size) {
clear(); cache = new float[size];
} validity = new boolean[size];
clear();
/** }
* Put a value in the cache.
* /**
* @param index the index of the value * Put a value in the cache.
* @param value the value itself *
*/ * @param index the index of the value
public void put(int index, float value) { * @param value the value itself
validity[index] = true; */
cache[index] = value; 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 * Read a value from the cache.
* @return the value required *
* @throws Exception if value not stored or declared invalid * @param index the index of the value
*/ * @return the value required
public float get(int index) throws Exception { * @throws Exception if value not stored or declared invalid
if (validity[index]) { */
return cache[index]; public float get(int index) throws Exception {
} else { if (validity[index]) {
throw new Exception("Value not present"); 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 * Returns true if required value is present and valid in the cache.
* @return true if has given value *
*/ * @param index which value to check
public boolean has(int index) { * @return true if has given value
return validity[index]; */
} public boolean has(int index) {
return validity[index];
/** }
* Clears cache.
*/ public float getCachedOutput() {
public void clear() { return cachedOutput;
for (int i = 0; i < cache.length; i++) { }
validity[i] = false;
} 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;
}
}