Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Java

Self-Driven Car Simulator Using a Neural Network and Genetic Algorithm Training

5.00/5 (15 votes)
15 Dec 2016CPOL10 min read 35.8K  
This program simulates an auto-driven car learning to drive around a track.
This project is a practical application and helps beginners to learn about Artificial Intelligence, Neural Network and Genetic Algorithm.

Introduction

Artificial Intelligence impacts human life in many ways nowadays. An example is in the auto industry; many companies are trying to make their cars smarter. The cars can self-drive, avoid obstacles, find destinations … without controls from human. This paper is about a car simulation program in which the car itself will move without any controls from outside. The approach uses a Neural Network and a Genetic Algorithm to train the car by making the car learn after each time it fails to finish the track.

Using the Code

1) FPS

Every computer has different speeds so we need a mechanism to normalize that to make the game run at the same speed in any computer. We have two main methods: update and render. Usually, the game runs at 60 fps so the update method will be called 60 times per second and the render method will be called as fast as the computer’s speed.

Java
package com.auto.car;
import java.awt.BasicStroke;
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import javax.swing.JFrame;
import com.auto.algorithm.HelperFunction;
import com.auto.car.entity.EntityManager;
import com.auto.graphics.Screen;
import com.auto.input.Keyboard;
import com.auto.input.Mouse;

/*
 * @Description: This class contains main method - entry point to the program
 * 
 */
public class CarDemo extends Canvas implements Runnable {
    private static final long serialVersionUID = 1L;
    private static int width = 600;
    private static int height = width * 9 / 16;
    public static int scale = 2;
    private Thread thread;
    private JFrame frame;
    private boolean running = false;
    private BufferedImage image = new BufferedImage(width, height,
            BufferedImage.TYPE_INT_RGB);
    private int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer())
            .getData();
    private Screen screen;
    private Keyboard keyboard;
    private EntityManager entityManager;

    public CarDemo() {
        Dimension size = new Dimension(width * scale, height * scale);
        setPreferredSize(size);
        screen = new Screen(width, height);
        frame = new JFrame();
        Mouse mouse = new Mouse();
        addMouseListener(mouse);
        addMouseMotionListener(mouse);
        keyboard = new Keyboard();
        addKeyListener(keyboard);
        entityManager = new EntityManager();
    }

    public synchronized void start() {
        thread = new Thread(this, "Auto Car");
        running = true;
        thread.start();
    }

    public synchronized void stop() {
        running = false;
        try {
            System.out.println("Goodbye");
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static int fps = 60;
    @Override
    public void run() {
        long lastTime = System.nanoTime();
        long timer = System.currentTimeMillis();

        double deltaTime = 0;
        int frameCount = 0;
        int updateCount = 0;
        requestFocus();
        while (running) {
            double nanoSecondsPerCount = 1000000000.0 / fps;
            long now = System.nanoTime();
            deltaTime += (now - lastTime) / nanoSecondsPerCount;
            lastTime = now;
            while (deltaTime >= 1) {
                update();
                updateCount++;
                //System.out.println(updateCount + " - " + deltaTime);
                deltaTime--;
            }
            render();
            frameCount++;
            if (System.currentTimeMillis() - timer > 1000) {
                timer += 1000;
                frame.setTitle(updateCount + "ups, " + frameCount + " frames");
                updateCount = 0;
                frameCount = 0;
            }
        }
        stop();
    }

    private void update() {
        keyboard.update();
        if (keyboard.getKeys()[KeyEvent.VK_ESCAPE]) {
            stop();
        } else if (keyboard.getKeys()[KeyEvent.VK_R]) {
            restartSimulation();
        } else if (keyboard.getKeys()[KeyEvent.VK_DOWN]) {
            fps -= 1;
            fps = (int) HelperFunction
                    .getValueInRange(fps, 30, 300);
        } else if (keyboard.getKeys()[KeyEvent.VK_UP]) {
            fps += 1;
            fps = (int) HelperFunction
                    .getValueInRange(fps, 30, 300);
        } else if (keyboard.getKeys()[KeyEvent.VK_N]) {
            entityManager.nextMapIndex();
        } else {
            if (keyboard.getKeys()[KeyEvent.VK_SPACE]) {
                entityManager.forceToNextAgent();
            }
        }
        entityManager.update();
    }

    private void restartSimulation() {
        entityManager = new EntityManager();
    }

    private void render() {
        BufferStrategy bs = getBufferStrategy();
        if (bs == null) {
            createBufferStrategy(3);
            return;
        }
        Graphics2D g = (Graphics2D) bs.getDrawGraphics();
        screen.setGraphic(g);
        entityManager.renderByPixels(screen);
        for (int i = 0; i < pixels.length; i++) {
            pixels[i] = screen.getPixels()[i];
        }
        g.setStroke(new BasicStroke(2));
        g.drawImage(image, 0, 0, getWidth(), getHeight(), null);
        entityManager.renderByGraphics(screen);
        screen.dispose();
        bs.show();
    }

    public static int getWindowWidth() {
        return width * scale;
    }

    public static int getWindowHeight() {
        return height * scale;
    }

    public static void main(String[] args) {
        CarDemo car = new CarDemo();
        car.frame.setResizable(false);
        car.frame.setTitle("Auto Car");
        car.frame.add(car);
        car.frame.pack();
        car.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        car.frame.setLocationRelativeTo(null);
        car.frame.setVisible(true);
        car.start();
    }
}

2) Graphics

Image 1

The Screen class handles all graphic rendering for the program. There are two ways to render in this program: by manipulating individual pixels or by using built-in functions of the Java library. For the map, manipulating individual pixels is used and for the other objects such as the Car Sprite, the Sensor Line and the Sensor Circle, built-in functions of Java are used to gain better graphics. This class has these main fields: width, height, pixels, xOffset, yOffset and graphics. If we want to manipulate an individual pixel, we will change data in this pixels array. Two values, xOffset and yOffset change when the Car moves around. Because we always need to see the Car on the screen, the Car will stand at a fixed position on the screen; we just need to adjust the offset of the screen to make us feel like the Car is moving. There are five main functions in this class:

renderTile

The renderTile method renders a Tile on screen. It requires as arguments the x, y coordinates of the Tile and the Tile itself.

renderCar

The renderCar method renders the Car object. It requires as arguments the x, y coordinates of the Car, the angle which the Car is heading and the Sprite for the image of the Car.

renderLine

The method renderLine requires the arguments color and x, y for start point and end point to render the line between these points with a specific color.

renderCircle

The method renderCircle requires arguments x, y coordinates for the center of circle, the radius r and the color for this Circle.

dispose

The dispose method is used to dispose the object graphics to release the resource after rendering.

Java
package com.auto.graphics;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import com.auto.car.CarDemo;
import com.auto.car.level.tile.Tile;
/*
 * @Description: This class takes care of all graphic rendering on the screen.   
 */
public class Screen {
    private int width;
    private int height;
    private int[] pixels;
    private int xOffset, yOffset;
    private Graphics2D graphics;
    private int scale = CarDemo.scale;    
    public Screen() {

    }
    public Screen(int w, int h) {
        width = w;
        height = h;
        pixels = new int[w * h];
    }

    public int[] getPixels() {
        return pixels;
    }

    /*
     * @Description: This function renders single Tile on screen
     * @param xPosition x coordinate of Tile on screen.
     * @param yPosition y coordinate of Tile on screen.
     * @param tile the tile to be rendered.
     */
    public void renderTile(int xPosition, int yPosition, Tile tile) {
        // substract the tile position by the offset of screen when screen move
        xPosition -= xOffset;
        yPosition -= yOffset;
        for (int yTile = 0; yTile < tile.size; yTile++) {
            int yScreen = yTile + yPosition;
            for (int xTile = 0; xTile < tile.size; xTile++) {
                int xScreen = xTile + xPosition;
                // if the xScreen and yScreen are out of boundary then no
                // rendering. -tile.size is to render the boundary of the map
                // without rendering black strike.
                if (xScreen < -tile.size || xScreen >= width || yScreen < 0
                        || yScreen >= height) {
                    break;
                }
                if (xScreen < 0) {
                    xScreen = 0;
                }
                pixels[xScreen + yScreen * width] = tile.sprite.getPixels()[xTile
                        + yTile * tile.size];
            }
        }
    }

    /*
     * @Description: This function renders single Sprite on screen
     * @param xPosition x coordinate of Sprite on screen.
     * @param yPosition y coordinate of Sprite on screen.
     * @param angle The angle of the car.
     * @param sprite the sprite to be rendered.
     */
    public void renderCar(int xPosition, int yPosition, double angle,
            Sprite sprite) {
        // substract the Tile position by the offset of screen when screen moves
        xPosition -= xOffset;
        yPosition -= yOffset;
        BufferedImage img = new BufferedImage(Sprite.SIZE, Sprite.SIZE,
                BufferedImage.TYPE_INT_ARGB);
        for (int ySprite = 0; ySprite < Sprite.SIZE; ySprite++) {
            for (int xSprite = 0; xSprite < Sprite.SIZE; xSprite++) {
                int color = sprite.getPixels()[(xSprite + ySprite * Sprite.SIZE)];
                if (color != 0xffffffff) {
                    img.setRGB(xSprite, ySprite, color);
                }
            }
        }
        AffineTransform reset = new AffineTransform();
        reset.rotate(0, 0, 0);
        graphics.rotate(angle, (xPosition + Sprite.SIZE / 2) * scale,
                (yPosition + Sprite.SIZE / 2) * scale);
        graphics.drawImage(img, xPosition * scale, yPosition * scale,
                Sprite.SIZE * scale, Sprite.SIZE * scale, null);
        /*
         * graphics.drawRect(xPosition * scale, yPosition * scale, Sprite.SIZE
         * scale, Sprite.SIZE * scale);
         */
        graphics.setTransform(reset);
    }

    /*
     * @Description: draw a line between 2 points
     * @param x xCoordinate of first point
     * @param y yCoordinate of first point
     * @param x2 xCoordinate of second point
     * @param y2 yCoordinate of second point
     * @param color the color of line
     */
    public void renderLine(double x, double y, double x2, double y2, Color color) {
        graphics.setColor(color);
        graphics.drawLine(((int) (x - xOffset + Sprite.SIZE / 2)) * scale,
                ((int) (y - yOffset + Sprite.SIZE / 2)) * scale, ((int) (x2
                        - xOffset + Sprite.SIZE / 2))
                        * scale, ((int) (y2 - yOffset + Sprite.SIZE / 2))
                        * scale);
    }

    /*
     * @Description: render a circle from a certain point
     * @param x xCoordinate of center of circle
     * @param y yCoordinate of center of cicle
     * @param r radius of circle
     * @param color the color of circle
     */
    public void renderCircle(double x, double y, double r, Color color) {
        graphics.setColor(color);
        graphics.drawOval((int) (x - xOffset - r + Sprite.SIZE / 2) * scale,
                (int) (y - r - yOffset + Sprite.SIZE / 2) * scale,
                (int) (2 * r * scale), (int) (2 * r * scale));
    }

    public void setGraphic(Graphics2D g) {
        this.graphics = g;
    }

    public void renderStatistics(ArrayList<String> info) {
        graphics.setColor(Color.black);
        graphics.setFont(graphics.getFont().deriveFont(20f));
        graphics.drawString(info.get(0), 700, 20);
        graphics.drawString(info.get(1), 700, 40);
        graphics.drawString(info.get(2), 700, 60);
        graphics.drawString(info.get(3), 700, 80);
    }

    public void dispose() {
        graphics.dispose();
    }
    public int getHeight() {
        return height;
    }
    public void setHeight(int height) {
        this.height = height;
    }
    public int getWidth() {
        return width;
    }
    public void setWidth(int width) {
        this.width = width;
    }
    public void setOffset(int xOffset, int yOffset) {
        this.xOffset = xOffset;
        this.yOffset = yOffset;
    }
    public int getXOffset() {
        return xOffset;
    }
    public int getYOffset() {
        return yOffset;
    }
}

Image 2

The map uses 16x16 tiles to tile the screen. There are three kind of tiles in this program: GrassTile, BrickTile and VoidTile and they are all inherited from the Tile class. The GrassTile and the VoidTile are non-collided tiles; meanwhile, the BrickTile is collided. The BrickTile is used to build the track’s wall so the Car can detect the collision. The VoidTile will be used to cover the places where there is nothing to render on the screen.

Java
package com.auto.car.level.tile;
import com.auto.graphics.Screen;
import com.auto.graphics.Sprite;
/*
 * @Description: Tile class is the parent class of all other Tiles.
 */
public class Tile {
    public int x, y;
    public Sprite sprite;
    public int size;
    public static Tile grass = new GrassTile(Sprite.grass,16);
    public static Tile brick = new BrickTile(Sprite.brick,16);
    public static Tile voidTile = new VoidTile(Sprite.voidSprite,16);
    public static final int grassColor = 0xff00ff00;
    public static final int brickColor = 0xffFFD800;

    public Tile(Sprite sprite, int size) {
        this.sprite = sprite;
        this.size = size;
    }
    public void render(int x, int y, Screen screen) {

    }
    public boolean solid() {
        return false;
    }
}
Java
package com.auto.car.level.tile;
import com.auto.graphics.Screen;
import com.auto.graphics.Sprite;

public class BrickTile extends Tile {
    public BrickTile(Sprite sprite, int size) {
        super(sprite, size);
    }
    public void render(int x, int y, Screen screen) {
        screen.renderTile(x, y, this);
    }
    /*
     * BrickTile is solid so we can detect collision
     */
    public boolean solid() {
        return true;
    }
}
Java
package com.auto.car.level.tile;
import com.auto.graphics.Screen;
import com.auto.graphics.Sprite;

public class GrassTile extends Tile {
    public GrassTile(Sprite sprite, int size) {
        super(sprite, size);
    }
    public void render(int x, int y, Screen screen) {
        screen.renderTile(x, y, this);
    }
}
Java
package com.auto.car.level.tile;
import com.auto.graphics.Screen;
import com.auto.graphics.Sprite;
/*
 * @Description: VoidTile is render at where there's nothing on screen 
 */
public class VoidTile extends Tile {
    public VoidTile(Sprite sprite, int size) {
        super(sprite, size);
    }
    public void render(int x, int y, Screen screen) {
        screen.renderTile(x, y, this);
    }
}

Image 3        Image 4 (Sprite Sheet)

A Sprite object holds the pixels of a 16x16 image located in the Sprite Sheet. A Sprite is used to design for objects in the program such as Car, Brick or Grass. The Tile class can either load a Sprite image or just simply fill in itself by color. In this program, the VoidTile is just simply filled in by a color meanwhile the Brick, the Grass and the Car Sprite will load images from the SpriteSheet. The background of the Sprite, which is WHITE color, will not be rendered.

Java
package com.auto.graphics;
/*
 * @Description: This class represents the individual Sprite which is got from
 * the SpriteSheet. The SIZE of Sprite is fixed which is 16 in this program
 */
public class Sprite {
    // SIZE in Decimal
    public static final int SIZE = 16;
    // SIZE in Binary
    public static final int SIZE_2BASED = 4;// 2^4
    /*
     * The coordinate of sprite in the SpriteSheet. Every unit of x and y is
     * equal to 16 pixels in SpriteSheet
     */
    private int x, y;
    private int[] pixels;
    private SpriteSheet sheet;
    /*
     * Preloaded Sprites
     */
    public static Sprite grass = new Sprite(0, 0, SpriteSheet.tiles16x16);
    public static Sprite brick = new Sprite(2, 0, SpriteSheet.tiles16x16);
    public static Sprite voidSprite = new Sprite(0xE6FFA3);
    public static Sprite carSprite = new Sprite(4, 0, SpriteSheet.tiles16x16);

    /*
     * Sprite Constructor
     */
    public Sprite(int x, int y, SpriteSheet sheet) {
        pixels = new int[SIZE * SIZE];
        this.x = x * SIZE;
        this.y = y * SIZE;
        this.sheet = sheet;
        load();
    }
    public Sprite(int colour) {
        pixels = new int[SIZE * SIZE];
        setColour(colour);
    }
    private void setColour(int colour) {
        for (int i = 0; i < SIZE * SIZE; i++) {
            pixels[i] = colour;
        }
    }
    /*
     * This method get data from the SpriteSheet and load it into the Sprite
     */
    private void load() {
        for (int i = 0; i < SIZE; i++) {
            for (int j = 0; j < SIZE; j++) {
                pixels[j + i * SIZE] = sheet.pixels[(j + this.x) + (i + this.y)
                        * sheet.getSize()];
            }
        }
    }
    public int[] getPixels() {
        return pixels;
    }
}

Image 5        Image 6 (Level)

Level is a mini map of the tiles. A level loads an image file then based on the color of each individual pixel, it will determine which Tile should be rendered. One pixel in the image will be replaced by 16 pixels of Tile in the program when it is rendered on screen. In this program, the color 0x00FF00 (Green) will represent the GrassTile and the color 0xFFD800 (Dark Yellow) will represent the BrickTile; any other colors that are not defined will be replaced by the VoidTile. The SpawnLevel class is inherited from the Level class and it overrides the method loadLevel. This method will load an image from the path and store data in the array pixels. After that, the colors on the image will be replaced by the Tile to render on the screen. The following picture is a sample of a Level.

Java
package com.auto.car.level;
import com.auto.car.level.tile.Tile;
import com.auto.graphics.Screen;
import com.auto.graphics.Sprite;
/*
 * @Description: Level class represents the map of our program
 */
public class Level {
    protected int width, height;
    protected int[] pixels;
    public Level(String path) {
        loadLevel(path);
    }
    protected void loadLevel(String path) {
    }
    public void update() {
    }
    /*
     * @Description: render map on screen
     * @param xScroll: the xOffset of screen
     * @param yScroll: the yOffset of screen
     */
    public void render(int xScroll, int yScroll, Screen screen) {
        screen.setOffset(xScroll, yScroll);
        // because every single pixel in Level is equal to SIZE of a Sprite
        // so we have to convert the coordinate of screen into coordinate in
        // pixels of Level
        int xMostLeft = xScroll >> Sprite.SIZE_2BASED;
        int xMostRight = (xScroll + screen.getWidth() + Sprite.SIZE) >> Sprite.SIZE_2BASED;
        int yMostTop = yScroll >> Sprite.SIZE_2BASED;
        int yMostBottom = (yScroll + screen.getHeight() + Sprite.SIZE) >> Sprite.SIZE_2BASED;
        for (int y = yMostTop; y < yMostBottom; y++) {
            for (int x = xMostLeft; x < xMostRight; x++) {
                if (x < 0 || y < 0 || x >= width || y >= height) {
                    // We have to convert the Level coordinate back to screen
                    // coordinate before rendering it on screen
                    Tile.voidTile.render(x << Sprite.SIZE_2BASED,
                            y << Sprite.SIZE_2BASED, screen);
                    continue;
                }
                getTile(x, y).render(x << Sprite.SIZE_2BASED,
                        y << Sprite.SIZE_2BASED, screen);
            }
        }
    }

    /*
     * @Description: each pixels in Level object represents a Tile.
     * @param x: xCoordinate
     * @param y: yCoordinate
     */
    public Tile getTile(int x, int y) {
        int index = x + y * width;
        if (index >= 0 && index < pixels.length) {
            switch (pixels[index]) {
            case Tile.grassColor:
                return Tile.grass;
            case Tile.brickColor:
                return Tile.brick;
            }
        }
        return Tile.voidTile;
    }
}

Image 7

The Entity class is the parent class of the Car and the Mob c­lasses (Figure 9) (the Mob class stands for Mobile class which creates objects that are movable on the Screen). It has 2 fields for x, y coordinates and getters and setters for x and y.

The Mob class is inherited from the Entity class and it has two functions which are move and isCollided. The Mob object can only move to a new position when there is no collision at that position. To detect a collision, we will detect the Tile at that position. If the Tile is solid, then there is collision; if not, then there is no collision. A Mob object also has a Sprite to render on the screen.

The Car class is inherited from the Mob class. It has information about angle of where the car is heading. Its deltaDistance field is the distance that the car has just moved. And its 5 sensors, EAST, NORTH EAST, NORTH, WEST, NORTH WEST are used to detect the distances from the car to the wall while the Car is moving around the track. The intersections are the intersected points between the 5 sensors and the track. The intersected points will be represented by small yellow circles in this program. After the distances are detected, they will be normalized and sent to the Neural Network to make decisions. The main methods of the Car entity are:

buildFeelers

This method will calculate the coordinates of the head and tail of each feeler. All the feelers’ tails will have the same coordinates with the car’s current position. Other feelers’ heads will be calculated based on the current heading angle of the car.

detectFeelerIntersection

To detect the intersection between a feeler and the wall, first we build a line going through the head and the tail of that feeler. After computing the line’s information, we will iterate from the tail to the head of that feeler to determine the collision point. Here, we need help from function getTile of the Level object. We will pass the coordinates of the points on the line to this function to get out a Tile object. If the Tile object is not null and it is solid, then we have found an intersection between the feeler and the wall right at this point.

update

The car will constantly build the feelers, detect the intersections with the wall, then send data to the Neural Network. The Neural Network will call its update method, then give out the information about left force and right force which help the car turn right or left. The car uses this data to calculate the angle that it will turn and the deltaDistance that it will move then it sends this information to its move method.

render

After two methods, buildFeelers and detectFeelerIntersection are called, this method will use the output of these two methods to draw the Car itself, its sensors’ lines and the intersection circles.

Java
package com.auto.car.entity;
import com.auto.car.level.Level;
import com.auto.graphics.Screen;

public abstract class Entity {
    protected int x, y;
    protected Level level;
    public void update() {

    }
    public void render(Screen screen) {

    }
    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }
    public int getX() {
        return x;
    }
    public void setX(int x) {
        this.x = x;
    }
}
Java
package com.auto.car.entity.mob;

import com.auto.car.entity.Entity;
import com.auto.graphics.Sprite;

public abstract class Mob extends Entity {
    protected Sprite sprite;
    protected boolean collided = false;
    public void move(int xPosition, int yPosition) {
        if (xPosition != 0 && yPosition != 0) {
            move(xPosition, 0);
            move(0, yPosition);
            return;
        }
        if (!isCollided(xPosition, yPosition)) {
            x += xPosition;
            y += yPosition;
        }
    }
    public void update() {
    }
    private boolean isCollided(int xPosition, int yPosition) {
        for (int corner = 0; corner < 4; corner++) {
            int xt = ((x + xPosition) + (corner % 2) * 7 + 5) >> 4;
            int yt = ((y + yPosition) + (corner / 2) * 12 + 3) >> 4;
            if (level.getTile(xt, yt).solid()) {
                collided = true;
            }
        }
        return collided;
    }

    public void render() {

    }
}
Java
package com.auto.car.entity.mob;

import java.awt.Color;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import com.auto.algorithm.NeuralNetwork;
import com.auto.car.entity.EntityManager;
import com.auto.car.level.Level;
import com.auto.car.level.tile.Tile;
import com.auto.graphics.Screen;
import com.auto.graphics.Sprite;

/*
 * Description: Car has 5 sensors to measure the distance from its center
 * to the wall. It use NeuralNetwork, pass in these 5 sensors's values to 
 * get out the decision of making turn left or right or going straight.
 */
public class Car extends Mob {
    // The identifiers for the feelers of the agnet
    public static int FEELER_COUNT = 5;

    public static enum SensorFeelers {
        FEELER_EAST, FEELER_NORTH_EAST, FEELER_NORTH, FEELER_NORTH_WEST, FEELER_WEST,
    };

    public static float FEELER_LENGTH = 32.0f;
    private double angle;
    private double deltaDistance;
    private Sensor sensor;
    private NeuralNetwork neural;
    private Point2D[] intersections;
    private double[] normalizedIntersectionDepths;
    public int eastIdx = SensorFeelers.FEELER_EAST.ordinal();
    public int northEastIdx = SensorFeelers.FEELER_NORTH_EAST.ordinal();
    public int northIdx = SensorFeelers.FEELER_NORTH.ordinal();
    public int northWestIdx = SensorFeelers.FEELER_NORTH_WEST.ordinal();
    public int westIdx = SensorFeelers.FEELER_WEST.ordinal();
    public static final float MAX_ROTATION_PER_SECOND = 30.0f / 180;

    public class Sensor {
        public Point2D[] feelerTails;
        public Point2D[] feelerHeads;
        public Sensor() {
            feelerTails = new Point2D[FEELER_COUNT];
            feelerHeads = new Point2D[FEELER_COUNT];
        }
    }

    public Car(int x, int y, double angle, Level lv) {
        this.x = x;
        this.y = y;
        this.angle = angle;
        sensor = new Sensor();
        level = lv;
        buildFeelers();
        detectFeelerIntersection();
    }

    public void setLevel(Level lv) {
        level = lv;
    }

    public void update() {
        if (!this.collided) {
            buildFeelers();
            detectFeelerIntersection();
            neural.setInput(normalizedIntersectionDepths);
            neural.update();
            double leftForce = neural
                    .getOutput(EntityManager.NeuralNetOuputs.NN_OUTPUT_LEFT_FORCE
                            .ordinal());
            double rightForce = neural
                    .getOutput(EntityManager.NeuralNetOuputs.NN_OUTPUT_RIGHT_FORCE
                            .ordinal());
            System.out.println("left force: " + leftForce + "-right force: "
                    + rightForce);
            // Convert the outputs to a proportion of how much to turn.
            double leftTheta = MAX_ROTATION_PER_SECOND * leftForce;
            double rightTheta = MAX_ROTATION_PER_SECOND * rightForce;
            angle += (leftTheta - rightTheta) * 2;
            double movingX = Math.sin(angle) * 2;
            double movingY = -Math.cos(angle) * 2;
            deltaDistance = Math.sqrt(movingX * movingX + movingY * movingY);
            move((int) movingX, (int) movingY);
        }
    }

    /*
     * private void turn(int x, int y, int direction) { if (Mouse.getButton() ==
     * 1) { double dx = Mouse.getX() - CarDemo.getWindowWidth() / 2 - 24; double
     * dy = Mouse.getY() - CarDemo.getWindowHeight() / 2 - 24; angle =
     * Math.atan2(dy, dx); }
     * 
     * }
     */

    public Rectangle2D getCarBound() {
        return new Rectangle2D.Double(x - Sprite.SIZE / 2, y - Sprite.SIZE / 2,
                Sprite.SIZE, Sprite.SIZE);
    }

    /*
     * this function determines the position of sensors when the car turns
     */
    private void buildFeelers() {

        for (int i = 0; i < FEELER_COUNT; i++) {
            sensor.feelerHeads[i] = new Point2D.Float();
            sensor.feelerTails[i] = new Point2D.Float();
            // All feelers's tails has the same coordinate which is the center
            // of the car.
            sensor.feelerTails[i].setLocation(x, y);
        }
        // East feeler's head
        sensor.feelerHeads[eastIdx].setLocation(
                x + Math.sin(Math.PI - (angle + Math.PI / 2)) * FEELER_LENGTH,
                y + Math.cos(Math.PI - (angle + Math.PI / 2)) * FEELER_LENGTH);
        // North East feeler's head
        sensor.feelerHeads[northEastIdx].setLocation(
                x + Math.sin(Math.PI - (angle + Math.PI / 4)) * FEELER_LENGTH,
                y + Math.cos(Math.PI - (angle + Math.PI / 4)) * FEELER_LENGTH);
        // North feeler's head
        sensor.feelerHeads[northIdx].setLocation(x + Math.sin(Math.PI - angle)
                * FEELER_LENGTH, y + Math.cos(Math.PI - angle) * FEELER_LENGTH);
        // North West feeler's head
        sensor.feelerHeads[northWestIdx].setLocation(
                x + Math.sin(Math.PI - (angle - Math.PI / 4)) * FEELER_LENGTH,
                y + Math.cos(Math.PI - (angle - Math.PI / 4)) * FEELER_LENGTH);
        // West feeler's head
        sensor.feelerHeads[westIdx].setLocation(
                x + Math.sin(Math.PI - (angle - Math.PI / 2)) * FEELER_LENGTH,
                y + Math.cos(Math.PI - (angle - Math.PI / 2)) * FEELER_LENGTH);
    }

    /*
     * This function measures the distance from center of the car to the wall.
     */
    public void detectFeelerIntersection() {
        intersections = new Point2D[FEELER_COUNT];
        normalizedIntersectionDepths = new double[FEELER_COUNT];
        for (int k = 0; k < FEELER_COUNT; k++) {
            double xStart = sensor.feelerHeads[k].getX();
            double xEnd = sensor.feelerTails[k].getX();
            double yStart = sensor.feelerHeads[k].getY();
            double yEnd = sensor.feelerTails[k].getY();
            Line2D line = new Line2D.Double();
            line.setLine(sensor.feelerHeads[k], sensor.feelerTails[k]);
            double step = 0.001;
            double slope = (yStart - yEnd) / (xStart - xEnd);
            if (!java.lang.Double.isInfinite(slope)) {
                for (double i = xStart; i < xEnd; i += step) {
                    double j = slope * (i - xEnd) + yEnd;
                    Tile tile = level.getTile((int) (i + Sprite.SIZE / 2)
                            / Sprite.SIZE, (int) (j + Sprite.SIZE / 2)
                            / Sprite.SIZE);
                    if (tile != null) {
                        if (tile.solid()) {
                            intersections[k] = new Point2D.Float();
                            intersections[k].setLocation(i, j);
                        }
                    }
                }
                for (double i = xStart; i > xEnd; i -= step) {
                    double j = slope * (i - xEnd) + yEnd;
                    Tile tile = level.getTile((int) (i + Sprite.SIZE / 2)
                            / Sprite.SIZE, (int) (j + Sprite.SIZE / 2)
                            / Sprite.SIZE);
                    if (tile != null) {
                        if (tile.solid()) {
                            intersections[k] = new Point2D.Float();
                            intersections[k].setLocation(i, j);
                        }
                    }
                }
            } else {
                for (double j = yStart; j < yEnd; j += step) {
                    Tile tile = level.getTile((int) (xStart + Sprite.SIZE / 2)
                            / Sprite.SIZE, (int) (j + Sprite.SIZE / 2)
                            / Sprite.SIZE);
                    if (tile != null) {
                        if (tile.solid()) {
                            intersections[k] = new Point2D.Float();
                            intersections[k].setLocation(xStart, j);
                        }
                    }
                }
                for (double j = yStart; j > yEnd; j -= step) {
                    Tile tile = level.getTile((int) (xStart + Sprite.SIZE / 2)
                            / Sprite.SIZE, (int) (j + Sprite.SIZE / 2)
                            / Sprite.SIZE);
                    if (tile != null) {
                        if (tile.solid()) {
                            intersections[k] = new Point2D.Float();
                            intersections[k].setLocation(xStart, j);
                        }
                    }
                }
            }
            if (intersections[k] != null) {
                normalizedIntersectionDepths[k] = 1 - (Math.sqrt(Math.pow(x
                        - intersections[k].getX(), 2)
                        + Math.pow(y - intersections[k].getY(), 2)) / FEELER_LENGTH);
            } else {
                normalizedIntersectionDepths[k] = 0;
            }
        }
    }

    public void attach(NeuralNetwork neuralNet) {
        this.neural = neuralNet;
    }

    public void setPosition(Point2D defaultPosition) {
        x = (int) defaultPosition.getX();
        y = (int) defaultPosition.getY();
    }

    public void clearFailure() {
        collided = false;
    }

    public boolean hasFailed() {
        return collided;
    }

    public double getDistanceDelta() {
        return deltaDistance;
    }

    public void render(Screen screen) {
        // Render the car
        screen.renderCar(x, y, angle, Sprite.carSprite);
        // Render 5 sensors around the car
        screen.renderLine(sensor.feelerHeads[eastIdx].getX(),
                sensor.feelerHeads[eastIdx].getY(),
                sensor.feelerTails[eastIdx].getX(),
                sensor.feelerTails[eastIdx].getY(), Color.YELLOW);
        screen.renderLine(sensor.feelerHeads[northEastIdx].getX(),
                sensor.feelerHeads[northEastIdx].getY(),
                sensor.feelerTails[northEastIdx].getX(),
                sensor.feelerTails[northEastIdx].getY(), Color.YELLOW);

        screen.renderLine(sensor.feelerHeads[northIdx].getX(),
                sensor.feelerHeads[northIdx].getY(),
                sensor.feelerTails[northIdx].getX(),
                sensor.feelerTails[northIdx].getY(), Color.black);

        screen.renderLine(sensor.feelerHeads[northWestIdx].getX(),
                sensor.feelerHeads[northWestIdx].getY(),
                sensor.feelerTails[northWestIdx].getX(),
                sensor.feelerTails[northWestIdx].getY(), Color.YELLOW);

        screen.renderLine(sensor.feelerHeads[westIdx].getX(),
                sensor.feelerHeads[westIdx].getY(),
                sensor.feelerTails[westIdx].getX(),
                sensor.feelerTails[westIdx].getY(), Color.YELLOW);
        screen.renderCircle(x, y, FEELER_LENGTH, Color.YELLOW);
        // draw collisions by a small circle
        for (int k = 0; k < FEELER_COUNT; k++) {
            if (intersections[k] != null) {
                screen.renderCircle(intersections[k].getX(),
                        intersections[k].getY(), 3, Color.YELLOW);
            }
        }
    }

    public void setPosition(int x, int y) {
        setX(x);
        setY(y);
    }

3) Genetic Algorithm

Image 8

A Genetic Algorithm is used to train the Neural Network; it helps the Neural Network give a better decision.

Genome Class

A genome (Figure 10) has 3 pieces of information: ID, fitness and weights. The fitness information is the distance that the car has been able to move without collision and the weights information is the list of random Sigmoid values which are from -1 to 1.

GeneticAlgorithm Class

The GeneticAlgorithm class has a list of Genomes called population. Each population has good genomes with high fitness. Those genomes will be mixed up and mutated to create a new population of genomes. This class has the following main methods:

generateNewPopulation

This function will generate a new population of genomes. The genomes’ weights will be randomly assigned a Sigmoid value; the ID and the fitness values will be assigned to 0.

breedPopulation

This function will generate a new population using old population. First, 4 genomes with high fitness will be chosen from the old population. After that, we will mutate and cross over 4 of them together and add to new population. The left positions of new population will be filled up by a new random Genome.

Image 9

crossOver

The crossOver function is a function that mixes up 2 genomes and creates 2 other new genomes. To mix them, first we randomly choose the cross over point then split 2 genomes into 4 parts from that point, then mix 4 parts together.

mutate

The mutate function is a function that randomly picks up a gene in a genome and assigns a new value which is the sum of a random Sigmoid value multiplied by the constant MAX_PERMUTATION = 0.3 plus weight of that gene if the Sigmoid value is smaller than MUTATION_RATE = 0.15.

package com.auto.algorithm;
import java.util.ArrayList;
/*
 * @Description: Genome object simply keeps 2 important info: fitness and weights. 
 * The fitness is the distance that how long the car could go. The weights are
 * a list of Sigmoid values which is from -1 to 1.
 */
public class Genome {
    public int ID;
    public double fitness;
    public ArrayList<Double> weights;
}
package com.auto.algorithm;
import java.util.ArrayList;
import java.util.Random;

/*
 * @Description: GeneticAlgorithm is used to train neuron network. It has a list of 
 * genomes called population. Each population will have some of the best genomes
 * with highest fitness. Fitness is the sum of distance that how far a car could go. 
 * The best genomes will be used
 * to create other genomes by mixing them up (crossing over between 2 genomes) and mutate
 * some of their genes. It is a little bit different with basic genetic algorithm, 
 * the mutate will not turn on and off a gene but they randomly change the weight of genes.
 */
public class GeneticAlgorithm {
    public static final float MAX_PERMUTATION = 0.3f;
    public static final float MUTATION_RATE = 0.15f;
    private int currentGenome;
    private int totalPopulation;
    private int genomeID;
    private int generation;
    private ArrayList<Genome> population;

    public GeneticAlgorithm() {
        currentGenome = -1;
        totalPopulation = 0;
        genomeID = 0;
        generation = 1;
        population = new ArrayList<Genome>();
    }

    /*
     * Generate genomes population with ID, fitness and random Sigmoid weights
     */
    public void generateNewPopulation(int totalPop, int totalWeights) {
        generation = 1;
        clearPopulation();
        currentGenome = -1;
        totalPopulation = totalPop;
        for (int i = 0; i < totalPopulation; i++) {
            Genome genome = new Genome();
            genome.ID = genomeID;
            genome.fitness = 0.0;
            genome.weights = new ArrayList<Double>();
            for (int j = 0; j < totalWeights; j++) {
                genome.weights.add(HelperFunction.RandomSigmoid());
            }
            genomeID++;
            population.add(genome);
        }
    }

    public void setGenomeFitness(double fitness, int index) {
        if (index >= population.size() || index < 0)
            return;
        population.get(index).fitness = fitness;
    }

    public Genome getNextGenome() {
        currentGenome++;
        if (currentGenome >= population.size())
            return null;
        return population.get(currentGenome);
    }

    public void clearPopulation() {
        population.clear();
    }

    /*
     * This function will generate new population of genomes based on best 4
     * genomes (genomes which have highest fitness). The best genomes will be
     * mixed up and mutated to create new genomes.
     */
    public void breedPopulation() {
        ArrayList<Genome> bestGenomes = new ArrayList<Genome>();
        // Find the 4 best genomes which have highest fitness.
        bestGenomes = getBestGenomes(4);
        ArrayList<Genome> children = new ArrayList<Genome>();
        // Carry on the best genome.
        Genome bestGenome = new Genome();
        bestGenome.fitness = 0.0;
        bestGenome.ID = bestGenomes.get(0).ID;
        bestGenome.weights = bestGenomes.get(0).weights;
        // mutate few gene of genome to create new genome
        mutate(bestGenome);
        children.add(bestGenome);
        // Child genomes.
        ArrayList<Genome> crossedOverGenomes;
        // Breed with genome 0.
        crossedOverGenomes = crossOver(bestGenomes.get(0), bestGenomes.get(1));
        mutate(crossedOverGenomes.get(0));
        mutate(crossedOverGenomes.get(1));
        children.add(crossedOverGenomes.get(0));
        children.add(crossedOverGenomes.get(1));
        crossedOverGenomes = crossOver(bestGenomes.get(0), bestGenomes.get(2));
        mutate(crossedOverGenomes.get(0));
        mutate(crossedOverGenomes.get(1));
        children.add(crossedOverGenomes.get(0));
        children.add(crossedOverGenomes.get(1));
        crossedOverGenomes = crossOver(bestGenomes.get(0), bestGenomes.get(3));
        mutate(crossedOverGenomes.get(0));
        mutate(crossedOverGenomes.get(1));
        children.add(crossedOverGenomes.get(0));
        children.add(crossedOverGenomes.get(1));

        // Breed with genome 1.
        crossedOverGenomes = crossOver(bestGenomes.get(1), bestGenomes.get(2));
        mutate(crossedOverGenomes.get(0));
        mutate(crossedOverGenomes.get(1));
        children.add(crossedOverGenomes.get(0));
        children.add(crossedOverGenomes.get(1));
        crossedOverGenomes = crossOver(bestGenomes.get(1), bestGenomes.get(3));
        mutate(crossedOverGenomes.get(0));
        mutate(crossedOverGenomes.get(1));
        children.add(crossedOverGenomes.get(0));
        children.add(crossedOverGenomes.get(1));
        // For the remainding n population, add some random genomes.
        int remainingChildren = (totalPopulation - children.size());
        for (int i = 0; i < remainingChildren; i++) {
            children.add(this.createNewGenome(bestGenomes.get(0).weights.size()));
        }
        clearPopulation();
        population = children;
        currentGenome = -1;
        generation++;
    }

    private Genome createNewGenome(int totalWeights) {
        Genome genome = new Genome();
        genome.ID = genomeID;
        genome.fitness = 0.0f;
        genome.weights = new ArrayList<Double>();
        for (int j = 0; j < totalWeights; j++) {
            genome.weights.add(HelperFunction.RandomSigmoid());
        }
        genomeID++;
        return genome;
    }

    /*
     * This function will mix up two genomes to create 2 other new genomes
     */
    private ArrayList<Genome> crossOver(Genome g1, Genome g2) {
        Random random = new Random(System.nanoTime());
        // Select a random cross over point.
        int totalWeights = g1.weights.size();
        int crossover = Math.abs(random.nextInt()) % totalWeights;
        ArrayList<Genome> genomes = new ArrayList<Genome>();
        Genome genome1 = new Genome();
        genome1.ID = genomeID;
        genome1.weights = new ArrayList<Double>();
        genomeID++;
        Genome genome2 = new Genome();
        genome2.ID = genomeID;
        genome2.weights = new ArrayList<Double>();
        genomeID++;
        // Go from start to crossover point, copying the weights from g1 to children.
        for (int i = 0; i < crossover; i++) {
            genome1.weights.add(g1.weights.get(i));
            genome2.weights.add(g2.weights.get(i));
        }
        // Go from start to crossover point, copying the weights from g2 to children.
        for (int i = crossover; i < totalWeights; i++) {
            genome1.weights.add(g2.weights.get(i));
            genome2.weights.add(g1.weights.get(i));
        }
        genomes.add(genome1);
        genomes.add(genome2);
        return genomes;
    }

    /*
     * Generate a random chance of mutating the weight in the genome.
     */
    private void mutate(Genome genome) {
        for (int i = 0; i < genome.weights.size(); ++i) {
            double randomSigmoid = HelperFunction.RandomSigmoid();
            if (randomSigmoid < MUTATION_RATE) {
                genome.weights.set(i, genome.weights.get(i)
                        + (randomSigmoid * MAX_PERMUTATION));
            }
        }
    }

    /*
     * Get the best genomes to breed new population
     */
    private ArrayList<Genome> getBestGenomes(int totalGenomes) {
        int genomeCount = 0;
        int runCount = 0;
        ArrayList<Genome> bestGenomes = new ArrayList<Genome>();
        while (genomeCount < totalGenomes) {
            if (runCount > 10) {
                break;
            }
            runCount++;
            // Find the best cases for cross breeding based on fitness score.
            double bestFitness = 0;
            int bestIndex = -1;
            for (int i = 0; i < this.totalPopulation; i++) {
                if (population.get(i).fitness > bestFitness) {
                    boolean isUsed = false;
                    for (int j = 0; j < bestGenomes.size(); j++) {
                        if (bestGenomes.get(j).ID == population.get(i).ID) {
                            isUsed = true;
                        }
                    }
                    if (isUsed == false) {
                        bestIndex = i;
                        bestFitness = population.get(bestIndex).fitness;
                    }
                }
            }
            if (bestIndex != -1) {
                genomeCount++;
                bestGenomes.add(population.get(bestIndex));
            }

        }
        return bestGenomes;
    }

    public int getCurrentGeneration() {
        return generation;
    }
    public int getCurrentGenomeIndex() {
        return currentGenome;
    }

}

4) Neural Network

Image 10

Neuron Class

Neuron is the basic element in the Neural Network. It has two fields which are the numberOfInputs coming to a neuron and values of those inputs which are called weights. In this program, neuron will receive data from the genomes and stores into its weights. These weights will be used by the neuron layers to evaluate output.

Neuronslayer Class

The NeuronsLayer class (Figure 13) contains a list of neurons. It will use those neurons’ weights to evaluate and give out the outputs. There are two types of neurons layer. One is the Hidden Layer and one is the Output Layer. These layers will be managed by the Neural Network. The main method of this class is evaluate method. In this method, we will sum up the value of inputs multiplied by neuron’s weights and add the value of last weight multiplied by a constant BIAS = -1. The purpose of BIAS value is to make sure the output is not 0. After that, the sum will be normalized by Sigmoid function and stored in the outputs.

NeuralNetwork Class

NeuralNetwork class (Figure 14) contains 1 Output Layer and 1 or many Hidden Layers. These are the main methods of this class:

setInput

This method receives the inputs from car’s sensors and stores them.

getOutput

This method receives the index and give out the data at that index.

update

The NeuralNetwork constantly receives data from the sensors of the car, passes that data to Hidden Layer to process, then transfers output to the Output Layer for second processing. After that, the Output Layer will give out the decision back to the car to make a turn.

fromGenome

This function will get weights from genome of GeneticAlgorithm to store in Neurons Layers.

Java
package com.auto.algorithm;

import java.util.ArrayList;

/*
 * @Description: Neuron is the basic element in the neuron network. Each neuron has
 * a certain number of inputs. In this program Neuron from HiddenLayer has 5 inputs which
 * are from the car's 5 sensors and Neuron in OutputLayer has 8 inputs which are from
 * 8 HiddenLayers
 */
public class Neuron {
    protected int numberOfInputs;
    protected ArrayList<Double> weights;

    public void init(ArrayList<Double> weightsIn, int numOfInputs) {
        this.numberOfInputs = numOfInputs;
        weights = weightsIn;
    }
}
Java
package com.auto.algorithm;

import java.util.ArrayList;

/*
 * @Description: NeuronsLayer contains Neurons. It evaluates these nerons to give
 * out decision.
 */
public class NeuronsLayer {
    public static final float BIAS = -1.0f;
    private int totalNeurons;
    // int totalInputs;
    private ArrayList<Neuron> neurons;

    /*
     * Evaluate the inputs from sensors or HiddenLayer and give out the output
     */
    public void evaluate(ArrayList<Double> inputs, ArrayList<Double> outputs) {
        int inputIndex = 0;
        for (int i = 0; i < totalNeurons; i++) {
            float activation = 0.0f;
            int numOfInputs = neurons.get(i).numberOfInputs;
            Neuron neuron = neurons.get(i);
            // sum the weights up to numberOfInputs-1 and add the bias
            for (int j = 0; j < numOfInputs - 1; j++) {
                if (inputIndex < inputs.size()) {
                    activation += inputs.get(inputIndex)
                            * neuron.weights.get(j);
                    inputIndex++;
                }
            }
            // Add the bias.
            activation += neuron.weights.get(numOfInputs) * BIAS;
            outputs.add(HelperFunction.Sigmoid(activation, 1.0f));
            inputIndex = 0;
        }
    }

    public ArrayList<Double> getWeights() {
        // Calculate the size of the output vector by calculating the amount of
        // weights in each neurons.
        ArrayList<Double> out = new ArrayList<Double>();
        for (int i = 0; i < this.totalNeurons; i++) {
            Neuron n = neurons.get(i);
            for (int j = 0; j < n.weights.size(); j++) {
                out.add(n.weights.get(j));
            }
        }
        return out;
    }

    public void loadLayer(ArrayList<Neuron> neurons) {
        totalNeurons = neurons.size();
        this.neurons = neurons;
    }
}
Java
package com.auto.algorithm;

import java.util.ArrayList;

/*
 * @Description: NeuralNetwork is used to make decision for the car; decide that it
 * should turn right or turn left or go straight. It may contain many hidden NeuronsLayers 
 * and 1 output NeuronsLayer. 
 * These layers will constantly update to get new values from genomes
 * each time the car crashs to the wall. The network will use new values from Genetic 
 * to make a decision for the next try of car. 
 */
public class NeuralNetwork {
    private int inputAmount;
    private int outputAmount;
    private ArrayList<Double> outputs;
    private ArrayList<Double> inputs;
    // HiddenLayer produces the input for OutputLayer
    private ArrayList<NeuronsLayer> hiddenLayers;
    // OutputLayer will receive input from HiddenLayer
    private NeuronsLayer outputLayer;

    public NeuralNetwork() {
        outputs = new ArrayList<Double>();
        inputs = new ArrayList<Double>();
    }

    /*
     * receive input from sensors of car which is normalized distance from
     * center of car to the wall.
     */
    public void setInput(double[] normalizedDepths) {
        inputs.clear();
        for (int i = 0; i < normalizedDepths.length; i++) {
            inputs.add(normalizedDepths[i]);
        }
    }

    @SuppressWarnings("unchecked")
    public void update() {
        outputs.clear();
        for (int i = 0; i < hiddenLayers.size(); i++) {
            if (i > 0) {
                inputs = outputs;
            }
            // each hidden layer calculates the outputs based on inputs
            // from sensors of the car
            hiddenLayers.get(i).evaluate(inputs, outputs);
            System.out.println("Output of hidden layers: "
                    + outputs.toString());
        }
        // the outputs of HiddenLayers will be used as input for
        // OutputLayer
        inputs = (ArrayList<Double>) outputs.clone();
        // The output layer will give out the final outputs
        outputLayer.evaluate(inputs, outputs);
    }

    public double getOutput(int index) {
        if (index >= outputAmount)
            return 0.0f;
        return outputs.get(index);
    }

    /*
     * Initiate NeuronsNetwork
     */
    /*
     * public void createNet(int numOfHiddenLayers, int numOfInputs, int
     * neuronsPerHidden, int numOfOutputs) { inputAmount = numOfInputs;
     * outputAmount = numOfOutputs; hiddenLayers = new
     * ArrayList<NeuronsLayer>(); for (int i = 0; i < numOfHiddenLayers; i++) {
     * NeuronsLayer layer = new NeuronsLayer();
     * layer.populateLayer(neuronsPerHidden, numOfInputs);
     * hiddenLayers.add(layer); } outputLayer = new NeuronsLayer();
     * outputLayer.populateLayer(numOfOutputs, neuronsPerHidden); }
     */
    public void releaseNet() {
        // inputLayer = null;
        outputLayer = null;
        hiddenLayers = null;
    }

    /*
     * Neural network receives weights from genome to make new HiddenLayers and
     * OutputLayer.
     */
    public void fromGenome(Genome genome, int numOfInputs,
            int neuronsPerHidden, int numOfOutputs) {
        if (genome == null)
            return;
        releaseNet();
        hiddenLayers = new ArrayList<NeuronsLayer>();
        outputAmount = numOfOutputs;
        inputAmount = numOfInputs;
        NeuronsLayer hidden = new NeuronsLayer();
        ArrayList<Neuron> neurons = new ArrayList<Neuron>();
        for (int i = 0; i < neuronsPerHidden; i++) {
            ArrayList<Double> weights = new ArrayList<Double>();
            for (int j = 0; j < numOfInputs + 1; j++) {
                weights.add(genome.weights.get(i * neuronsPerHidden + j));
            }
            Neuron n = new Neuron();
            n.init(weights, numOfInputs);
            neurons.add(n);
        }
        hidden.loadLayer(neurons);
        hiddenLayers.add(hidden);

        // Clear weights and reassign the weights to the output.
        ArrayList<Neuron> neuronsOut = new ArrayList<Neuron>();
        for (int i = 0; i < numOfOutputs; i++) {
            ArrayList<Double> weights = new ArrayList<Double>();
            for (int j = 0; j < neuronsPerHidden + 1; j++) {
                weights.add(genome.weights.get(i * neuronsPerHidden + j));
            }
            Neuron n = new Neuron();
            n.init(weights, neuronsPerHidden);
            neuronsOut.add(n);
        }
        outputLayer = new NeuronsLayer();
        outputLayer.loadLayer(neuronsOut);
    }
}
Java
package com.auto.algorithm;

import java.util.Random;

/*
 * Description: Global helper functions
 */
public class HelperFunction {
    /*
     * normalize value to make it from 1 to -1
     */
    public static double Sigmoid(float a, float p) {
        float ap = (-a) / p;
        return (1 / (1 + Math.exp(ap)));
    }

    /*
     * random number from -1 to 1;
     */
    public static double RandomSigmoid() {
        Random ran = new Random(System.nanoTime());
        double r = ran.nextDouble() - ran.nextDouble();
        return r;
    }
    /*
     * compare value of a to b and c. If is smaller then b or greater than c, 
     * a will become b or c
     */
    public static double getValueInRange(double a, double b, double c) {
        if (a < b) {
            return b;
        } else if (a > c) {
            return c;
        }
        return a;
    }
}

Points of Interest

I felt so good when finish this project. It's my capstone project for Master Degree from Marshall University. The project is a practical application. It helps beginners to learn about Artificial Intelligence, Neural Network and Genetic Algorithm.

References

I'm sorry that I forgot to mention the references in the previous version. The idea and code I learnt from this author https://github.com/matthewrdev/Neural-Network written in C++. The graphics I learnt from this Youtube channel https://www.youtube.com/user/TheChernoProject. :)

History

  • 15th December, 2016: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)