Skip to content

3D Maze

Press the left mouse button (or space) to move forward. Move the mouse turn the camera.

Try to reach the green-colored exit. The green line points the right direction.

A gray three-dimensional maze with a yellow cube trying to find its way to a green spot while turning red cubes on the way into purple

const size = 25
let grid
let finish
let gridModel
let speed = 0
let won = false
let font
let turning = false
const accel = 0.035
const speedDecay = 0.92
const uiScale = size / 2 * 1.1
const mouseDeadzone = 0.05

const G_UNEXPLORED = 0
const G_PATH = 1
const G_VISITED = 2
const G_QUEUED = 3
const G_WALL = 4
const G_FINISH = 5

function preload() {
  font = loadFont("/Raleway-VariableFont_wght.ttf")
}

function setup() {
  let c = createCanvas(600, 600, WEBGL);
  //smooth()
  textFont(font)
  createGrid()
  gridModel = buildGeometry(drawGrid)
  mouseX = width / 2
  mouseY = height / 2
}

function createGrid() {
  won = false
  grid = Array(10).fill().map(() => Array(10).fill().map(() => Array(10).fill(G_UNEXPLORED)))
  let stack = []
  stack.push([3, 4, 5])
  resetMatrix()
  translate(-3*size,-4*size,-5*size)
  storedMatrix = saveMatrix()
  let putFinish = false
  while (stack.length) {
    let [i, j, k, queue] = stack[stack.length - 1]
    grid[i][j][k] = G_PATH
    if (queue === undefined) {
      let nexts = []
      if (i > 0) nexts.push([i - 1, j, k])
      if (j > 0) nexts.push([i, j - 1, k])
      if (k > 0) nexts.push([i, j, k - 1])
      if (i < grid.length - 1) nexts.push([i + 1, j, k])
      if (j < grid[i].length - 1) nexts.push([i, j + 1, k])
      if (k < grid[i][j].length - 1) nexts.push([i, j, k + 1])
      queue = []
      for (let [i, j, k] of nexts) {
        if (grid[i][j][k] == G_QUEUED) {
          grid[i][j][k] = G_WALL
        }
        if (grid[i][j][k] == G_UNEXPLORED) {
          grid[i][j][k] = G_QUEUED
          queue.push([i, j, k])
        }
      }
      queue = shuffle(queue)
    }
    stack[stack.length - 1] = [i, j, k, queue]
    let found = false
    while (queue.length) {
      let [i, j, k] = queue.pop()
      if (grid[i][j][k] == G_QUEUED) {
        stack.push([i, j, k])
        found = true
        break;
      }
    }
    if (!found) {
      if (!putFinish) {
        putFinish = true
        grid[i][j][k] = G_FINISH
        finish = [i, j, k]
      }
      stack.pop()
    }
  }
}
function drawGrid() {
  for (let i = -1; i <= grid.length; i ++) {
    for (let j = -1; j <= grid[0].length; j ++) {
      for (let k = -1; k <= grid[0][0].length; k ++) {
        if (i < 0 || j < 0 || k < 0 || i >= grid.length || j >= grid[i].length || k >= grid[i][j].length || grid[i][j][k] == G_WALL) {
          push()
          translate(i * size, j * size, k * size)
          fill(100)
          box(size)
          pop()
        }
      }
    }
  }
}
function drawPath() {
  for (let i = 0; i < grid.length; i ++) {
    for (let j = 0; j < grid[0].length; j ++) {
      for (let k = 0; k < grid[0][0].length; k ++) {
        if (grid[i][j][k] == G_FINISH) {
          push()
          translate(i * size, j * size, k * size)
          fill(0, 255, 0)
          noStroke()
          box(size * 0.1)
          pop()
        } else if (grid[i][j][k] == G_PATH || grid[i][j][k] == G_VISITED) {
          push()
          translate(i * size, j * size, k * size)
          if (grid[i][j][k] == G_PATH) {
            fill(255, 0, 0)
          } else {
            fill(100, 0, 100)
          }
          noStroke()
          if (!keyIsDown(65)) {
            box(size * 0.03)
          } else {
            box(size * 0.03, size * 0.03, size)
            box(size, size * 0.03, size * 0.03)
            box(size * 0.03, size, size * 0.03)
          }
          pop()
        }
      }
    }
  }
}

function saveMatrix() {
  return Array.from(_renderer.uModelMatrix.mat4)
}

let storedMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]

function draw() {
  background(220);
  if (frameCount == 1) {
    camera(0, 0, size/2, 0, 0, 0, 0, 1, 0)
    perspective(1.0, width / height, 3, 800)
  }
  resetMatrix()
  let ry = mouseY / height - 0.5
  let rx = mouseX / width - 0.5
  let rd = mag(rx, ry)
  if (rd > mouseDeadzone && turning) {
    rotate(mag(ry, rx) * 0.1, [-ry, rx, 0])
  }
  if (mouseIsPressed || keyIsDown(32)) {
    turning = true
    speed += accel
  }
  translate(0, 0, speed)
  speed *= speedDecay
  applyMatrix(storedMatrix)
  storedMatrix = saveMatrix()
  
  ambientLight(255, 255, 255)
  directionalLight(255, 255, 255, 1, 0.1, 0.4)
  directionalLight(255, 255, 255, -1, -0.1, -0.4)
  
  let inverse = new p5.Matrix()
  inverse.invert(storedMatrix)
  let pos = inverse.multiplyVec4(0,0,0,1)
  push()
  translate(pos[0], pos[1], pos[2])
  noStroke()
  fill(140, 140, 0)
  box(1)
  pop()
  let [i, j, k] = pos.map(x => round(x/size))
  if (i >= 0 && j >= 0 && k >= 0 && i < grid.length && j < grid[i].length && k < grid[i][j].length) {
    if (grid[i][j][k] == G_WALL) {
      speed = -speed / speedDecay - accel
    } else if (grid[i][j][k] == G_PATH) {
      grid[i][j][k] = G_VISITED
    } else if (grid[i][j][k] == G_FINISH) {
      won = true
    }
  }
  if (!keyIsDown(65)) {
    model(gridModel)
  }
  drawPath()
  resetMatrix()
  push()
  clearDepth()
  strokeWeight(0.015)
  stroke(0)
  line(-mouseDeadzone * uiScale, 0, 0, mouseDeadzone * uiScale, 0, 0)
  line(0, -mouseDeadzone * uiScale, 0, 0, mouseDeadzone * uiScale, 0)
  line(0, 0, rx * uiScale, ry * uiScale)
  if (rd > mouseDeadzone) {
    push()
    strokeWeight(0.03)
    stroke(255, 0, 0)
    line(ry * uiScale / 2, -rx * uiScale / 2, -ry * uiScale / 2, rx * uiScale / 2)
    pop()
  }
  let mat = new p5.Matrix(storedMatrix)
  let pt = mat.multiplyVec4(i*size, j*size, k*size, 1)
  stroke(100, 0, 100)
  line(0, 0, 0, pt[0], pt[1], pt[2])
  let pt2 = mat.multiplyVec4(finish[0]*size, finish[1]*size, finish[2]*size, 1)
  pt2 = createVector(...pt2).normalize()
  stroke(0, 255, 0)
  line(0, 0, 0, pt2.x, pt2.y, pt2.z)
  if (won) {
    noStroke()
    fill(0, 0, 255)
    textAlign(CENTER, CENTER)
    textSize(uiScale * 0.1)
    text("You won!\nR to restart", 0, 0)
  }
  pop()
  if (won && keyIsDown(82)) {
    createGrid()
    gridModel = buildGeometry(drawGrid)
  }
}

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

Experiments tagged p5 (67/85)

Experiments tagged game (9/11)

Experiments on this site (67/85)