Langton's Ant In JavaScript

22nd June 2020

After looking at Conway's game of life I have been looking at other forms of cellular automata. This lead me to discover Langton's ant, which is a different kind of cellular automata where an agent (namely an ant) is used to turn the squares on or off as it travels around a grid.

The rules of Langton's ant are quite simple. The ant simply follows two rules as it moves around the grid.

  • At a white square, turn 90° clockwise, flip the colour of the square, move forward one unit.
  • At a black square, turn 90° counter-clockwise, flip the colour of the square, move forward one unit.

This can be represented in code by looking at the current state of the square that the ant is stood on and then performing an action. Movement in a particular direction is achieved by adding or subtracting from the x or y coordinates depending on which way the ant is pointing.

I have been teaching myself a little more about JavaScript recently so I wanted to create a Langton's ant in JavaScript using a HTML canvas element.

To generate a canvas we just start a web page with the following content.

<canvas id="grid" width="500" height="500"></canvas>

As with Conway's game of life we can easily represent the grid of squares as a two dimensional array. Each cell in our grid can be represented as an object of type Cell. This object has a single property of 'alive' and two functions that allow us to interact with this property.

  1. class Cell {
  2. alive = false;
  3.  
  4. setAlive(alive) {
  5. this.alive = alive;
  6. }
  7.  
  8. get isAlive() {
  9. return this.alive;
  10. }
  11. }

The ant itself can be represented by an object of type Ant. The properties of the Ant object are its coordinates on our grid and in what direction it is currently facing. The direction is defined as a constant that we can define later.

  1. class Ant {
  2. x = 0;
  3. y = 0;
  4.  
  5. direction = ANTUP;
  6. }

The grid of cells can be represented by an object of type Grid. This has a number of properties, including the array of cells, the ant, and the height and width of the grid itself. An initialisation function is then included so that we can pre-populate the cells of the grid with Cell objects and then create our Ant object.

  1. class Grid {
  2. cells = [];
  3. ant;
  4. height = 0;
  5. width = 0;
  6.  
  7. constructor(height, width) {
  8. this.height = height;
  9. this.width = width;
  10. }
  11.  
  12. init() {
  13. for (let x = 0; x < this.width; x++) {
  14. this.cells[x] = [];
  15. for (let y = 0; y < this.height; y++) {
  16. const cell = new Cell();
  17. this.cells[x][y] = cell;
  18. }
  19. }
  20. this.ant = new Ant();
  21. this.ant.x = this.width / 2;
  22. this.ant.y = this.height / 2;
  23. }
  24.  
  25. move() {
  26. // fill in this later
  27. }
  28. }

With all that in place we can now instantiate the Grid object and start running our animation. The code below won't do an awful lot at the moment as we haven't defined out the and moves, but it will setup all the things we need to get going. The last call to setInterval() will call the 'ant' function about 13 times a second.

  1. window.onload = function() {
  2. canvas = document.getElementById('grid');
  3. ctx = canvas.getContext('2d');
  4. const grid = new Grid(canvas.width, canvas.height);
  5. grid.init();
  6. setInterval(moveAnt, 1000/13, grid);
  7. }
  8.  
  9. function moveAnt(grid) {
  10. grid.move();
  11. ctx.stroke();
  12. }

We defined the Ant object with a direction, so let's define those directions now. We set each axis of the compass as a number, which means that in order to rotate clockwise we just need to increment and to rotate anti-clockwise we just need to decrement.

  1. const ANTUP = 0;
  2. const ANTRIGHT = 1;
  3. const ANTDOWN = 2;
  4. const ANTLEFT = 3;

With that in place we can flesh out the Ant object with some movement and rotation functions. Note that we can use neat little bit of modulus maths here in order to loop around the height, width and direction values. This not only means that we will loop from top to bottom and left to right, but also around in circles without having lots of if statements cluttering up the code.

  1. class Ant {
  2. x = 0;
  3. y = 0;
  4.  
  5. direction = ANTUP;
  6.  
  7. moveForward(width, height) {
  8. switch (this.direction) {
  9. case ANTUP:
  10. this.x = ((this.x - 1) + width) % width;
  11. break;
  12. case ANTRIGHT:
  13. this.y = ((this.y + 1) + height) % height;
  14. break;
  15. case ANTDOWN:
  16. this.x = ((this.x + 1) + width) % width;
  17. break;
  18. case ANTLEFT:
  19. this.y = ((this.y - 1) + height) % height;
  20. break;
  21. }
  22. }
  23.  
  24. rotateRight() {
  25. this.direction = ((this.direction + 1) + (ANTLEFT + 1)) % (ANTLEFT + 1);
  26. }
  27.  
  28. rotateLeft() {
  29. this.direction = ((this.direction - 1) + (ANTLEFT + 1)) % (ANTLEFT + 1);
  30. }
  31. }

In the code above, the movement of the Ant is achieved by adding or subtracting from the x or y coordinates. For example, in order to move up the grid we would subtract from the x value as x in a canvas starts from the upper left hand side.

With the Ant defined we can now fill in the move function in the Grid object. The canvas element lends itself very well to this kind of cellular processing as it's possible to draw a single 1x1 pixel square using the fillRect() function, so we are taking advantage of this function here.

The move function needs to do the following:

  • Grab the current Cell object that the Ant is sitting on.
  • Make a decision on the cell depending on its current state.
    • If the cell is active then we fill in the square with white, rotate the Ant right and move the Ant forward one cell.
    • If the cell is inactive then we fill the square with black, rotate the Ant left and move the Ant forward one cell.

To create a 1x1 pixel square on the canvas we just need to set the fill style of the canvas and then call the fillRect() function. In the code below I am drawing a black square on the current Ant location.

  1. ctx.fillStyle = 'black';
  2. ctx.fillRect(this.ant.x, this.ant.y, 1, 1);

At the end of the process we turn the cell that the Ant is currently on red, which allows us to see where the and it currently situated on the grid. 

  1. class Grid {
  2. /// .. snip .. ///
  3. move () {
  4. let cell = this.cells[this.ant.x][this.ant.y];
  5. if (cell.isAlive) {
  6. cell.alive = false;
  7. ctx.fillStyle = 'white';
  8. ctx.fillRect(this.ant.x, this.ant.y, 1, 1);
  9. this.ant.rotateRight();
  10. this.ant.moveForward(this.width, this.height);
  11. }
  12. else {
  13. cell.alive = true;
  14. ctx.fillStyle = 'black';
  15. ctx.fillRect(this.ant.x, this.ant.y, 1, 1);
  16. this.ant.rotateLeft();
  17. this.ant.moveForward(this.width, this.height);
  18. }
  19. ctx.fillStyle = 'red';
  20. ctx.fillRect(this.ant.x, this.ant.y, 1, 1);
  21. }
  22. }

With all that in place we can run the simulation and we should see something like this.

Langton's Ant Simulation

Note that this structure starts to build itself after 10,000 steps in the move cycle. Unfortunately, at 13 frames a second getting to this structure takes about 15 minutes. We can speed this up substantially by adding a loop to the move() function. This means that every time the move functions runs we process 100 septs in the Ant cycle and effectively jump ahead in the process. We therefore don't need to refresh the canvas until the Ant has made some progress.

  1. class Grid {
  2. /// .. snip .. ///
  3. move () {
  4. for (let i = 0; i < 100; i++) {
  5. let cell = this.cells[this.ant.x][this.ant.y];
  6. if (cell.isAlive) {
  7. cell.alive = false;
  8. ctx.fillStyle = 'white';
  9. ctx.fillRect(this.ant.x, this.ant.y, 1, 1);
  10. this.ant.rotateRight();
  11. this.ant.moveForward(this.width, this.height);
  12. }
  13. else {
  14. cell.alive = true;
  15. ctx.fillStyle = 'black';
  16. ctx.fillRect(this.ant.x, this.ant.y, 1, 1);
  17. this.ant.rotateLeft();
  18. this.ant.moveForward(this.width, this.height);
  19. }
  20. ctx.fillStyle = 'red';
  21. ctx.fillRect(this.ant.x, this.ant.y, 1, 1);
  22. this.moves++;
  23. }
  24. }
  25. }

With this in place it isn't long before we start seeing this kind of thing being displayed.

Late Stage Langton's Ant Simulation

I think it's really interesting how creating something with just a couple of simple rules can create something with this kind of complexity. The picture created starts of random, and then the ant starts moving in these highways.

If you want to have a go with this code yourself then I have created a Langton's Ant codepen with all of the code above running. As a minor improvement to the above code I have also added a step counter so you can easily see how many moves the Ant has made. This involved adding a moves property to the Grid object and then incrementing it every time the move() function is run. A running printout is then added underneath the canvas by injecting the value of move into the HTML of the page.

If you want to read more then I can recommend looking at the wikipedia page about Langton's Ant. This article talks more about how the Ant moves and also mentions extensions that people have made that use more than one colour to alter the way the Ant moves on the grid.

Add new comment

The content of this field is kept private and will not be shown publicly.