aztec_diamond/aztec_diamond.js

234 lines
6.1 KiB
JavaScript
Raw Permalink Normal View History

2020-12-28 14:37:46 +01:00
console.log("aztec_diamond")
const size = Math.min(innerWidth, innerHeight)
const canvas = document.querySelector("canvas")
canvas.width = size
canvas.height = size
const ctx = canvas.getContext("2d")
2020-12-28 17:23:29 +01:00
function tile_board(n, tiles) {
2020-12-28 14:37:46 +01:00
let board = []
// i tracks x, j tracks y. row major, so j is the outer loop.
// That's a bit unusual, but I think more intuitive than the
// alternative.
for (let j = 0; j < 2*n; j++) {
board[j] = []
for (let i = 0; i < 2*n; i++) {
board[j][i] = undefined
}
}
for (let ti = 0; ti < tiles.length; ti++) {
t = tiles[ti]
for (let part = 0; part < 2; part++) {
i = t[part].x + n - 0.5
j = t[part].y + n - 0.5
board[j][i] = ti
}
}
2020-12-28 17:23:29 +01:00
return board
}
function ad_1() {
console.log("ad_1")
const n = 1
let tiles;
if (Math.random() >= 0.5) {
// split vertically
tiles = [[{x: 0.5, y: 0.5}, {x: 0.5, y: -0.5}],
[{x: -0.5, y: -0.5}, {x: -0.5, y: 0.5}]]
} else {
tiles = [[{x: -0.5, y: 0.5}, {x: 0.5, y: 0.5}],
[{x: 0.5, y: -0.5}, {x: -0.5, y: -0.5}]]
}
const board = tile_board(n, tiles)
return {n, tiles, board, shrink: 0, phase: "shrink"}
2020-12-28 14:37:46 +01:00
}
function x2sx(x, scale, size) {
const sx = (x - 0.5) * scale + size/2
return sx
}
function y2sy(y, scale, size) {
const sy = size/2 - (y + 0.5) * scale
return sy
}
function color(t) {
if (t[0].x == t[1].x && t[0].y > t[1].y) {
return "#F008"
}
if (t[0].x == t[1].x && t[0].y < t[1].y) {
return "#FF08"
}
if (t[0].y == t[1].y && t[0].x < t[1].x) {
return "#0F08"
}
if (t[0].y == t[1].y && t[0].x > t[1].x) {
return "#00F8"
}
throw("impossible direction")
}
function draw(state) {
2020-12-28 17:23:29 +01:00
console.log("draw", state)
2020-12-28 14:37:46 +01:00
const n = state.n
2020-12-28 17:23:29 +01:00
const scale = size / (2 * (n + state.shrink/8))
ctx.fillStyle = "#FFF8"
ctx.fillRect(0, 0, size, size)
2020-12-28 14:37:46 +01:00
2020-12-28 17:23:29 +01:00
ctx.beginPath()
2020-12-28 14:37:46 +01:00
for (y = 0.5 - n; y < n; y += 1) {
for (x = 0.5 - n; x < n; x += 1) {
2020-12-28 17:23:29 +01:00
if (Math.abs(x) + Math.abs(y) <= n) {
sx = x2sx(x, scale, size)
sy = y2sy(y, scale, size)
console.log(x, y, sx, sy)
ctx.rect(sx, sy, scale, scale)
}
2020-12-28 14:37:46 +01:00
}
}
ctx.strokeStyle = "#CCCCCC"
ctx.stroke()
for (const t of state.tiles) {
console.log(t)
const sx0 = Math.min(x2sx(t[0].x, scale, size), x2sx(t[1].x, scale, size))
const sx1 = Math.max(x2sx(t[0].x, scale, size), x2sx(t[1].x, scale, size))
const sy0 = Math.min(y2sy(t[0].y, scale, size), y2sy(t[1].y, scale, size))
const sy1 = Math.max(y2sy(t[0].y, scale, size), y2sy(t[1].y, scale, size))
console.log(sx0, sx1, sy0, sy1)
ctx.beginPath()
ctx.rect(sx0, sy0, sx1 - sx0 + scale, sy1 - sy0 + scale)
ctx.fillStyle = color(t)
ctx.strokeStyle = "#000000"
ctx.fill()
ctx.stroke()
}
}
2020-12-28 17:23:29 +01:00
function expand(state) {
const n = state.n + 1
const board = tile_board(n, state.tiles)
state = {...state, n, board, shrink: 0}
return state
}
function sanitize(state) {
console.log("sanitize", state)
let tiles = []
const n = state.n
for (let ti = 0; ti < state.tiles.length; ti++) {
const t = state.tiles[ti];
console.log(ti, t)
const direction = {x: t[0].y - t[1].y, y: t[1].x - t[0].x}
const i = t[0].x + n - 0.5
const j = t[0].y + n - 0.5
const oi = i + direction.x
2020-12-28 22:58:20 +01:00
const oj = j + direction.y
2020-12-28 17:23:29 +01:00
const oti = state.board[oj][oi]
if (oti === undefined) {
// no neighbour
tiles.push(t)
} else {
const ot = state.tiles[oti]
const odirection = {x: ot[0].y - ot[1].y, y: ot[1].x - ot[0].x}
if (odirection.x == -direction.x && odirection.y == - direction.y) {
// Omit this tile. ot will also be omitted
} else {
tiles.push(t)
}
}
}
state = {...state, tiles, phase: "move", move: 0}
return state
}
function move_tiles(state) {
console.log("move_tiles", state)
let tiles = []
for (t of state.tiles) {
const direction = {x: t[0].y - t[1].y, y: t[1].x - t[0].x}
tiles.push([{x: t[0].x + direction.x / 8, y: t[0].y + direction.y / 8},
{x: t[1].x + direction.x / 8, y: t[1].y + direction.y / 8}, ])
}
state = {...state, tiles, move: state.move + 1}
return state
}
2020-12-28 22:58:49 +01:00
function assert(b) {
if (!b) {
throw("assertion failed")
}
}
function fill_gaps(state) {
n = state.n
// state.board is inconsistent after move, so we recompute it
board = tile_board(n, state.tiles)
tiles = [...state.tiles]
for (y = 0.5 - n; y < n; y += 1) {
for (x = 0.5 - n; x < n; x += 1) {
if (Math.abs(x) + Math.abs(y) <= n) {
const i = x + n - 0.5
const j = y + n - 0.5
if (board[j][i] === undefined) {
assert(board[j][i+1] == undefined)
assert(board[j+1][i] == undefined)
assert(board[j+1][i+1] == undefined)
if (Math.random() >= 0.5) {
// split vertically
tiles.push([{x: x+1, y: y+1}, {x: x+1, y: y}])
tiles.push([{x: x, y: y}, {x: x, y: y+1}])
} else {
tiles.push([{x: x, y: y+1}, {x: x+1, y: y+1}])
tiles.push([{x: x+1, y: y}, {x: x, y: y }])
}
// mark cells as in use. Don't care about tile numbers,
// we'll recompute anyway
board[j][i] = -1
board[j][i+1] = -1
board[j+1][i] = -1
board[j+1][i+1] = -1
}
}
}
}
board = tile_board(n, tiles)
state = {...state, tiles, board, phase: "shrink", shrink: 0}
return state
}
2020-12-28 17:23:29 +01:00
function step(state) {
draw(state)
if (state.phase == "shrink") {
if (state.shrink < 8) {
state = {...state, shrink: state.shrink + 1}
} else {
state = {...state, phase: "sanitize"}
}
} else if (state.phase == "sanitize") {
state = expand(state)
state = sanitize(state)
} else if (state.phase == "move") {
if (state.move < 8) {
state = move_tiles(state)
} else {
state = {...state, phase: "fill"}
}
} else if (state.phase = "fill") {
state = fill_gaps(state)
} else {
throw("impossible state " + state.phase)
}
if (state.n < 100) {
window.setTimeout(step, 100, state)
}
2020-12-28 17:23:29 +01:00
}
initial_state = ad_1()
window.setTimeout(step, 100, initial_state)