A declarative interface for generative art via procedural composition

A generated image

An area with grass generated procedurally

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title></title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
  </head>
  <body>
    <script type="module">
      await generate([
        {
          probability: 0.3,
          children: [
            {
              probability: 0.1,
              row: 5,
              column: 0,
            },
            {
              probability: 0.3,
              row: 5,
              column: 1,
            },
            {
              probability: 0.6,
              row: 5,
              column: 2,
            },
          ],
        },
        {
          probability: 0.7,
          row: 3,
          column: 1,
        },
      ])

      async function generate(configuration) {
        const image = await loadImage("grass.png")
        const canvas = document.createElement("canvas")
        canvas.width = 10 * 32
        canvas.height = 10 * 32
        document.body.appendChild(canvas)
        const context = canvas.getContext("2d")
        for (let x = 0; x < 10; x++) {
          for (let y = 0; y < 10; y++) {
            const { row, column } = selectTile(configuration)
            context.drawImage(
              image,
              column * 32,
              row * 32,
              32,
              32,
              x * 32,
              y * 32,
              32,
              32,
            )
          }
        }
      }

      function selectTile(configuration) {
        let options = configuration
        let tile
        do {
          tile = selectRandomWeighted(options)
          options = tile?.children
        } while (options)
        return tile
      }

      function loadImage(url) {
        return new Promise((resolve, onError) => {
          const image = new Image()
          image.src = url
          image.onload = function () {
            resolve(image)
          }
          image.onerror = function (error) {
            onError(error)
          }
        })
      }

      function selectRandomWeighted(options) {
        const random = Math.random()
        let accumulatedProbability = 0
        for (const option of options) {
          accumulatedProbability += option.probability
          if (random < accumulatedProbability) {
            return option
          }
        }
        return null
      }
    </script>
  </body>
</html>