Code viewer for World: Perceptron World
/* A list of points that we will use to train the perceptron. */
let training = new Array(500);

/* The constant to use when training the perceptron. */
const LearningConstant = 0.01;

/* Whether or not the training occurred without error. */
var errorFree = true;

/* The minimum and maximum x and y coordinates within which our points should reside. */
let xmin = -1;
let ymin = -1;
let xmax = 1;
let ymax = 1;

/* The fixed width within which our header should stay. */
AB.headerWidth(400);

/* Draws a line using "y = ax + b". */
const a = AB.randomFloatAtoB(0.1, 0.7);
const b = AB.randomFloatAtoB(0.1, 0.8);

/* Calculates the value for a given point. */
function f(x) {
    return (a * x + b);
}

/* Classifies whether or not we are above the threshold line. */
function getClassification(x, y) {
    if (y < f(x)) return (-1);
    else return (1);
}

/* A perceptron that is created with n weights and a learning constant. */ 
class Perceptron {
    constructor(n, c) {
        /* The collection of weights for each of our inputs. */
        this.weights = new Array(n);
        
        /* Initialise our weights with random values. */
        for (let i = 0; i < this.weights.length; i++) {
            this.weights[i] = random(-1, 1);
        }
        
        /* Our learning constant. */
        this.c = c;
    }

    /* Train the perceptron, and adjust the weights based on our desired answer. */
    train(inputs, desired) {
        /* Guess the result. */
        let guess = this.feedforward(inputs);
        
        /* Determine the amount by which the weight should be changed, based on the error.
               - Error = desired output - guessed output.
               - Note: this can only be 0, -2, or 2.
               - Multiply it by learning constant. */
        let error = desired - guess;
        if (error) {
            errorFree = false;
            console.log("Error occurred.");
        }
        
        /* Adjust the weights based on weightChange * input. */
        for (let i = 0; i < this.weights.length; i++) {
            var previousWeight = this.weights[i];
            this.weights[i] += this.c * error * inputs[i];
            
            if (previousWeight !== this.weights[i]) {
                console.log("Weight adjusted for input: " + inputs[i]);
            } else {
                console.log("Weight preserved for input: " + inputs[i]);
            }
        }
    }
    
    /* Guess -1 or 1 based on the input values. */
    feedforward(inputs) {
        /* Sum up all of the values. */
        let sum = 0;
        
        for (let i = 0; i < this.weights.length; i++) {
            sum += inputs[i] * this.weights[i];
        }
        
        // The result is either -1 or 1.
        return this.activate(sum);
    }

    /* Activate the neuron in question, if necessary. */
    activate(sum) {
        if (sum > 0) return 1;
        else return -1;
    }
    
    /* Return the weights. */
    getWeights() {
        return this.weights;
    }
}

/* A Perceptron object. */
let ptron;

/* We will train the perceptron with one point at a time. */
let count = 0;

/* Carry out any initial setup. */
function setup() {
    createCanvas(800, 800);
    
    /* The perceptron has 3 inputs, x, y, and bias. */
    ptron = new Perceptron(3, LearningConstant); 

    /* Create a random set of training points and calculate the known answer. */
    for (let i = 0; i < training.length; i++) {
        let x = random(xmin, xmax);
        let y = random(ymin, ymax);
        let answer = getClassification(x, y);
        
        training[i] = {
            input: [x, y, 1],
            output: answer
        };
    }
}

/* To track which step of the training process that we are currently at. */
var step = 1;

/* Draw the training process. */
function draw() {
    frameRate(60);
    
    /* Reset our error-free flag. */
    if (count === 0) {
        errorFree = true;
    }
    
    AB.msg("Line: y = " + a.toFixed(2) + " x + " + b.toFixed(2) + "<br> Step: " + step);
    step++;
    
    /* Set the background to be black. */
    background('black');
    
    /* Draw the line based on the current weights. */
    /* The formula is weights[0] * x + weights[1] * y + weights[2] = 0. */
    stroke('white');
    let weights = ptron.getWeights();
    
    x1 = xmin;
    y1 = (-weights[2] - weights[0] * x1) / weights[1];
    x2 = xmax;
    y2 = (-weights[2] - weights[0] * x2) / weights[1];

    x1 = map(x1, xmin, xmax, 0, width);
    y1 = map(y1, ymin, ymax, height, 0);
    x2 = map(x2, xmin, xmax, 0, width);
    y2 = map(y2, ymin, ymax, height, 0);
    line(x1, y1, x2, y2);

    /* Train the Perceptron with one training point at a time. */
    AB.msg("<br> Training on single point: " + count, 2);
    ptron.train(training[count].input, training[count].output);
    count = (count + 1) % training.length;

    /* Draw all of the points. */
    AB.msg("<br> Drawing points 0 to " + (count - 1), 3);
  
    for (let i = 0; i < count; i++) {
        strokeWeight(1);
        let guess = ptron.feedforward(training[i].input);
        let x = map(training[i].input[0], xmin, xmax, 0, width);
        let y = map(training[i].input[1], ymin, ymax, height, 0);
        
        if (getClassification(training[i].input[0], training[i].input[1]) == 1) {
            stroke('lightgreen');
            fill('lightgreen');
        } else {
            stroke('lightpink');
            fill('lightpink');
        }
        
        ellipse(x, y, 12, 12);
    }
    
    if (count === training.length - 1 && errorFree) {
        console.log("Completed a bout of training, error-free.");
        noLoop();
    }
}