Skip to content

Puzzle shapes

An investigation of puzzle shapes for "Jigsaw puzzle maths".

Click on the puzzle to regenerate the shape; click near the edges to toggle edge pieces; click on the statistics to toggle percentage mode.

A generated jigsaw puzzle, along with a few sliders to control its behavior, and some statistics at the bottom

function setup() {
  let w = 500
  createCanvas(w, w * 2.15);
  noLoop()
  rand = random(10000000)
}

let rand = 43
let regular = 0.9
let noiseScale = 0.5
let _pmouseX = 0
let _pmouseY = 0
let percents = false
let edges = false
let N = 15
let colors = {
  regular: [255, 255, 255],
  bend: [255, 150, 150],
  sink: [255, 100, 150],
  source: [255, 100, 150],
  edge: [176, 176, 176]
  
}

function draw() {
  let o = 20
  background(220);
  
  if (mouseIsPressed && mouseX > o && mouseX < width - o && mouseY > o && mouseY < width - o) {
    mouseIsPressed = false
    noLoop()
    rand = random(10000000)
  } else if (mouseIsPressed && mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < width) {
    mouseIsPressed = false
    noLoop()
    edges = !edges
  }
  N = edges ? 17 : 15
  
  let s = (width - 2 * o) / N
  
  randomSeed(rand)
  let o1 = random(5000)
  let o2 = random(5000)
  let mem = {}
  
  function getTL(i, j) {
    if (!mem[[i,j]]) {
      mem[[i, j]] = [random([-1, 1]), random([-1, 1])]
      if (random() < regular) {
        let sgn = (i + j) % 2 ? -1 : 1
        if (noise(i * noiseScale * 0.9 + o1, j * noiseScale * 0.9 + o2) < 0.5) {
          sgn *= -1
        }
        mem[[i, j]] = [sgn, -sgn]
      }
    }
    if (edges) {
      return [j % N && mem[[i,j]][0], i % N && mem[[i,j]][1]]
    } else {
      return mem[[i,j]]
    }
  }
  
  let counts = {}
  let memTypes = {}
  
  for (let i = 0; i < N; i ++) {
    for (let j = 0; j < N; j ++) {
      let [t, l] = getTL(i, j)
      let [b, ] = getTL(i, j + 1)
      let [, r] = getTL(i + 1, j)
      
      let type = piece(i * s + o, j * s + o, s, s, t, l, b, r)
      
      counts[type] = (counts[type] || 0) + 1
      
      memTypes[[i,j]] = type
      let typeT = memTypes[[i,j-1]]
      let typeL = memTypes[[i-1,j]]
      counts[[typeT, type]] = (counts[[typeT, type]] || 0) + 1
      counts[[type, typeT]] = (counts[[type, typeT]] || 0) + 1
      counts[[typeL, type]] = (counts[[typeL, type]] || 0) + 1
      counts[[type, typeL]] = (counts[[type, typeL]] || 0) + 1
    }
  }
  
  textSize(s * 0.6)
  textAlign(LEFT, CENTER)
  let tw = max(textWidth("Regularity:"), textWidth("Domain noise:"))
  {
    fill(0)
    text("Regularity:", o, s * N + 4 * o)
    regular += bar(tw + 2 * o, s * N + 4 * o, s * N - tw - o, s * 0.15, regular)
    regular = constrain(regular, 0, 1)
  }
  {
    fill(0)
    text("Noise scale:", o, s * (N + 1) + 4 * o)
    noiseScale += bar(tw + 2 * o, s * (N + 1) + 4 * o, s * N - tw - o, s * 0.15, noiseScale)
    noiseScale = constrain(noiseScale, 0, 1)
  }
  
  let types = [
    [0, "regular", [-1, 1, 1, -1]],
    [1, "bend", [1, 1, 1, 1]],
    [2, "sink", [1, 1, 1, -1]],
    [3, "source", [-1, 1, 1, 1]],
  ]
  if (edges) {
    types.push([4, "edge", [0, 1, 1, 0]])
  }
  
  if (mouseIsPressed && mouseX > o && mouseX < width - o && mouseY > s * (N + 5)) {
    mouseIsPressed = false
    noLoop()
    percents = !percents
  }
  
  textAlign(CENTER, CENTER)
  for (let [i, k, [t,l,b,r]] of types) {
    let x = i * (o * 2 + s * 2)
    fill(0)
    textSize(s * 0.9)
    let txt = (counts[k] || 0)
    if (percents) {
      txt = floor(txt / N / N * 100) + '%'
    }
    
    text(txt, x + o + s * 0.5, s * (N + 2.5) + 5 * o)
    piece(x + o * 2 + s * 1, s * (N + 2) + 5 * o, s, s, t,l,b,r)
    
    let subsum = 0 // Approaches counts[k] * 4 minus the pieces around the edge
    for (let [j, k2, [l2,t2,r2,b2]] of types) {
      subsum += (counts[[k,k2]] || 0)
    }
    for (let [j, k2, [l2,t2,r2,b2]] of types) {
      let y = j * (o * 1.5 + s * 2)
      fill(0)
      textSize(s * 0.7)
      let txt = (counts[[k,k2]] || 0)
      if (percents) {
        txt = floor(txt / subsum * 100) + '%'
      }
      text(txt, x + o + s * 0.5, y + s * (N + 5) + 5 * o)
      piece(x + o * 2 + s * 1, y + s * (N + 4) + 5 * o, s, s, t,l,b,r)
      piece(x + o * 2 + s * 1, y + s * (N + 5) + 5 * o, s, s, t2,l2,b2,r2)
    }
  }
  
  
  _pmouseX = mouseX
  _pmouseY = mouseY
}

function noiseVertex(x,y) {
  splineVertex(
    x + (noise(x * 0.012 + 400, y * 0.043 + 302) - 0.5) * width / N * 0.1,
    y + (noise(x * 0.012 + 231, y * 0.043 + 542) - 0.5) * width / N * 0.1
  )
}
function bar(x,y,w,h,p) {
  fill(128)
  beginShape()
  for (let i = 0; i <= N * 3; i ++) {
    noiseVertex(x + w / N / 3 * i, y)
  }
  for (let i = N * 3; i >= 0; i --) {
    noiseVertex(x + w / N / 3 * i, y + h)
  }
  endShape(CLOSE)
  fill(255, 128, 128)
  beginShape()
  for (let i = 0; i <= floor(N * 3 * p); i ++) {
    noiseVertex(x + w / N / 3 * i, y)
  }
  for (let i = floor(N * 3 * p); i >= 0; i --) {
    noiseVertex(x + w / N / 3 * i, y + h)
  }
  endShape(CLOSE)
  fill(255)
  let motion = 0
  if (dist(_pmouseX, _pmouseY, x + w * p, y + h * 0.5) < h * 5 / 2) {
    if (mouseIsPressed) {
      motion = (mouseX - _pmouseX) / w
    }
  }
  circle(x + w * p, y + h * 0.5, h * 5)
  return motion
}

function mousePressed() {
  _pmouseX = mouseX
  _pmouseY = mouseY
  loop()
}

function mouseReleased() {
  noLoop()
}

function piece(x,y,w,h, t,l,b,r) {
  let type
  if (t * b * r * l == 0) {
    type = "edge"
  } else if (t * b < 0 && r * l < 0 && t * l < 0) {
    type = "regular"
  } else if ((t < 0) + (l < 0) + (b > 0) + (r > 0) >= 3) {
    type = "source"
  } else if ((t < 0) + (l < 0) + (b > 0) + (r > 0) <= 1) {
    type = "sink"
  } else {
    type = "bend"
  }
  fill(...colors[type])
  beginShape()
  noiseVertex(x + w * 0, y + h * 0)
  noiseVertex(x + w * 0.3, y + h * 0)
  noiseVertex(x + w * 0.3, y + h * 0.2 * t)
  noiseVertex(x + w * 0.7, y + h * 0.2 * t)
  noiseVertex(x + w * 0.7, y + h * 0)
  noiseVertex(x + w * 1, y + h * 0)
  noiseVertex(x + w * 1, y + h * 0.3)
  noiseVertex(x + w * (1 + 0.2 * r), y + h * 0.3)
  noiseVertex(x + w * (1 + 0.2 * r), y + h * 0.7)
  noiseVertex(x + w * 1, y + h * 0.7)
  noiseVertex(x + w * 1, y + h * 1)
  noiseVertex(x + w * 0.7, y + h * 1)
  noiseVertex(x + w * 0.7, y + h * (1 + 0.2 * b))
  noiseVertex(x + w * 0.3, y + h * (1 + 0.2 * b))
  noiseVertex(x + w * 0.3, y + h * 1)
  noiseVertex(x + w * 0, y + h * 1)
  noiseVertex(x + w * 0, y + h * 0.7)
  noiseVertex(x + w * 0.2 * l, y + h * 0.7)
  noiseVertex(x + w * 0.2 * l, y + h * 0.3)
  noiseVertex(x + w * 0, y + h * 0.3)
  noiseVertex(x + w * 0, y + h * 0)
  endShape(CLOSE)
  return type
}

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

Experiments tagged p5 (84/85)

Experiments tagged interactive (26/26) →|

Experiments on this site (84/85)