Skip to content

Hexagon explorer

An unfinished experiment in interactive map generation. Each hexagon color affects the likelihood of nearby hexagons being same/different -- similar to there being a deck of cards contributed by the surroundings. The idea was that you could focus on exploring the areas you like, thus developing them further than the areas you dislike.

Legend:

Ideally, would introduce longer-ranged card contributions—e.g. an "outpost" card which contributes 1 "monster den" card to all hexagons exactly 3 tiles away. Also, conditional contributions, such as a road tile which contributes "8" roads to nearby explorations if it's adjacent to less than 2 roads. And finally, sub-tiles/cards, such as a "sheep" card which can randomly appear on top of grass tiles or a "townfolk" card which appears randomly in towns.

Combining all three would be a way to make questlines: e.g. the "terrorized townfolk" sub-card of "city" tiles contributes 1 "goblin king" sub-card to all "mountain" tiles, as long as there hasn't been a "goblin king" already drawn on the map. Then, the "goblin king" card contributes 1 "heroic adventurer" sub-sub-card to any "tavern" sub-cards of "road" tiles, and so on.

An unassuming map of hexagons

let decks = {[[0,0]]: {hill: 0, grass: 1, mountain: 0}}
let tiles = {}
let colors = {
  hill: [255, 255, 50],
  mountain: [50, 50, 255],
  grass: [0, 255, 0],
}
let spreads = {
  hill: {hill: 4, grass: 1, mountain: 1},
  mountain: {hill: 2, grass: 2, mountain: 2},
  grass: {hill: 1, grass: 5, mountain: 0},
}

function setup() {
  createCanvas(600, 600);
}

let offsetPx = [-300, -300]
let sizePx = 20

function draw() {
  background(220);
  
  let corners = [
    px2hex(vadd(offsetPx, [0, 0]), sizePx),
    px2hex(vadd(offsetPx, [width, 0]), sizePx),
    px2hex(vadd(offsetPx, [0, height]), sizePx),
    px2hex(vadd(offsetPx, [width, height]), sizePx),
  ]
  
  let minQr = vfloor(corners.reduce(vmin))
  let maxQr = vceil(corners.reduce(vmax))
  
  for (let q = minQr[0]; q <= maxQr[0]; q ++) {
    for (let r = minQr[1]; r <= maxQr[1]; r ++) {
      let [x, y] = vsub(hex2px([q, r], sizePx), offsetPx)
      
      let s = 1
      if (tiles[[q, r]]) {
        fill(...colors[tiles[[q, r]]])
      } else if (decks[[q, r]]) {
        noFill()
        line(x - 3, y, x + 3, y)
        line(x, y - 3, x, y + 3)
        s = 0.75
      } else {
        continue
      }
      
      beginShape()
      vertex(x + SQRT3 / 2 * sizePx * s, y + 1/2 * sizePx * s)
      vertex(x + 0 * sizePx * s, y + 1 * sizePx * s)
      vertex(x - SQRT3 / 2 * sizePx * s, y + 1/2 * sizePx * s)
      vertex(x - SQRT3 / 2 * sizePx * s, y - 1/2 * sizePx * s)
      vertex(x + 0 * sizePx * s, y - 1 * sizePx * s)
      vertex(x + SQRT3 / 2 * sizePx * s, y - 1/2 * sizePx * s)
      endShape(CLOSE)
    }
  }
  let [q, r] = hexround(px2hex(vadd(offsetPx, [mouseX, mouseY]), sizePx))
  circle(...vsub(hex2px([q, r], sizePx), offsetPx), 10)
}

function mousePressed() {
  let [q, r] = hexround(px2hex(vadd(offsetPx, [mouseX, mouseY]), sizePx))
  if (decks[[q, r]]) {
    let deck = decks[[q, r]]
    delete decks[[q, r]]
    // Pick a tile from the deck:
    let total = Object.values(deck).reduce((a, b) => a + b)
    let rand = random(total)
    let picked = Object.entries(deck).find(([_, w]) => (rand -= w) < 0)[0]
    tiles[[q, r]] = picked
    // Spread tiles around
    let toSpread = spreads[picked]
    // https://www.redblobgames.com/grids/hexagons/#neighbors
    for (let dir of [[1, 0], [1, -1], [0, -1], [-1, 0], [-1, 1], [0, 1]]) {
      let pos = vadd([q, r], dir)
      if (!tiles[pos]) {
        decks[pos] = madd_mod(decks[pos], toSpread)
      }
    }
  }
}

function vadd([a,b], [c,d]) {
  return [a + c, b + d]
}
function vsub([a,b], [c,d]) {
  return [a - c, b - d]
}
function vmin([a,b], [c,d]) {
  return [min(a, c), min(b, d)]
}
function vmax([a,b], [c,d]) {
  return [max(a, c), max(b, d)]
}
function vfloor([a, b]) {
  return [floor(a), floor(b)]
}
function vceil([a, b]) {
  return [ceil(a), ceil(b)]
}
// https://observablehq.com/@jrus/hexround
function hexround([q, r]) {
  let qg = Math.round(q), rg = Math.round(r);
  q -= qg;
  r -= rg;
  let dq = Math.round(q + 0.5*r) * (q*q >= r*r);
  let dr = Math.round(r + 0.5*q) * (q*q < r*r);
  return [qg + dq, rg + dr];
}

function madd_mod(a, b) {
  b = b || {}
  let r = a || {}
  for (let k in b) {
    r[k] = (r[k] || 0) + b[k]
  }
  return r
}
// https://www.redblobgames.com/grids/hexagons/#pixel-to-hex
const SQRT3 = Math.sqrt(3)
function px2hex([x, y], size) {
  let q = x * SQRT3 / 3 - y * 1/3
  let r = y * 2/3
  return [q / size, r / size]
}
function hex2px([q, r], size) {
  let x = q * SQRT3 + r * SQRT3/2
  let y = r * 3/2
  return [x * size, y * size]
}

(Originally seen at https://editor.p5js.org/bojidar-bg/sketches/fog9K46F7)

Experiments tagged p5 (74/85)

Experiments tagged wip (5/5) →|

Experiments on this site (74/85)