AI Demo Canon Shooting - ML5js

Legos LightLegos Light
3 min read

I created a canon shooting demo to make the AI Canon tries to aim and shoot at a target. Basically, the neural network will receive the target’s location and will try to predict the angle and the force for the canon.

Code explanation notes

Neural Network Settings

I used the ML5js library to create a simple neural network with only 2 input neurons (x, y) , and 2 output neurons (angle, force).

Import the library:

<script src="https://unpkg.com/ml5@0.12.2/dist/ml5.min.js"></script>

Setup the network:

// Setup neural network
function setupNN() {
    let options = {
        inputs: 2, // Target (x, y)
        outputs: 2, // Angle and Force
        task: 'regression',
        debug: true
    };
    model = ml5.neuralNetwork(options);

    // Add data to the model
    trainingData.forEach(data => {
        model.addData(data.input, data.output);
    });

    model.normalizeData();
}

Data Preparation

Base on the network structure, I will create a dataset by randomly picking a location in the area and calculate the accurate force and angle of the canon. Then I will feed to the network.

Given \((x,y)\), we must calculate the \((\theta, f)\) by solving the system of equations:

Having:

$$\begin{cases} x = f\cos\theta t \\ y = f \sin \theta t - \frac 1 2 g t^2\end{cases} \iff \begin{cases} f\cos\theta = \frac x t \\ f \sin \theta = \frac y t + \frac 1 2 g t\end{cases}$$

With \(t\) is the time of the projectile. Now, I have 2 equations and 3 unknowns. I fix the time \(t=30\) to make the system of equations solvable. Then the right hand sides of the equations becomes constants. Call them \(A\) and \(B\), and to simplify the equation, let \(a = \cos\theta \implies \sin\theta = \sqrt{1 - a^2}\). The equations become:

$$\begin{cases} fa = \frac x t = A \\ f \sqrt{1 - a^2} = \frac y t + \frac 1 2 g t = B \end{cases} \iff \begin{cases} f = \frac A a \\ f \sqrt{1 - a^2} = B \end{cases} \iff \begin{cases} f = \frac A a \\ f^2 (\sqrt{1 - a^2})^2 = B^2 \end{cases}$$

Replacing \(f\) with \(\frac A a\):

$$\begin{cases} f = \frac A a \\ \frac {A^2}{a^2} (\sqrt{1 - a^2})^2 = B^2 \end{cases} \iff \begin{cases} f = \frac A a \\ a^2 = \frac {A^2} {A^2+B^2} \iff a = \frac A {\sqrt{A^2+B^2}} \end{cases}$$

Now, put them into the code:

// given target (x,y), calculate the angle and force required to hit the target
function simulateProjectile(x, y) {
    // calculate time of flight
    const T = 30;

    // calculate angle theta and force f
    // knowing x = f * cos(theta) * t and y = f * sin(theta) * t - 0.5 * g * t^2
    // we can solve for f and theta
    const A = x / T;
    const B = y / T + 0.5 * g * T;    
    const cosTheta = A / Math.sqrt(A**2 + B**2);
    const angle = Math.acos(cosTheta) * 180 / Math.PI;
    const force = A / cosTheta;

    // test the prediction
    // testSimulateProjectile(x, y, T, angle, force);

    return [T, angle, force];
}

And generate the training Data:

// Generate training data
function generateTrainingData() {
  for (let i = 0; i < sampleSize; i++) {
    let [x, y] = [random(0, width), random(0, height)];
    let [_, angle, force] = simulateProjectile(x, y);
    console.log(`Generating... Target: ${x}, ${y}, Angle: ${angle}, Force: ${force}`);
    trainingData.push({ input: [x, y], output: [angle, force] });
  }
}

The rest of the code is simple, you can understand easily.

Demo

0
Subscribe to my newsletter

Read articles from Legos Light directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Legos Light
Legos Light