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;
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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}