Code Tools > Island Generator

Island Generator

In this page, you can set specific start parameters, and get a procedurally generated island map using the diamond-square algorithm. You can download a standalone here: Diamond-Square Island Gen Zip File

How to use this tool

  • Select a scaling coefficient. This determines the sharpness of the result. Lower values result in maps with much less definition. Higher values result in somewhat more chaotic results.
  • Select a seed value. This determines which specific map will be generated. Note that by saying "which specific map", I am implying that the result will be the same every time. More on that later.
  • Choose whether to use smoothing in the result. This simply determines whether the raw data should be used, or a slightly flattened version instead.
  • Click the button to generate a map! This can be saved (right-click, save image as) if you want to use it later. The time it takes will depend on your browser and your computer. In testing, we saw about 5 seconds on average without smoothing and 10 seconds with.
Your browser does not support the canvas tag.

Controls

Scale Coefficient:

Seed Value:

Use Smoothing? (very slow)

Discussion

Before we get too deeply into this, let me explain about what this tool is, what it does, and how it works. At the bottom of the page, I'll provide an algorithm spec for what's happening to make it explicit for all the nerds out there.

So first, what this thing is. This tool is:

  • a procedural content generator (defined below)
  • implemented using the diamond-square algorithm (defined below)
  • free to steal, use, change, et c.
  • offered without any guarantees except that it will generate a unique island map for each of the 232 initial seed values, available in approximately 2000 variants (one per selection of scale, doubled for using smoothing) per seed

And now, what this thing isn't. This tool is NOT:

  • generative machine learning, deep learning, or artificial intelligence
  • truly random. at best, it's pseudo-random, but the choices to change the underpinning structure are not surfaced to you.
  • a database of pre-generated maps. While that could be done, I don't have time to go around generating 4 billion+ maps. Even if I did, I wouldn't.

So then, given all of that up there ... what does this thing even do? Like ... how's it work. To properly discuss what this does, let's introduce what it means to generate content procedurally

Procedural content generation is a subset of strictly algorithmic programming that applies step-by-step process using (usually) random choices to generate either predictable or repeatable outcomes, given a similar starting point.

To break that down, if I feed a procedural content generator (pcg) the same starting point, I should expect to see the same or at least similar result. But what does a starting point look like? That depends on the specific pcg. In this case, it looks like a "random" number and a (usually) positive fraction that's (usually) less than 1. But it could be more complex.

How is this different than "ai" or "gml" or "llm"?

It's important in the current era to understand what tools constitute ai, machine learning, and large language models. Otherwise, all computer generated content (note that I am not saying "art" here) comes under the same scrutiny that is levelled (deservedly) at the trash (to put it kindly) generated by artificially "intelligent" systems. So in order to make it clear what the difference between these two paradigms, let's define the terms I've used.

Artificial intelligence is a subset of hyper-parallel programming by which a computer or a programme might appear to be intelligent without necessary human intervention. Results from such a system are necessarily repeatable, but not necessarily consistently.

Machine learning is a subset of iterative linear programming that uses repeated automated testing to build a direct connection between input data and desired output data while being able to filter undesired output data.

Large language models are a subset of machine learning that establish extensive recognition for "desired" output in order to reverse engineer input in a semi-predictive manner that appears "intelligent" by using natural language as input.

Generative models are a subset of machine learning, possibly including large language models, that generate content based on a potentially complex input that was tested for in the process of building the model, which in most cases has been reversed in order to take "output" and provide conceivably accurate "input".

Each of these is a generally accepted clean definition (rewritten to be more similar to the way I speak and write) of the types systems they define. One thing to note about these is that in almost no way do the definitions for machine learning, large language models, and generative models necessarily overlap with artificial intelligence. But more than that, none of these definitions overlap with procedural content generation. And there's a reason for this.

With procedural content generation, the starting point is algorithm design, analysis, and implementation. What the algorithm is meant to do varies from pcg to pcg. And in some cases the same algorithm can be the heart of two VERY different pcgs. But the algorithm is always the heart of it. For example, the diamond-square algorithm used here was designed specifically to generate semi-smooth two dimensional noise as an extension to the midpoint displacement algorithm, which can be used for cloud textures or islands. There are potentially infinitely many uses for any given algorithm.

Meanwhile, machine learning abstracts the algorithm entirely. Machine learning is a basic algorithm that starts with a mountain of data and an assumed functional scaffold for "learning". When the data is fed into the algorithm, it takes the scaffold and builds connections between nodes so that the output from the scaffold begins to align with expected results. Once fully trained on the data, the scaffold replaces the algorithm in its completed form and becomes the structure of the "input-output" loop.

Is it possible to use machine learning to build an algorithm that could be used for procedural content generation? Sure. Is that the way it gets used in general? No. And even if it were, there would be entirely too much risk of loss due to corruption of the tool. A procedural algorithm can be re-implemented over and over and over. But if the underlying mechanism is the result of machine learning, then re-implementing it becomes nearly impossible, even if the same data is fed in using the same order.

So. This tool is procedural. There's nothing intelligent about it. There's nothing about it that was trained with mountains of data or a complex development of a scaffold that can't be scrutinised for what it's actually doing.

Why do we care?

You might not. But I do. I care because of three important things.

  1. I always want to know exactly how my programmes work.
  2. Large-scale machine learning requires theft of data.
  3. Algorithmic design expands knowledge while machine learning (usually) restricts it.

Let's address the first now. As mentioned above, machine learning abstracts the algorithms that are the desired end result. Someone with the time and energy could probably parse specifically what the system is doing, but some of the models generated are enormous with millions of steps to check and parse. There's almost no way to parse that. But someone with the understanding of programming and algorithmic design can always parse what a programme is doing at every single step. And that means we can truly debug it and guarantee accurate/correct/expected results.

For the second. Not all machine learning requires theft of data. Look at the early work of Code Bullet on YouTube, which builds a playground for an entity to work in, then builds the model from the work done by that entity. "I trained an AI to play ..." doesn't have to steal data because those models are built with live data from the model attempting to play the game. But larger models aren't using live data. They take data from across the internet, get it labelled, and chuck it into the model. It's indiscriminant about the data it pulls, taking things that are copyrighted as quickly as things that are public domain. I will not intentionally engage with stolen art, writing, et c.

For the third. I've already essentially spelled this out, but let me give a much cleaner explanation. Because algorithmic design requires us to be able to state, understand, and solve a problem in clean steps, it forces people to actively expand their methods and their approaches to those problems. "Need semi-smooth random data? Midpoint Displacement. Need it in 2d? Diamond-Square. Need it in 3d? Perlin Noise, which can be simplified to 2d and 1d." And with every advance, we grow the body of human knowledge. Meanwhile, machine learning takes the understanding out of the problem. It explicitly says "put data into the machine. the machine will solve the problem" and then follows up with "this tool solves the problem. no, I can't say how it works". Which is the opposite of expanding knowledge.

Do you have to care? No. Do I? Absolutely. And I will continue to do so. Ad infinitum.


The Algorithm

Illustrations to come ... in time.

Here's a brief overview of the steps of the algorithm.

  1. Set up a square array of size 2n+1 x 2n+1
  2. Set initial conditions at the four corners.
  3. Perform the following loop:
    1. Select a square in your array whose corners are set
    2. Take the average of the corners and add a random value
    3. Set that value as the value at the centre of the square
    4. For each edge of the square
      1. If the midpoint of the adjacent square sharing the edge is unset, skip the edge. Otherwise move to step 2.
      2. Identify the midpoint of the edge
      3. Take the average of the two corners on the edge along with the centres of the current square and the one that shares the edge
      4. Add a random value to that average and set it as the value of the identified midpoint
    5. If the whole array is set, exit.

Ideally, the loop would be recursive with the following structure:

  1. Find all squares of appropriate size whose corners are set, but whose edges are not.
  2. Calculate the centres of those squares using the above method.
  3. Once all squares are set, find all the edges of those squares.
  4. Calculate the midpoints of those edges using the above method.
  5. Once all edges are set, recurse using half the current size.

Using such a method would likely result in cleaner outcomes, but implementing it in an efficient way is much more challenging.

-----

This overview isn't perfect, but it's enough information for someone with the relevant skillset to be able to implement the actual intent of the algorithm. In order to provide full transparency for my own implementation, the below algorithm spec will be using my method with a slight modification.

Step 1: Initialise

Initialise a 2d array of length and width both 2n+1 for some n>0. So valid lengths would be 3, 5, 9, 17, 33, 65, 129, 257, et c.

Set the corners, edges, or other data to a known initial condition. For this implementation I set all edges to zero as well as the first several rows and columns in both directions. This allowed for a guarantee that the end would be "island-like".

For the purpose of consistent looping, define a stepSize as the array width minus 1. Also define a scale for random input and store two copies, one for use and one for adjustment.

Step 2: Loops

Begin an outer loop that closes when the stepSize hits 1. At the end of each loop, divide the stepSize by 2.

Begin an inner loop at position 0 vertically. At the end of each loop, move down by the stepSize. Don't go past the edge of the array.

Begin a nested inner loop at position 0 horizontally. At the end of each loop, move right by the stepSize. Don't go past the edge of the array.

Inside the nested loop, identify the centre of the current square by adding half the stepSize to vertical and horizontal position. Average the corners of the square. These are found at position, position+stepSizeX, position+stepSizeY, and position+stepSizeXY. Take a random number between 0 and 1 and multiply it by the useScale. Add that random number to that average and save it in the centre.

Inside the nested loop, identify the (top, left, bottom, right) edge of the square as (centre+stepSize/2). Average the shared corners as (position+stepSize,position+stepSize) with the centre and the next square's centre as (centre+stepSize). Take a random number between 0 and 1 and multiply it by the useScale. Add that random number to that average and save it in the edge. For edges that are on the absolute edge of the array, you can make the array tileable by wrapping the "next square"

Loop the nested loop.

Loop the inner loop.

Loop the outer loop.

Step 3: Post Processing

Upon loop completion, perform post-processing on the data. In this tool, post-processing includes scaling the data to the range 0-255 using a quadratic measure and possibly smoothing any data that's above the water line using a local weighted average with total weight 16 and taxi distance less than 2.

Format the data in whatever is the desired style.

PseudoCode

Below, you will find pseudo-code for the algorithm, in case you need it for your own implementation.

/********************************************************************
 *                Diamond-Square Generation                         *
 ********************************************************************/
instance the following:
    array - heightMap - float - 2d - 2^n+1 x 2^n+1 - initialise 0
    float - scaleFact - 0<this<1
    float - currScale - 0<this<1 - initialise scaleFact
    int   - currWidth - 2^n

begin outer loop on currWidth>1
    begin inner loop on vPos from 0 while vPos<2^n
        begin nested loop on hPos from 0 while hPos<2^n
            perform squareStep at (hPos,vPos)
            perform diamondStep at (hPos,vPos)
            increase hPos by currWidth
            iterate nested loop
        increase vPos by currWidth
        iterate inner loop
    divide currWidth by 2
    iterate outer loop

function squareStep (hPos,vPos,currWidth)
    instance midPoint as (hPos+currWidth/2 , vPos+currWidth/2)
    set heightMap at midPoint as (
            heightMap at (hPos , vPos) +
            heightMap at (hPos+currWidth , vPos) +
            heightMap at (hPos , vPos+currWidth) +
            heightMap at (hPos+currWidth , vPos+currWidth) +
        ) / 4 + random(0 , currScale)
    return

function diamondStep (hPos,vPos,currWidth)
    instance halfWidth as currWidth/2
    instance midPoint as (hPos+currWidth/2 , vPos+currWidth/2)
    instance spot as midPoint+(0,halfWidth)
    perform singleDiamond at spot
    set spot as midPoint+(halfWidth,0)
    perform singleDiamond at spot
    set spot as midPoint-(halfWidth,0)
    perform singleDiamond at spot
    set spot as midPoint-(0,halfWidth)
    return

function singleDiamond (spot,halfWidth)
    for each spot + vector : assume wrapping on heightMap
    set heightMap at spot as (
            heightMap at spot+(0,halfWidth)
            heightMap at spot+(halfWidth,0)
            heightMap at spot-(0,halfWidth)
            heightMap at spot-(halfWidth,0)
        ) /4 + random(0,currScale)
    return