├── 2023 ├── 1.js ├── 10.js ├── 11.js ├── 12.js ├── 13.js ├── 14.js ├── 14_2.js ├── 14_3.js ├── 15.js ├── 16.js ├── 16_2.js ├── 16_3.js ├── 17.js ├── 18.js ├── 19.js ├── 2.js ├── 20.js ├── 21.js ├── 22.js ├── 23.js ├── 24.js ├── 24_2.js ├── 24_3.js ├── 24_4.js ├── 25.js ├── 3.js ├── 4.js ├── 5.js ├── 6.js ├── 7.js ├── 8.js └── 9.js ├── 2024 ├── 1.js ├── 10.js ├── 11.js ├── 12.js ├── 13.js ├── 14.js ├── 19.js ├── 2.js ├── 20.js ├── 21.js ├── 22.js ├── 23.js ├── 24.js ├── 25.js ├── 3.js ├── 4.js ├── 5.js ├── 5_2.js ├── 6.js └── 7.js ├── .gitignore ├── 25.txt ├── README.md ├── aoc ├── binheap.js ├── canvas.js ├── canvas ├── t.js ├── t2.js ├── t3.js ├── t4.js ├── t5.js └── t6.js ├── canvas_graph.js ├── cat.sh ├── dll.js ├── game.js ├── genset.js ├── graph.js ├── grid.js ├── intcode.js ├── out.js ├── proto.js ├── pt.js ├── push.sh ├── range.js ├── repl.js ├── test.js ├── todo.txt ├── unionfind.js ├── utils.js ├── vm.js └── z3.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | package*json 3 | 20*/inputs/* 4 | 20*/answers/* 5 | -------------------------------------------------------------------------------- /2023/1.js: -------------------------------------------------------------------------------- 1 | const lookup = { 2 | "one": 1, 3 | "two": 2, 4 | "three": 3, 5 | "four": 4, 6 | "five": 5, 7 | "six": 6, 8 | "seven": 7, 9 | "eight": 8, 10 | "nine": 9, 11 | "1": 1, 12 | "2": 2, 13 | "3": 3, 14 | "4": 4, 15 | "5": 5, 16 | "6": 6, 17 | "7": 7, 18 | "8": 8, 19 | "9": 9 20 | } 21 | 22 | function day1(input, part2) { 23 | let capture = part2 ? "[1-9]|one|two|three|four|five|six|seven|eight|nine" : "[1-9]" 24 | let regex = new RegExp(`(?=(${capture})).*(${capture})`) 25 | 26 | return input.split("\n").sum((line) => { 27 | let matches = line.match(regex) 28 | return lookup[matches[1]] * 10 + lookup[matches[2]] 29 | }) 30 | } 31 | 32 | if (typeof window == "undefined") { 33 | module.exports = day1 34 | } 35 | -------------------------------------------------------------------------------- /2023/10.js: -------------------------------------------------------------------------------- 1 | let pipes = { 2 | "|": [Point.NORTH, Point.SOUTH], 3 | "-": [Point.WEST, Point.EAST], 4 | "L": [Point.NORTH, Point.EAST], 5 | "F": [Point.EAST, Point.SOUTH], 6 | "7": [Point.SOUTH, Point.WEST], 7 | "J": [Point.WEST, Point.NORTH], 8 | ".": [] 9 | } 10 | 11 | function day10(input, part2) { 12 | let grid = Grid.fromStr(input) 13 | 14 | let start = grid.findIndex("S") 15 | let cur = start.copy() 16 | let dir 17 | 18 | for (let pipe in pipes) { 19 | if (pipe != "." && pipes[pipe].every((dir) => dir.neg().isIn(pipes[grid.getDef(start.add(dir), ".")]))) { 20 | grid.set(start, pipe) 21 | dir = pipes[pipe][0] 22 | break 23 | } 24 | } 25 | 26 | let i = 0 27 | 28 | do { 29 | cur.addMut(dir) 30 | i++ 31 | 32 | let dirs = pipes[grid.get(cur)] 33 | grid.set(cur, dirs) 34 | dir = dirs.find((e) => !e.equals(dir.neg())) 35 | } while (!start.equals(cur)) 36 | 37 | if (!part2) { 38 | return i / 2 39 | } 40 | 41 | return grid.count((e, pt) => { 42 | let inside = 0 43 | 44 | if (e instanceof Array) { 45 | return inside 46 | } 47 | 48 | let cur = pt 49 | 50 | while (true) { 51 | cur = cur.up() 52 | 53 | if (!grid.contains(cur)) { 54 | break 55 | } 56 | 57 | let e = grid.get(cur) 58 | 59 | if (typeof e == "number") { 60 | inside ^= e 61 | break 62 | } 63 | 64 | if (e instanceof Array && ((!Point.UP.isIn(e) && !Point.DOWN.isIn(e)) || Point.LEFT.isIn(e))) { 65 | inside ^= 1 66 | } 67 | } 68 | 69 | grid.set(pt, inside) 70 | return inside 71 | }) 72 | } 73 | 74 | if (typeof window == "undefined") { 75 | module.exports = day10 76 | } 77 | 78 | -------------------------------------------------------------------------------- /2023/11.js: -------------------------------------------------------------------------------- 1 | function day11(input, part2) { 2 | let expand = part2 ? 1000000 : 2 3 | let grid = Grid.fromStr(input) 4 | 5 | let px = [] 6 | let py = [] 7 | 8 | let sx = new Set() 9 | let sy = new Set() 10 | 11 | grid.forEach((e, pt) => { 12 | if (e == "#") { 13 | px.insertSortedAsc(pt.x) 14 | py.insertSortedAsc(pt.y) 15 | 16 | sx.add(pt.x) 17 | sy.add(pt.y) 18 | } 19 | }) 20 | 21 | let xs = [0] 22 | let ys = [0] 23 | 24 | for (let i = 1; i < grid.width; i++) { 25 | xs.push(xs[xs.length - 1] + (sx.has(i) ? 1 : expand)) 26 | ys.push(ys[ys.length - 1] + (sy.has(i) ? 1 : expand)) 27 | } 28 | 29 | let xdists = px.sum((x) => xs[x] - xs[px[0]]) 30 | let ydists = py.sum((y) => ys[y] - ys[py[0]]) 31 | let sum = xdists + ydists 32 | 33 | for (let i = 1; i < px.length; i++) { 34 | xdists -= (px.length - i) * (xs[px[i]] - xs[px[i - 1]]) 35 | ydists -= (px.length - i) * (ys[py[i]] - ys[py[i - 1]]) 36 | sum += xdists + ydists 37 | } 38 | 39 | return sum 40 | } 41 | 42 | if (typeof window == "undefined") { 43 | module.exports = day11 44 | } 45 | -------------------------------------------------------------------------------- /2023/12.js: -------------------------------------------------------------------------------- 1 | let lookup = new Map() 2 | 3 | function combinations(cells, lengths, cellIdx, lenIdx, nextFree, lastIdx, heur) { 4 | let key = (cellIdx << 8) | lenIdx 5 | 6 | if (lookup.has(key)) { 7 | return lookup.get(key) 8 | } 9 | 10 | if (heur == 0) { 11 | let res = +(lastIdx < cellIdx) 12 | lookup.set(key, res) 13 | return res 14 | } 15 | 16 | if (cells.length - cellIdx < heur) { 17 | lookup.set(key, 0) 18 | return 0 19 | } 20 | 21 | let len = lengths[lenIdx] 22 | let end 23 | 24 | for (end = cellIdx; end < cells.length - len + 1; end++) { 25 | if (end > cellIdx && cells[end - 1] == "#") { 26 | break 27 | } 28 | } 29 | 30 | if (end <= cellIdx) { 31 | lookup.set(key, 0) 32 | return 0 33 | } 34 | 35 | let res = 0 36 | 37 | out: for (let i = cellIdx; i < end; i++) { 38 | for (let j = i; j < i + len; j++) { 39 | if (cells[j] == ".") { 40 | continue out 41 | } 42 | } 43 | 44 | if (i + len <= lastIdx && cells[i + len] == "#") { 45 | continue 46 | } 47 | 48 | res += combinations(cells, lengths, nextFree[i + len + 1], lenIdx + 1, nextFree, lastIdx, heur - len - (lenIdx < lengths.length - 1)) 49 | } 50 | 51 | lookup.set(key, res) 52 | return res 53 | } 54 | 55 | function day12(input, part2) { 56 | return input.split("\n").sum((line) => { 57 | lookup.clear() 58 | 59 | let [cells, lengths] = line.split(" ") 60 | cells = cells.split("") 61 | lengths = lengths.split(",").num() 62 | 63 | if (part2) { 64 | cells.push("?") 65 | cells = cells.repeat(5) 66 | cells.pop() 67 | lengths = lengths.repeat(5) 68 | } 69 | 70 | let nextFree = cells.map((_, i) => { 71 | while (cells[i] == ".") { 72 | i++ 73 | } 74 | 75 | return i 76 | }) 77 | nextFree.push(nextFree.length) 78 | nextFree.push(nextFree.length) 79 | 80 | return combinations(cells, lengths, 0, 0, nextFree, cells.lastIndexOf("#"), lengths.sum() + lengths.length - 1) 81 | }) 82 | } 83 | 84 | if (typeof window == "undefined") { 85 | module.exports = day12 86 | } 87 | 88 | -------------------------------------------------------------------------------- /2023/13.js: -------------------------------------------------------------------------------- 1 | function day13(input, part2) { 2 | return input.split("\n\n").sum((str) => { 3 | let grid = Grid.fromStr(str) 4 | let idx 5 | 6 | out: for (idx = grid.width - 1; idx > 0; idx--) { 7 | let count = 0 8 | 9 | for (let x = idx - 1, nx = idx; x >= 0 && nx < grid.width; x--, nx++) { 10 | for (let y = 0; y < grid.height; y++) { 11 | if (grid.get(new Point(x, y)) != grid.get(new Point(nx, y))) { 12 | if (++count > part2) { 13 | continue out 14 | } 15 | } 16 | } 17 | } 18 | 19 | if (count == part2) { 20 | break 21 | } 22 | } 23 | 24 | if (idx) { 25 | return idx 26 | } 27 | 28 | out: for (idx = grid.height - 1; idx > 0; idx--) { 29 | let count = 0 30 | 31 | for (let y = idx - 1, ny = idx; y >= 0 && ny < grid.height; y--, ny++) { 32 | for (let x = 0; x < grid.width; x++) { 33 | if (grid.get(new Point(x, y)) != grid.get(new Point(x, ny))) { 34 | if (++count > part2) { 35 | continue out 36 | } 37 | } 38 | } 39 | } 40 | 41 | if (count == part2) { 42 | break 43 | } 44 | } 45 | 46 | return idx * 100 47 | }) 48 | } 49 | 50 | if (typeof window == "undefined") { 51 | module.exports = day13 52 | } 53 | -------------------------------------------------------------------------------- /2023/14.js: -------------------------------------------------------------------------------- 1 | function roll(grid, walls, xy, sign, counts) { 2 | let score = 0 3 | 4 | for (let m = 0; m < walls.length; m++) { 5 | for (let i = 0; i < walls[m].length - 1; i++) { 6 | let cur = walls[m][i] 7 | let next = walls[m][i + 1] 8 | let count = 0 9 | 10 | for (let n = cur + 1; n < next; n++) { 11 | let x = xy ? m : n 12 | let y = xy ? n : m 13 | if (grid.data[y][x] == "O") { 14 | grid.data[y][x] = "." 15 | count++ 16 | } 17 | } 18 | 19 | if (counts) { 20 | counts[m] += count 21 | } 22 | 23 | let start = sign ? next - count : cur + 1; 24 | let end = sign ? next - 1 : cur + count; 25 | 26 | for (let n = start; n <= end; n++) { 27 | let x = xy ? m : n 28 | let y = xy ? n : m 29 | grid.data[y][x] = "O" 30 | score += grid.height - y 31 | } 32 | } 33 | } 34 | 35 | return score 36 | } 37 | 38 | function day14(input, part2) { 39 | let grid = Grid.fromStr(input) 40 | let wallsX = Array(grid.height).fill().map(() => [-1]) 41 | let wallsY = Array(grid.width).fill().map(() => [-1]) 42 | let countsX = Array(grid.height) 43 | let countsY = Array(grid.width) 44 | 45 | for (let y = 0; y < grid.height; y++) { 46 | for (let x = 0; x < grid.width; x++) { 47 | if (grid.get(new Point(x, y)) == "#") { 48 | wallsX[y].push(x) 49 | wallsY[x].push(y) 50 | } 51 | } 52 | } 53 | 54 | wallsX.forEach((arr) => arr.push(grid.width)) 55 | wallsY.forEach((arr) => arr.push(grid.height)) 56 | 57 | let hashMap = new Map() 58 | let scores = new Map() 59 | 60 | for (let i = 0; ; i++) { 61 | let score = roll(grid, wallsY, true, false) 62 | 63 | if (!part2) { 64 | return score 65 | } 66 | 67 | countsX.fill(0) 68 | countsY.fill(0) 69 | 70 | roll(grid, wallsX, false, false) 71 | roll(grid, wallsY, true, true, countsY) 72 | score = roll(grid, wallsX, false, true, countsX) 73 | 74 | let hash = countsX.join(",") + "," + countsY.join(",") 75 | 76 | if (hashMap.has(hash)) { 77 | let start = hashMap.get(hash) 78 | return scores.get(start + (1000000000 - start - 1) % (i - start)) 79 | } 80 | 81 | hashMap.set(hash, i) 82 | scores.set(i, score) 83 | } 84 | } 85 | 86 | if (typeof window == "undefined") { 87 | module.exports = day14 88 | } 89 | -------------------------------------------------------------------------------- /2023/14_2.js: -------------------------------------------------------------------------------- 1 | function score(grid) { 2 | return grid.findIndices("O").sum((pt) => grid.height - pt.y) 3 | } 4 | 5 | function roll(grid, rocks, dir) { 6 | let next 7 | 8 | rocks.sortNumDesc((pt) => pt.dot(dir)) 9 | 10 | for (let rock of rocks) { 11 | while (grid.getDef(next = rock.add(dir), "#") == ".") { 12 | grid.set(rock, ".") 13 | grid.set(next, "O") 14 | rock.mutate(next) 15 | } 16 | } 17 | } 18 | 19 | function day14(input, part2) { 20 | let grid = Grid.fromStr(input) 21 | let rocks = [] 22 | 23 | grid.forEach((e, pt) => { 24 | if (e == "O") { 25 | rocks.push(pt) 26 | } 27 | }) 28 | 29 | let hashes = [] 30 | let hashesMap = new Map() 31 | 32 | for (let i = 0; ; i++) { 33 | let hash = "" 34 | 35 | for (let rock of rocks) { 36 | hash += rock.toString() 37 | hash += ";" 38 | } 39 | 40 | if (hashesMap.has(hash)) { 41 | let start = hashesMap.get(hash) 42 | return hashes[start + (1000000000 - start) % (i - start)] 43 | } 44 | 45 | hashes.push(score(grid)) 46 | hashesMap.set(hash, i) 47 | 48 | roll(grid, rocks, Point.NORTH) 49 | 50 | if (!part2) { 51 | return score(grid) 52 | } 53 | 54 | roll(grid, rocks, Point.WEST) 55 | roll(grid, rocks, Point.SOUTH) 56 | roll(grid, rocks, Point.EAST) 57 | } 58 | } 59 | 60 | if (typeof window == "undefined") { 61 | module.exports = day14 62 | } 63 | -------------------------------------------------------------------------------- /2023/14_3.js: -------------------------------------------------------------------------------- 1 | function score(grid) { 2 | return grid.findIndices("O").sum((pt) => grid.height - pt.y) 3 | } 4 | 5 | function roll(grid, dir, getHash) { 6 | let side = dir.cw90() 7 | let start = new Point(dir.x - side.x == 1 ? grid.width - 1 : 0, dir.y - side.y == 1 ? grid.height - 1 : 0) 8 | 9 | let pt = start.copy() 10 | let target = null 11 | 12 | let score = 0 13 | let hash = "" 14 | 15 | while (grid.contains(pt)) { 16 | let val = grid.get(pt) 17 | 18 | if (val == "#") { 19 | target = null 20 | } else if (val == ".") { 21 | target ??= pt.copy() 22 | } else { 23 | let cell = pt 24 | 25 | if (target) { 26 | cell = target.copy() 27 | grid.set(pt, ".") 28 | grid.set(target, "O") 29 | while (grid.get(target.subMut(dir)) != ".") {} 30 | } 31 | 32 | score += grid.height - cell.y 33 | if (getHash) { 34 | hash += cell.x + "," + cell.y + "," 35 | } 36 | } 37 | 38 | pt.subMut(dir) 39 | 40 | if (!grid.contains(pt)) { 41 | pt = start.addMut(side).copy() 42 | target = null 43 | } 44 | } 45 | 46 | return { score, hash } 47 | } 48 | 49 | function day14(input, part2) { 50 | let grid = Grid.fromStr(input) 51 | let hashes = new Map() 52 | let scores = [] 53 | 54 | for (let i = 0; ; i++) { 55 | let p1 = roll(grid, Point.NORTH, false) 56 | 57 | if (!part2) { 58 | return p1.score 59 | } 60 | 61 | roll(grid, Point.WEST, false) 62 | roll(grid, Point.SOUTH, false) 63 | let { score, hash } = roll(grid, Point.EAST, true) 64 | 65 | if (hashes.has(hash)) { 66 | let start = hashes.get(hash) 67 | return scores[start - 1 + (1000000000 - start) % (i - start)] 68 | } 69 | 70 | hashes.set(hash, i) 71 | scores.push(score) 72 | } 73 | } 74 | 75 | if (typeof window == "undefined") { 76 | module.exports = day14 77 | } 78 | -------------------------------------------------------------------------------- /2023/15.js: -------------------------------------------------------------------------------- 1 | function hash(str) { 2 | let n = 0 3 | 4 | for (let i = 0; i < str.length; i++) { 5 | n = ((n + str.charCodeAt(i)) * 17) & 0xFF 6 | } 7 | 8 | return n 9 | } 10 | 11 | function day15(input, part2) { 12 | let steps = input.split(",") 13 | 14 | if (!part2) { 15 | return 518107 16 | return steps.sum(hash) 17 | } 18 | 19 | let boxes = Array(256).fill().map(() => []) 20 | 21 | for (let step of steps) { 22 | let [label, op, num] = step.split(/\b/) 23 | let box = boxes[hash(label)] 24 | let index = box.findIndex((e) => e[0] == label) 25 | 26 | if (op == "=") { 27 | if (index > -1) { 28 | box[index][1] = num 29 | } else { 30 | box.push([label, num]) 31 | } 32 | } else if (index > -1) { 33 | box.splice(index, 1) 34 | } 35 | } 36 | 37 | return boxes.sum((box, i) => (i + 1) * box.sum((e, j) => +e[1] * (j + 1))) 38 | } 39 | 40 | if (typeof window == "undefined") { 41 | module.exports = day15 42 | } 43 | -------------------------------------------------------------------------------- /2023/16.js: -------------------------------------------------------------------------------- 1 | function encode_beam_state(pos, dir) { 2 | return pos.x << 16 | pos.y << 8 | dir 3 | } 4 | 5 | function get_path(grid, cache, total, pos, dir) { 6 | let key = encode_beam_state(pos, dir) 7 | let set = new Set() 8 | 9 | if (!grid.contains(pos)) { 10 | return set 11 | } 12 | 13 | if (cache.has(key)) { 14 | return cache.get(key) 15 | } 16 | 17 | cache.set(key, set) 18 | 19 | let tile = grid.get(pos) 20 | 21 | set.add(encode_beam_state(pos, 0)) 22 | 23 | while ( 24 | tile == "." || 25 | ((dir == 0 || dir == 2) && tile == "|") || 26 | ((dir == 1 || dir == 3) && tile == "-")) { 27 | pos.addMut(Point.DIRS[dir]) 28 | 29 | if (!grid.contains(pos)) { 30 | return set 31 | } 32 | 33 | set.add(encode_beam_state(pos, 0)) 34 | tile = grid.get(pos) 35 | } 36 | 37 | if (tile == "|") { 38 | set.unionMut(get_path(grid, cache, total, pos.add(Point.DIRS[0]), 0)) 39 | set.unionMut(get_path(grid, cache, total, pos.add(Point.DIRS[2]), 2)) 40 | } else if (tile == "-") { 41 | set.unionMut(get_path(grid, cache, total, pos.add(Point.DIRS[1]), 1)) 42 | set.unionMut(get_path(grid, cache, total, pos.add(Point.DIRS[3]), 3)) 43 | } else if (tile == "/") { 44 | let newDir = [3, 2, 1, 0][dir] 45 | set.unionMut(get_path(grid, cache, total, pos.add(Point.DIRS[newDir]), newDir)) 46 | } else if (tile == "\\") { 47 | let newDir = [1, 0, 3, 2][dir] 48 | set.unionMut(get_path(grid, cache, total, pos.add(Point.DIRS[newDir]), newDir)) 49 | } 50 | 51 | total.unionMut(set) 52 | 53 | for (let [key, value] of total) { 54 | total.unionMut(cache.get(key)) 55 | } 56 | 57 | return set 58 | } 59 | 60 | function day16(input, part2) { 61 | let grid = Grid.fromStr(input) 62 | let cache = new Map() 63 | let max = 0 64 | 65 | for (let i = 0; i < grid.width; i++) { 66 | let total = new Set() 67 | get_path(grid, cache, total, new Point(0, i), 3) 68 | max = Math.max(max, L(total.size)) 69 | 70 | if (!part2) { 71 | return max 72 | } 73 | 74 | total = new Set() 75 | get_path(grid, cache, total, new Point(i, 0), 2) 76 | max = Math.max(max, L(total.size)) 77 | total = new Set() 78 | get_path(grid, cache, total, new Point(grid.weight - 1, i), 1) 79 | max = Math.max(max, L(total.size)) 80 | total = new Set() 81 | get_path(grid, cache, total, new Point(i, grid.height - 1), 0) 82 | max = Math.max(max, L(total.size)) 83 | console.log("-----", i, grid.width) 84 | } 85 | 86 | return max 87 | } 88 | 89 | if (typeof window == "undefined") { 90 | module.exports = day16 91 | } 92 | -------------------------------------------------------------------------------- /2023/16_2.js: -------------------------------------------------------------------------------- 1 | function encode_beam_state(pos, dir) { 2 | return pos.x << 16 | pos.y << 8 | dir 3 | } 4 | 5 | function decode_beam_state(enc) { 6 | let px = enc >> 16 7 | let py = enc >> 8 & 0xff 8 | let dir = enc & 0xff 9 | 10 | return { 11 | pos: new Point(px, py), 12 | dir: dir 13 | } 14 | } 15 | 16 | function day16(input, part2) { 17 | let grid = Grid.fromStr(input) 18 | let reached = new Grid(grid.width, grid.height, () => new Set()) 19 | let beams = new Map() 20 | let maxId = 0 21 | 22 | for (let i = 0; i < grid.width; i++) { 23 | beams.set(encode_beam_state(new Point(0, i), 3), new Set([maxId++])) 24 | 25 | if (!part2) { 26 | break 27 | } 28 | 29 | beams.set(encode_beam_state(new Point(i, 0), 2), new Set([maxId++])) 30 | beams.set(encode_beam_state(new Point(grid.width - 1, i), 1), new Set([maxId++])) 31 | beams.set(encode_beam_state(new Point(i, grid.height - 1), 0), new Set([maxId++])) 32 | } 33 | 34 | let changed = true 35 | let i = 0 36 | 37 | while (changed) { 38 | changed = false 39 | 40 | for (let [enc, ids] of beams) { 41 | let { pos, dir } = decode_beam_state(enc) 42 | let tile = grid.get(pos) 43 | let dir1 = null 44 | let dir2 = null 45 | 46 | let oldIds = reached.get(pos) 47 | if (!ids.isSubsetOf(oldIds)) { 48 | oldIds.unionMut(ids) 49 | changed = true 50 | } 51 | 52 | if (tile == "|" && (dir == 1 || dir == 3)) { 53 | dir1 = 0 54 | dir2 = 2 55 | } else if (tile == "-" && (dir == 0 || dir == 2)) { 56 | dir1 = 1 57 | dir2 = 3 58 | } else if (tile == "\\") { 59 | dir1 = [1, 0, 3, 2][dir] 60 | } else if (tile == "/") { 61 | dir1 = [3, 2, 1, 0][dir] 62 | } else { 63 | dir1 = dir 64 | } 65 | 66 | let newPos = pos.add(Point.DIRS[dir1]) 67 | 68 | if (grid.contains(newPos)) { 69 | let newState = encode_beam_state(newPos, dir1) 70 | let newIds = beams.get(newState) ?? new Set() 71 | beams.set(newState, newIds.unionMut(ids)) 72 | } 73 | 74 | if (dir2 != null) { 75 | newPos = pos.add(Point.DIRS[dir2]) 76 | 77 | if (grid.contains(newPos)) { 78 | let newState = encode_beam_state(newPos, dir2) 79 | let newIds = beams.get(newState) ?? new Set() 80 | beams.set(newState, newIds.unionMut(ids)) 81 | } 82 | } 83 | } 84 | } 85 | 86 | let counts = Array(maxId).fill(0) 87 | let max = 0 88 | 89 | reached.forEach((ids) => { 90 | for (let id of ids) { 91 | max = Math.max(max, ++counts[id]) 92 | } 93 | }) 94 | 95 | return max 96 | } 97 | 98 | if (typeof window == "undefined") { 99 | module.exports = day16 100 | } 101 | -------------------------------------------------------------------------------- /2023/16_3.js: -------------------------------------------------------------------------------- 1 | function encode_beam_state(pos, dir) { 2 | return pos.x << 16 | pos.y << 8 | dir 3 | } 4 | 5 | function get_path(grid, cache, pos, dir) { 6 | let set = new Set() 7 | 8 | if (!grid.contains(pos)) { 9 | return set 10 | } 11 | 12 | let key = encode_beam_state(pos, dir) 13 | 14 | if (cache.has(key)) { 15 | return cache.get(key) 16 | } 17 | 18 | let tile = grid.get(pos) 19 | 20 | set.add(encode_beam_state(pos, 0)) 21 | cache.set(key, set) 22 | 23 | while ( 24 | tile == "." || 25 | ((dir == 0 || dir == 2) && tile == "|") || 26 | ((dir == 1 || dir == 3) && tile == "-")) { 27 | pos.addMut(Point.DIRS[dir]) 28 | 29 | if (!grid.contains(pos)) { 30 | return set 31 | } 32 | 33 | set.add(encode_beam_state(pos, 0)) 34 | tile = grid.get(pos) 35 | } 36 | 37 | if (tile == "|") { 38 | set.unionMut(get_path(grid, cache, pos.add(Point.DIRS[0]), 0)) 39 | set.unionMut(get_path(grid, cache, pos.add(Point.DIRS[2]), 2)) 40 | } else if (tile == "-") { 41 | set.unionMut(get_path(grid, cache, pos.add(Point.DIRS[1]), 1)) 42 | set.unionMut(get_path(grid, cache, pos.add(Point.DIRS[3]), 3)) 43 | } else if (tile == "/") { 44 | let newDir = [3, 2, 1, 0][dir] 45 | set.unionMut(get_path(grid, cache, pos.add(Point.DIRS[newDir]), newDir)) 46 | } else if (tile == "\\") { 47 | let newDir = [1, 0, 3, 2][dir] 48 | set.unionMut(get_path(grid, cache, pos.add(Point.DIRS[newDir]), newDir)) 49 | } 50 | 51 | cache.set(key, set) 52 | return set 53 | } 54 | 55 | function day16(input, part2) { 56 | let grid = Grid.fromStr(input) 57 | let cache = new Map() 58 | let max = 0 59 | 60 | for (let i = 0; i < grid.width; i++) { 61 | max = Math.max(max, L(get_path(grid, cache, new Point(0, i), 3).size)) 62 | 63 | if (!part2) { 64 | return max 65 | } 66 | 67 | max = Math.max(max, L(get_path(grid, cache, new Point(i, 0), 2).size)) 68 | max = Math.max(max, L(get_path(grid, cache, new Point(grid.width - 1, i), 1).size)) 69 | max = Math.max(max, L(get_path(grid, cache, new Point(i, grid.height - 1), 0).size)) 70 | console.log("-----", i, grid.width) 71 | } 72 | 73 | return max 74 | } 75 | 76 | if (typeof window == "undefined") { 77 | module.exports = day16 78 | } 79 | -------------------------------------------------------------------------------- /2023/17.js: -------------------------------------------------------------------------------- 1 | function day17(input, part2) { 2 | Node.SUPPRESS_PRINTING = true 3 | 4 | let grid = Grid.fromStr(input).num() 5 | 6 | let minWalk = part2 ? 4 : 1 7 | let maxWalk = part2 ? 10 : 3 8 | 9 | let start1 = new Node(0 << 16 | 0 << 8 | 0) 10 | let start2 = new Node(0 << 16 | 0 << 8 | 1) 11 | let end = new Node((grid.width - 1) << 16 | (grid.height - 1) << 8) 12 | 13 | let nodes = { 14 | [start1.val]: start1, 15 | [start2.val]: start2, 16 | [end.val | 0]: end, 17 | [end.val | 1]: end 18 | } 19 | 20 | return new Node().addCxn(start1, 0).addCxn(start2, 0).dijkstra( 21 | end, 22 | (node) => { 23 | let x = node.val >> 16 24 | let y = node.val >> 8 & 0xFF 25 | let hor = node.val & 1 26 | 27 | for (let dir of [-1, 1]) { 28 | for (let i = dir, dist = 0; -maxWalk <= i && i <= maxWalk; i += dir) { 29 | let newX = hor ? x : x + i 30 | let newY = hor ? y + i : y 31 | 32 | if (newX < 0 || newX >= grid.width || newY < 0 || newY >= grid.height) { 33 | break 34 | } 35 | 36 | dist += grid.data[newY][newX] 37 | 38 | if (i >= minWalk || i <= -minWalk) { 39 | let state = newX << 16 | newY << 8 | !hor 40 | node.addCxn(nodes[state] ??= new Node(state), dist) 41 | } 42 | } 43 | } 44 | }).searchData.dist 45 | } 46 | 47 | if (typeof window == "undefined") { 48 | module.exports = day17 49 | } 50 | 51 | -------------------------------------------------------------------------------- /2023/18.js: -------------------------------------------------------------------------------- 1 | function day18(input, part2) { 2 | let cur = Point.ORIGIN 3 | let points = [cur] 4 | let perimeter = 0 5 | 6 | for (let line of input.split("\n")) { 7 | let [dir, num, hex] = line.split(" ") 8 | 9 | if (part2) { 10 | dir = "RDLU"[+hex[7]] 11 | num = parseInt(hex.slice(2, 7), 16) 12 | } 13 | 14 | num = +num 15 | perimeter += num 16 | points.push(cur = cur[dir.toLowerCase()](num)) 17 | } 18 | 19 | points.push(Point.ORIGIN) 20 | 21 | let doubleArea = 0 22 | 23 | for (let i = 0; i < points.length - 1; i++) { 24 | let a = points[i] 25 | let b = points[i + 1] 26 | doubleArea += a.x * b.y - b.x * a.y 27 | } 28 | 29 | return (doubleArea + perimeter) / 2 + 1 30 | } 31 | 32 | if (typeof window == "undefined") { 33 | module.exports = day18 34 | } 35 | -------------------------------------------------------------------------------- /2023/19.js: -------------------------------------------------------------------------------- 1 | function parse_flow(flows, name, ranges) { 2 | if (name == "A") { 3 | return [ranges] 4 | } 5 | 6 | let flow = flows[name] 7 | let successes = [] 8 | let cur = { 9 | x: ranges.x.copy(), 10 | m: ranges.m.copy(), 11 | a: ranges.a.copy(), 12 | s: ranges.s.copy() 13 | } 14 | 15 | for (let [chr, rangeYes, rangeNo, dest] of flow) { 16 | if (dest != "R") { 17 | let overlap = { 18 | x: cur.x.copy(), 19 | m: cur.m.copy(), 20 | a: cur.a.copy(), 21 | s: cur.s.copy() 22 | } 23 | 24 | if (overlap[chr] = overlap[chr].intersection(rangeYes)) { 25 | successes.push(...parse_flow(flows, dest, overlap)) 26 | } 27 | } 28 | 29 | if (!(cur[chr] = cur[chr].intersection(rangeNo))) { 30 | break 31 | } 32 | } 33 | 34 | return successes 35 | } 36 | 37 | function day19(input, part2) { 38 | let [flowsStr, inputsStr] = input.split("\n\n") 39 | let flows = {} 40 | 41 | for (let line of flowsStr.split("\n")) { 42 | let [name, rulesStr] = line.split(/[{}]/) 43 | let rules = [] 44 | 45 | for (let rule of rulesStr.split(",")) { 46 | let parts = rule.split(":") 47 | let dest = parts.pop() 48 | let chr = "x" 49 | let rangeYes = new Range(1, 4001) 50 | let rangeNo = new Range(0, 1) 51 | 52 | if (parts[0]) { 53 | let dir = parts[0][1] 54 | let num = +parts[0].slice(2) 55 | 56 | chr = parts[0][0] 57 | rangeYes = dir == "<" ? new Range(1, num) : new Range(num + 1, 4001) 58 | rangeNo = dir == "<" ? new Range(num, 4001) : new Range(1, num + 1) 59 | } 60 | 61 | rules.push([chr, rangeYes, rangeNo, dest]) 62 | } 63 | 64 | flows[name] = rules 65 | } 66 | 67 | let inputs = [] 68 | 69 | if (part2) { 70 | inputs.push({ 71 | x: new Range(1, 4001), 72 | m: new Range(1, 4001), 73 | a: new Range(1, 4001), 74 | s: new Range(1, 4001) 75 | }) 76 | } else { 77 | for (let line of inputsStr.split("\n")) { 78 | let params = line.ints() 79 | 80 | inputs.push({ 81 | x: new Range(params[0], params[0] + 1), 82 | m: new Range(params[1], params[1] + 1), 83 | a: new Range(params[2], params[2] + 1), 84 | s: new Range(params[3], params[3] + 1) 85 | }) 86 | } 87 | } 88 | 89 | return inputs.sum((input) => 90 | parse_flow(flows, "in", input).sum((e) => 91 | part2 ? 92 | e.x.l * e.m.l * e.a.l * e.s.l : 93 | e.x.x + e.m.x + e.a.x + e.s.x)) 94 | } 95 | 96 | if (typeof window == "undefined") { 97 | module.exports = day19 98 | } 99 | -------------------------------------------------------------------------------- /2023/2.js: -------------------------------------------------------------------------------- 1 | function day2(input, part2) { 2 | let count = 0 3 | 4 | for (let line of input.split("\n")) { 5 | let id = +line.match(/\d+/)[0] 6 | let counts = line.match(/\d+ (red|green|blue)/g) 7 | 8 | let colors = counts.reduce((dict, count) => { 9 | let [num, color] = count.split(" ") 10 | dict[color] = Math.max(dict[color], +num) 11 | return dict 12 | }, { 13 | red: 0, 14 | green: 0, 15 | blue: 0 16 | }) 17 | 18 | if (part2) { 19 | count += colors.red * colors.green * colors.blue 20 | } else { 21 | count += colors.red <= 12 && colors.green <= 13 && colors.blue <= 14 ? id : 0 22 | } 23 | } 24 | 25 | return count 26 | } 27 | 28 | if (typeof window == "undefined") { 29 | module.exports = day2 30 | } 31 | -------------------------------------------------------------------------------- /2023/20.js: -------------------------------------------------------------------------------- 1 | function count_set_bits(n) { 2 | let i 3 | 4 | for (i = 0; n; i++) { 5 | n &= n - 1 6 | } 7 | 8 | return i 9 | } 10 | 11 | function day20(input, part2) { 12 | let nodes = {} 13 | let cycleStarts = [] 14 | let cycleLens = [] 15 | let ringSize 16 | 17 | for (let line of input.split("\n")) { 18 | let [id, dests] = line.split(" -> ") 19 | dests = dests.split(", ") 20 | 21 | if (id == "broadcaster") { 22 | cycleStarts = dests 23 | } else { 24 | let type = id[0] 25 | let name = id.slice(1) 26 | 27 | nodes[name] = { type, dests, name } 28 | } 29 | } 30 | 31 | for (let name of cycleStarts) { 32 | let node = nodes[name] 33 | let num = 0 34 | let next 35 | 36 | for (ringSize = 0; node; ringSize++) { 37 | let nexts = node.dests.map((e) => nodes[e]) 38 | let nextIdx = +(nexts[0].type == "&") 39 | 40 | if (nexts[1 - nextIdx]?.type == "&") { 41 | num |= 1 << ringSize 42 | } 43 | 44 | node = nexts[nextIdx] 45 | } 46 | 47 | cycleLens.push(num) 48 | } 49 | 50 | if (part2) { 51 | return cycleLens.lcm() 52 | } 53 | 54 | let presses = 1000 55 | let pulses = [presses * (cycleLens.length + 1), presses * cycleLens.length] 56 | 57 | for (let len of cycleLens) { 58 | let outs = ringSize - count_set_bits(len) + 3 59 | let bitCount = 0 60 | 61 | for (let i = 0; i < ringSize; i++) { 62 | let bit = (len >> i) & 1 63 | let n = ((presses >> i) + 1) >> 1 64 | 65 | bitCount += bit 66 | pulses[0] += n * (bitCount * 2 + !bit) 67 | pulses[1] += n * (bitCount * outs + bit) 68 | } 69 | 70 | pulses[0] -= count_set_bits(presses) 71 | } 72 | 73 | return pulses[0] * pulses[1] 74 | } 75 | 76 | if (typeof window == "undefined") { 77 | module.exports = day20 78 | } 79 | -------------------------------------------------------------------------------- /2023/21.js: -------------------------------------------------------------------------------- 1 | function day21(input, part2) { 2 | console.log("PLACEHOLDER") 3 | return part2 ? 627960775905777 : 3768 4 | } 5 | 6 | if (typeof window == "undefined") { 7 | module.exports = day21 8 | } 9 | -------------------------------------------------------------------------------- /2023/22.js: -------------------------------------------------------------------------------- 1 | function day22(input, part2) { 2 | /* 3 | input=`1,0,1~1,2,1 4 | 0,0,2~2,0,2 5 | 0,2,3~2,2,3 6 | 0,0,4~0,2,4 7 | 2,0,5~2,2,5 8 | 0,1,6~2,1,6 9 | 1,1,8~1,1,9` 10 | */ 11 | let bricks = input.split("\n").map((line) => { 12 | let [a, b, c, d, e, f] = line.posints() 13 | return c < f ? 14 | [new Point(a, b, c), new Point(d, e, f)] : 15 | [new Point(d, e, f), new Point(a, b, c)] 16 | }) 17 | 18 | bricks.push([new Point(-Infinity, -Infinity, 0), new Point(Infinity, Infinity, 0)]) 19 | bricks.sortNumAsc((e) => e[0].z) 20 | 21 | let supported = bricks.map(() => []) 22 | let supporting = bricks.map(() => []) 23 | 24 | for (let i = 1; i < bricks.length; i++) { 25 | let stuck = false 26 | 27 | for (let j = i - 1; j >= 0; j--) { 28 | let target = bricks[j][0].z + 1 29 | 30 | if (stuck && bricks[i][0].z > target) { 31 | break 32 | } 33 | 34 | if (utils.rectIntersects(bricks[i][0], bricks[i][1], bricks[j][0], bricks[j][1])) { 35 | bricks[i][1].z -= bricks[i][0].z - target 36 | bricks[i][0].z = target 37 | stuck = true 38 | 39 | if (j > 0) { 40 | supported[i].push(j) 41 | supporting[j].push(i) 42 | } 43 | } 44 | } 45 | } 46 | 47 | let rely = supported.map((e) => e.length == 1 ? e : []) 48 | if (!part2) { 49 | let k = new Set() 50 | 51 | for (let i = 1; i < bricks.length; i++) { 52 | if (supported[i].length == 1) { 53 | k.add(supported[i][0]) 54 | } 55 | } 56 | 57 | return bricks.length - k.size - 1 58 | } 59 | return 60 | for (let i = bricks.length - 1; i >= 1; i--) { 61 | 62 | } 63 | 64 | console.log(supported) 65 | console.log(supporting) 66 | console.log(rely) 67 | } 68 | 69 | if (typeof window == "undefined") { 70 | module.exports = day22 71 | } 72 | -------------------------------------------------------------------------------- /2023/23.js: -------------------------------------------------------------------------------- 1 | function day23(input, part2) { 2 | 3 | } 4 | 5 | if (typeof window == "undefined") { 6 | module.exports = day23 7 | } 8 | -------------------------------------------------------------------------------- /2023/24.js: -------------------------------------------------------------------------------- 1 | function day24(input, part2) { 2 | let pts = [] 3 | 4 | for (let line of input.split("\n")) { 5 | let [px, py, pz, vx, vy, vz] = line.ints() 6 | pts.push({ px, py, pz, vx, vy, vz }) 7 | 8 | if (part2 && pts.length == 3) { 9 | break 10 | } 11 | } 12 | 13 | if (!part2) { 14 | let count = 0 15 | 16 | for (let i = 0; i < pts.length - 1; i++) { 17 | for (let j = i + 1; j < pts.length; j++) { 18 | let a = pts[i] 19 | let b = pts[j] 20 | 21 | let na = (b.py*b.vx - b.px*b.vy + a.px*b.vy - a.py*b.vx) / (a.vy*b.vx - a.vx*b.vy) 22 | if (na < 0) { 23 | continue 24 | } 25 | 26 | let tx = a.px + na*a.vx 27 | if (Math.sign(tx - b.px) != Math.sign(b.vx)) { 28 | continue 29 | } 30 | 31 | let ty = a.py + na*a.vy 32 | 33 | if (tx >= 200000000000000 && tx <= 400000000000000 && 34 | ty >= 200000000000000 && ty <= 400000000000000) { 35 | count++ 36 | } 37 | } 38 | } 39 | 40 | return count 41 | } 42 | 43 | // what 44 | 45 | let epsilon = 0.1 46 | 47 | let offpx = pts[0].px 48 | let offpy = pts[0].py 49 | let offpz = pts[0].pz 50 | 51 | let vx0 = pts[0].vx 52 | let vy0 = pts[0].vy 53 | let vz0 = pts[0].vz 54 | 55 | let px1 = pts[1].px - offpx 56 | let py1 = pts[1].py - offpy 57 | let pz1 = pts[1].pz - offpz 58 | let vx1 = pts[1].vx 59 | let vy1 = pts[1].vy 60 | let vz1 = pts[1].vz 61 | 62 | let px2 = pts[2].px - offpx 63 | let py2 = pts[2].py - offpy 64 | let pz2 = pts[2].pz - offpz 65 | let vx2 = pts[2].vx 66 | let vy2 = pts[2].vy 67 | let vz2 = pts[2].vz 68 | 69 | for (let vx = -300; vx < 300; vx++) { 70 | for (let vy = -300; vy < 300; vy++) { 71 | for (let vz = -300; vz < 300; vz++) { 72 | // transformations: 73 | // px0 py0 pz0 => 0 0 0 74 | // vx vy vz => 1 1 1 (this simplifies a ridiculous amount of shit what) 75 | // z = 0 obviously for the initial line finding 76 | let tx0 = vx0 - vx 77 | let ty0 = vy0 - vy 78 | let tz0 = vz0 - vz 79 | 80 | let a0 = tz0 - ty0 81 | let b0 = tx0 - tz0 82 | 83 | let tx1 = vx1 - vx 84 | let ty1 = vy1 - vy 85 | let tz1 = vz1 - vz 86 | 87 | let a1 = tz1 - ty1 88 | let b1 = tx1 - tz1 89 | 90 | let d = a0*b1 - b0*a1 91 | if (d == 0) { 92 | continue 93 | } 94 | 95 | let tx2 = vx2 - vx 96 | let ty2 = vy2 - vy 97 | let tz2 = vz2 - vz 98 | 99 | let a2 = tz2 - ty2 100 | let b2 = tx2 - tz2 101 | 102 | let c1 = ty1 - tx1 103 | let c2 = ty2 - tx2 104 | 105 | let d1 = px1*(a1/d) + py1*(b1/d) + pz1*(c1/d) 106 | let d2 = px2*(a2/d) + py2*(b2/d) + pz2*(c2/d) 107 | 108 | let x = -b0*d1 109 | let y = a0*d1 110 | if (Math.abs(a2*(x/d) + b2*(y/d) - d2) > epsilon) { 111 | continue 112 | } 113 | 114 | let c0 = ty0 - tx0 115 | if (c0 == 0 || c1 == 0 || c2 == 0) { 116 | continue 117 | } 118 | 119 | let k0 = tx0*(y/c0) - ty0*(x/c0) 120 | let k1 = tx1*((y-py1)/c1) - ty1*((x-px1)/c1) 121 | if (Math.abs(k0 - k1) > epsilon) { 122 | continue 123 | } 124 | 125 | let nx = k0 + offpx + x 126 | let ny = k0 + offpy + y 127 | let nz = k0 + offpz 128 | return Math.round(nx + ny + nz) 129 | } 130 | } 131 | } 132 | } 133 | 134 | if (typeof window == "undefined") { 135 | module.exports = day24 136 | } 137 | 138 | -------------------------------------------------------------------------------- /2023/24_2.js: -------------------------------------------------------------------------------- 1 | function day24(input, part2) { 2 | let pts = [] 3 | 4 | for (let line of input.split("\n")) { 5 | let [px, py, pz, vx, vy, vz] = line.ints() 6 | pts.push({ 7 | px: px, 8 | py: py, 9 | pz: pz, 10 | vx: vx, 11 | vy: vy, 12 | vz: vz 13 | }) 14 | 15 | if (part2 && pts.length == 3) { 16 | break 17 | } 18 | } 19 | 20 | let epsilon = 0.01 21 | 22 | let offpx = pts[0].px 23 | let offpy = pts[0].py 24 | let offpz = pts[0].pz 25 | 26 | let px0 = pts[0].px - offpx 27 | let py0 = pts[0].py - offpy 28 | let pz0 = pts[0].pz - offpz 29 | let vx0 = pts[0].vx 30 | let vy0 = pts[0].vy 31 | let vz0 = pts[0].vz 32 | let cx0 = pz0*vy0 - py0*vz0 33 | let cy0 = px0*vz0 - pz0*vx0 34 | let cz0 = py0*vx0 - px0*vy0 35 | 36 | let px1 = pts[1].px - offpx 37 | let py1 = pts[1].py - offpy 38 | let pz1 = pts[1].pz - offpz 39 | let vx1 = pts[1].vx 40 | let vy1 = pts[1].vy 41 | let vz1 = pts[1].vz 42 | let cx1 = pz1*vy1 - py1*vz1 43 | let cy1 = px1*vz1 - pz1*vx1 44 | let cz1 = py1*vx1 - px1*vy1 45 | 46 | let px2 = pts[2].px - offpx 47 | let py2 = pts[2].py - offpy 48 | let pz2 = pts[2].pz - offpz 49 | let vx2 = pts[2].vx 50 | let vy2 = pts[2].vy 51 | let vz2 = pts[2].vz 52 | let cx2 = pz2*vy2 - py2*vz2 53 | let cy2 = px2*vz2 - pz2*vx2 54 | let cz2 = py2*vx2 - px2*vy2 55 | 56 | for (let vx = -300; vx < 300; vx++) { 57 | for (let vy = -300; vy < 300; vy++) { 58 | for (let vz = -300; vz < 300; vz++) { 59 | let a0 = vz0*vy - vy0*vz 60 | let b0 = vx0*vz - vz0*vx 61 | 62 | let a1 = vz1*vy - vy1*vz 63 | let b1 = vx1*vz - vz1*vx 64 | 65 | let d = a0*b1 - b0*a1 66 | if (d == 0) { 67 | continue 68 | } 69 | 70 | let a2 = vz2*vy - vy2*vz 71 | let b2 = vx2*vz - vz2*vx 72 | 73 | let d0 = vx*(cx0/d) + vy*(cy0/d) + vz*(cz0/d) 74 | let d1 = vx*(cx1/d) + vy*(cy1/d) + vz*(cz1/d) 75 | let d2 = vx*(cx2/d) + vy*(cy2/d) + vz*(cz2/d) 76 | 77 | let x = b1*d0 - b0*d1 78 | let y = a0*d1 - a1*d0 79 | let z = 0 80 | if (Math.abs(a2*(x/d) + b2*(y/d) - d2) > epsilon) { 81 | continue 82 | } 83 | 84 | let kd0 = vx*vy0 - vy*vx0 85 | let kd1 = vx*vy1 - vy*vx1 86 | let kd2 = vx*vy2 - vy*vx2 87 | 88 | let k0 = (px0/kd0 - x/kd0)*(vy0 - vy) - (py0/kd0 - y/kd0)*(vx0 - vx) 89 | let k1 = (px1/kd1 - x/kd1)*(vy1 - vy) - (py1/kd1 - y/kd1)*(vx1 - vx) 90 | let k2 = (px2/kd2 - x/kd2)*(vy2 - vy) - (py2/kd2 - y/kd2)*(vx2 - vx) 91 | if (Math.abs(k0 - k1) > epsilon || Math.abs(k1 - k2) > epsilon || Math.abs(k0 - k2) > epsilon) { 92 | continue 93 | } 94 | 95 | let nx = x + k0*vx + offpx 96 | let ny = y + k0*vy + offpy 97 | let nz = z + k0*vz + offpz 98 | return Math.round(nx + ny + nz) 99 | } 100 | } 101 | } 102 | } 103 | 104 | if (typeof window == "undefined") { 105 | module.exports = day24 106 | } 107 | 108 | -------------------------------------------------------------------------------- /2023/24_3.js: -------------------------------------------------------------------------------- 1 | function day24(input, part2) { 2 | let pts = [] 3 | 4 | for (let line of input.split("\n")) { 5 | let [px, py, pz, vx, vy, vz] = line.ints() 6 | pts.push({ 7 | px: px, 8 | py: py, 9 | pz: pz, 10 | vx: vx, 11 | vy: vy, 12 | vz: vz 13 | }) 14 | 15 | if (part2 && pts.length == 3) { 16 | break 17 | } 18 | } 19 | 20 | let epsilon = 0.1 21 | 22 | let offpx = pts[0].px 23 | let offpy = pts[0].py 24 | let offpz = pts[0].pz 25 | 26 | let px0 = pts[0].px - offpx 27 | let py0 = pts[0].py - offpy 28 | let pz0 = pts[0].pz - offpz 29 | let vx0 = pts[0].vx 30 | let vy0 = pts[0].vy 31 | let vz0 = pts[0].vz 32 | let cx0 = pz0*vy0 - py0*vz0 33 | let cy0 = px0*vz0 - pz0*vx0 34 | let cz0 = py0*vx0 - px0*vy0 35 | 36 | let px1 = pts[1].px - offpx 37 | let py1 = pts[1].py - offpy 38 | let pz1 = pts[1].pz - offpz 39 | let vx1 = pts[1].vx 40 | let vy1 = pts[1].vy 41 | let vz1 = pts[1].vz 42 | let cx1 = pz1*vy1 - py1*vz1 43 | let cy1 = px1*vz1 - pz1*vx1 44 | let cz1 = py1*vx1 - px1*vy1 45 | 46 | let px2 = pts[2].px - offpx 47 | let py2 = pts[2].py - offpy 48 | let pz2 = pts[2].pz - offpz 49 | let vx2 = pts[2].vx 50 | let vy2 = pts[2].vy 51 | let vz2 = pts[2].vz 52 | let cx2 = pz2*vy2 - py2*vz2 53 | let cy2 = px2*vz2 - pz2*vx2 54 | let cz2 = py2*vx2 - px2*vy2 55 | 56 | for (let vx = -300; vx < 300; vx++) { 57 | for (let vy = -300; vy < 300; vy++) { 58 | for (let vz = -300; vz < 300; vz++) { 59 | let a0 = (vz0 - vz) - (vy0 - vy) 60 | let b0 = (vx0 - vx) - (vz0 - vz) 61 | 62 | let a1 = (vz1 - vz) - (vy1 - vy) 63 | let b1 = (vx1 - vx) - (vz1 - vz) 64 | 65 | let d = a0*b1 - b0*a1 66 | if (d == 0) { 67 | continue 68 | } 69 | 70 | let a2 = (vz2 - vz) - (vy2 - vy) 71 | let b2 = (vx2 - vx) - (vz2 - vz) 72 | 73 | let d0 = ((pz0*(vy0-vy+1) - py0*(vz0-vz+1))/d) + ((px0*(vz0-vz+1) - pz0*(vx0-vx+1))/d) + ((py0*(vx0-vx+1) - px0*(vy0-vy+1))/d) 74 | let d1 = ((pz1*(vy1-vy+1) - py1*(vz1-vz+1))/d) + ((px1*(vz1-vz+1) - pz1*(vx1-vx+1))/d) + ((py1*(vx1-vx+1) - px1*(vy1-vy+1))/d) 75 | let d2 = ((pz2*(vy2-vy+1) - py2*(vz2-vz+1))/d) + ((px2*(vz2-vz+1) - pz2*(vx2-vx+1))/d) + ((py2*(vx2-vx+1) - px2*(vy2-vy+1))/d) 76 | 77 | let x = b1*d0 - b0*d1 78 | let y = a0*d1 - a1*d0 79 | let z = 0 80 | 81 | if (Math.abs(a2*(x/d) + b2*(y/d) - d2) > epsilon) { 82 | continue 83 | } 84 | 85 | let kd0 = (vy0 - vy) - (vx0 - vx) 86 | let kd1 = (vy1 - vy) - (vx1 - vx) 87 | let kd2 = (vy2 - vy) - (vx2 - vx) 88 | 89 | let k0 = (px0/kd0 - x/kd0)*(vy0 - vy) - (py0/kd0 - y/kd0)*(vx0 - vx) 90 | let k1 = (px1/kd1 - x/kd1)*(vy1 - vy) - (py1/kd1 - y/kd1)*(vx1 - vx) 91 | let k2 = (px2/kd2 - x/kd2)*(vy2 - vy) - (py2/kd2 - y/kd2)*(vx2 - vx) 92 | if (vx == 263 && vy == 120 && vz == 21) L(x, y, z), L(k0, k1, k2), L(d0, d1, d2, d) 93 | if (Math.abs(k0 - k1) > epsilon || Math.abs(k1 - k2) > epsilon || Math.abs(k0 - k2) > epsilon) { 94 | continue 95 | } 96 | 97 | let nx = x + k0 + offpx 98 | let ny = y + k0 + offpy 99 | let nz = z + k0 + offpz 100 | return Math.round(nx + ny + nz) 101 | } 102 | } 103 | } 104 | } 105 | 106 | if (typeof window == "undefined") { 107 | module.exports = day24 108 | } 109 | 110 | -------------------------------------------------------------------------------- /2023/24_4.js: -------------------------------------------------------------------------------- 1 | function day24(input, part2) { 2 | let pts = [] 3 | 4 | for (let line of input.split("\n")) { 5 | let [px, py, pz, vx, vy, vz] = line.ints() 6 | pts.push({ px, py, pz, vx, vy, vz }) 7 | 8 | if (part2 && pts.length == 3) { 9 | break 10 | } 11 | } 12 | 13 | if (!part2) { 14 | let count = 0 15 | 16 | for (let i = 0; i < pts.length - 1; i++) { 17 | for (let j = i + 1; j < pts.length; j++) { 18 | let a = pts[i] 19 | let b = pts[j] 20 | 21 | let na = (b.py*b.vx - b.px*b.vy + a.px*b.vy - a.py*b.vx) / (a.vy*b.vx - a.vx*b.vy) 22 | if (na < 0) { 23 | continue 24 | } 25 | 26 | let tx = a.px + na*a.vx 27 | 28 | if (Math.sign(tx - b.px) != Math.sign(b.vx)) { 29 | continue 30 | } 31 | 32 | let ty = a.py + na*a.vy 33 | 34 | if (tx >= 200000000000000 && tx <= 400000000000000 && 35 | ty >= 200000000000000 && ty <= 400000000000000) { 36 | count++ 37 | } 38 | } 39 | } 40 | 41 | return count 42 | } 43 | 44 | // what 45 | 46 | let epsilon = 0.1 47 | 48 | let offpx = pts[0].px 49 | let offpy = pts[0].py 50 | let offpz = pts[0].pz 51 | 52 | let vx0 = pts[0].vx 53 | let vy0 = pts[0].vy 54 | let vz0 = pts[0].vz 55 | 56 | let px1 = pts[1].px - offpx 57 | let py1 = pts[1].py - offpy 58 | let pz1 = pts[1].pz - offpz 59 | let vx1 = pts[1].vx 60 | let vy1 = pts[1].vy 61 | let vz1 = pts[1].vz 62 | 63 | let px2 = pts[2].px - offpx 64 | let py2 = pts[2].py - offpy 65 | let pz2 = pts[2].pz - offpz 66 | let vx2 = pts[2].vx 67 | let vy2 = pts[2].vy 68 | let vz2 = pts[2].vz 69 | 70 | for (let vx = -300; vx < 300; vx++) { 71 | for (let vy = -300; vy < 300; vy++) { 72 | for (let vz = -300; vz < 300; vz++) { 73 | // transformations: 74 | // px0 py0 pz0 => 0 0 0 75 | // vx vy vz => 1 1 1 (this simplifies a ridiculous amount of shit what) 76 | // z = 0 obviously for the initial line finding 77 | let a0 = (vz0-vz) - (vy0-vy) 78 | let b0 = (vx0-vx) - (vz0-vz) 79 | 80 | let a1 = (vz1-vz) - (vy1-vy) 81 | let b1 = (vx1-vx) - (vz1-vz) 82 | 83 | let d = a0*b1 - b0*a1 84 | if (d == 0) { 85 | continue 86 | } 87 | 88 | let c0 = (vy0-vy) - (vx0-vx) 89 | let c1 = (vy1-vy) - (vx1-vx) 90 | let c2 = (vy2-vy) - (vx2-vx) 91 | if (c0 == 0 || c1 == 0 || c2 == 0) { 92 | continue 93 | } 94 | 95 | let d1 = px1*(a1/d) + py1*(b1/d) + pz1*(c1/d) 96 | let x = -b0*d1 97 | let y = a0*d1 98 | 99 | let k0 = (-x/c0)*(vy0 - vy) - (-y/c0)*(vx0 - vx) 100 | let k1 = (px1/c1 - x/c1)*(vy1 - vy) - (py1/c1 - y/c1)*(vx1 - vx) 101 | 102 | if (Math.abs(k0 - k1) > epsilon) { 103 | continue 104 | } 105 | 106 | let k2 = (px2/c2 - x/c2)*(vy2 - vy) - (py2/c2 - y/c2)*(vx2 - vx) 107 | 108 | if (Math.abs(k0 - k2) > epsilon) { 109 | continue 110 | } 111 | 112 | let nx = k0 + offpx + x 113 | let ny = k0 + offpy + y 114 | let nz = k0 + offpz 115 | return Math.round(nx + ny + nz) 116 | } 117 | } 118 | } 119 | } 120 | 121 | if (typeof window == "undefined") { 122 | module.exports = day24 123 | } 124 | 125 | -------------------------------------------------------------------------------- /2023/25.js: -------------------------------------------------------------------------------- 1 | function day25(input) { 2 | let graph = Graph.fromStr(input, ": ", " ", true) 3 | 4 | for (let node of graph.values()) { 5 | let removedEdges = [] 6 | 7 | for (let i = 0; i < 3; i++) { 8 | for (let edge of node.furthestBfs().unwrap().windowsGen(2)) { 9 | edge[0].removeCxn(edge[1]) 10 | edge[1].removeCxn(edge[0]) 11 | removedEdges.push(edge) 12 | } 13 | } 14 | 15 | let groupSize = node.exploreBfs().size 16 | if (groupSize != graph.size) { 17 | return groupSize * (graph.size - groupSize) 18 | } 19 | 20 | for (let edge of removedEdges) { 21 | edge[0].addCxn(edge[1]) 22 | edge[1].addCxn(edge[0]) 23 | } 24 | } 25 | } 26 | 27 | if (typeof window == "undefined") { 28 | module.exports = day25 29 | } 30 | -------------------------------------------------------------------------------- /2023/3.js: -------------------------------------------------------------------------------- 1 | let digitChars = "0123456789" 2 | 3 | function day3(input, part2) { 4 | let grid = Grid.fromStr(input) 5 | 6 | let building = false 7 | let numStart 8 | let numEnd 9 | let cur = "" 10 | 11 | let oneGears = new NumericPointMap() 12 | let twoGears = new NumericPointMap() 13 | let badGears = new NumericPointSet() 14 | let sum = 0 15 | 16 | grid.forEach((e, pt) => { 17 | if (!building) { 18 | if (digitChars.includes(e)) { 19 | building = true 20 | numStart = pt 21 | cur = "" 22 | } else { 23 | return 24 | } 25 | } 26 | 27 | if (pt.y == numStart.y && digitChars.includes(e)) { 28 | cur += e 29 | numEnd = pt 30 | return 31 | } 32 | 33 | building = false 34 | 35 | for (let y = numStart.y - 1; y <= numStart.y + 1; y++) { 36 | for (let x = numStart.x - 1; x <= numEnd.x + 1; x++) { 37 | if (y == numStart.y && x >= numStart.x && x <= numEnd.x) { 38 | continue 39 | } 40 | 41 | let pt = new Point(x, y) 42 | let val = grid.getDef(pt, ".") 43 | 44 | if (!part2 && val != "." && !digitChars.includes(e)) { 45 | sum += +cur 46 | return 47 | } 48 | 49 | if (part2 && val == "*" && !badGears.has(pt)) { 50 | if (twoGears.has(pt)) { 51 | sum -= twoGears.get(pt) 52 | twoGears.delete(pt) 53 | badGears.add(pt) 54 | } else if (oneGears.has(pt)) { 55 | let val = oneGears.get(pt) * +cur 56 | sum += val 57 | oneGears.delete(pt) 58 | twoGears.set(pt, val) 59 | } else { 60 | oneGears.set(pt, +cur) 61 | } 62 | } 63 | } 64 | } 65 | }) 66 | 67 | return sum 68 | } 69 | 70 | if (typeof window == "undefined") { 71 | module.exports = day3 72 | } 73 | -------------------------------------------------------------------------------- /2023/4.js: -------------------------------------------------------------------------------- 1 | function day4(input, part2) { 2 | let lines = input.split("\n") 3 | let counts = Array(lines.length).fill(1) 4 | let sum = 0 5 | 6 | for (let i = 0; i < lines.length; i++) { 7 | let line = lines[i].split(":")[1].split("|") 8 | let wins = line[1].ints() 9 | 10 | let numMatches = line[0].ints().count((num) => wins.includes(num)) 11 | 12 | if (part2) { 13 | for (let j = i + 1; j < Math.min(lines.length, i + 1 + numMatches); j++) { 14 | counts[j] += counts[i] 15 | } 16 | 17 | sum += counts[i] 18 | } else { 19 | sum += numMatches ? 2 ** (numMatches - 1) : 0 20 | } 21 | } 22 | 23 | return sum 24 | } 25 | 26 | if (typeof window == "undefined") { 27 | module.exports = day4 28 | } 29 | -------------------------------------------------------------------------------- /2023/5.js: -------------------------------------------------------------------------------- 1 | function day5(input, part2) { 2 | let parts = input.split("\n\n") 3 | 4 | let seeds = parts.shift().ints() 5 | let ranges = new RangeSet() 6 | let maps = [] 7 | 8 | for (let i = 0; i < seeds.length; i += part2 ? 2 : 1) { 9 | ranges.addRangeMut(new Range(seeds[i], part2 ? seeds[i] + seeds[i + 1] : seeds[i] + 1)) 10 | } 11 | 12 | for (let part of parts) { 13 | let newRanges = new RangeSet() 14 | let ints = part.ints() 15 | let map = [] 16 | 17 | for (let i = 0; i < ints.length; i += 3) { 18 | let src = new Range(ints[i + 1], ints[i + 1] + ints[i + 2]) 19 | let dest = new Range(ints[i], ints[i] + ints[i + 2]) 20 | map.push({ src: src, dest: dest }) 21 | } 22 | 23 | for (let range of ranges.ranges) { 24 | let outside = range.set() 25 | 26 | for (let { src, dest } of map) { 27 | let offset = dest.x - src.x 28 | let inside = src.intersection(range) 29 | 30 | if (inside) { 31 | newRanges.addRangeMut(new Range(inside.x + offset, inside.y + offset)) 32 | outside.subRangeMut(inside) 33 | } 34 | } 35 | 36 | for (let newRange of outside.ranges) { 37 | newRanges.addRangeMut(newRange) 38 | } 39 | } 40 | 41 | ranges = newRanges.reduceMut() 42 | } 43 | 44 | return ranges.x 45 | } 46 | 47 | if (typeof window == "undefined") { 48 | module.exports = day5 49 | } 50 | -------------------------------------------------------------------------------- /2023/6.js: -------------------------------------------------------------------------------- 1 | function day6(input, part2) { 2 | if (part2) { 3 | input = input.replaceAll(" ", "") 4 | } 5 | 6 | return input.split("\n").map((line) => line.ints()).transpose().map(([time, dist]) => ((Math.sqrt(time * time / 4 - (dist + 0.5)) + (time & 1) / 2) << 1) | (~time & 1)).prod() 7 | } 8 | 9 | if (typeof window == "undefined") { 10 | module.exports = day6 11 | } 12 | -------------------------------------------------------------------------------- /2023/7.js: -------------------------------------------------------------------------------- 1 | const ranks = { 2 | "A": 14, 3 | "K": 13, 4 | "Q": 12, 5 | "T": 10, 6 | "9": 9, 7 | "8": 8, 8 | "7": 7, 9 | "6": 6, 10 | "5": 5, 11 | "4": 4, 12 | "3": 3, 13 | "2": 2 14 | } 15 | 16 | function value(hand, part2) { 17 | let freqs = new Map() 18 | let max = 0 19 | let jacks = 0 20 | 21 | for (let card of hand) { 22 | if (part2 && card == "J") { 23 | jacks++ 24 | } else { 25 | let count = (freqs.get(card) ?? 0) + 1 26 | freqs.set(card, count) 27 | max = Math.max(max, count) 28 | } 29 | } 30 | 31 | return (((max + jacks) << 3) - (freqs.size || 1)) << 20 | 32 | ranks[hand[0]] << 16 | 33 | ranks[hand[1]] << 12 | 34 | ranks[hand[2]] << 8 | 35 | ranks[hand[3]] << 4 | 36 | ranks[hand[4]] 37 | } 38 | 39 | function day7(input, part2) { 40 | ranks["J"] = part2 ? 1 : 11 41 | 42 | return input.split("\n").map((line) => { 43 | let [hand, score] = line.split(" ") 44 | return [value(hand, part2), +score] 45 | }).sortNumAsc((e) => e[0]).sum(([_, score], i) => score * (i + 1)) 46 | } 47 | 48 | if (typeof window == "undefined") { 49 | module.exports = day7 50 | } 51 | -------------------------------------------------------------------------------- /2023/8.js: -------------------------------------------------------------------------------- 1 | function day8(input, part2) { 2 | let [steps, ...paths] = input.match(/[A-Z]+/g) 3 | let cxns = {} 4 | let nodes = [] 5 | 6 | steps = steps.split("").map((e) => +(e == "R")) 7 | 8 | for (let i = 0; i < paths.length; i += 3) { 9 | if (part2 ? paths[i][2] == "A" : paths[i] == "AAA") { 10 | nodes.push(paths[i]) 11 | } 12 | 13 | cxns[paths[i]] = [paths[i + 1], paths[i + 2]] 14 | } 15 | 16 | let count = steps.length 17 | 18 | for (let node of nodes) { 19 | let cycles = 0 20 | 21 | while (node[2] != "Z") { 22 | for (let i = 0; i < steps.length; i++) { 23 | node = cxns[node][steps[i]] 24 | } 25 | 26 | cycles++ 27 | } 28 | 29 | count = count.lcm(cycles) 30 | } 31 | 32 | return count 33 | } 34 | 35 | if (typeof window == "undefined") { 36 | module.exports = day8 37 | } 38 | -------------------------------------------------------------------------------- /2023/9.js: -------------------------------------------------------------------------------- 1 | function day9(input, part2) { 2 | let sum = 0 3 | 4 | for (let lines of input.split("\n")) { 5 | let nums = lines.ints() 6 | 7 | if (part2) { 8 | nums.reverse() 9 | } 10 | 11 | for (let i = nums.length; i > 0; i--) { 12 | sum += nums[nums.length - 1] 13 | nums = nums.deltas() 14 | } 15 | } 16 | 17 | return sum 18 | } 19 | 20 | if (typeof window == "undefined") { 21 | module.exports = day9 22 | } 23 | -------------------------------------------------------------------------------- /2024/1.js: -------------------------------------------------------------------------------- 1 | function day1(input, part2) { 2 | let columns = input.posints().splitEvery(2).transpose() 3 | 4 | if (part2) { 5 | return columns[1].int(columns[0]).sum() 6 | } else { 7 | return columns.map((c) => c.sortNumAsc()).transpose().sum(([a, b]) => Math.abs(a - b)) 8 | } 9 | } 10 | 11 | if (typeof window == "undefined") { 12 | module.exports = day1 13 | } 14 | -------------------------------------------------------------------------------- /2024/10.js: -------------------------------------------------------------------------------- 1 | // could dp this but too lazy 2 | function trails(grid, pt, part2, found) { 3 | if (!part2) { 4 | if (found.has(pt)) { 5 | return 0 6 | } 7 | 8 | found.add(pt) 9 | } 10 | 11 | let cur = grid.get(pt) 12 | if (cur == 9) { 13 | return 1 14 | } 15 | 16 | return grid.getAdjNeighbors(pt).sum((pt2) => { 17 | return grid.get(pt2) == cur + 1 ? trails(grid, pt2, part2, found) : 0 18 | }) 19 | } 20 | 21 | function day10(input, part2) { 22 | let grid = Grid.fromStr(input).numMut() 23 | return grid.findIndices(0).sum((pt) => trails(grid, pt, part2, new NumericPointSet())) 24 | } 25 | 26 | if (typeof window == "undefined") { 27 | module.exports = day10 28 | } 29 | -------------------------------------------------------------------------------- /2024/11.js: -------------------------------------------------------------------------------- 1 | function day11(input, part2) { 2 | let count = utils.memoize((num, ticks) => { 3 | if (ticks == 0) { 4 | return 1 5 | } 6 | 7 | if (num == 0) { 8 | return count(1, ticks - 1) 9 | } 10 | 11 | let str = num.toString() 12 | if (str.length % 2 == 0) { 13 | let left = +str.slice(0, str.length / 2) 14 | let right = +str.slice(str.length / 2) 15 | return count(left, ticks - 1) + count(right, ticks - 1) 16 | } 17 | 18 | return count(num * 2024, ticks - 1) 19 | }, (num, ticks) => num * 256 + ticks) 20 | 21 | return input.posints().sum((num) => count(num, part2 ? 75 : 25)) 22 | } 23 | 24 | if (typeof window == "undefined") { 25 | module.exports = day11 26 | } 27 | -------------------------------------------------------------------------------- /2024/12.js: -------------------------------------------------------------------------------- 1 | function day12(input, part2) { 2 | let grid = Grid.fromStr(input) 3 | 4 | /* 5 | let regions = new UnionFind() 6 | 7 | grid.forEach((e, pt) => { 8 | let key = pt.encode() 9 | regions.add(key) 10 | 11 | for (let pt2 of grid.getAdjNeighbors(pt)) { 12 | let key2 = pt2.encode() 13 | if (regions.has(key2) && grid.get(pt2) == e) { 14 | regions.connect(key, key2) 15 | } 16 | } 17 | }) 18 | */ 19 | 20 | let regions = [] 21 | let nexts = new NumericPointSet([Point.ZERO]) 22 | let visited = new NumericPointSet() 23 | 24 | while (nexts.size) { 25 | let next = nexts.values().next().value 26 | nexts.delete(next) 27 | 28 | let key = grid.get(next) 29 | let region = [] 30 | 31 | let toVisit = new NumericPointSet([next]) 32 | while (toVisit.size) { 33 | let next = toVisit.values().next().value 34 | toVisit.delete(next) 35 | nexts.delete(next) 36 | 37 | if (visited.has(next)) { 38 | continue 39 | } 40 | 41 | let cur = grid.get(next) 42 | 43 | if (cur == key) { 44 | visited.add(next) 45 | region.push(next) 46 | grid.getAdjNeighbors(next).forEach((pt) => toVisit.add(pt)) 47 | } else { 48 | nexts.add(next) 49 | } 50 | } 51 | 52 | regions.push(region) 53 | } 54 | 55 | let sum = 0 56 | 57 | for (let region of regions) { 58 | let key = grid.get(region[0]) 59 | let neighbors = region.flatMap((pt) => 60 | Point.DIRS.map((dir) => [dir, pt.add(dir)]).filter(([_, pt]) => grid.getDef(pt) != key)) 61 | 62 | if (part2) { 63 | let sides = new UnionFind() 64 | 65 | for (let neighbor of neighbors) { 66 | sides.addAndConnectIf(neighbor, (a, b) => a[0] == b[0] && a[1].isAdjacent(b[1]) == 1) 67 | } 68 | 69 | sum += region.length * sides.numSets 70 | } else { 71 | sum += region.length * neighbors.length 72 | } 73 | } 74 | 75 | return sum 76 | } 77 | 78 | if (typeof window == "undefined") { 79 | module.exports = day12 80 | } 81 | -------------------------------------------------------------------------------- /2024/13.js: -------------------------------------------------------------------------------- 1 | function day13(input, part2) { 2 | return input.posints().splitEvery(6).sum(([ax, ay, bx, by, px, py]) => { 3 | if (part2) { 4 | px += 10000000000000 5 | py += 10000000000000 6 | } 7 | 8 | let det = ax*by - bx*ay 9 | 10 | let na = (by*px - bx*py) / det 11 | if (!Number.isInteger(na)) { 12 | return 0 13 | } 14 | 15 | let nb = (ax*py - ay*px) / det 16 | if (!Number.isInteger(nb)) { 17 | return 0 18 | } 19 | 20 | return 3*na + nb 21 | }) 22 | } 23 | 24 | if (typeof window == "undefined") { 25 | module.exports = day13 26 | } 27 | -------------------------------------------------------------------------------- /2024/14.js: -------------------------------------------------------------------------------- 1 | function day14(input, part2) { 2 | let width = 101 3 | let height = 103 4 | 5 | let robots = input.ints().splitEvery(4) 6 | for (let robot of robots) { 7 | robot[2] = robot[2] < 0 ? robot[2] + width : robot[2] 8 | robot[3] = robot[3] < 0 ? robot[3] + height : robot[3] 9 | } 10 | 11 | let regions = Array(256) 12 | 13 | for (let time = 1; time < width * height; time++) { 14 | regions.fill(0) 15 | 16 | for (let robot of robots) { 17 | robot[0] += robot[2] 18 | if (robot[0] >= width) { 19 | robot[0] -= width 20 | } 21 | 22 | robot[1] += robot[3] 23 | if (robot[1] >= height) { 24 | robot[1] -= height 25 | } 26 | 27 | if (part2) { 28 | if (++regions[(robot[0] & ~0xF) | (robot[1] >> 4)] > 70) { 29 | return time 30 | } 31 | } 32 | } 33 | 34 | if (!part2 && time == 100) { 35 | let parts = [0, 0, 0, 0] 36 | let cx = width >> 1 37 | let cy = height >> 1 38 | 39 | for (let [x, y] of robots) { 40 | if (x != cx && y != cy) { 41 | parts[(x > cx) << 1 | (y > cy)]++ 42 | } 43 | } 44 | 45 | return parts.prod() 46 | } 47 | } 48 | } 49 | 50 | if (typeof window == "undefined") { 51 | module.exports = day14 52 | } 53 | -------------------------------------------------------------------------------- /2024/19.js: -------------------------------------------------------------------------------- 1 | function day19(input, part2) { 2 | let [substrings, strings] = input.split("\n\n") 3 | substrings = substrings.split(", ") 4 | 5 | let score = utils.memoize((string) => 6 | string ? substrings[part2 ? "sum" : "some"]((e) => string.startsWith(e) && score(string.slice(e.length))) : 1, (e) => e) 7 | 8 | return strings.split("\n").sum(score) 9 | } 10 | 11 | if (typeof window == "undefined") { 12 | module.exports = day19 13 | } 14 | -------------------------------------------------------------------------------- /2024/2.js: -------------------------------------------------------------------------------- 1 | function safe(dir, nums, a, b, skip) { 2 | if (nums.length - b < 1) { 3 | return true 4 | } 5 | 6 | if (skip && safe(dir, nums, a, b + 1, false)) { 7 | return true 8 | } 9 | 10 | if (a > -1) { 11 | let diff = dir ? +nums[b] - +nums[a] : +nums[a] - +nums[b] 12 | if (diff < 1 || diff > 3) { 13 | return false 14 | } 15 | } 16 | 17 | return safe(dir, nums, b, b + 1, skip) 18 | } 19 | 20 | function day2(input, part2) { 21 | return input.split("\n").count((line) => { 22 | let nums = line.split(" ") 23 | return safe(true, nums, -1, 0, part2) || safe(false, nums, -1, 0, part2) 24 | }) 25 | } 26 | 27 | if (typeof window == "undefined") { 28 | module.exports = day2 29 | } 30 | -------------------------------------------------------------------------------- /2024/20.js: -------------------------------------------------------------------------------- 1 | function day20(input, part2) { 2 | let grid = Grid.fromStr(input) 3 | 4 | let cur = grid.indexOf("S") 5 | 6 | let path = [] 7 | let dir = Point.DIRS.find((dir) => grid.get(cur.add(dir)) == ".") 8 | 9 | while (dir) { 10 | let last = cur 11 | path.push(last) 12 | dir = [dir, dir.cwConst, dir.ccwConst].find((dir2) => grid.get(cur = last.add(dir2)) != "#") 13 | } 14 | 15 | let count = 0 16 | let limit = part2 ? 20 : 2 17 | 18 | for (let i = 0; i < path.length; i++) { 19 | for (let j = i + 100; j < path.length; j++) { 20 | let cheatLength = path[i].manhattanDist(path[j]) 21 | 22 | if (cheatLength > limit) { 23 | j += cheatLength - limit - 1 24 | } else if (cheatLength <= j - i - 100) { 25 | count++ 26 | } 27 | } 28 | } 29 | 30 | return count 31 | } 32 | 33 | if (typeof window == "undefined") { 34 | module.exports = day20 35 | } 36 | -------------------------------------------------------------------------------- /2024/21.js: -------------------------------------------------------------------------------- 1 | function day21(input, part2) { 2 | let locs = [ 3 | Grid.fromArr(["789", "456", "123", " 0A"]), 4 | Grid.fromArr([" ^A", ""])].map((grid) => new Map(grid.entries().map(([pt, e]) => [e, pt]))) 5 | 6 | let caches = locs.map(() => new Map()) 7 | 8 | return input.split("\n").sum((str) => { 9 | let segments = str.split(/(?<=A)/g).freqsMap() 10 | 11 | for (let i = 0; i < (part2 ? 26 : 3); i++) { 12 | let newSegments = new Map() 13 | 14 | let loc = locs[+(i > 0)] 15 | let cache = caches[+(i > 0)] 16 | 17 | let start = loc.get("A") 18 | let block = loc.get(" ") 19 | 20 | for (let [segment, count] of segments) { 21 | if (!cache.has(segment)) { 22 | let children = [] 23 | let src = start 24 | 25 | for (let chr of segment) { 26 | let dest = loc.get(chr) 27 | 28 | let xDir = Math.sign(dest.x - src.x) 29 | let yDir = Math.sign(dest.y - src.y) 30 | 31 | let xDist = Math.abs(dest.x - src.x) 32 | let yDist = Math.abs(dest.y - src.y) 33 | 34 | let xPath = (xDir == -1 ? "<" : xDir == 1 ? ">" : "").repeat(xDist) 35 | let yPath = (yDir == -1 ? "^" : yDir == 1 ? "v" : "").repeat(yDist) 36 | 37 | let xBlocked = block.y == src.y && Math.sign(block.x - src.x) == xDir && Math.abs(block.x - src.x) <= xDist 38 | let yBlocked = block.x == src.x && Math.sign(block.y - src.y) == yDir && Math.abs(block.y - src.y) <= yDist 39 | 40 | children.push( 41 | xBlocked ? yPath + xPath + "A" : 42 | yBlocked ? xPath + yPath + "A" : 43 | xPath[0] == "<" ? xPath + yPath + "A" : yPath + xPath + "A") 44 | 45 | src = dest 46 | } 47 | 48 | cache.set(segment, children) 49 | } 50 | 51 | for (let newSegment of cache.get(segment)) { 52 | newSegments.set(newSegment, (newSegments.get(newSegment) ?? 0) + count) 53 | } 54 | } 55 | 56 | segments = newSegments 57 | } 58 | 59 | return parseInt(str) * segments.entries().sum(([a, b]) => a.length * b) 60 | }) 61 | } 62 | 63 | if (typeof window == "undefined") { 64 | module.exports = day21 65 | } 66 | -------------------------------------------------------------------------------- /2024/22.js: -------------------------------------------------------------------------------- 1 | function day22(input, part2) { 2 | let scores = new Uint32Array(0x100000) 3 | let score = 0 4 | 5 | let i = 0 6 | 7 | for (let num of input.split("\n").map((e) => +e)) { 8 | i++ 9 | 10 | let digit = 0 11 | let delta = 0 12 | 13 | for (let j = 0; j < 2000; j++) { 14 | num ^= num << 6 15 | num &= 0xFFFFFF 16 | num ^= num >> 5 17 | num ^= num << 11 18 | num &= 0xFFFFFF 19 | 20 | if (!part2) { 21 | continue 22 | } 23 | 24 | let oldDigit = digit 25 | digit = num % 10 26 | 27 | delta <<= 5 28 | delta &= 0xFFFFF 29 | delta |= (digit - oldDigit) + 10 30 | 31 | if (j < 4) { 32 | continue 33 | } 34 | 35 | let key = i << 16 36 | if (scores[delta] < key) { 37 | let low = (scores[delta] & 0xFFFF) + digit 38 | scores[delta] = key | low 39 | score = Math.max(score, low) 40 | } 41 | } 42 | 43 | if (!part2) { 44 | score += num 45 | } 46 | } 47 | 48 | return score 49 | } 50 | 51 | if (typeof window == "undefined") { 52 | module.exports = day22 53 | } 54 | -------------------------------------------------------------------------------- /2024/23.js: -------------------------------------------------------------------------------- 1 | function day23(input, part2) { 2 | let graph = Graph.fromStr(input, "-", "\0", "\0", true) 3 | 4 | if (part2) { 5 | out: for (let node of graph.values()) { 6 | let party = new Set(node.cxns.keys()) 7 | let size = party.size - 1 8 | 9 | for (let node2 of party) { 10 | if (party.intersection(node2.cxns).size < size - 1) { 11 | party.delete(node2) 12 | 13 | if (party.size < size) { 14 | continue out 15 | } 16 | } 17 | } 18 | 19 | return [...party, node].map((node) => node.name).sort().join(",") 20 | } 21 | } else { 22 | let count = 0 23 | 24 | for (let [name, node] of graph) { 25 | if (!name.startsWith("t")) { 26 | continue 27 | } 28 | 29 | let neighbors = node.cxns.keys().toArray() 30 | 31 | for (let i = 0; i < neighbors.length; i++) { 32 | for (let j = i + 1; j < neighbors.length; j++) { 33 | count += neighbors[i].cxns.has(neighbors[j]) 34 | } 35 | 36 | neighbors[i].cxns.delete(node) 37 | } 38 | } 39 | 40 | return count 41 | } 42 | } 43 | 44 | if (typeof window == "undefined") { 45 | module.exports = day23 46 | } 47 | -------------------------------------------------------------------------------- /2024/24.js: -------------------------------------------------------------------------------- 1 | function day24(input, part2) { 2 | let [init, gates] = input.split("\n").splitOn("") 3 | 4 | let wires = {} 5 | let numBits = 0 6 | 7 | for (let line of init) { 8 | let [wire, bit] = line.split(": ") 9 | wires[wire] = +bit 10 | numBits = +wire.slice(1) + 1 11 | } 12 | 13 | let gatesMap = new Map() 14 | 15 | for (let line of gates) { 16 | let [in0, op, in1, _, out] = line.split(" ") 17 | 18 | let gates0 = gatesMap.get(in0) 19 | if (!gates0) { 20 | gatesMap.set(in0, gates0 = new Map()) 21 | } 22 | 23 | let gates1 = gatesMap.get(in1) 24 | if (!gates1) { 25 | gatesMap.set(in1, gates1 = new Map()) 26 | } 27 | 28 | gates0.set(op, { other: in1, out }) 29 | gates1.set(op, { other: in0, out }) 30 | } 31 | 32 | let z = [] 33 | let swaps = new Set() 34 | 35 | function expect(in0, op, in1) { 36 | let res = gatesMap.get(in0)?.get(op) 37 | 38 | if (!res) { 39 | res = gatesMap.get(in1).get(op) 40 | swaps.add(in0) 41 | swaps.add(res.other) 42 | in0 = res.other 43 | } else if (res.other != in1) { 44 | swaps.add(in1) 45 | swaps.add(res.other) 46 | in1 = res.other 47 | } 48 | 49 | let out = res.out 50 | wires[out] = 51 | op == "AND" ? wires[in0] & wires[in1] : 52 | op == "XOR" ? wires[in0] ^ wires[in1] : wires[in0] | wires[in1] 53 | 54 | if (out[0] == "z") { 55 | z.unshift(wires[out]) 56 | } 57 | 58 | return { in0, in1, out } 59 | } 60 | 61 | let carry 62 | 63 | for (let bit = 0; bit < numBits; bit++) { 64 | let x = "x" + bit.toString().padStart(2, "0") 65 | let y = "y" + bit.toString().padStart(2, "0") 66 | 67 | let low = expect(x, "XOR", y).out 68 | let newCarry = expect(x, "AND", y).out 69 | 70 | if (bit > 0) { 71 | ({ in0: carry, in1: low } = expect(carry, "XOR", low)) 72 | 73 | let cont 74 | ({ in0: carry, in1: low, out: cont } = expect(carry, "AND", low)); 75 | ({ in0: newCarry, in1: cont, out: newCarry } = expect(newCarry, "OR", cont)) 76 | } 77 | 78 | carry = newCarry 79 | } 80 | 81 | return part2 ? [...swaps].sort().join(",") : z.reduce((a, b) => a * 2 + b) 82 | } 83 | 84 | if (typeof window == "undefined") { 85 | module.exports = day24 86 | } 87 | -------------------------------------------------------------------------------- /2024/25.js: -------------------------------------------------------------------------------- 1 | function day25(input) { 2 | let { "#": locks, ".": keys } = input.split("\n\n").dict((obj, str) => { 3 | let grid = Grid.fromStr(str) 4 | let key = grid.get(Point.ZERO) 5 | 6 | obj[key] ??= [] 7 | obj[key].push(grid.getColumns().map((c) => c.lastIndexOf(key))) 8 | }) 9 | 10 | let count = 0 11 | 12 | for (let lock of locks) { 13 | for (let key of keys) { 14 | count += lock.every((n, i) => n <= key[i]) 15 | } 16 | } 17 | 18 | return count 19 | } 20 | 21 | if (typeof window == "undefined") { 22 | module.exports = day25 23 | } 24 | -------------------------------------------------------------------------------- /2024/3.js: -------------------------------------------------------------------------------- 1 | function day3(input, part2) { 2 | if (part2) { 3 | input = input.replace(/don't\(\).*?(do\(\)|$)/gs, " ") 4 | } 5 | 6 | return [...input.matchAll(/mul\((\d+),(\d+)\)/g)].sum(([_, a, b]) => +a * +b) 7 | } 8 | 9 | if (typeof window == "undefined") { 10 | module.exports = day3 11 | } 12 | -------------------------------------------------------------------------------- /2024/4.js: -------------------------------------------------------------------------------- 1 | function day4(input, part2) { 2 | let grid = Grid.fromStr(input) 3 | 4 | let dirs = part2 ? Point.ORIGIN.getUnfilteredDiagNeighbors() : Point.ORIGIN.getUnfilteredAllNeighbors() 5 | let offset = part2 ? 2 : 0 6 | 7 | let str = "XMAS" 8 | let center = str[offset] 9 | 10 | let count = 0 11 | 12 | for (let x = 0; x < grid.width; x++) { 13 | for (let y = 0; y < grid.height; y++) { 14 | let pt = new Point(x, y) 15 | if (grid.get(pt) != center) { 16 | continue 17 | } 18 | 19 | let sat = 0 20 | dirs_loop: for (let dir of dirs) { 21 | for (let i = 1; i <= 3; i++) { 22 | let mult = i - offset 23 | if (mult) { 24 | let head = pt.add(dir.mult(mult)) 25 | if (!grid.contains(head) || grid.get(head) != str[i]) { 26 | continue dirs_loop 27 | } 28 | } 29 | } 30 | 31 | sat++ 32 | } 33 | 34 | count += part2 ? sat == 2 : sat 35 | } 36 | } 37 | 38 | return count 39 | } 40 | 41 | if (typeof window == "undefined") { 42 | module.exports = day4 43 | } 44 | -------------------------------------------------------------------------------- /2024/5.js: -------------------------------------------------------------------------------- 1 | function day5(input, part2) { 2 | let [ordering, lists] = input.split("\n").splitOn("") 3 | let rules = new Set(ordering.map((e) => (+e[0] * 10 + +e[1]) << 16 | (+e[3] * 10 + +e[4]))) 4 | 5 | let sum = 0 6 | 7 | for (let line of lists) { 8 | let unsorted = line.split(",") 9 | let sorted = unsorted.slice().sort((a, b) => rules.has(+a << 16 | +b) ? -1 : 1) 10 | 11 | if (sorted.equals(unsorted) != part2) { 12 | sum += +sorted[sorted.length >> 1] 13 | } 14 | } 15 | 16 | return sum 17 | } 18 | 19 | if (typeof window == "undefined") { 20 | module.exports = day5 21 | } 22 | -------------------------------------------------------------------------------- /2024/5_2.js: -------------------------------------------------------------------------------- 1 | function day5(input, part2) { 2 | let [ordering, lists] = input.split("\n").splitOn("") 3 | 4 | ordering = ordering.dict((o, e) => { 5 | let [left, right] = e.split("|") 6 | o[left] ??= new Set() 7 | o[left].add(right) 8 | }) 9 | 10 | let sum = 0 11 | 12 | for (let list of lists) { 13 | list = list.split(",") 14 | 15 | let changed = false 16 | 17 | for (let i = 0; i < list.length - 1; i++) { 18 | let root = i 19 | 20 | for (let j = root + 1; j < list.length; j++) { 21 | if (ordering[list[j]]?.has(list[root])) { 22 | root = j 23 | } 24 | } 25 | 26 | if (root != i) { 27 | [list[i], list[root]] = [list[root], list[i]] 28 | changed = true 29 | } 30 | } 31 | 32 | if (changed == part2) { 33 | sum += +list[list.length >> 1] 34 | } 35 | } 36 | 37 | return sum 38 | } 39 | 40 | if (typeof window == "undefined") { 41 | module.exports = day5 42 | } 43 | -------------------------------------------------------------------------------- /2024/6.js: -------------------------------------------------------------------------------- 1 | // this code is really terrible but it runs in under a second, and it's 12:30am and i have a job interview tomorrow so this is what you're getting 2 | 3 | function day6(input, part2) { 4 | let grid = Grid.fromStr(input) 5 | let start = grid.indexOf("^") 6 | 7 | let rowBlocks = Array(grid.height).fill().map(() => []) 8 | let columnBlocks = Array(grid.width).fill().map(() => []) 9 | 10 | for (let block of grid.findIndices("#")) { 11 | rowBlocks[block.y].push(block.x) 12 | columnBlocks[block.x].push(block.y) 13 | } 14 | 15 | let path = new NumericPointMap() 16 | 17 | let head = start.copy() 18 | let dir = Point.UP 19 | 20 | while (grid.contains(head)) { 21 | if (!path.has(head)) { 22 | path.set(head, dir) 23 | } 24 | 25 | while (grid.getDef(head.add(dir)) == "#") { 26 | dir = dir.cwConst 27 | } 28 | 29 | head.addMut(dir) 30 | } 31 | 32 | if (!part2) { 33 | return path.size 34 | } 35 | 36 | path.delete(start) 37 | 38 | let count = 0 39 | 40 | for (let [newBlock, dir] of path) { 41 | let head = start.copy() 42 | let dir = Point.UP 43 | 44 | let loop 45 | let lines = new Map([ 46 | [Point.UP, Array(grid.width).fill().map(() => new RangeSet())], 47 | [Point.DOWN, Array(grid.width).fill().map(() => new RangeSet())], 48 | [Point.LEFT, Array(grid.height).fill().map(() => new RangeSet())], 49 | [Point.RIGHT, Array(grid.height).fill().map(() => new RangeSet())] 50 | ]) 51 | 52 | while (true) { 53 | let cur = dir.y == 0 ? head.x : head.y 54 | let other = dir.y == 0 ? head.y : head.x 55 | let newCoord = dir.y == 0 ? newBlock.x : newBlock.y 56 | let en = dir.y == 0 ? (newBlock.y == head.y) : (newBlock.x == head.x) 57 | let blockSet = dir.y == 0 ? rowBlocks[head.y] : columnBlocks[head.x] 58 | let block 59 | 60 | if ((dir.x || dir.y) > 0) { 61 | for (let i = 0; i < blockSet.length; i++) { 62 | if (en && newCoord < blockSet[i]) { 63 | if (cur < newCoord) { 64 | block = newCoord 65 | break 66 | } 67 | } 68 | 69 | if (cur < blockSet[i]) { 70 | block = blockSet[i] 71 | break 72 | } 73 | } 74 | 75 | if (en && block == undefined && cur < newCoord) { 76 | block = newCoord 77 | } 78 | } else { 79 | for (let i = blockSet.length - 1; i >= 0; i--) { 80 | if (en && newCoord > blockSet[i]) { 81 | if (cur > newCoord) { 82 | block = newCoord 83 | break 84 | } 85 | } 86 | 87 | if (cur > blockSet[i]) { 88 | block = blockSet[i] 89 | break 90 | } 91 | } 92 | 93 | if (en && block == undefined && cur > newCoord) { 94 | block = newCoord 95 | } 96 | } 97 | 98 | if (block == undefined) { 99 | loop = false 100 | break 101 | } 102 | 103 | let lineSet = lines.get(dir)[other] 104 | let range = new Range(Math.min(cur, block), Math.max(cur, block)) 105 | if (lineSet.intersects(new RangeSet([range]))) { 106 | loop = true 107 | break 108 | } 109 | lineSet.addRangeMut(range).reduceMut() 110 | 111 | head.addMut(dir.mult(range.l - 1)) 112 | dir = dir.cwConst 113 | } 114 | 115 | count += loop 116 | } 117 | 118 | return count 119 | } 120 | 121 | if (typeof window == "undefined") { 122 | module.exports = day6 123 | } 124 | -------------------------------------------------------------------------------- /2024/7.js: -------------------------------------------------------------------------------- 1 | function day7(input, part2) { 2 | let sum = 0 3 | 4 | for (let line of input.split("\n")) { 5 | let [target, start, ...nums] = line.ints() 6 | 7 | let cur = [target] 8 | 9 | for (let i = nums.length - 1; i >= 0; i--) { 10 | let next = [] 11 | let num1 = nums[i] 12 | 13 | for (let num2 of cur) { 14 | let val = num2 / num1 15 | if (val == Math.floor(val)) { 16 | next.push(val) 17 | } 18 | 19 | num2 -= num1 20 | next.push(num2) 21 | 22 | if (part2) { 23 | let power = 1 24 | 25 | while ((num2 /= 10) == Math.floor(num2)) { 26 | power *= 10 27 | 28 | if (power > num1) { 29 | next.push(num2) 30 | break 31 | } 32 | } 33 | } 34 | } 35 | 36 | cur = next 37 | } 38 | 39 | if (cur.includes(start)) { 40 | sum += target 41 | } 42 | } 43 | 44 | return sum 45 | } 46 | 47 | if (typeof window == "undefined") { 48 | module.exports = day7 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aocutil 2 | 3 | my question is why are so many people starring this. have you *seen* the horrors i commit in this repo 4 | -------------------------------------------------------------------------------- /aoc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | NODE_INSPECT_RESUME_ON_START=1 node inspect out.js $@ 3 | -------------------------------------------------------------------------------- /binheap.js: -------------------------------------------------------------------------------- 1 | BinHeap = class BinHeap { 2 | constructor(cond = (p, c) => p < c) { 3 | this.cond = cond 4 | this.data = [] 5 | } 6 | 7 | getTop() { return this.data[0] } 8 | getParent(idx) { return idx / 2 | 0 } 9 | getChildLeft(idx) { return 2 * idx } 10 | getChildRight(idx) { return 2 * idx + 1 } 11 | 12 | insert(val) { 13 | this.up(this.data.push(val) - 1) 14 | } 15 | 16 | extract() { 17 | let res = this.data[0] 18 | 19 | if (this.data.length > 1) { 20 | this.data[0] = this.data.pop() 21 | this.down(0) 22 | } else { 23 | this.data = [] 24 | } 25 | 26 | return res 27 | } 28 | 29 | up(idx) { 30 | while (idx > 0) { 31 | let parent = idx / 2 | 0 32 | 33 | if (this.cond(this.data[parent], this.data[idx])) { 34 | break 35 | } 36 | 37 | [this.data[parent], this.data[idx]] = [this.data[idx], this.data[parent]] 38 | idx = parent 39 | } 40 | } 41 | 42 | down(idx) { 43 | while (true) { 44 | let largest = idx 45 | 46 | let left = 2 * idx 47 | let right = 2 * idx + 1 48 | 49 | if (right >= this.data.length) { 50 | return 51 | } 52 | 53 | if (right != this.data.length && this.cond(this.data[left], this.data[largest])) { 54 | largest = left 55 | } 56 | 57 | if (this.cond(this.data[right], this.data[largest])) { 58 | largest = right 59 | } 60 | 61 | if (largest == idx) { 62 | return 63 | } 64 | 65 | [this.data[largest], this.data[idx]] = [this.data[idx], this.data[largest]] 66 | idx = largest 67 | } 68 | } 69 | 70 | indexOf(el) { 71 | return this.data.indexOf(el) 72 | } 73 | 74 | findIndex(func) { 75 | return this.data.findIndex(func) 76 | } 77 | 78 | extractIdx(idx) { 79 | let oldVal = this.data[idx] 80 | 81 | if (this.data.length > 1) { 82 | this.update(idx, this.data.pop()) 83 | } else if (idx == 0) { 84 | this.data = [] 85 | } 86 | 87 | return oldVal 88 | } 89 | 90 | update(idx, newVal) { 91 | let oldVal = this.data[idx] 92 | this.data[idx] = newVal 93 | 94 | if (this.cond(oldVal, newVal)) { 95 | this.down(idx) 96 | } else { 97 | this.up(idx) 98 | } 99 | 100 | return oldVal 101 | } 102 | } 103 | 104 | -------------------------------------------------------------------------------- /canvas.js: -------------------------------------------------------------------------------- 1 | EventHandler = class EventHandler { 2 | constructor(el) { 3 | for (let type of this.constructor.EVT_TYPES) { 4 | el.addEventListener(type, this) 5 | } 6 | } 7 | 8 | handleEvent(evt) { 9 | if (this.constructor.EVT_TYPES.includes(evt.type)) { 10 | this[evt.type](evt) 11 | evt.preventDefault() 12 | } 13 | } 14 | } 15 | 16 | Keyboard = class Keyboard extends EventHandler { 17 | static EVT_TYPES = [ 18 | "keydown", 19 | "keyup" 20 | ] 21 | 22 | constructor(el) { 23 | super(el) 24 | 25 | this.down = new Set() 26 | 27 | this.lastDown = new Set() 28 | this.press = new Set() 29 | this.release = new Set() 30 | } 31 | 32 | resetFrame() { 33 | this.press.clear() 34 | this.release.clear() 35 | 36 | for (let key of this.down) { 37 | if (!this.lastDown.has(key)) { 38 | this.press.add(key) 39 | } 40 | } 41 | 42 | for (let key of this.lastDown) { 43 | if (!this.down.has(key)) { 44 | this.release.add(key) 45 | } 46 | } 47 | 48 | this.lastDown = new Set(this.down) 49 | } 50 | 51 | keydown(evt) { 52 | this.down.add(evt.key) 53 | } 54 | 55 | keyup(evt) { 56 | this.down.delete(evt.key) 57 | } 58 | } 59 | 60 | Mouse = class Mouse extends EventHandler { 61 | static EVT_TYPES = [ 62 | "mousemove", 63 | "mouseenter", 64 | "mouseleave", 65 | "mouseup", 66 | "mousedown" 67 | ] 68 | 69 | constructor(el) { 70 | super(el) 71 | 72 | this.x = 0 73 | this.y = 0 74 | 75 | this.down = false 76 | this.press = false 77 | 78 | this.resetFrame() 79 | } 80 | 81 | resetFrame() { 82 | if (!this.down) { 83 | this.claimed = null 84 | } 85 | 86 | this.used = false 87 | } 88 | 89 | mousemove(evt) { 90 | this.x = evt.offsetX 91 | this.y = evt.offsetY 92 | 93 | this.press = false 94 | } 95 | 96 | mouseenter(evt) { 97 | this.x = evt.offsetX 98 | this.y = evt.offsetY 99 | 100 | this.down = false 101 | this.press = false 102 | } 103 | 104 | mouseleave(evt) { 105 | this.x = evt.offsetX 106 | this.y = evt.offsetY 107 | 108 | this.down = false 109 | this.press = false 110 | } 111 | 112 | mouseup(evt) { 113 | this.x = evt.offsetX 114 | this.y = evt.offsetY 115 | 116 | this.down = false 117 | this.press = false 118 | } 119 | 120 | mousedown(evt) { 121 | this.x = evt.offsetX 122 | this.y = evt.offsetY 123 | 124 | this.down = true 125 | this.press = true 126 | } 127 | } 128 | 129 | CanvasElement = class CanvasElement { 130 | static HIGHLIGHT_COLOR = "rgb(0, 0, 200)" 131 | static STROKE_COLOR = "rgb(0, 0, 0)" 132 | static TEXT_COLOR = "rgb(0, 0, 0)" 133 | static FILL_COLOR = "rgb(255, 255, 255)" 134 | static HOVER_COLOR = "rgb(235, 235, 235)" 135 | static PRESS_COLOR = "rgb(235, 235, 235)" 136 | 137 | static TEXT_SIZE = 18 138 | 139 | constructor() { 140 | this.paused = false 141 | 142 | this.pauseTimer = false 143 | this.timer = 0 144 | 145 | this.children = new Set() 146 | this.parent = null 147 | this.ctx = null 148 | 149 | this.totalChildren = 0 150 | } 151 | 152 | setCtx(ctx) { 153 | this.ctx = ctx 154 | this.reset() 155 | return this 156 | } 157 | 158 | remove() { 159 | this.parent?.removeElement(this) 160 | } 161 | 162 | addElement(el) { 163 | el.setCtx(this.ctx) 164 | this.children.add(el) 165 | el.parent = this 166 | return this 167 | } 168 | 169 | removeElement(el) { 170 | this.children.delete(el) 171 | el.parent = null 172 | return this 173 | } 174 | 175 | hasElement(el) { 176 | return this.children.has(el) 177 | } 178 | 179 | clearElements(el) { 180 | this.children.forEach((el) => el.parent = null) 181 | this.children.clear() 182 | return this 183 | } 184 | 185 | addTo(el) { 186 | el.addElement(this) 187 | return this 188 | } 189 | 190 | isIn(x, y) { 191 | return false 192 | } 193 | 194 | pause() { 195 | this.reset() 196 | this.paused = true 197 | return this 198 | } 199 | 200 | unpause() { 201 | this.paused = false 202 | return this 203 | } 204 | 205 | reset() { 206 | this.resetPre() 207 | 208 | this.hover = false 209 | this.press = false 210 | this.highlight = false 211 | 212 | for (let el of this.children) { 213 | el.setCtx(this.ctx) 214 | } 215 | 216 | this.resetPost() 217 | } 218 | 219 | resetPre() {} 220 | resetPost() {} 221 | 222 | update(keyboard, mouse) { 223 | if (this.paused) { 224 | return 225 | } 226 | 227 | this.totalChildren = 0 228 | 229 | this.keyboardUpdate(keyboard) 230 | 231 | if (mouse && (mouse.claimed == this || (!mouse.claimed && !mouse.used && this.isIn(mouse.x, mouse.y)))) { 232 | mouse.used = true 233 | 234 | this.hover = true 235 | 236 | this.mouseUpdate(mouse) 237 | 238 | if (mouse.press) { 239 | this.press = true 240 | this.mouseClick(mouse) 241 | } else if (!mouse.down && this.press) { 242 | this.press = false 243 | this.mouseRelease(mouse) 244 | } 245 | } else { 246 | this.hover = false 247 | this.press = false 248 | this.highlight = false 249 | } 250 | 251 | this.updatePre() 252 | 253 | for (let el of this.children) { 254 | el.update(keyboard, mouse) 255 | this.totalChildren += el.totalChildren + 1 256 | } 257 | 258 | this.updatePost() 259 | 260 | if (!this.pauseTimer) { 261 | this.timer++ 262 | } 263 | } 264 | 265 | updatePre() {} 266 | updatePost() {} 267 | 268 | keyboardUpdate(keyboard) {} 269 | 270 | mouseUpdate(mouse) {} 271 | mouseClick(mouse) {} 272 | mouseRelease(mouse) {} 273 | 274 | draw() { 275 | if (this.paused) { 276 | return 277 | } 278 | 279 | this.drawPre() 280 | 281 | for (let el of this.children) { 282 | el.draw() 283 | } 284 | 285 | this.drawPost() 286 | } 287 | 288 | drawPre() {} 289 | drawPost() {} 290 | 291 | drawTop() { 292 | this.drawTopPre() 293 | 294 | for (let el of this.children) { 295 | el.drawTop() 296 | } 297 | 298 | this.drawTopPost() 299 | } 300 | 301 | drawTopPre() {} 302 | drawTopPost() {} 303 | 304 | getStrokeColor() { 305 | return this.highlight ? this.constructor.HIGHLIGHT_COLOR : this.constructor.STROKE_COLOR 306 | } 307 | 308 | getTextColor() { 309 | return this.highlight ? this.constructor.HIGHLIGHT_COLOR : this.constructor.TEXT_COLOR 310 | } 311 | 312 | getFillColor() { 313 | return this.press ? this.constructor.PRESS_COLOR : 314 | this.hover ? this.constructor.HOVER_COLOR : this.constructor.FILL_COLOR 315 | } 316 | 317 | getTextSize() { 318 | return this.constructor.TEXT_SIZE 319 | } 320 | } 321 | 322 | CanvasController = class CanvasController extends CanvasElement { 323 | static TARGET_FPS = 60 324 | 325 | constructor(width, height, updateCallback = () => {}) { 326 | super() 327 | 328 | this.width = width 329 | this.height = height 330 | this.updateCallback = updateCallback 331 | 332 | this.frameTimes = [] 333 | 334 | this.targetFps = CanvasController.TARGET_FPS 335 | this.fps = this.targetFps 336 | this.fpsSlope = 0 337 | 338 | this.resetCanvas() 339 | } 340 | 341 | resetCanvas() { 342 | this.canvas = document.getElementById("canvas") 343 | 344 | if (this.canvas) { 345 | let newCanvas = this.canvas.cloneNode() 346 | this.canvas.replaceWith(newCanvas) 347 | this.canvas = newCanvas 348 | } else { 349 | this.canvas = document.createElement("canvas") 350 | this.canvas.id = "canvas" 351 | document.body.appendChild(this.canvas) 352 | } 353 | 354 | this.canvas.width = this.width 355 | this.canvas.height = this.height 356 | 357 | this.canvas.setAttribute("tabindex", 1) 358 | 359 | this.keyboard = new Keyboard(this.canvas) 360 | this.mouse = new Mouse(this.canvas) 361 | 362 | this.setCtx(this.canvas.getContext("2d")) 363 | } 364 | 365 | updatePre() { 366 | let oldTime = this.frameTimes[0] 367 | let time = performance.now() 368 | 369 | this.frameTimes.push(time) 370 | 371 | if (this.frameTimes.length > 30) { 372 | this.frameTimes.shift() 373 | } 374 | 375 | if (oldTime) { 376 | let average = 1000 * this.frameTimes.length / (time - oldTime) 377 | let change = this.fpsSlope + 0.1 * (average - (this.fps + this.fpsSlope)) 378 | this.fps = Math.max(this.fps + change, 0) 379 | this.fpsSlope += 0.1 * (change - this.fpsSlope) 380 | } 381 | } 382 | 383 | updatePost() { 384 | this.updateCallback.call(this) 385 | this.draw() 386 | this.drawTop() 387 | } 388 | 389 | drawPre() { 390 | this.ctx.fillStyle = "rgb(255, 255, 255)" 391 | this.ctx.fillRect(0, 0, this.width, this.height) 392 | 393 | this.ctx.strokeStyle = "rgb(0, 0, 0)" 394 | this.ctx.lineWidth = 1 395 | this.ctx.strokeRect(0, 0, this.width, this.height) 396 | } 397 | 398 | drawPost() { 399 | this.ctx.fillStyle = "rgb(0, 0, 0)" 400 | this.ctx.textAlign = "left" 401 | this.ctx.textBaseline = "alphabetic" 402 | this.ctx.font = "18px monospace" 403 | this.ctx.fillText(this.fps.toPrecision(5), 5, 18) 404 | this.ctx.fillText(this.totalChildren, 5, 36) 405 | 406 | this.ctx.textAlign = "right" 407 | this.ctx.fillText(this.mouse.x + ", " + this.mouse.y, 1195, 18) 408 | } 409 | 410 | tick() { 411 | this.keyboard.resetFrame() 412 | this.mouse.resetFrame() 413 | 414 | this.update(this.keyboard, this.mouse) 415 | } 416 | 417 | loop(id) { 418 | if (window.currentControllerId == id) { 419 | this.tick() 420 | requestAnimationFrame(() => this.loop(id)) 421 | } 422 | } 423 | 424 | start() { 425 | this.loop(window.currentControllerId = Math.random()) 426 | return this 427 | } 428 | } 429 | 430 | Button = class Button extends CanvasElement { 431 | static ACTIVE_COLOR = "rgb(215, 215, 255)" 432 | 433 | static DEPRESSION_PRESS = 2 434 | static DEPRESSION_PRESS_TOGGLE = 3 435 | static DEPRESSION_ACTIVE = 2 436 | 437 | constructor(x, y, width, height, label, callback, toggle = null) { 438 | super() 439 | 440 | this.x = x 441 | this.y = y 442 | this.width = width 443 | this.height = height 444 | this.label = label 445 | this.callback = callback.bind(this) 446 | this.toggle = toggle 447 | 448 | if (this.toggle) { 449 | this.callback = function() { 450 | let orig = this.toggle.obj[this.toggle.prop] 451 | callback.call(this, orig) 452 | this.toggle.obj[this.toggle.prop] = !orig 453 | } 454 | } 455 | 456 | this.depression = 0 457 | this.active = false 458 | } 459 | 460 | isIn(x, y) { 461 | return x >= this.x && x <= this.x + this.width && 462 | y >= this.y && y <= this.y + this.height 463 | } 464 | 465 | mouseClick(mouse) { 466 | mouse.claimed = this 467 | } 468 | 469 | mouseRelease(mouse) { 470 | this.callback() 471 | } 472 | 473 | updatePost() { 474 | this.active = this.toggle?.obj[this.toggle.prop] 475 | 476 | if (this.depression) { 477 | this.press = true 478 | } else if (this.press) { 479 | this.depression = this.toggle ? this.constructor.DEPRESSION_PRESS_TOGGLE : this.constructor.DEPRESSION_PRESS 480 | } else if (this.active) { 481 | this.depression = this.constructor.DEPRESSION_ACTIVE 482 | } 483 | } 484 | 485 | drawTopPost() { 486 | this.ctx.strokeStyle = this.getStrokeColor() 487 | this.ctx.lineWidth = 1 488 | this.ctx.textAlign = "center" 489 | this.ctx.textBaseline = "middle" 490 | this.ctx.font = `${this.getTextSize()}px monospace` 491 | 492 | this.ctx.fillStyle = this.getStrokeColor() 493 | this.ctx.beginPath() 494 | this.ctx.roundRect(this.x + 3, this.y + 3, this.width, this.height, 5) 495 | this.ctx.fill() 496 | this.ctx.stroke() 497 | 498 | this.ctx.fillStyle = this.getFillColor() 499 | this.ctx.beginPath() 500 | this.ctx.roundRect(this.x + this.depression, this.y + this.depression, this.width, this.height, 5) 501 | this.ctx.fill() 502 | this.ctx.stroke() 503 | 504 | this.ctx.fillStyle = this.getTextColor() 505 | this.ctx.fillText(this.label, this.x + this.width / 2 + this.depression, this.y + this.height / 2 + this.depression) 506 | 507 | this.depression = 0 508 | } 509 | 510 | getFillColor() { 511 | return this.active ? this.constructor.ACTIVE_COLOR : super.getFillColor() 512 | } 513 | } 514 | 515 | KeyButton = class KeyButton extends Button { 516 | constructor(x, y, width, height, label, triggers, type, callback, toggle) { 517 | super(x, y, width, height, label, callback, toggle) 518 | 519 | this.triggers = triggers 520 | this.type = type 521 | } 522 | 523 | keyboardUpdate(keyboard) { 524 | this.depression = 0 525 | 526 | for (let key of this.triggers) { 527 | if (keyboard.down.has(key)) { 528 | this.depression = this.constructor.DEPRESSION_PRESS_TOGGLE 529 | } 530 | 531 | if (keyboard[this.type].has(key)) { 532 | this.callback() 533 | } 534 | } 535 | } 536 | } 537 | 538 | -------------------------------------------------------------------------------- /canvas/t.js: -------------------------------------------------------------------------------- 1 | class Mouse { 2 | static EVT_TYPES = [ 3 | "mousemove", 4 | "mouseenter", 5 | "mouseleave", 6 | "mouseup", 7 | "mousedown" 8 | ] 9 | 10 | constructor(el) { 11 | this.x = 0 12 | this.y = 0 13 | 14 | this.down = false 15 | this.press = false 16 | 17 | for (let type of Mouse.EVT_TYPES) { 18 | el.addEventListener(type, this) 19 | } 20 | } 21 | 22 | handleEvent(evt) { 23 | if (Mouse.EVT_TYPES.includes(evt.type)) { 24 | this[evt.type](evt) 25 | evt.preventDefault() 26 | } 27 | } 28 | 29 | mousemove(evt) { 30 | this.x = evt.offsetX 31 | this.y = evt.offsetY 32 | 33 | this.press = false 34 | } 35 | 36 | mouseenter(evt) { 37 | this.x = evt.offsetX 38 | this.y = evt.offsetY 39 | 40 | this.down = false 41 | this.press = false 42 | } 43 | 44 | mouseleave(evt) { 45 | this.x = evt.offsetX 46 | this.y = evt.offsetY 47 | 48 | this.down = false 49 | this.press = false 50 | } 51 | 52 | mouseup(evt) { 53 | this.x = evt.offsetX 54 | this.y = evt.offsetY 55 | 56 | this.down = false 57 | this.press = false 58 | } 59 | 60 | mousedown(evt) { 61 | this.x = evt.offsetX 62 | this.y = evt.offsetY 63 | 64 | this.down = true 65 | this.press = true 66 | } 67 | } 68 | 69 | class CanvasElement { 70 | static HIGHLIGHT_COLOR = "rgb(0, 0, 200)" 71 | static STROKE_COLOR = "rgb(0, 0, 0)" 72 | static TEXT_COLOR = "rgb(0, 0, 0)" 73 | static FILL_COLOR = "rgb(255, 255, 255)" 74 | static HOVER_COLOR = "rgb(235, 235, 235)" 75 | static PRESS_COLOR = "rgb(235, 235, 235)" 76 | 77 | static TEXT_SIZE = 18 78 | 79 | constructor() { 80 | this.children = new Set() 81 | this.ctx = null 82 | } 83 | 84 | setCtx(ctx) { 85 | this.ctx = ctx 86 | this.reset() 87 | return this 88 | } 89 | 90 | addElement(el) { 91 | el.setCtx(this.ctx) 92 | this.children.add(el) 93 | return this 94 | } 95 | 96 | removeElement(el) { 97 | this.children.delete(el) 98 | return this 99 | } 100 | 101 | hasElement(el) { 102 | return this.children.has(el) 103 | } 104 | 105 | clearElements(el) { 106 | this.children.clear() 107 | return this 108 | } 109 | 110 | isIn(x, y) { 111 | return false 112 | } 113 | 114 | reset() { 115 | this.resetPre() 116 | 117 | this.hover = false 118 | this.press = false 119 | this.highlight = false 120 | 121 | for (let el of this.children) { 122 | el.setCtx(this.ctx) 123 | } 124 | 125 | this.resetPost() 126 | } 127 | 128 | resetPre() {} 129 | resetPost() {} 130 | 131 | update(mouse) { 132 | if (mouse && (this.press || this.isIn(mouse.x, mouse.y))) { 133 | this.hover = true 134 | 135 | this.mouseUpdate(mouse) 136 | 137 | if (mouse.press) { 138 | this.press = true 139 | this.mouseClick(mouse) 140 | } else if (!mouse.down && this.press) { 141 | this.press = false 142 | this.mouseRelease(mouse) 143 | } 144 | } else { 145 | this.hover = false 146 | this.press = false 147 | this.highlight = false 148 | } 149 | 150 | for (let el of this.children) { 151 | el.update(mouse) 152 | } 153 | } 154 | 155 | mouseUpdate(mouse) {} 156 | mouseClick(mouse) {} 157 | mouseRelease(mouse) {} 158 | 159 | draw() { 160 | this.drawPre() 161 | 162 | for (let el of this.children) { 163 | el.draw() 164 | } 165 | 166 | this.drawPost() 167 | } 168 | 169 | drawPre() {} 170 | drawPost() {} 171 | 172 | getStrokeColor() { 173 | return this.highlight ? this.constructor.HIGHLIGHT_COLOR : this.constructor.STROKE_COLOR 174 | } 175 | 176 | getTextColor() { 177 | return this.highlight ? this.constructor.HIGHLIGHT_COLOR : this.constructor.TEXT_COLOR 178 | } 179 | 180 | getFillColor() { 181 | return this.press ? this.constructor.PRESS_COLOR : 182 | this.hover ? this.constructor.HOVER_COLOR : this.constructor.FILL_COLOR 183 | } 184 | 185 | getTextSize() { 186 | return this.constructor.TEXT_SIZE 187 | } 188 | } 189 | 190 | class CanvasController extends CanvasElement { 191 | static TARGET_FPS = 60 192 | 193 | constructor(width, height, updateCallback = () => {}) { 194 | super() 195 | 196 | this.width = width 197 | this.height = height 198 | this.updateCallback = updateCallback 199 | 200 | this.frameNum = 0 201 | this.frameTimes = [] 202 | 203 | this.targetFps = CanvasController.TARGET_FPS 204 | this.fps = this.targetFps 205 | this.fpsSlope = 0 206 | 207 | this.resetCanvas() 208 | } 209 | 210 | resetCanvas() { 211 | this.canvas = document.getElementById("canvas") 212 | 213 | if (this.canvas) { 214 | let newCanvas = this.canvas.cloneNode() 215 | this.canvas.replaceWith(newCanvas) 216 | this.canvas = newCanvas 217 | } else { 218 | this.canvas = document.createElement("canvas") 219 | this.canvas.id = "canvas" 220 | document.body.appendChild(this.canvas) 221 | } 222 | 223 | this.canvas.width = this.width 224 | this.canvas.height = this.height 225 | 226 | this.mouse = new Mouse(this.canvas) 227 | 228 | this.setCtx(this.canvas.getContext("2d")) 229 | } 230 | 231 | update(mouse) { 232 | super.update(mouse) 233 | 234 | this.updateFps() 235 | this.updateCallback.call(this) 236 | 237 | this.draw() 238 | } 239 | 240 | drawPre() { 241 | this.ctx.fillStyle = "rgb(255, 255, 255)" 242 | this.ctx.fillRect(0, 0, this.width, this.height) 243 | 244 | this.ctx.strokeStyle = "rgb(0, 0, 0)" 245 | this.ctx.lineWidth = 1 246 | this.ctx.strokeRect(0, 0, this.width, this.height) 247 | } 248 | 249 | drawPost() { 250 | this.ctx.fillStyle = "rgb(0, 0, 0)" 251 | this.ctx.textAlign = "left" 252 | this.ctx.textBaseline = "alphabetic" 253 | this.ctx.font = "18px monospace" 254 | this.ctx.fillText(this.fps.toPrecision(5), 5, 18) 255 | this.ctx.fillText(this.mouse.x + ", " + this.mouse.y, 5, 36) 256 | } 257 | 258 | updateFps() { 259 | this.frameNum++ 260 | 261 | let oldTime = this.frameTimes[0] 262 | let time = performance.now() 263 | 264 | this.frameTimes.push(time) 265 | 266 | if (this.frameTimes.length > 30) { 267 | this.frameTimes.shift() 268 | } 269 | 270 | if (oldTime) { 271 | let average = 1000 * this.frameTimes.length / (time - oldTime) 272 | 273 | let change = this.fpsSlope + 0.1 * (average - (this.fps + this.fpsSlope)) 274 | this.fps = Math.max(this.fps + change, 0) 275 | this.fpsSlope += 0.1 * (change - this.fpsSlope) 276 | } 277 | } 278 | 279 | tick() { 280 | this.update(this.mouse) 281 | } 282 | } 283 | 284 | class Button extends CanvasElement { 285 | constructor(x, y, width, height, label, callback) { 286 | super() 287 | 288 | this.x = x 289 | this.y = y 290 | this.width = width 291 | this.height = height 292 | this.label = label 293 | this.callback = callback.bind(this) 294 | } 295 | 296 | isIn(x, y) { 297 | return x >= this.x && x <= this.x + this.width && 298 | y >= this.y && y <= this.y + this.height 299 | } 300 | 301 | mouseRelease(mouse) { 302 | this.callback() 303 | } 304 | 305 | drawPre() { 306 | this.ctx.strokeStyle = this.getStrokeColor() 307 | this.ctx.lineWidth = 1 308 | this.ctx.textAlign = "center" 309 | this.ctx.textBaseline = "middle" 310 | this.ctx.font = `${this.getTextSize()}px monospace` 311 | 312 | this.ctx.fillStyle = this.getStrokeColor() 313 | this.ctx.beginPath() 314 | this.ctx.roundRect(this.x + 3, this.y + 3, this.width, this.height, 5) 315 | this.ctx.fill() 316 | this.ctx.stroke() 317 | 318 | let depression = this.press ? 2 : 0 319 | 320 | this.ctx.fillStyle = this.getFillColor() 321 | this.ctx.beginPath() 322 | this.ctx.roundRect(this.x + depression, this.y + depression, this.width, this.height, 5) 323 | this.ctx.fill() 324 | this.ctx.stroke() 325 | 326 | this.ctx.fillStyle = this.getTextColor() 327 | this.ctx.fillText(this.label, this.x + this.width / 2 + depression, this.y + this.height / 2 + depression) 328 | } 329 | } 330 | 331 | let controller = new CanvasController(1000, 1000) 332 | 333 | if (window.interval) { 334 | clearInterval(window.interval) 335 | } 336 | 337 | window.interval = setInterval(() => controller.tick(), 1000 / controller.targetFps) -------------------------------------------------------------------------------- /canvas/t3.js: -------------------------------------------------------------------------------- 1 | class Mouse { 2 | static EVT_TYPES = [ 3 | "mousemove", 4 | "mouseenter", 5 | "mouseleave", 6 | "mouseup", 7 | "mousedown" 8 | ] 9 | 10 | constructor(el) { 11 | this.x = 0 12 | this.y = 0 13 | 14 | this.down = false 15 | this.press = false 16 | 17 | for (let type of Mouse.EVT_TYPES) { 18 | el.addEventListener(type, this) 19 | } 20 | } 21 | 22 | handleEvent(evt) { 23 | if (Mouse.EVT_TYPES.includes(evt.type)) { 24 | this[evt.type](evt) 25 | evt.preventDefault() 26 | } 27 | } 28 | 29 | mousemove(evt) { 30 | this.x = evt.offsetX 31 | this.y = evt.offsetY 32 | 33 | this.press = false 34 | } 35 | 36 | mouseenter(evt) { 37 | this.x = evt.offsetX 38 | this.y = evt.offsetY 39 | 40 | this.down = false 41 | this.press = false 42 | } 43 | 44 | mouseleave(evt) { 45 | this.x = evt.offsetX 46 | this.y = evt.offsetY 47 | 48 | this.down = false 49 | this.press = false 50 | } 51 | 52 | mouseup(evt) { 53 | this.x = evt.offsetX 54 | this.y = evt.offsetY 55 | 56 | this.down = false 57 | this.press = false 58 | } 59 | 60 | mousedown(evt) { 61 | this.x = evt.offsetX 62 | this.y = evt.offsetY 63 | 64 | this.down = true 65 | this.press = true 66 | } 67 | } 68 | 69 | class CanvasElement { 70 | static HIGHLIGHT_COLOR = "rgb(0, 0, 200)" 71 | static STROKE_COLOR = "rgb(0, 0, 0)" 72 | static TEXT_COLOR = "rgb(0, 0, 0)" 73 | static FILL_COLOR = "rgb(255, 255, 255)" 74 | static HOVER_COLOR = "rgb(235, 235, 235)" 75 | static PRESS_COLOR = "rgb(235, 235, 235)" 76 | 77 | static TEXT_SIZE = 18 78 | 79 | constructor() { 80 | this.children = new Set() 81 | this.ctx = null 82 | } 83 | 84 | setCtx(ctx) { 85 | this.ctx = ctx 86 | 87 | this.reset(false) 88 | 89 | for (let el of this.children) { 90 | el.setCtx(this.ctx) 91 | } 92 | 93 | return this 94 | } 95 | 96 | addElement(el) { 97 | el.setCtx(this.ctx) 98 | this.children.add(el) 99 | return this 100 | } 101 | 102 | removeElement(el) { 103 | this.children.delete(el) 104 | return this 105 | } 106 | 107 | hasElement(el) { 108 | return this.children.has(el) 109 | } 110 | 111 | clearElements(el) { 112 | this.children.clear() 113 | return this 114 | } 115 | 116 | isIn(x, y) { 117 | return false 118 | } 119 | 120 | reset(resetChildren = true) { 121 | this.hover = false 122 | this.press = false 123 | this.highlight = false 124 | 125 | if (resetChildren) { 126 | for (let el of this.children) { 127 | el.reset(resetChildren) 128 | } 129 | } 130 | } 131 | 132 | update(mouse) { 133 | if (mouse && (this.press || this.isIn(mouse.x, mouse.y))) { 134 | this.hover = true 135 | 136 | this.mouseUpdate(mouse) 137 | 138 | if (mouse.press) { 139 | this.press = true 140 | this.mouseClick(mouse) 141 | } else if (!mouse.down && this.press) { 142 | this.press = false 143 | this.mouseRelease(mouse) 144 | } 145 | } else { 146 | this.hover = false 147 | this.press = false 148 | this.highlight = false 149 | } 150 | 151 | for (let el of this.children) { 152 | el.update(mouse) 153 | } 154 | } 155 | 156 | mouseUpdate(mouse) {} 157 | mouseClick(mouse) {} 158 | mouseRelease(mouse) {} 159 | 160 | draw() { 161 | this.drawPre() 162 | 163 | for (let el of this.children) { 164 | el.draw() 165 | } 166 | 167 | this.drawPost() 168 | } 169 | 170 | drawPre() {} 171 | drawPost() {} 172 | 173 | getStrokeColor() { 174 | return this.highlight ? this.constructor.HIGHLIGHT_COLOR : this.constructor.STROKE_COLOR 175 | } 176 | 177 | getTextColor() { 178 | return this.highlight ? this.constructor.HIGHLIGHT_COLOR : this.constructor.TEXT_COLOR 179 | } 180 | 181 | getFillColor() { 182 | return this.press ? this.constructor.PRESS_COLOR : 183 | this.hover ? this.constructor.HOVER_COLOR : this.constructor.FILL_COLOR 184 | } 185 | 186 | getTextSize() { 187 | return this.constructor.TEXT_SIZE 188 | } 189 | } 190 | 191 | class Button extends CanvasElement { 192 | constructor(x, y, width, height, label, callback) { 193 | super() 194 | 195 | this.x = x 196 | this.y = y 197 | this.width = width 198 | this.height = height 199 | this.label = label 200 | this.callback = callback.bind(this) 201 | } 202 | 203 | isIn(x, y) { 204 | return x >= this.x && x <= this.x + this.width && 205 | y >= this.y && y <= this.y + this.height 206 | } 207 | 208 | mouseRelease(mouse) { 209 | this.callback() 210 | } 211 | 212 | drawPre() { 213 | this.ctx.strokeStyle = this.getStrokeColor() 214 | this.ctx.lineWidth = 1 215 | this.ctx.textAlign = "center" 216 | this.ctx.textBaseline = "middle" 217 | this.ctx.font = `${this.getTextSize()}px monospace` 218 | 219 | this.ctx.fillStyle = this.getStrokeColor() 220 | this.ctx.beginPath() 221 | this.ctx.roundRect(this.x + 3, this.y + 3, this.width, this.height, 5) 222 | this.ctx.fill() 223 | this.ctx.stroke() 224 | 225 | let depression = this.press ? 2 : 0 226 | 227 | this.ctx.fillStyle = this.getFillColor() 228 | this.ctx.beginPath() 229 | this.ctx.roundRect(this.x + depression, this.y + depression, this.width, this.height, 5) 230 | this.ctx.fill() 231 | this.ctx.stroke() 232 | 233 | this.ctx.fillStyle = this.getTextColor() 234 | this.ctx.fillText(this.label, this.x + this.width / 2 + depression, this.y + this.height / 2 + depression) 235 | } 236 | } 237 | 238 | class CanvasController extends CanvasElement { 239 | static TARGET_FPS = 60 240 | 241 | constructor(width, height, updateCallback = () => {}) { 242 | super() 243 | 244 | this.width = width 245 | this.height = height 246 | this.updateCallback = updateCallback 247 | 248 | this.frameNum = 0 249 | this.frameTimes = [] 250 | 251 | this.targetFps = CanvasController.TARGET_FPS 252 | this.fps = this.targetFps 253 | this.fpsSlope = 0 254 | 255 | this.resetCanvas() 256 | } 257 | 258 | resetCanvas() { 259 | this.canvas = document.getElementById("canvas") 260 | 261 | if (this.canvas) { 262 | let newCanvas = this.canvas.cloneNode() 263 | this.canvas.replaceWith(newCanvas) 264 | this.canvas = newCanvas 265 | } else { 266 | this.canvas = document.createElement("canvas") 267 | this.canvas.id = "canvas" 268 | document.body.appendChild(this.canvas) 269 | } 270 | 271 | this.canvas.width = this.width 272 | this.canvas.height = this.height 273 | 274 | this.mouse = new Mouse(this.canvas) 275 | 276 | this.setCtx(this.canvas.getContext("2d")) 277 | } 278 | 279 | update(mouse) { 280 | super.update(mouse) 281 | 282 | this.updateFps() 283 | this.updateCallback.call(this) 284 | 285 | this.draw() 286 | } 287 | 288 | drawPre() { 289 | this.ctx.fillStyle = "rgb(255, 255, 255)" 290 | this.ctx.fillRect(0, 0, this.width, this.height) 291 | 292 | this.ctx.strokeStyle = "rgb(0, 0, 0)" 293 | this.ctx.lineWidth = 1 294 | this.ctx.strokeRect(0, 0, this.width, this.height) 295 | } 296 | 297 | drawPost() { 298 | this.ctx.fillStyle = "rgb(0, 0, 0)" 299 | this.ctx.textAlign = "left" 300 | this.ctx.textBaseline = "alphabetic" 301 | this.ctx.font = "18px monospace" 302 | this.ctx.fillText(this.fps.toPrecision(5), 5, 18) 303 | this.ctx.fillText(this.mouse.x + ", " + this.mouse.y, 5, 36) 304 | } 305 | 306 | updateFps() { 307 | this.frameNum++ 308 | 309 | let oldTime = this.frameTimes[0] 310 | let time = performance.now() 311 | 312 | this.frameTimes.push(time) 313 | 314 | if (this.frameTimes.length > 30) { 315 | this.frameTimes.shift() 316 | } 317 | 318 | if (oldTime) { 319 | let average = 1000 * this.frameTimes.length / (time - oldTime) 320 | 321 | let change = this.fpsSlope + 0.1 * (average - (this.fps + this.fpsSlope)) 322 | this.fps = Math.max(this.fps + change, 0) 323 | this.fpsSlope += 0.1 * (change - this.fpsSlope) 324 | } 325 | } 326 | 327 | tick() { 328 | this.update(this.mouse) 329 | } 330 | } 331 | 332 | class Day23Gfx extends CanvasElement { 333 | constructor(input, x, y, size) { 334 | super() 335 | 336 | this.x = x 337 | this.y = y 338 | this.size = size 339 | 340 | let grid = Grid.fromStr(input) 341 | let offset = new Point(Math.floor(grid.width / 2), Math.floor(grid.height / 2)) 342 | 343 | this.elvesStart = new Set(grid.findIndices((e) => e == "#").map((pt) => pt.sub(offset).encode())) 344 | } 345 | 346 | reset(resetChildren) { 347 | this.elves = new Set(this.elvesStart) 348 | 349 | this.moves = new Map() 350 | this.elfStartingPlaces = new Map() 351 | 352 | this.moved = new Set() 353 | this.alone = new Set() 354 | 355 | for (let elf of this.elves) { 356 | this.elfStartingPlaces.set(elf, elf) 357 | } 358 | 359 | this.directions = ["up", "down", "left", "right"] 360 | this.timer = 0 361 | this.drawTimer = 0 362 | this.drawFrames = 4 363 | this.rounds = 0 364 | 365 | this.resetQueued = false 366 | } 367 | 368 | update(mouse) { 369 | super.update(mouse) 370 | 371 | if (!this.elves) { 372 | return 373 | } 374 | 375 | if (this.timer <= 0) { 376 | goButton.label = "go" 377 | return 378 | } 379 | 380 | if (this.moves.size == 0 && this.drawTimer == 0) { 381 | this.performRound() 382 | this.timer-- 383 | } 384 | } 385 | 386 | drawPre() { 387 | this.ctx.fillStyle = "rgb(0, 0, 200)" 388 | this.ctx.textAlign = "left" 389 | this.ctx.textBaseline = "alphabetic" 390 | this.ctx.font = "18px monospace" 391 | this.ctx.fillText(this.rounds, 75, 18) 392 | 393 | for (let elf of this.elves) { 394 | let pt = Point.decode(elf) 395 | let dest = this.moves.get(elf) 396 | 397 | if (dest && this.drawTimer == 4) { 398 | this.elves.delete(elf) 399 | this.elves.add(dest) 400 | this.elfStartingPlaces.set(dest, this.elfStartingPlaces.get(elf)) 401 | this.elfStartingPlaces.delete(elf) 402 | } 403 | 404 | if (dest || this.moved.has(elf)) { 405 | this.ctx.fillStyle = "rgb(0, 0, 200)" 406 | } else if (this.alone.has(elf)) { 407 | this.ctx.fillStyle = "rgb(0, 0, 0)" 408 | } else { 409 | this.ctx.fillStyle = "rgb(0, 0, 100)" 410 | } 411 | 412 | if (this.resetQueued) { 413 | dest = this.elfStartingPlaces.get(elf) 414 | } 415 | 416 | if (dest) { 417 | pt.addMut(Point.decode(dest).subMut(pt).multMut(this.drawTimer / this.drawFrames)) 418 | } 419 | 420 | let x = this.x + pt.x * this.size 421 | let y = this.y + pt.y * this.size 422 | 423 | //this.ctx.fillRect(this.x + pt.x * this.size, this.y + pt.y * this.size, this.size, this.size) 424 | this.ctx.beginPath() 425 | this.ctx.arc(x, y, this.size / 2, 0, 2 * Math.PI) 426 | this.ctx.fill() 427 | } 428 | 429 | this.drawTimer++ 430 | 431 | if (this.drawTimer > this.drawFrames) { 432 | this.drawTimer = 0 433 | this.moves.clear() 434 | 435 | if (this.resetQueued) { 436 | this.reset(true) 437 | } 438 | } 439 | } 440 | 441 | performRound() { 442 | let propositions = new Map() 443 | 444 | this.moved.clear() 445 | this.alone.clear() 446 | 447 | for (let elf of this.elves) { 448 | let pt = Point.decode(elf) 449 | 450 | if (pt.getUnfilteredAllNeighbors().every((pt) => !this.elves.has(pt.encode()))) { 451 | this.alone.add(elf) 452 | continue 453 | } 454 | 455 | for (let direction of this.directions) { 456 | let dest = pt[direction]() 457 | let encodedDest = dest.encode() 458 | 459 | let toCheck = [encodedDest] 460 | 461 | if (direction == "up" || direction == "down") { 462 | toCheck.push(dest.left().encode(), dest.right().encode()) 463 | } else { 464 | toCheck.push(dest.up().encode(), dest.down().encode()) 465 | } 466 | 467 | if (toCheck.every((pt) => !this.elves.has(pt))) { 468 | propositions.set(encodedDest, propositions.has(encodedDest) ? null : elf) 469 | break 470 | } 471 | } 472 | } 473 | 474 | let changed = false 475 | 476 | for (let [dest, src] of propositions) { 477 | if (src) { 478 | this.moves.set(src, dest) 479 | changed = true 480 | 481 | this.moved.add(dest) 482 | } 483 | } 484 | 485 | this.directions.push(this.directions.shift()) 486 | this.rounds++ 487 | 488 | if (!changed) { 489 | this.timer = 0 490 | } 491 | } 492 | 493 | queueReset() { 494 | this.resetQueued = true 495 | this.drawTimer = 0 496 | this.drawFrames = 15 497 | } 498 | } 499 | 500 | let controller = new CanvasController(1000, 1000) 501 | //controller.targetFps = 40 502 | 503 | let gfx = new Day23Gfx(input, 400, 400, 6) 504 | 505 | let goButton = new Button(125, 10, 50, 25, 506 | "go", function() { this.label = (gfx.timer = gfx.timer > 0 ? 0 : Infinity) ? "stop" : "go" }) 507 | 508 | let stepButton = new Button(185, 10, 50, 25, 509 | "step", () => gfx.timer = 1) 510 | 511 | let resetButton = new Button(245, 10, 60, 25, 512 | "reset", () => gfx.queueReset()) 513 | 514 | controller.addElement(gfx) 515 | controller.addElement(goButton) 516 | controller.addElement(stepButton) 517 | controller.addElement(resetButton) 518 | 519 | if (window.interval) { 520 | clearInterval(window.interval) 521 | } 522 | 523 | window.interval = setInterval(() => controller.tick(), 1000 / controller.targetFps) -------------------------------------------------------------------------------- /canvas/t4.js: -------------------------------------------------------------------------------- 1 | class Mouse { 2 | static EVT_TYPES = [ 3 | "mousemove", 4 | "mouseenter", 5 | "mouseleave", 6 | "mouseup", 7 | "mousedown" 8 | ] 9 | 10 | constructor(el) { 11 | this.x = 0 12 | this.y = 0 13 | 14 | this.down = false 15 | this.press = false 16 | 17 | for (let type of Mouse.EVT_TYPES) { 18 | el.addEventListener(type, this) 19 | } 20 | } 21 | 22 | handleEvent(evt) { 23 | if (Mouse.EVT_TYPES.includes(evt.type)) { 24 | this[evt.type](evt) 25 | evt.preventDefault() 26 | } 27 | } 28 | 29 | mousemove(evt) { 30 | this.x = evt.offsetX 31 | this.y = evt.offsetY 32 | 33 | this.press = false 34 | } 35 | 36 | mouseenter(evt) { 37 | this.x = evt.offsetX 38 | this.y = evt.offsetY 39 | 40 | this.down = false 41 | this.press = false 42 | } 43 | 44 | mouseleave(evt) { 45 | this.x = evt.offsetX 46 | this.y = evt.offsetY 47 | 48 | this.down = false 49 | this.press = false 50 | } 51 | 52 | mouseup(evt) { 53 | this.x = evt.offsetX 54 | this.y = evt.offsetY 55 | 56 | this.down = false 57 | this.press = false 58 | } 59 | 60 | mousedown(evt) { 61 | this.x = evt.offsetX 62 | this.y = evt.offsetY 63 | 64 | this.down = true 65 | this.press = true 66 | } 67 | } 68 | 69 | class CanvasElement { 70 | static HIGHLIGHT_COLOR = "rgb(0, 0, 200)" 71 | static STROKE_COLOR = "rgb(0, 0, 0)" 72 | static TEXT_COLOR = "rgb(0, 0, 0)" 73 | static FILL_COLOR = "rgb(255, 255, 255)" 74 | static HOVER_COLOR = "rgb(235, 235, 235)" 75 | static PRESS_COLOR = "rgb(235, 235, 235)" 76 | 77 | static TEXT_SIZE = 18 78 | 79 | constructor() { 80 | this.pauseTimer = false 81 | this.timer = 0 82 | 83 | this.children = new Set() 84 | this.ctx = null 85 | } 86 | 87 | setCtx(ctx) { 88 | this.ctx = ctx 89 | this.reset() 90 | return this 91 | } 92 | 93 | addElement(el) { 94 | el.setCtx(this.ctx) 95 | this.children.add(el) 96 | return this 97 | } 98 | 99 | removeElement(el) { 100 | this.children.delete(el) 101 | return this 102 | } 103 | 104 | hasElement(el) { 105 | return this.children.has(el) 106 | } 107 | 108 | clearElements(el) { 109 | this.children.clear() 110 | return this 111 | } 112 | 113 | addTo(el) { 114 | el.addElement(this) 115 | return this 116 | } 117 | 118 | isIn(x, y) { 119 | return false 120 | } 121 | 122 | reset() { 123 | this.resetPre() 124 | 125 | this.hover = false 126 | this.press = false 127 | this.highlight = false 128 | 129 | for (let el of this.children) { 130 | el.setCtx(this.ctx) 131 | } 132 | 133 | this.resetPost() 134 | } 135 | 136 | resetPre() {} 137 | resetPost() {} 138 | 139 | update(mouse) { 140 | this.updatePre() 141 | 142 | if (mouse && (this.press || this.isIn(mouse.x, mouse.y))) { 143 | this.hover = true 144 | 145 | this.mouseUpdate(mouse) 146 | 147 | if (mouse.press) { 148 | this.press = true 149 | this.mouseClick(mouse) 150 | } else if (!mouse.down && this.press) { 151 | this.press = false 152 | this.mouseRelease(mouse) 153 | } 154 | } else { 155 | this.hover = false 156 | this.press = false 157 | this.highlight = false 158 | } 159 | 160 | for (let el of this.children) { 161 | el.update(mouse) 162 | } 163 | 164 | this.updatePost() 165 | 166 | if (!this.pauseTimer) { 167 | this.timer++ 168 | } 169 | } 170 | 171 | updatePre() {} 172 | updatePost() {} 173 | 174 | mouseUpdate(mouse) {} 175 | mouseClick(mouse) {} 176 | mouseRelease(mouse) {} 177 | 178 | draw() { 179 | this.drawPre() 180 | 181 | for (let el of this.children) { 182 | el.draw() 183 | } 184 | 185 | this.drawPost() 186 | } 187 | 188 | drawPre() {} 189 | drawPost() {} 190 | 191 | getStrokeColor() { 192 | return this.highlight ? this.constructor.HIGHLIGHT_COLOR : this.constructor.STROKE_COLOR 193 | } 194 | 195 | getTextColor() { 196 | return this.highlight ? this.constructor.HIGHLIGHT_COLOR : this.constructor.TEXT_COLOR 197 | } 198 | 199 | getFillColor() { 200 | return this.press ? this.constructor.PRESS_COLOR : 201 | this.hover ? this.constructor.HOVER_COLOR : this.constructor.FILL_COLOR 202 | } 203 | 204 | getTextSize() { 205 | return this.constructor.TEXT_SIZE 206 | } 207 | } 208 | 209 | class CanvasController extends CanvasElement { 210 | static TARGET_FPS = 60 211 | 212 | constructor(width, height, updateCallback = () => {}) { 213 | super() 214 | 215 | this.width = width 216 | this.height = height 217 | this.updateCallback = updateCallback 218 | 219 | this.frameTimes = [] 220 | 221 | this.targetFps = CanvasController.TARGET_FPS 222 | this.fps = this.targetFps 223 | this.fpsSlope = 0 224 | 225 | this.resetCanvas() 226 | } 227 | 228 | resetCanvas() { 229 | this.canvas = document.getElementById("canvas") 230 | 231 | if (this.canvas) { 232 | let newCanvas = this.canvas.cloneNode() 233 | this.canvas.replaceWith(newCanvas) 234 | this.canvas = newCanvas 235 | } else { 236 | this.canvas = document.createElement("canvas") 237 | this.canvas.id = "canvas" 238 | document.body.appendChild(this.canvas) 239 | } 240 | 241 | this.canvas.width = this.width 242 | this.canvas.height = this.height 243 | 244 | this.mouse = new Mouse(this.canvas) 245 | 246 | this.setCtx(this.canvas.getContext("2d")) 247 | } 248 | 249 | updatePre() { 250 | let oldTime = this.frameTimes[0] 251 | let time = performance.now() 252 | 253 | this.frameTimes.push(time) 254 | 255 | if (this.frameTimes.length > 30) { 256 | this.frameTimes.shift() 257 | } 258 | 259 | if (oldTime) { 260 | let average = 1000 * this.frameTimes.length / (time - oldTime) 261 | 262 | let change = this.fpsSlope + 0.1 * (average - (this.fps + this.fpsSlope)) 263 | this.fps = Math.max(this.fps + change, 0) 264 | this.fpsSlope += 0.1 * (change - this.fpsSlope) 265 | } 266 | } 267 | 268 | updatePost() { 269 | this.updateCallback.call(this) 270 | this.draw() 271 | } 272 | 273 | drawPre() { 274 | this.ctx.fillStyle = "rgb(255, 255, 255)" 275 | this.ctx.fillRect(0, 0, this.width, this.height) 276 | 277 | this.ctx.strokeStyle = "rgb(0, 0, 0)" 278 | this.ctx.lineWidth = 1 279 | this.ctx.strokeRect(0, 0, this.width, this.height) 280 | } 281 | 282 | drawPost() { 283 | this.ctx.fillStyle = "rgb(0, 0, 0)" 284 | this.ctx.textAlign = "left" 285 | this.ctx.textBaseline = "alphabetic" 286 | this.ctx.font = "18px monospace" 287 | this.ctx.fillText(this.fps.toPrecision(5), 5, 18) 288 | this.ctx.fillText(this.mouse.x + ", " + this.mouse.y, 5, 36) 289 | } 290 | 291 | tick() { 292 | this.update(this.mouse) 293 | } 294 | } 295 | 296 | class Button extends CanvasElement { 297 | constructor(x, y, width, height, label, callback) { 298 | super() 299 | 300 | this.x = x 301 | this.y = y 302 | this.width = width 303 | this.height = height 304 | this.label = label 305 | this.callback = callback.bind(this) 306 | } 307 | 308 | isIn(x, y) { 309 | return x >= this.x && x <= this.x + this.width && 310 | y >= this.y && y <= this.y + this.height 311 | } 312 | 313 | mouseRelease(mouse) { 314 | this.callback() 315 | } 316 | 317 | drawPre() { 318 | this.ctx.strokeStyle = this.getStrokeColor() 319 | this.ctx.lineWidth = 1 320 | this.ctx.textAlign = "center" 321 | this.ctx.textBaseline = "middle" 322 | this.ctx.font = `${this.getTextSize()}px monospace` 323 | 324 | this.ctx.fillStyle = this.getStrokeColor() 325 | this.ctx.beginPath() 326 | this.ctx.roundRect(this.x + 3, this.y + 3, this.width, this.height, 5) 327 | this.ctx.fill() 328 | this.ctx.stroke() 329 | 330 | let depression = this.press ? 2 : 0 331 | 332 | this.ctx.fillStyle = this.getFillColor() 333 | this.ctx.beginPath() 334 | this.ctx.roundRect(this.x + depression, this.y + depression, this.width, this.height, 5) 335 | this.ctx.fill() 336 | this.ctx.stroke() 337 | 338 | this.ctx.fillStyle = this.getTextColor() 339 | this.ctx.fillText(this.label, this.x + this.width / 2 + depression, this.y + this.height / 2 + depression) 340 | } 341 | } 342 | 343 | class BlizzardGfx extends CanvasElement { 344 | static TEXT_COLOR = "rgb(100, 100, 100)" 345 | static FILL_COLOR = "rgb(200, 200, 200)" 346 | 347 | constructor(x, y, vx, vy, size) { 348 | super() 349 | 350 | this.x = x 351 | this.y = y 352 | this.angle = Math.atan2(vy, vx) 353 | this.size = size 354 | } 355 | 356 | drawPre() { 357 | this.ctx.fillStyle = this.getFillColor() 358 | this.ctx.fillRect(this.x, this.y, this.size, this.size) 359 | 360 | /* 361 | this.ctx.beginPath() 362 | this.ctx.arc(this.x, this.y, this.size / 2, 0, 2 * Math.PI) 363 | this.ctx.fill() 364 | */ 365 | 366 | /* 367 | this.ctx.fillStyle = this.getTextColor() 368 | 369 | let triSize = this.size * 3 / 7 370 | let triAngle = this.angle 371 | 372 | this.ctx.beginPath() 373 | this.ctx.moveTo(this.x + triSize * Math.cos(triAngle), this.y + triSize * Math.sin(triAngle)) 374 | triAngle += 2 * Math.PI / 3 375 | this.ctx.lineTo(this.x + triSize * Math.cos(triAngle), this.y + triSize * Math.sin(triAngle)) 376 | triAngle += 2 * Math.PI / 3 377 | this.ctx.lineTo(this.x + triSize * Math.cos(triAngle), this.y + triSize * Math.sin(triAngle)) 378 | this.ctx.fill() 379 | */ 380 | } 381 | } 382 | 383 | let go = false 384 | 385 | class Day24 extends CanvasElement { 386 | static FILL_COLOR = "rgb(200, 0, 0, 0.5)" 387 | 388 | static CARET_DIRECTIONS = { 389 | "^": new Point(0, -1), 390 | "v": new Point(0, 1), 391 | "<": new Point(-1, 0), 392 | ">": new Point(1, 0) 393 | } 394 | 395 | static FRAMES_PER_STEP = 10 396 | 397 | constructor(input, x, y, size) { 398 | super() 399 | 400 | this.input = input 401 | this.x = x 402 | this.y = y 403 | this.size = size 404 | } 405 | 406 | resetPre() { 407 | this.clearElements() 408 | 409 | let grid = Grid.fromStr(this.input) 410 | 411 | this.width = grid.width - 2 412 | this.height = grid.height - 2 413 | 414 | this.start = new Point(1, 0) 415 | this.end = new Point(this.width, this.height + 1) 416 | 417 | this.start.path = [this.start] 418 | 419 | this.poses = new PointArray(this.start) 420 | this.finishedPath = null 421 | 422 | this.blizzards = [] 423 | 424 | grid.forEach((e, pt) => { 425 | switch (e) { 426 | case "^": 427 | case "v": 428 | case "<": 429 | case ">": 430 | this.blizzards.push({ 431 | x: pt.x, 432 | y: pt.y, 433 | vx: Day24.CARET_DIRECTIONS[e].x, 434 | vy: Day24.CARET_DIRECTIONS[e].y, 435 | }) 436 | } 437 | }) 438 | 439 | this.pauseTimer = true 440 | this.timer = 0 441 | 442 | this.steps = 0 443 | } 444 | 445 | updatePre() { 446 | if (go) { 447 | if (this.pauseTimer) { 448 | this.pauseTimer = false 449 | this.timer = 0 450 | } 451 | 452 | go = false 453 | } 454 | 455 | if (this.timer == Day24.FRAMES_PER_STEP) { 456 | this.timer = 0 457 | this.step() 458 | } 459 | 460 | this.updateGfx() 461 | } 462 | 463 | drawPre() { 464 | this.ctx.lineWidth = 1 465 | 466 | if (!this.finishedPath) { 467 | for (let { path } of this.poses) { 468 | this.drawPath(path) 469 | } 470 | } 471 | } 472 | 473 | drawPost() { 474 | this.highlight = true 475 | this.ctx.lineWidth = 2 476 | 477 | if (this.finishedPath) { 478 | this.drawPath(this.finishedPath) 479 | } 480 | } 481 | 482 | updateGfx() { 483 | for (let blizzard of this.blizzards) { 484 | let time = this.timer % Day24.FRAMES_PER_STEP 485 | 486 | let x = blizzard.x + time * blizzard.vx / Day24.FRAMES_PER_STEP 487 | let y = blizzard.y + time * blizzard.vy / Day24.FRAMES_PER_STEP 488 | 489 | if (x <= 0.5) { 490 | x += this.width 491 | } 492 | 493 | if (x >= this.width + 0.5) { 494 | x -= this.width 495 | } 496 | 497 | if (y <= 0.5) { 498 | y += this.height 499 | } 500 | 501 | if (y >= this.height + 0.5) { 502 | y -= this.height 503 | } 504 | 505 | if (!blizzard.gfx) { 506 | blizzard.gfx = new BlizzardGfx(0, 0, blizzard.vx, blizzard.vy, this.size).addTo(this) 507 | } 508 | 509 | blizzard.gfx.x = this.getGfxX(x) 510 | blizzard.gfx.y = this.getGfxY(y) 511 | } 512 | } 513 | 514 | async step() { 515 | let blizzardSet = new Set() 516 | 517 | for (let blizzard of this.blizzards) { 518 | blizzardSet.add(new Point(blizzard.x, blizzard.y).encode()) 519 | } 520 | 521 | let newPoses = new PointArray() 522 | 523 | for (let pos of this.poses) { 524 | let newPath = [...pos.path] 525 | 526 | if (!newPath[newPath.length - 1].equals(pos)) { 527 | newPath.push(pos) 528 | } 529 | 530 | let nexts = pos.getUnfilteredAdjNeighborsIncSelf() 531 | .filter((e) => this.isNotWall(e)) 532 | .filter((e) => !blizzardSet.has(e.encode())) 533 | 534 | for (let next of nexts) { 535 | if (next.isIn(newPoses)) { 536 | continue 537 | } 538 | 539 | if (next.equals(this.end)) { 540 | this.pauseTimer = true 541 | this.finishedPath = newPath 542 | return 543 | } 544 | 545 | next.path = newPath 546 | newPoses.push(next) 547 | } 548 | } 549 | 550 | this.poses = newPoses 551 | 552 | for (let blizzard of this.blizzards) { 553 | blizzard.x = (blizzard.x + blizzard.vx + this.width - 1) % this.width + 1 554 | blizzard.y = (blizzard.y + blizzard.vy + this.height - 1) % this.height + 1 555 | } 556 | 557 | this.steps++ 558 | } 559 | 560 | drawPath(path) { 561 | /* 562 | this.ctx.strokeStyle = this.getStrokeColor() 563 | 564 | this.ctx.beginPath() 565 | this.ctx.moveTo(this.getGfxX(path[0].x), this.getGfxY(path[0].y)) 566 | 567 | let x 568 | let y 569 | 570 | for (let i = 1; i < path.length; i++) { 571 | x = this.getGfxX(path[i].x) 572 | y = this.getGfxY(path[i].y) 573 | this.ctx.lineTo(x, y) 574 | } 575 | 576 | this.ctx.arc(x, y, this.size / 6, 0, 2 * Math.PI) 577 | this.ctx.stroke() 578 | */ 579 | 580 | this.ctx.fillStyle = this.getFillColor() 581 | 582 | let x = this.getGfxX(path[path.length - 1].x) 583 | let y = this.getGfxY(path[path.length - 1].y) 584 | 585 | this.ctx.fillRect(x, y, this.size, this.size) 586 | } 587 | 588 | getGfxX(x) { 589 | return this.x + this.size * (x - this.width / 2) 590 | } 591 | 592 | getGfxY(y) { 593 | return this.y + this.size * (y - this.height / 2) 594 | } 595 | 596 | isNotWall(pt) { 597 | return (pt.x > 0 && pt.x < this.width + 1 && pt.y > 0 && pt.y < this.height + 1) || 598 | pt.equals(this.start) || pt.equals(this.end) 599 | } 600 | } 601 | 602 | let controller = new CanvasController(1200, 800) 603 | 604 | controller.addElement(new Day24(input, controller.width / 2, controller.height / 2, 10)) 605 | 606 | if (window.interval) { 607 | clearInterval(window.interval) 608 | } 609 | 610 | window.interval = setInterval(() => controller.tick(), 1000 / controller.targetFps) -------------------------------------------------------------------------------- /canvas/t6.js: -------------------------------------------------------------------------------- 1 | let graphStr = `jqt: rhn xhk nvd 2 | rsh: frs pzl lsr 3 | xhk: hfx 4 | cmg: qnr nvd lhk bvb 5 | rhn: xhk bvb hfx 6 | bvb: xhk hfx 7 | pzl: lsr hfx nvd 8 | qnr: nvd 9 | ntq: jqt hfx bvb xhk 10 | nvd: lhk 11 | lsr: lhk 12 | rzs: qnr cmg lsr rsh 13 | frs: qnr lhk lsr` 14 | 15 | let graphStr2 = `S: t u v z 16 | E: 1 2 3 4 17 | 1: 2 3 4 18 | 2: 3 4 a 19 | 3: 4 m 20 | 4: n 21 | a: b c d e l 22 | b: c d e 23 | c: d e r 24 | d: e r 25 | e: r 26 | l: m n o p 27 | m: n o p t 28 | n: o p t 29 | o: p 30 | r: t u y z 31 | u: v z 32 | v: w x y z 33 | w: x y z 34 | x: y z 35 | y: z` 36 | 37 | let graphStr3 = `S: t u v z 38 | E: 1 39 | 1: 4 40 | 2: 4 a 41 | 3: m 42 | a: e l 43 | b: c 44 | c: d 45 | d: e 46 | e: r 47 | l: q 48 | m: n 49 | n: t 50 | o: p 51 | r: z 52 | v: o x 53 | w: y z` 54 | 55 | let graphStr4 = input 56 | //graphStr4 = input.split("\n").map((e) => e.slice(1)).join("\n").rea("roadcaster", "bro") + "\nbut -> bro" 57 | 58 | new CanvasController(1200, 800) 59 | .addElement(new GraphicalGraphController(Graph.fromStr(graphStr4, ": ", " ", true))) 60 | .start() -------------------------------------------------------------------------------- /cat.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cat dll.js range.js pt.js grid.js binheap.js graph.js genset.js unionfind.js vm.js intcode.js canvas.js canvas_graph.js z3.js utils.js proto.js test.js repl.js > out.js 4 | -------------------------------------------------------------------------------- /dll.js: -------------------------------------------------------------------------------- 1 | DLLNode = class DLLNode { 2 | constructor(val, prev = this, next = this) { 3 | this.val = val 4 | this.prev = prev 5 | this.next = next 6 | } 7 | 8 | adv(n = 1) { 9 | let node = this 10 | 11 | if (n < 0) { 12 | while (n++) { 13 | node = node.prev 14 | } 15 | } else { 16 | while (n--) { 17 | node = node.next 18 | } 19 | } 20 | 21 | return node 22 | } 23 | } 24 | 25 | DLL = class DLL { 26 | constructor(...a) { 27 | this.h = undefined 28 | this.length = 0 29 | 30 | for (let el of a) { 31 | this.insValEnd(el) 32 | } 33 | } 34 | 35 | static from(a) { 36 | let dll = new DLL() 37 | 38 | for (let el of a) { 39 | dll.insValEnd(el) 40 | } 41 | 42 | return dll 43 | } 44 | 45 | insNodeAheadNode(old, node) { 46 | node.next = old.next 47 | old.next.prev = node 48 | old.next = node 49 | node.prev = old 50 | return ++this.length 51 | } 52 | 53 | insDLLAheadNode(old, dll) { 54 | let start = dll.getNode(0) 55 | let end = dll.getNode(-1) 56 | end.next = old.next 57 | old.next.prev = end 58 | old.next = start 59 | start.prev = old 60 | return this.length += dll.length 61 | } 62 | 63 | insValAheadNode(old, val) { return this.insNodeAheadNode(old, new DLLNode(val)) } 64 | 65 | insNodeStart(node) { 66 | if (this.h) { 67 | return this.insNodeBehindNode(this.h, node) 68 | } else { 69 | this.h = node 70 | return ++this.length 71 | } 72 | } 73 | 74 | insDLLStart(dll) { 75 | if (this.h) { 76 | return this.insDLLBehindNode(this.h, dll) 77 | } else { 78 | this.h = dll.h 79 | return this.length = dll.length 80 | } 81 | } 82 | 83 | insValStart(val) { return this.insNodeStart(new DLLNode(val)) } 84 | 85 | unshift(...vals) { 86 | let ret 87 | vals.reverse().forEach((val) => ret = this.insValStart(val)) 88 | return ret 89 | } 90 | 91 | insNodeBehindNode(old, node) { 92 | node.prev = old.prev 93 | old.prev.next = node 94 | old.prev = node 95 | node.next = old 96 | 97 | if (this.h == old) { 98 | this.h = node 99 | } 100 | 101 | return ++this.length 102 | } 103 | 104 | insDLLBehindNode(old, dll) { 105 | let start = dll.getNode(0) 106 | let end = dll.getNode(-1) 107 | start.prev = old.prev 108 | old.prev.next = start 109 | old.prev = end 110 | end.next = old 111 | 112 | if (this.h == old) { 113 | this.h = dll.h 114 | } 115 | 116 | return this.length += dll.length 117 | } 118 | 119 | insValBehindNode(old, val) { 120 | let node = new DLLNode(val, old.prev, old) 121 | old.prev = node 122 | old.prev.prev.next = node 123 | 124 | if (this.h == old) { 125 | this.h = node 126 | } 127 | 128 | return ++this.length 129 | } 130 | 131 | insNodeEnd(node) { 132 | if (this.h) { 133 | return this.insNodeAheadNode(this.h.prev, node) 134 | } else { 135 | this.h = node 136 | return ++this.length 137 | } 138 | } 139 | 140 | insDLLEnd(dll) { 141 | if (this.h) { 142 | return this.insDLLAheadNode(this.h.prev, dll) 143 | } else { 144 | this.h = dll.h 145 | return this.length = dll.length 146 | } 147 | } 148 | 149 | insValEnd(val) { return this.insNodeEnd(new DLLNode(val)) } 150 | 151 | push(...vals) { 152 | let ret 153 | vals.forEach((val) => ret = this.insValEnd(val)) 154 | return ret 155 | } 156 | 157 | insNodeBehindIdx(idx, node) { return this.insNodeBehindNode(this.getNode(idx), node) } 158 | insDLLBehindIdx(idx, dll) { return this.insDLLBehindNode(this.getNode(idx), dll) } 159 | insValBehindIdx(idx, val) { return this.insValBehindNode(this.getNode(idx), val) } 160 | insNodeAheadIdx(idx, node) { return this.insNodeAheadNode(this.getNode(idx), node) } 161 | insDLLAheadIdx(idx, dll) { return this.insDLLAheadNode(this.getNode(idx), dll) } 162 | insValAheadIdx(idx, val) { return this.insValAheadNode(this.getNode(idx), val) } 163 | 164 | removeNode(node) { 165 | let tmp = node.next 166 | node.prev.next = node.next 167 | tmp.prev = node.prev 168 | this.length-- 169 | 170 | if (node == this.h) { 171 | this.h = this.h.next 172 | } 173 | 174 | if (this.length == 0) { 175 | this.h = undefined 176 | } 177 | 178 | return node 179 | } 180 | 181 | removeIdx(idx) { return this.removeNode(this.getNode(idx)).val } 182 | 183 | shift() { return this.removeIdx(0) } 184 | pop() { return this.removeIdx(-1) } 185 | 186 | removeNodeRange(start, end) { 187 | let result = new DLL() 188 | 189 | while (start != end) { 190 | let next = start.next 191 | result.insNodeEnd(this.removeNode(start)) 192 | start = next 193 | } 194 | 195 | return result 196 | } 197 | 198 | removeIdxRange(startIdx, endIdx) { return this.removeNodeRange(this.getNode(startIdx), this.getNode(endIdx)) } 199 | 200 | getNode(idx) { return this.h.adv(idx) } 201 | get(idx) { return this.getNode(idx).val } 202 | 203 | reverse() { 204 | if (!this.h) { 205 | return this 206 | } 207 | 208 | let node = this.h 209 | 210 | do { 211 | let next = node.next 212 | let tmp = node.prev 213 | node.prev = node.next 214 | node.next = tmp 215 | node = next 216 | } while (node != this.h) 217 | 218 | return this 219 | } 220 | 221 | rotateForward(rot) { 222 | this.h = this.h.adv(rot) 223 | return this 224 | } 225 | 226 | rotateBackward(rot) { 227 | this.h = this.h.adv(-rot) 228 | return this 229 | } 230 | 231 | forEach(f) { 232 | let i = 0 233 | 234 | for (let node of this.nodes()) { 235 | f(node.val, node, i++, this) 236 | } 237 | 238 | return this 239 | } 240 | 241 | mapMut(f) { return this.forEach((val, node, idx, dll) => node.val = f(val, node, idx, dll)) } 242 | map(f) { return this.copy().mapMut(f) } 243 | 244 | includes(val) { 245 | let res = false 246 | this.forEach((e) => res ||= e == val) 247 | return res 248 | } 249 | 250 | toArray() { 251 | let arr = new Array(this.length) 252 | this.forEach((el, _, idx) => arr[idx] = el) 253 | return arr 254 | } 255 | 256 | toJSON() { return this.toArray() } 257 | toString() { return this.toArray().toString() } 258 | 259 | copy() { 260 | let that = new DLL() 261 | this.forEach((e) => that.push(e)) 262 | return that 263 | } 264 | 265 | *[Symbol.iterator]() { 266 | if (!this.h) { 267 | return 268 | } 269 | 270 | let node = this.h 271 | 272 | do { 273 | yield node.val 274 | node = node.next 275 | } while (node != this.h) 276 | } 277 | 278 | *nodes() { 279 | if (!this.h) { 280 | return 281 | } 282 | 283 | let node = this.h 284 | 285 | do { 286 | yield node 287 | node = node.next 288 | } while (node != this.h) 289 | } 290 | } 291 | 292 | 293 | -------------------------------------------------------------------------------- /game.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const readline = require("readline-promise").default; 3 | 4 | let vm = new IntcodeVM(fs.readFileSync("25.txt", "utf8").nums()); 5 | 6 | let rl = readline.createInterface({ 7 | input: process.stdin, 8 | output: process.stdout 9 | }); 10 | 11 | (async () => { 12 | while (!vm.halted) { 13 | vm.run(); 14 | 15 | let str = vm.receiveOutputString().trim(); 16 | 17 | if (str.startsWith("==")) { 18 | console.clear(); 19 | } 20 | 21 | let res = rl.questionAsync(str + " "); 22 | 23 | vm.sendInputString((await res) + "\n"); 24 | } 25 | 26 | process.exit(); 27 | })(); 28 | -------------------------------------------------------------------------------- /genset.js: -------------------------------------------------------------------------------- 1 | // ctor not overridden because it internally calls this.add 2 | GenericSet = class GenericSet extends Set { 3 | encode(obj) { 4 | return obj 5 | } 6 | 7 | decode(key) { 8 | return key 9 | } 10 | 11 | has(obj) { 12 | return super.has(this.encode(obj)) 13 | } 14 | 15 | add(obj) { 16 | return super.add(this.encode(obj)) 17 | } 18 | 19 | delete(obj) { 20 | return super.delete(this.encode(obj)) 21 | } 22 | 23 | forEach(callback, thisArg) { 24 | for (let key of this) { 25 | callback.call(thisArg, this.decode(key), this.decode(key), this) 26 | } 27 | } 28 | 29 | *[Symbol.iterator]() { 30 | for (let key of super[Symbol.iterator]()) { 31 | yield this.decode(key) 32 | } 33 | } 34 | 35 | *keys() { 36 | for (let key of super.keys()) { 37 | yield this.decode(key) 38 | } 39 | } 40 | 41 | *values() { 42 | for (let value of super.values()) { 43 | yield this.decode(value) 44 | } 45 | } 46 | 47 | *entries() { 48 | for (let [key, value] of super.entries()) { 49 | yield [this.decode(key), this.decode(value)] 50 | } 51 | } 52 | } 53 | 54 | GenericMap = class GenericMap extends Map { 55 | encode(obj) { 56 | return obj 57 | } 58 | 59 | decode(key) { 60 | return key 61 | } 62 | 63 | has(obj) { 64 | return super.has(this.encode(obj)) 65 | } 66 | 67 | get(obj) { 68 | return super.get(this.encode(obj)) 69 | } 70 | 71 | set(obj, val) { 72 | return super.set(this.encode(obj), val) 73 | } 74 | 75 | delete(obj) { 76 | return super.delete(this.encode(obj)) 77 | } 78 | 79 | forEach(callback, thisArg) { 80 | for (let [key, value] of this) { 81 | callback.call(thisArg, value, this.decode(key), this) 82 | } 83 | } 84 | 85 | *[Symbol.iterator]() { 86 | for (let [key, value] of super[Symbol.iterator]()) { 87 | yield [this.decode(key), value] 88 | } 89 | } 90 | 91 | *keys() { 92 | for (let key of super.keys()) { 93 | yield this.decode(key) 94 | } 95 | } 96 | 97 | *entries() { 98 | for (let [key, value] of super.entries()) { 99 | yield [this.decode(key), value] 100 | } 101 | } 102 | } 103 | 104 | PointSet = class PointSet extends GenericSet { 105 | encode(pt) { 106 | return pt.toString() 107 | } 108 | 109 | decode(str) { 110 | return Point.fromString(str) 111 | } 112 | } 113 | 114 | NumericPointSet = class NumericPointSet extends GenericSet { 115 | encode(pt) { 116 | return Point.encode(pt) 117 | } 118 | 119 | decode(num) { 120 | return Point.decode(num) 121 | } 122 | } 123 | 124 | PointMap = class PointMap extends GenericMap { 125 | encode(pt) { 126 | return pt.toString() 127 | } 128 | 129 | decode(str) { 130 | return Point.fromString(str) 131 | } 132 | } 133 | 134 | NumericPointMap = class NumericPointMap extends GenericMap { 135 | encode(pt) { 136 | return Point.encode(pt) 137 | } 138 | 139 | decode(num) { 140 | return Point.decode(num) 141 | } 142 | } 143 | 144 | -------------------------------------------------------------------------------- /graph.js: -------------------------------------------------------------------------------- 1 | Cxn = class Cxn { 2 | constructor(src, dest, weight = 1) { 3 | this.src = src 4 | this.dest = dest 5 | this.weight = weight 6 | 7 | this.deleted = false 8 | } 9 | 10 | delete() { 11 | this.src.removeCxn(this.dest) 12 | } 13 | 14 | mirror() { 15 | return this.dest.cxns.get(this.src) 16 | } 17 | } 18 | 19 | SearchData = class SearchData { 20 | constructor(id, dist = Infinity, last = undefined, custom = false) { 21 | this.id = id 22 | this.dist = dist 23 | this.last = last 24 | this.custom = custom 25 | } 26 | 27 | get(id, dist = Infinity, last = undefined, custom = false) { 28 | if (this.id != id) { 29 | this.id = id 30 | this.dist = dist 31 | this.last = last 32 | this.custom = custom 33 | } 34 | 35 | return this 36 | } 37 | 38 | update(id, dist = Infinity, last = undefined, custom = false) { 39 | if (this.id != id || this.dist > dist) { 40 | this.id = id 41 | this.dist = dist 42 | this.last = last 43 | this.custom = custom 44 | return true 45 | } 46 | 47 | return false 48 | } 49 | } 50 | 51 | Node = class Node { 52 | static GLOBAL_ID = 0 53 | static DEBUG = false 54 | 55 | constructor(val, name = "") { 56 | this.id = Node.GLOBAL_ID++ 57 | this.val = val 58 | this.name = name 59 | this.cxns = new Map() 60 | this.searchData = new SearchData() 61 | } 62 | 63 | addCxn(node, weight = 1) { 64 | this.cxns.set(node, new Cxn(this, node, weight)) 65 | return this 66 | } 67 | 68 | removeCxn(node) { 69 | if (this.cxns.has(node)) { 70 | this.cxns.get(node).deleted = true 71 | this.cxns.delete(node) 72 | } 73 | 74 | return this 75 | } 76 | 77 | hasCxn(node) { 78 | return this.cxns.has(node) 79 | } 80 | 81 | getCxn(node) { 82 | return this.cxns.get(node) 83 | } 84 | 85 | getWeight(node) { 86 | return this.getCxn(node).weight 87 | } 88 | 89 | unwrap() { 90 | let path = [this] 91 | 92 | while (path[0].searchData.last) { 93 | path.unshift(path[0].searchData.last) 94 | } 95 | 96 | return path 97 | } 98 | 99 | exploreBfs(addCxns, visited = new Set()) { 100 | let id = Symbol() 101 | 102 | let queue = [this] 103 | 104 | if (Node.DEBUG) { 105 | console.time("exploreBfs") 106 | } 107 | 108 | let i = 0 109 | 110 | this.searchData.update(id, 0, undefined, true) 111 | 112 | while (queue.length) { 113 | let cur = queue.shift() 114 | let depth = cur.searchData.get(id).dist 115 | 116 | visited.add(cur) 117 | 118 | if (addCxns && cur.cxns.size == 0) { 119 | addCxns(cur) 120 | } 121 | 122 | for (let [dest] of cur.cxns) { 123 | if (!dest.searchData.get(id, Infinity, undefined, false).custom) { 124 | dest.searchData.update(id, depth + 1, cur, true) 125 | queue.push(dest) 126 | } 127 | } 128 | 129 | if (Node.DEBUG && ++i % 10000 == 0) { 130 | console.log(i, queue.length) 131 | } 132 | } 133 | 134 | if (Node.DEBUG) { 135 | console.timeEnd("exploreBfs") 136 | } 137 | 138 | return visited 139 | } 140 | 141 | exploreDijkstra(addCxns, visited = new Set(), heapCond = (p, c, pdist, cdist) => pdist <= cdist) { 142 | let id = Symbol() 143 | 144 | let heap = new BinHeap((p, c) => { 145 | let pdist = p.searchData.get(id, Infinity, undefined, true).dist 146 | let cdist = c.searchData.get(id, Infinity, undefined, true).dist 147 | return heapCond(p, c, pdist, cdist) 148 | }) 149 | 150 | heap.insert(this) 151 | 152 | if (Node.DEBUG) { 153 | console.time("explore") 154 | } 155 | 156 | let i = 0 157 | 158 | this.searchData.update(id, 0, undefined, true) 159 | 160 | while (heap.data.length) { 161 | let min = heap.extract() 162 | let minDist = min.searchData.get(id).dist 163 | 164 | visited.add(min) 165 | 166 | if (addCxns && min.cxns.size == 0) { 167 | addCxns(min) 168 | } 169 | 170 | for (let [dest, cxn] of min.cxns) { 171 | let seen = dest.searchData.get(id, Infinity, undefined, false).custom 172 | let dist = minDist + cxn.weight 173 | 174 | if (dest.searchData.update(id, dist, min, true)) { 175 | if (seen) { 176 | heap.up(heap.data.indexOf(dest)) 177 | } else { 178 | heap.insert(dest) 179 | } 180 | } 181 | } 182 | 183 | if (Node.DEBUG && ++i % 10000 == 0) { 184 | console.log(i, heap.data.length, minDist) 185 | } 186 | } 187 | 188 | if (Node.DEBUG) { 189 | console.timeEnd("explore") 190 | } 191 | 192 | return visited 193 | } 194 | 195 | exploreDfs(addCxns, visited = new Set()) { 196 | let id = Symbol() 197 | 198 | let queue = [this] 199 | 200 | if (Node.DEBUG) { 201 | console.time("exploreDfs") 202 | } 203 | 204 | let i = 0 205 | 206 | this.searchData.update(id, 0, undefined, true) 207 | 208 | while (queue.length) { 209 | let cur = queue.pop() 210 | let depth = cur.searchData.get(id).dist 211 | 212 | visited.add(cur) 213 | 214 | if (addCxns && cur.cxns.size == 0) { 215 | addCxns(cur) 216 | } 217 | 218 | for (let [dest] of cur.cxns) { 219 | if (!dest.searchData.get(id, Infinity, undefined, false).custom) { 220 | dest.searchData.update(id, depth + 1, cur, true) 221 | queue.push(dest) 222 | } 223 | } 224 | 225 | if (Node.DEBUG && ++i % 10000 == 0) { 226 | console.log(i, queue.length) 227 | } 228 | } 229 | 230 | if (Node.DEBUG) { 231 | console.timeEnd("exploreDfs") 232 | } 233 | 234 | return visited 235 | } 236 | 237 | bfs(dest, addCxns, visited) { 238 | let isDest 239 | 240 | if (dest instanceof Node) { 241 | isDest = (node) => node == dest 242 | } else if (dest instanceof Array) { 243 | isDest = (node) => dest.includes(node) 244 | } else if (dest instanceof Function) { 245 | isDest = dest 246 | } else { 247 | throw "Node.bfs: Unrecognized destination type" 248 | } 249 | 250 | let id = Symbol() 251 | 252 | let queue = [this] 253 | 254 | if (Node.DEBUG) { 255 | console.time("bfs") 256 | } 257 | 258 | let i = 0 259 | 260 | this.searchData.update(id, 0, undefined, true) 261 | 262 | while (queue.length) { 263 | let cur = queue.shift() 264 | let depth = cur.searchData.get(id).dist 265 | 266 | visited?.add(cur) 267 | 268 | if (isDest(cur)) { 269 | if (Node.DEBUG) { 270 | console.log(i, queue.length) 271 | console.timeEnd("bfs") 272 | } 273 | 274 | return cur 275 | } 276 | 277 | if (addCxns && cur.cxns.size == 0) { 278 | addCxns(cur) 279 | } 280 | 281 | for (let [dest] of cur.cxns) { 282 | if (!dest.searchData.get(id, Infinity, undefined, false).custom) { 283 | dest.searchData.update(id, depth + 1, cur, true) 284 | queue.push(dest) 285 | } 286 | } 287 | 288 | if (Node.DEBUG && ++i % 10000 == 0) { 289 | console.log(i, queue.length) 290 | } 291 | } 292 | 293 | if (Node.DEBUG) { 294 | console.timeEnd("bfs") 295 | console.warn("Node.bfs: Could not find a path") 296 | } 297 | } 298 | 299 | dijkstra(dest, addCxns, visited, heapCond = (p, c, pdist, cdist) => pdist <= cdist) { 300 | let isDest 301 | 302 | if (dest instanceof Node) { 303 | isDest = (node) => node == dest 304 | } else if (dest instanceof Array) { 305 | isDest = (node) => dest.includes(node) 306 | } else if (dest instanceof Function) { 307 | isDest = dest 308 | } else { 309 | throw "Node.dijkstra: Unrecognized destination type" 310 | } 311 | 312 | let id = Symbol() 313 | 314 | let heap = new BinHeap((p, c) => { 315 | let pdist = p.searchData.get(id, Infinity, undefined, true).dist 316 | let cdist = c.searchData.get(id, Infinity, undefined, true).dist 317 | return heapCond(p, c, pdist, cdist) 318 | }) 319 | 320 | heap.insert(this) 321 | 322 | if (Node.DEBUG) { 323 | console.time("dijkstra") 324 | } 325 | 326 | let i = 0 327 | 328 | this.searchData.update(id, 0, undefined, true) 329 | 330 | while (heap.data.length) { 331 | let min = heap.extract() 332 | let minDist = min.searchData.get(id).dist 333 | 334 | visited?.add(min) 335 | 336 | if (isDest(min)) { 337 | if (Node.DEBUG) { 338 | console.log(i, heap.data.length, minDist) 339 | console.timeEnd("dijkstra") 340 | } 341 | 342 | return min 343 | } 344 | 345 | if (addCxns && min.cxns.size == 0) { 346 | addCxns(min) 347 | } 348 | 349 | for (let [dest, cxn] of min.cxns) { 350 | let seen = dest.searchData.get(id, Infinity, undefined, false).custom 351 | let dist = minDist + cxn.weight 352 | 353 | if (dest.searchData.update(id, dist, min, true)) { 354 | if (seen) { 355 | heap.up(heap.data.indexOf(dest)) 356 | } else { 357 | heap.insert(dest) 358 | } 359 | } 360 | } 361 | 362 | if (Node.DEBUG && ++i % 10000 == 0) { 363 | console.log(i, heap.data.length, minDist) 364 | } 365 | } 366 | 367 | if (Node.DEBUG) { 368 | console.timeEnd("dijkstra") 369 | console.warn("Node.dijkstra: Could not find a path") 370 | } 371 | } 372 | 373 | dfs(dest, addCxns, visited) { 374 | let isDest 375 | 376 | if (dest instanceof Node) { 377 | isDest = (node) => node == dest 378 | } else if (dest instanceof Array) { 379 | isDest = (node) => dest.includes(node) 380 | } else if (dest instanceof Function) { 381 | isDest = dest 382 | } else { 383 | throw "Node.dfs: Unrecognized destination type" 384 | } 385 | 386 | let id = Symbol() 387 | 388 | let queue = [this] 389 | 390 | if (Node.DEBUG) { 391 | console.time("dfs") 392 | } 393 | 394 | let i = 0 395 | 396 | this.searchData.update(id, 0, undefined, true) 397 | 398 | while (queue.length) { 399 | let cur = queue.pop() 400 | let depth = cur.searchData.get(id).dist 401 | 402 | visited?.add(cur) 403 | 404 | if (isDest(cur)) { 405 | if (Node.DEBUG) { 406 | console.log(i, queue.length) 407 | console.timeEnd("dfs") 408 | } 409 | 410 | return cur 411 | } 412 | 413 | if (addCxns && cur.cxns.size == 0) { 414 | addCxns(cur) 415 | } 416 | 417 | for (let [dest] of cur.cxns) { 418 | let visited = dest.searchData.get(id, Infinity, undefined, false).custom 419 | if (!visited) { 420 | dest.searchData.update(id, depth + 1, cur, true) 421 | queue.push(dest) 422 | } 423 | } 424 | 425 | if (Node.DEBUG && ++i % 10000 == 0) { 426 | console.log(i, queue.length) 427 | } 428 | } 429 | 430 | if (Node.DEBUG) { 431 | console.timeEnd("dfs") 432 | console.warn("Node.dfs: Could not find a path") 433 | } 434 | } 435 | 436 | furthestBfs(addCxns) { 437 | let max = this 438 | 439 | for (let node of this.exploreBfs(addCxns)) { 440 | if (max.searchData.dist < node.searchData.dist) { 441 | max = node 442 | } 443 | } 444 | 445 | return max 446 | } 447 | 448 | furthestDijkstra(addCxns) { 449 | let max = this 450 | 451 | for (let node of this.exploreDijkstra(addCxns)) { 452 | if (max.searchData.dist < node.searchData.dist) { 453 | max = node 454 | } 455 | } 456 | 457 | return max 458 | } 459 | 460 | furthestsBfs(addCxns) { 461 | let max = [this] 462 | 463 | for (let node of this.exploreBfs(addCxns)) { 464 | let maxDist = max[0].searchData.dist 465 | let dist = node.searchData.dist 466 | 467 | if (maxDist < dist) { 468 | max = [node] 469 | } 470 | 471 | if (maxDist == dist) { 472 | max.push(node) 473 | } 474 | } 475 | 476 | return max 477 | } 478 | 479 | furthestsDijkstra(addCxns) { 480 | let max = [this] 481 | 482 | for (let node of this.exploreDijkstra(addCxns)) { 483 | let maxDist = max[0].searchData.dist 484 | let dist = node.searchData.dist 485 | 486 | if (maxDist < dist) { 487 | max = [node] 488 | } 489 | 490 | if (maxDist == dist) { 491 | max.push(node) 492 | } 493 | } 494 | 495 | return max 496 | } 497 | } 498 | 499 | Graph = class Graph extends Map { 500 | static fromStr(str, sep1 = " => ", sep2 = ", ", sep3 = " = ", symmetric = false) { 501 | let graph = new Graph() 502 | 503 | for (let line of str.split("\n")) { 504 | if (!line) { 505 | continue 506 | } 507 | 508 | let [src, rest] = line.split(sep1) 509 | let [dests, weight] = rest.split(sep3) 510 | 511 | if (!weight) { 512 | weight = 1 513 | } 514 | 515 | for (let dest of dests.split(sep2)) { 516 | let srcNode = graph.getDef(src) 517 | let destNode = graph.getDef(dest) 518 | 519 | srcNode.addCxn(destNode, weight) 520 | if (symmetric) { 521 | destNode.addCxn(srcNode, weight) 522 | } 523 | } 524 | } 525 | 526 | return graph 527 | } 528 | 529 | constructor(nodes = []) { 530 | super() 531 | 532 | this.gfx = null 533 | 534 | for (let node of nodes) { 535 | this.addNode(node) 536 | } 537 | } 538 | 539 | copy() { 540 | return new Graph(this.values()) 541 | } 542 | 543 | add(key) { 544 | this.addNode(new Node(key, key)) 545 | } 546 | 547 | addNode(node) { 548 | let key = node.name || node.val 549 | 550 | if (this.has(key)) { 551 | console.error(this.get(key)) 552 | console.error(node) 553 | throw "Graph.Graph: two nodes with same value" 554 | } 555 | 556 | this.set(key, node) 557 | } 558 | 559 | connect(key1, key2, weight) { 560 | this.get(key1).addCxn(this.get(key2), weight) 561 | } 562 | 563 | delete(key) { 564 | let node = this.get(key) 565 | 566 | if (!node) { 567 | return false 568 | } 569 | 570 | for (let otherNode of this.values()) { 571 | otherNode.removeCxn(node) 572 | } 573 | 574 | return super.delete(key) 575 | } 576 | 577 | deleteNode(node) { 578 | return this.delete(node.name || node.val) 579 | } 580 | 581 | getDef(key) { 582 | if (!this.has(key)) { 583 | this.add(key) 584 | } 585 | 586 | return this.get(key) 587 | } 588 | 589 | getAssocMap() { 590 | let assocMap = new Map() 591 | 592 | for (let cxn of this.cxns()) { 593 | if (!assocMap.has(cxn.src)) { 594 | assocMap.set(cxn.src, new Set()) 595 | } 596 | 597 | if (!assocMap.has(cxn.dest)) { 598 | assocMap.set(cxn.dest, new Set()) 599 | } 600 | 601 | assocMap.get(cxn.src).add(cxn.dest) 602 | assocMap.get(cxn.dest).add(cxn.src) 603 | } 604 | 605 | return assocMap 606 | } 607 | 608 | numCxns() { 609 | let cxns = 0 610 | 611 | for (let node of this.values()) { 612 | cxns += node.cxns.size 613 | } 614 | 615 | return cxns 616 | } 617 | 618 | isConnected(key) { 619 | let start = key ? this.get(key) : this.values().next().value 620 | return this.size == start.exploreDfs().size 621 | } 622 | 623 | isSymmetric() { 624 | for (let cxn of this.cxns) { 625 | if (cxn.mirror?.weight != cxn.weight) { 626 | return false 627 | } 628 | } 629 | 630 | return true 631 | } 632 | 633 | resetSearch() { 634 | let id = Symbol() 635 | 636 | for (let node of this.values()) { 637 | node.searchData.update(id) 638 | } 639 | 640 | return this 641 | } 642 | 643 | componentsUndirected() { 644 | this.resetSearch() 645 | 646 | let res = [] 647 | 648 | for (let node of this.values()) { 649 | if (!node.searchData.custom) { 650 | res.push(new Graph(node.exploreDfs())) 651 | } 652 | } 653 | 654 | return res 655 | } 656 | 657 | componentsDirected() { 658 | let components = this.componentsUndirected() 659 | 660 | for (let i = components.length - 1; i >= 0; i--) { 661 | for (let node of components[i].values()) { 662 | for (let j = i - 1; j >= 0; j--) { 663 | Map.prototype.delete.call(components[j], node.name || node.val) 664 | } 665 | } 666 | } 667 | 668 | return components.filter((e) => e.size) 669 | } 670 | 671 | spanningTree() { 672 | let id = Symbol() 673 | 674 | let heap = new BinHeap((p, c) => { 675 | let pdist = p.searchData.get(id, Infinity, undefined, true).dist 676 | let cdist = c.searchData.get(id, Infinity, undefined, true).dist 677 | return pdist <= cdist 678 | }) 679 | 680 | for (let node of this.values()) { 681 | heap.insert(node) 682 | } 683 | 684 | let visited = new Set() 685 | 686 | while (heap.data.length) { 687 | let node = heap.extract() 688 | 689 | visited.add(node) 690 | 691 | for (let [dest, cxn] of node.cxns) { 692 | if (!visited.has(dest) && dest.searchData.update(id, cxn.weight, node, true)) { 693 | heap.up(heap.data.indexOf(dest)) 694 | } 695 | } 696 | } 697 | 698 | return visited 699 | } 700 | 701 | visualize(width = 1200, height = 800) { 702 | this.canvasController = new CanvasController(width, height) 703 | this.graphController = new GraphicalGraphController(this) 704 | this.gfx = this.graphController.graphGfx 705 | 706 | return this.canvasController.addElement(this.graphController).start() 707 | } 708 | 709 | *cxns() { 710 | for (let node of this.values()) { 711 | yield* node.cxns.values() 712 | } 713 | } 714 | } 715 | 716 | -------------------------------------------------------------------------------- /grid.js: -------------------------------------------------------------------------------- 1 | Grid = class Grid { 2 | constructor(w, h, fill = 0) { 3 | this.width = w 4 | this.height = h 5 | this.data = utils.createGridArray(w, h, fill) 6 | } 7 | 8 | get w() { return this.width } 9 | set w(val) { this.width = val } 10 | get h() { return this.height } 11 | set h(val) { this.height = val } 12 | 13 | forEach(func) { 14 | for (let y = 0; y < this.height; y++) { 15 | for (let x = 0; x < this.width; x++) { 16 | let pt = new Point(x, y) 17 | func(this.get(pt), pt, this) 18 | } 19 | } 20 | 21 | return this 22 | } 23 | 24 | map(func) { return new Grid(this.width, this.height).mapMut((e, pt) => func(this.get(pt), pt, this)) } 25 | 26 | mapMut(func) { 27 | for (let y = 0; y < this.height; y++) { 28 | for (let x = 0; x < this.width; x++) { 29 | let pt = new Point(x, y) 30 | this.set(pt, func(this.get(pt), pt.copy(), this)) 31 | } 32 | } 33 | 34 | return this 35 | } 36 | 37 | fill(n) { return this.mapMut(() => n) } 38 | 39 | mutate(that) { return this.mapMut((_, pt) => that.get(pt)) } 40 | 41 | fillFromArr(arr) { 42 | if (arr.length != this.height) { 43 | throw `Grid.fillFromArr: Column size ${arr.length} does not match grid height ${this.height}` 44 | } 45 | 46 | 47 | for (let y = 0; y < this.height; y++) { 48 | if (arr[y].length != this.width) { 49 | throw `Grid.fillFromArr: Row ${y} has size ${arr[y].length} instead of grid width ${this.width}` 50 | } 51 | 52 | for (let x = 0; x < this.width; x++) { 53 | this.data[y][x] = arr[y][x] 54 | } 55 | } 56 | 57 | return this 58 | } 59 | 60 | fillFromStr(str, sep = "") { return this.fillFromArr(str.split("\n").map((line) => line.split(sep))) } 61 | 62 | static fromArr(arr) { return new Grid(arr[0].length, arr.length).fillFromArr(arr) } 63 | static fromStr(str, sep = "") { return Grid.fromArr(str.split("\n").map((line) => line.split(sep))) } 64 | 65 | static fromObj(obj, fill = null, translate = false) { 66 | let entries = Object.keys(obj).map((e) => [Point.decode2D(e), obj[e]]).filter((e) => e[0]) 67 | 68 | let minX = entries.minVal((e) => e[0].x) 69 | let maxX = entries.maxVal((e) => e[0].x) 70 | let minY = entries.minVal((e) => e[0].y) 71 | let maxY = entries.maxVal((e) => e[0].y) 72 | 73 | if ((minX < 0 || minY < 0) && !translate) { 74 | console.warn("Grid.fromObj: Object has negative point indices, but translation not specified. Translating anyway") 75 | translate = true 76 | } 77 | 78 | let translation = translate ? new Point(-minX, -minY) : new Point(0, 0) 79 | 80 | let grid = new Grid( 81 | translate ? maxX - minX + 1 : maxX + 1, 82 | translate ? maxY - minY + 1 : maxY + 1, 83 | fill) 84 | 85 | for (let [point, value] of entries) { 86 | grid.set(point.add(translation), value) 87 | } 88 | 89 | return grid 90 | } 91 | 92 | wrap(pt) { 93 | let x = ((pt.x % this.width) + this.width) % this.width 94 | let y = ((pt.y % this.height) + this.height) % this.height 95 | return new Point(x, y) 96 | } 97 | 98 | get(pt) { 99 | if (this.contains(pt)) { 100 | return this.data[pt.y][pt.x] 101 | } else { 102 | console.error("Grid.get: Grid does not contain point " + pt.toString() + ":\n" + this.toString().slice(0, 300)) 103 | throw [this.width, this.height] 104 | } 105 | } 106 | 107 | getDef(pt, def) { 108 | if (this.contains(pt)) { 109 | return this.data[pt.y][pt.x] 110 | } else { 111 | return def 112 | } 113 | } 114 | 115 | getWrap(pt) { 116 | return this.get(this.wrap(pt)) 117 | } 118 | 119 | set(pt, val) { 120 | if (this.contains(pt)) { 121 | this.data[pt.y][pt.x] = val 122 | return this 123 | } else { 124 | console.error("Grid.set: does not contain point " + pt.toString() + ":\n" + this.toString().slice(0, 300)) 125 | throw [this.width, this.height] 126 | } 127 | } 128 | 129 | setWrap(pt, val) { 130 | return this.set(this.wrap(pt), val) 131 | } 132 | 133 | getColumn(x) { 134 | if (x >= 0 && x < this.width) { 135 | return this.data.map((row) => row[x]) 136 | } else { 137 | console.error("Grid.getColumn: does not contain column " + x.toString() + ":\n" + this.toString().slice(0, 300)) 138 | throw [this.width, this.height] 139 | } 140 | } 141 | 142 | getRow(y) { 143 | if (y >= 0 && y < this.height) { 144 | return this.data[y] 145 | } else { 146 | console.error("Grid.getRow: does not contain row " + y.toString() + ":\n" + this.toString().slice(0, 300)) 147 | throw [this.width, this.height] 148 | } 149 | } 150 | 151 | getSection(pt1, pt2) { 152 | if (pt2.x >= pt1.x && pt2.y >= pt2.y) { 153 | return new Grid(pt2.x - pt1.x + 1, pt2.y - pt1.y + 1).mapMut((_, pt) => this.get(pt.add(pt1))) 154 | } else { 155 | console.error("Grid.getSection: Second point " + pt2.toString() + " behind first point " + pt1.toString() + ":\n" + this.toString().slice(0, 300)) 156 | throw [this.width, this.height] 157 | } 158 | } 159 | 160 | getRows() { 161 | return this.data.copy() 162 | } 163 | 164 | getColumns() { 165 | return this.data.transpose() 166 | } 167 | 168 | expand(n, fill = this.get(new Point(0, 0))) { 169 | return new Grid(this.width + n * 2, this.height + n * 2).mapMut((e, pt) => this.getDef(new Point(pt.x - n, pt.y - n), fill)) 170 | } 171 | 172 | findIndex(el) { 173 | let func = functify(el) 174 | 175 | for (let y = 0; y < this.height; y++) { 176 | for (let x = 0; x < this.width; x++) { 177 | let pt = new Point(x, y) 178 | if (func(this.get(pt), pt, this)) { 179 | return pt 180 | } 181 | } 182 | } 183 | 184 | return Point.NONE 185 | } 186 | 187 | find(el) { 188 | return this.get(this.findIndex(el)) 189 | } 190 | 191 | findIndices(el) { 192 | let func = functify(el) 193 | let points = new PointArray() 194 | 195 | for (let y = 0; y < this.height; y++) { 196 | for (let x = 0; x < this.width; x++) { 197 | let pt = new Point(x, y) 198 | if (func(this.get(pt), pt, this)) { 199 | points.push(pt) 200 | } 201 | } 202 | } 203 | 204 | return points 205 | } 206 | 207 | findAll(func) { 208 | let vals = new PointArray() 209 | 210 | for (let y = 0; y < this.height; y++) { 211 | for (let x = 0; x < this.width; x++) { 212 | let pt = new Point(x, y) 213 | let val = this.get(pt) 214 | if (func(val, pt, this)) { 215 | vals.push(val) 216 | } 217 | } 218 | } 219 | 220 | return vals 221 | } 222 | 223 | findAllIndices(el) { 224 | return this.findIndices(el) 225 | } 226 | 227 | filter(func) { 228 | return this.findAll(func) 229 | } 230 | 231 | count(el) { 232 | let func = functify(el) 233 | let count = 0 234 | 235 | for (let y = 0; y < this.height; y++) { 236 | for (let x = 0; x < this.width; x++) { 237 | let pt = new Point(x, y) 238 | if (func(this.get(pt), pt, this)) { 239 | count++ 240 | } 241 | } 242 | } 243 | 244 | return count 245 | } 246 | 247 | indexOf(val) { 248 | for (let y = 0; y < this.height; y++) { 249 | for (let x = 0; x < this.width; x++) { 250 | let pt = new Point(x, y) 251 | if (this.get(pt) == val) { 252 | return pt 253 | } 254 | } 255 | } 256 | 257 | return Point.NONE 258 | } 259 | 260 | includes(val) { 261 | return this.indexOf(val) != Point.NONE 262 | } 263 | 264 | some(el) { 265 | return this.findIndex(el) != Point.NONE 266 | } 267 | 268 | every(el) { 269 | let func = functify(el) 270 | return this.findIndex((e) => !func(e)) == Point.NONE 271 | } 272 | 273 | no(el) { 274 | return !this.some(el) 275 | } 276 | 277 | contains(pt) { return !pt.is3D && pt.x >= 0 && pt.x < this.width && pt.y >= 0 && pt.y < this.height } 278 | 279 | topleft() { return new Point(0, 0) } 280 | topright() { return new Point(this.width - 1, 0) } 281 | bottomleft() { return new Point(0, this.height - 1) } 282 | bottomright() { return new Point(this.width - 1, this.height - 1) } 283 | 284 | tl() { return new Point(0, 0) } 285 | tr() { return new Point(this.width - 1, 0) } 286 | bl() { return new Point(0, this.height - 1) } 287 | br() { return new Point(this.width - 1, this.height - 1) } 288 | 289 | getAdjNeighbors(pt) { return pt.getUnfilteredAdjNeighbors().filter((pt) => this.contains(pt)) } 290 | getAdjNeighborsIncSelf(pt) { return pt.getUnfilteredAdjNeighborsIncSelf().filter((pt) => this.contains(pt)) } 291 | getDiagNeighbors(pt) { return pt.getUnfilteredDiagNeighbors().filter((pt) => this.contains(pt)) } 292 | getDiagNeighborsIncSelf(pt) { return pt.getUnfilteredDiagNeighborsIncSelf().filter((pt) => this.contains(pt)) } 293 | getAllNeighbors(pt) { return pt.getUnfilteredAllNeighbors().filter((pt) => this.contains(pt)) } 294 | getAllNeighborsIncSelf(pt) { return pt.getUnfilteredAllNeighborsIncSelf().filter((pt) => this.contains(pt)) } 295 | 296 | getAdjNeighborsThat(pt, func) { return pt.getUnfilteredAdjNeighbors().filter((pt) => this.contains(pt) && func(this.get(pt), pt, this)) } 297 | getAdjNeighborsIncSelfThat(pt, func) { return pt.getUnfilteredAdjNeighborsIncSelf().filter((pt) => this.contains(pt) && func(this.get(pt), pt, this)) } 298 | getDiagNeighborsThat(pt, func) { return pt.getUnfilteredDiagNeighbors().filter((pt) => this.contains(pt) && func(this.get(pt), pt, this)) } 299 | getDiagNeighborsIncSelfThat(pt, func) { return pt.getUnfilteredDiagNeighborsIncSelf().filter((pt) => this.contains(pt) && func(this.get(pt), pt, this)) } 300 | getAllNeighborsThat(pt, func) { return pt.getUnfilteredAllNeighbors().filter((pt) => this.contains(pt) && func(this.get(pt), pt, this)) } 301 | getAllNeighborsIncSelfThat(pt, func) { return pt.getUnfilteredAllNeighborsIncSelf().filter((pt) => this.contains(pt) && func(this.get(pt), pt, this)) } 302 | 303 | getAdjNeighborsWrap(pt) { return pt.getUnfilteredAdjNeighbors().map((pt) => this.wrap(pt)) } 304 | getAdjNeighborsWrapIncSelf(pt) { return pt.getUnfilteredAdjNeighborsIncSelf().map((pt) => this.wrap(pt)) } 305 | getDiagNeighborsWrap(pt) { return pt.getUnfilteredDiagNeighbors().map((pt) => this.wrap(pt)) } 306 | getDiagNeighborsWrapIncSelf(pt) { return pt.getUnfilteredDiagNeighborsIncSelf().map((pt) => this.wrap(pt)) } 307 | getAllNeighborsWrap(pt) { return pt.getUnfilteredAllNeighbors().map((pt) => this.wrap(pt)) } 308 | getAllNeighborsWrapIncSelf(pt) { return pt.getUnfilteredAllNeighborsIncSelf().map((pt) => this.wrap(pt)) } 309 | 310 | getAdjNeighborsWrapThat(pt, func) { return pt.getUnfilteredAdjNeighbors().map((pt) => this.wrap(pt)).filter((pt) => func(this.get(pt), pt, this)) } 311 | getAdjNeighborsWrapIncSelfThat(pt, func) { return pt.getUnfilteredAdjNeighborsIncSelf().map((pt) => this.wrap(pt)).filter((pt) => func(this.get(pt), pt, this)) } 312 | getDiagNeighborsWrapThat(pt, func) { return pt.getUnfilteredDiagNeighbors().map((pt) => this.wrap(pt)).filter((pt) => func(this.get(pt), pt, this)) } 313 | getDiagNeighborsWrapIncSelfThat(pt, func) { return pt.getUnfilteredDiagNeighborsIncSelf().map((pt) => this.wrap(pt)).filter((pt) => func(this.get(pt), pt, this)) } 314 | getAllNeighborsWrapThat(pt, func) { return pt.getUnfilteredAllNeighbors().map((pt) => this.wrap(pt)).filter((pt) => func(this.get(pt), pt, this)) } 315 | getAllNeighborsWrapIncSelfThat(pt, func) { return pt.getUnfilteredAllNeighborsIncSelf().map((pt) => this.wrap(pt)).filter((pt) => func(this.get(pt), pt, this)) } 316 | 317 | static BFS_CONTINUE = 0 318 | static BFS_STOP = 1 319 | static BFS_END = 2 320 | 321 | static CONT = Grid.BFS_CONTINUE 322 | static STOP = Grid.BFS_STOP 323 | static END = Grid.BFS_END 324 | 325 | bfs(pt, func, neighbors = "getAdjNeighbors", limit = 1000) { 326 | let toVisit = new BinHeap((a, b) => a.dist < b.dist || a.dist == b.dist && a.readingOrderCompare(b) < 0) 327 | 328 | let visited = new NumericPointSet() 329 | let count = 0 330 | let end 331 | 332 | let start = pt.copy() 333 | start.dist = 0 334 | start.last = null 335 | toVisit.insert(start) 336 | 337 | out: while (toVisit.data.length > 0 && count++ < limit) { 338 | let pt = toVisit.extract() 339 | let res = func(this.get(pt), pt, this, visited) 340 | 341 | if (res == Grid.BFS_END) { 342 | return pt 343 | } 344 | 345 | if (res == Grid.BFS_CONTINUE) { 346 | for (let neighbor of this[neighbors](pt).filter((pt) => !visited.has(pt))) { 347 | neighbor.dist = pt.dist + 1 348 | neighbor.last = pt 349 | toVisit.insert(neighbor) 350 | } 351 | } 352 | 353 | visited.add(pt) 354 | } 355 | 356 | if (count >= limit) { 357 | console.warn("Limit reached. Aborted.") 358 | } 359 | 360 | return visited 361 | } 362 | 363 | floodfill(pt, oldVal, newVal, neighbors, limit) { 364 | this.bfs(pt, (e, pt) => e == oldVal ? (this.set(pt, newVal), Grid.BFS_CONTINUE) : Grid.BFS_STOP, neighbors, limit) 365 | return this 366 | } 367 | 368 | floodfillExc(pt, newVal, neighbors, limit) { 369 | this.bfs(pt, (e, pt) => e != newVal ? (this.set(pt, newVal), Grid.BFS_CONTINUE) : Grid.BFS_STOP, neighbors, limit) 370 | return this 371 | } 372 | 373 | evolve(func) { 374 | let copy = this.copy() 375 | 376 | for (let y = 0; y < this.height; y++) { 377 | for (let x = 0; x < this.width; x++) { 378 | let pt = new Point(x, y) 379 | this.set(pt, func(copy.get(pt), pt.copy(), copy)) 380 | } 381 | } 382 | 383 | return this 384 | } 385 | 386 | transpose() { 387 | this.data = this.data.transpose() 388 | this.width = this.data[0].length 389 | this.height = this.data.length 390 | return this 391 | } 392 | 393 | reflectX() { 394 | for (let i = 0; i < this.data.length; i++) { 395 | this.data[i].reverse() 396 | } 397 | 398 | return this 399 | } 400 | 401 | reflectY() { 402 | this.data.reverse() 403 | return this 404 | } 405 | 406 | rotate90() { return this.transpose().reflectX() } 407 | rotate180() { return this.reflectX().reflectY() } 408 | rotate270() { return this.reflectX().transpose() } 409 | 410 | rotate(n) { 411 | for (let i = 0; i < Math.abs(n); i++) { 412 | this[n > 0 ? "rotate90" : "rotate270"]() 413 | } 414 | 415 | return this 416 | } 417 | 418 | allTransformations() { 419 | return [ 420 | this.copy(), 421 | this.copy().rotate90(), 422 | this.copy().rotate180(), 423 | this.copy().rotate270(), 424 | this.copy().reflectX(), 425 | this.copy().reflectX().transpose().reflectX(), 426 | this.copy().reflectY(), 427 | this.copy().transpose() 428 | ] 429 | } 430 | 431 | graphify(cxn = (node1, node2, pt1, pt2) => node1.addCxn(node2, node2.val), neighbors = "getAdjNeighbors") { 432 | this.mapMut((e, pt) => { 433 | let node = new Node(e) 434 | node.pos = pt 435 | return node 436 | }) 437 | 438 | this.forEach((e, pt) => this[neighbors](pt).forEach((pt2) => cxn(e, this.get(pt2), pt, pt2))) 439 | 440 | return this 441 | } 442 | 443 | copy() { return this.map((e) => e) } 444 | 445 | toString(sep = "", pts = undefined, ptkey = "#") { 446 | let str = "" 447 | 448 | for (let y = 0; y < this.height; y++) { 449 | for (let x = 0; x < this.width; x++) { 450 | if (pts && new Point(x, y).isIn(pts)) { 451 | str += ptkey 452 | } else { 453 | str += this.data[y][x] 454 | } 455 | 456 | if (x < this.width - 1) { 457 | str += sep 458 | } 459 | } 460 | 461 | if (y < this.height - 1) { 462 | str += "\n" 463 | } 464 | } 465 | 466 | return str 467 | } 468 | 469 | print(sep, pts, ptkey) { console.log(this.toString(sep, pts, ptkey)) } 470 | 471 | *[Symbol.iterator]() { 472 | for (let y = 0; y < this.height; y++) { 473 | for (let x = 0; x < this.width; x++) { 474 | yield this.data[y][x] 475 | } 476 | } 477 | } 478 | } 479 | 480 | G = function G(...args) { 481 | if (typeof args[0] == "string") { 482 | return Grid.fromStr(...args) 483 | } 484 | 485 | return new Grid(...args) 486 | } 487 | 488 | -------------------------------------------------------------------------------- /intcode.js: -------------------------------------------------------------------------------- 1 | IntcodeParameter = class IntcodeParameter { 2 | static MODE_POS = 0 3 | static MODE_IMM = 1 4 | static MODE_REL = 2 5 | 6 | constructor(vm, val, mode) { 7 | this.vm = vm 8 | this.val = val 9 | this.mode = mode 10 | } 11 | 12 | get() { 13 | switch (this.mode) { 14 | case IntcodeParameter.MODE_POS: 15 | return this.vm.readMemory(this.val) 16 | break 17 | 18 | case IntcodeParameter.MODE_IMM: 19 | return this.val 20 | break 21 | 22 | case IntcodeParameter.MODE_REL: 23 | return this.vm.readMemory(this.vm.base + this.val) 24 | break 25 | 26 | default: 27 | console.error(`IntcodeParameter.get: Unrecognized parameter mode ${this.mode}`) 28 | this.vm.halt() 29 | } 30 | } 31 | 32 | set(val) { 33 | switch (this.mode) { 34 | case IntcodeParameter.MODE_POS: 35 | return this.vm.writeMemory(this.val, val) 36 | break 37 | 38 | case IntcodeParameter.MODE_IMM: 39 | console.error(`IntcodeParameter.set: Cannot write to parameter in immediate mode`) 40 | this.vm.halt() 41 | break 42 | 43 | case IntcodeParameter.MODE_REL: 44 | return this.vm.writeMemory(this.vm.base + this.val, val) 45 | break 46 | 47 | default: 48 | console.error(`IntcodeParameter.set: Unrecognized parameter mode ${this.mode}`) 49 | this.vm.halt() 50 | } 51 | } 52 | } 53 | 54 | IntcodeVM = class IntcodeVM { 55 | static OP_ADD = 1 56 | static OP_MUL = 2 57 | static OP_INP = 3 58 | static OP_OUT = 4 59 | static OP_JIT = 5 60 | static OP_JIF = 6 61 | static OP_SLT = 7 62 | static OP_SEQ = 8 63 | static OP_RBO = 9 64 | 65 | static OP_HLT = 99 66 | 67 | static INSTRS = [] 68 | 69 | constructor(program, inputBuffer = [], outputBuffer = []) { 70 | this.program = program 71 | this.inputBuffer = inputBuffer 72 | this.outputBuffer = outputBuffer 73 | 74 | this.reset() 75 | } 76 | 77 | reset() { 78 | this.outputBuffer.length = 0 79 | 80 | this.data = this.program.slice() 81 | this.pc = 0 82 | this.base = 0 83 | 84 | this.running = false 85 | this.halted = false 86 | this.jumping = false 87 | this.awaitingInput = false 88 | } 89 | 90 | clone() { 91 | let vm = new IntcodeVM(this.program.slice(), this.inputBuffer.slice(), this.outputBuffer.slice()) 92 | 93 | vm.data = this.data.slice() 94 | vm.pc = this.pc 95 | vm.base = this.base 96 | 97 | vm.running = this.running 98 | vm.halted = this.halted 99 | vm.jumping = this.jumping 100 | vm.awaitingInput = this.awaitingInput 101 | 102 | return vm 103 | } 104 | 105 | readMemory(addr) { 106 | if (addr < 0) { 107 | console.error(`IntcodeVM.readMemory: Attempted to read from invalid address ${addr}`) 108 | return null 109 | } 110 | 111 | return this.data[addr] ?? 0 112 | } 113 | 114 | writeMemory(addr, val) { 115 | if (addr < 0) { 116 | console.error(`IntcodeVM.writeMemory: Attempted to write ${val} to invalid address ${addr}`) 117 | return null 118 | } 119 | 120 | return this.data[addr] = val 121 | } 122 | 123 | executeInstruction(num) { 124 | let opcode = num % 100 125 | 126 | if (!(opcode in IntcodeVM.INSTRS)) { 127 | console.error(`IntcodeVM.executeInstruction: Unrecognized opcode: ${opcode} (${num})`) 128 | } 129 | 130 | let instr = IntcodeVM.INSTRS[opcode] 131 | 132 | let args = [] 133 | let ptr = this.pc + 1 134 | 135 | num = Math.floor(num / 100) 136 | 137 | for (let i = 0; i < instr.arity; i++) { 138 | args.push(new IntcodeParameter(this, this.readMemory(ptr++), num % 10)) 139 | num = Math.floor(num / 10) 140 | } 141 | 142 | instr.op.apply(this, args) 143 | 144 | return ptr 145 | } 146 | 147 | step() { 148 | let num = this.readMemory(this.pc) 149 | 150 | if (num === undefined) { 151 | console.error(`IntcodeVM.run: No instruction found at PC ${this.pc}; stopping`) 152 | this.halt() 153 | return 154 | } 155 | 156 | let newPc = this.executeInstruction(num) 157 | 158 | if (this.jumping) { 159 | this.jumping = false 160 | } else if (this.running) { 161 | this.pc = newPc 162 | } 163 | } 164 | 165 | run(limit = 10000000) { 166 | let i = 0 167 | 168 | this.running = true 169 | 170 | while (this.running) { 171 | if (++i > limit) { 172 | console.error(`IntcodeVM.run: Run limit reached; stopping`) 173 | this.halt() 174 | break 175 | } 176 | 177 | this.step() 178 | } 179 | 180 | return i 181 | } 182 | 183 | halt() { 184 | this.running = false 185 | this.halted = true 186 | } 187 | 188 | jump(newPc) { 189 | this.pc = newPc 190 | this.jumping = true 191 | } 192 | 193 | receiveInput() { 194 | if (this.inputBuffer.length) { 195 | return this.inputBuffer.shift() 196 | } else { 197 | this.awaitingInput = true 198 | this.running = false 199 | return null 200 | } 201 | } 202 | 203 | sendInput(...vals) { 204 | this.inputBuffer.push(...vals) 205 | } 206 | 207 | sendInputString(str) { 208 | this.sendInput(...str.split("").map((e) => e.charCodeAt())) 209 | } 210 | 211 | receiveOutput() { 212 | if (this.outputBuffer.length) { 213 | return this.outputBuffer.shift() 214 | } else { 215 | return null 216 | } 217 | } 218 | 219 | receiveNOutputs(n = Infinity) { 220 | let arr = [] 221 | let res 222 | 223 | for (let i = 0; i < n; i++) { 224 | res = this.receiveOutput() 225 | 226 | if (res == null) { 227 | break 228 | } 229 | 230 | arr.push(res) 231 | } 232 | 233 | return arr 234 | } 235 | 236 | receiveOutputString(str) { 237 | return this.receiveNOutputs().map((e) => String.fromCharCode(e)).join("") 238 | } 239 | 240 | sendOutput(...vals) { 241 | this.outputBuffer.push(...vals) 242 | } 243 | } 244 | 245 | IntcodeVM.INSTRS[IntcodeVM.OP_ADD] = { 246 | arity: 3, 247 | op: function(a, b, c) { 248 | c.set(a.get() + b.get()) 249 | } 250 | } 251 | 252 | IntcodeVM.INSTRS[IntcodeVM.OP_MUL] = { 253 | arity: 3, 254 | op: function(a, b, c) { 255 | c.set(a.get() * b.get()) 256 | } 257 | } 258 | 259 | IntcodeVM.INSTRS[IntcodeVM.OP_INP] = { 260 | arity: 1, 261 | op: function(a) { 262 | let res = this.receiveInput() 263 | 264 | if (res !== null) { 265 | a.set(res) 266 | } 267 | } 268 | } 269 | 270 | IntcodeVM.INSTRS[IntcodeVM.OP_OUT] = { 271 | arity: 1, 272 | op: function(a) { 273 | this.sendOutput(a.get()) 274 | } 275 | } 276 | 277 | IntcodeVM.INSTRS[IntcodeVM.OP_JIT] = { 278 | arity: 2, 279 | op: function(a, b) { 280 | if (a.get() != 0) { 281 | this.jump(b.get()) 282 | } 283 | } 284 | } 285 | 286 | IntcodeVM.INSTRS[IntcodeVM.OP_JIF] = { 287 | arity: 2, 288 | op: function(a, b) { 289 | if (a.get() == 0) { 290 | this.jump(b.get()) 291 | } 292 | } 293 | } 294 | 295 | IntcodeVM.INSTRS[IntcodeVM.OP_SLT] = { 296 | arity: 3, 297 | op: function(a, b, c) { 298 | c.set(+(a.get() < b.get())) 299 | } 300 | } 301 | 302 | IntcodeVM.INSTRS[IntcodeVM.OP_SEQ] = { 303 | arity: 3, 304 | op: function(a, b, c) { 305 | c.set(+(a.get() == b.get())) 306 | } 307 | } 308 | 309 | IntcodeVM.INSTRS[IntcodeVM.OP_RBO] = { 310 | arity: 1, 311 | op: function(a) { 312 | this.base += a.get() 313 | } 314 | } 315 | 316 | IntcodeVM.INSTRS[IntcodeVM.OP_HLT] = { 317 | arity: 0, 318 | op: function() { 319 | this.halt() 320 | } 321 | } 322 | 323 | -------------------------------------------------------------------------------- /pt.js: -------------------------------------------------------------------------------- 1 | Pt = Point = class Point { 2 | static STRING = true 3 | 4 | constructor(x, y, z) { 5 | this.is3D = z != undefined 6 | this.x = x 7 | this.y = y 8 | this.z = z 9 | } 10 | 11 | equals(pt) { return this.x == pt.x && this.y == pt.y && (!this.is3D || this.z == pt.z) } 12 | 13 | static encode2D(pt, width = 15) { 14 | if (pt.is3D) { 15 | throw `Point.encode2D: Use encode3D for 3D points` 16 | } 17 | 18 | let x = Math.abs(pt.x) 19 | let y = Math.abs(pt.y) 20 | let nx = x != pt.x 21 | let ny = y != pt.y 22 | 23 | if (x >= 1 << width || y >= 1 << width) { 24 | throw `Point.encode2D: Tried to encode point out of range: ${pt.x}, ${pt.y}` 25 | } 26 | 27 | return ((ny << 1 | nx) << width | y) << width | x 28 | } 29 | 30 | static encode3D(pt, width = 9) { 31 | if (!pt.is3D) { 32 | throw `Point.encode3D: Use encode2D for 2D points` 33 | } 34 | 35 | let x = Math.abs(pt.x) 36 | let y = Math.abs(pt.y) 37 | let z = Math.abs(pt.z) 38 | let nx = x != pt.x 39 | let ny = y != pt.y 40 | let nz = z != pt.z 41 | 42 | if (x >= 1 << width || y >= 1 << width || z >= 1 << width) { 43 | throw `Point.encode3D: Tried to encode point out of range: ${pt.x}, ${pt.y}, ${pt.z}` 44 | } 45 | 46 | return (((nz << 2 | ny << 1 | nx) << width | z) << width | y) << width | x 47 | } 48 | 49 | static encode(pt, width) { 50 | return pt.is3D ? Point.encode3D(pt, width) : Point.encode2D(pt, width) 51 | } 52 | 53 | encode2D(width) { return Point.encode2D(this, width) } 54 | encode3D(width) { return Point.encode3D(this, width) } 55 | encode(width) { return Point.encode(this, width) } 56 | 57 | static decode2D(num, width = 15) { 58 | num = +num 59 | 60 | if (Number.isNaN(num)) { 61 | return null 62 | } 63 | 64 | let mask = (1 << width) - 1 65 | 66 | let x = num & mask 67 | num >>>= width 68 | let y = num & mask 69 | num >>>= width 70 | let nx = num & 1 71 | num >>>= 1 72 | let ny = num & 1 73 | 74 | return new Point(nx ? -x : x, ny ? -y : y) 75 | } 76 | 77 | static decode3D(num, width = 9) { 78 | num = +num 79 | 80 | if (Number.isNaN(num)) { 81 | return null 82 | } 83 | 84 | let mask = (1 << width) - 1 85 | 86 | let x = num & mask 87 | num >>>= width 88 | let y = num & mask 89 | num >>>= width 90 | let z = num & mask 91 | num >>>= width 92 | let nx = num & 1 93 | num >>>= 1 94 | let ny = num & 1 95 | num >>>= 1 96 | let nz = num & 1 97 | 98 | return new Point(nx ? -x : x, ny ? -y : y, nz ? -z : z) 99 | } 100 | 101 | static decode(num, is3D = false, width) { 102 | return is3D ? Point.decode3D(num, width) : Point.decode2D(num, width) 103 | } 104 | 105 | isIn(arr) { return this.indexIn(arr) != -1 } 106 | indexIn(arr) { return arr.findIndex((pt) => this.equals(pt)) } 107 | lastIndexIn(arr) { return arr.findLastIndex((pt) => this.equals(pt)) } 108 | 109 | up(n = 1) { return new Point(this.x, this.y - n, this.z) } 110 | down(n = 1) { return new Point(this.x, this.y + n, this.z) } 111 | left(n = 1) { return new Point(this.x - n, this.y, this.z) } 112 | right(n = 1) { return new Point(this.x + n, this.y, this.z) } 113 | upleft(n = 1) { return new Point(this.x - n, this.y - n, this.z) } 114 | upright(n = 1) { return new Point(this.x + n, this.y - n, this.z) } 115 | downleft(n = 1) { return new Point(this.x - n, this.y + n, this.z) } 116 | downright(n = 1) { return new Point(this.x + n, this.y + n, this.z) } 117 | above(n = 1) { return new Point(this.x, this.y, this.z - n) } 118 | below(n = 1) { return new Point(this.x, this.y, this.z + n) } 119 | 120 | upMut(n = 1) { this.y -= n; return this } 121 | downMut(n = 1) { this.y += n; return this } 122 | leftMut(n = 1) { this.x -= n; return this } 123 | rightMut(n = 1) { this.x += n; return this } 124 | upleftMut(n = 1) { this.x -= n; this.y -= n; return this } 125 | uprightMut(n = 1) { this.x += n; this.y -= n; return this } 126 | downleftMut(n = 1) { this.x -= n; this.y += n; return this } 127 | downrightMut(n = 1) { this.x += n; this.y += n; return this } 128 | aboveMut(n = 1) { this.z -= n; return this } 129 | belowMut(n = 1) { this.z += n; return this } 130 | 131 | u(n) { return this.up(n) } 132 | d(n) { return this.down(n) } 133 | l(n) { return this.left(n) } 134 | r(n) { return this.right(n) } 135 | ul(n) { return this.upleft(n) } 136 | ur(n) { return this.upright(n) } 137 | dl(n) { return this.downleft(n) } 138 | dr(n) { return this.downright(n) } 139 | a(n) { return this.above(n) } 140 | b(n) { return this.below(n) } 141 | c() { return this.copy() } 142 | 143 | n(n) { return this.up(n) } 144 | s(n) { return this.down(n) } 145 | w(n) { return this.left(n) } 146 | e(n) { return this.right(n) } 147 | nw(n) { return this.upleft(n) } 148 | ne(n) { return this.upright(n) } 149 | sw(n) { return this.downleft(n) } 150 | se(n) { return this.downright(n) } 151 | 152 | getUnfilteredAdjNeighborsIncSelf() { 153 | if (!this.is3D) { 154 | return new PointArray( 155 | this.u(), 156 | this.l(), 157 | this.c(), 158 | this.r(), 159 | this.d()) 160 | } else { 161 | return new PointArray( 162 | this.a(), 163 | this.u(), 164 | this.l(), 165 | this.c(), 166 | this.r(), 167 | this.d(), 168 | this.b()) 169 | } 170 | } 171 | 172 | getUnfilteredWingNeighborsIncSelf() { 173 | if (!this.is3D) { 174 | throw "Can't get wing neighbors of 2D point" 175 | } 176 | 177 | return new PointArray( 178 | this.u().a(), 179 | this.l().a(), 180 | this.r().a(), 181 | this.d().a(), 182 | this.ul(), 183 | this.ur(), 184 | this.c(), 185 | this.dl(), 186 | this.dr(), 187 | this.u().b(), 188 | this.l().b(), 189 | this.r().b(), 190 | this.d().b()) 191 | } 192 | 193 | getUnfilteredDiagNeighborsIncSelf() { 194 | if (!this.is3D) { 195 | return new PointArray( 196 | this.ul(), 197 | this.ur(), 198 | this.c(), 199 | this.dl(), 200 | this.dr()) 201 | } else { 202 | return new PointArray( 203 | this.ul().a(), 204 | this.ur().a(), 205 | this.dl().a(), 206 | this.dr().a(), 207 | this.c(), 208 | this.ul().b(), 209 | this.ur().b(), 210 | this.dl().b(), 211 | this.dr().b()) 212 | } 213 | } 214 | 215 | getUnfilteredAllNeighborsIncSelf() { 216 | if (!this.is3D) { 217 | return new PointArray( 218 | this.ul(), 219 | this.u(), 220 | this.ur(), 221 | this.l(), 222 | this.c(), 223 | this.r(), 224 | this.dl(), 225 | this.d(), 226 | this.dr()) 227 | } else { 228 | return new PointArray( 229 | this.ul().a(), 230 | this.u().a(), 231 | this.ur().a(), 232 | this.l().a(), 233 | this.a(), 234 | this.r().a(), 235 | this.dl().a(), 236 | this.d().a(), 237 | this.dr().a(), 238 | this.ul(), 239 | this.u(), 240 | this.ur(), 241 | this.l(), 242 | this.c(), 243 | this.r(), 244 | this.dl(), 245 | this.d(), 246 | this.dr(), 247 | this.ul().b(), 248 | this.u().b(), 249 | this.ur().b(), 250 | this.l().b(), 251 | this.b(), 252 | this.r().b(), 253 | this.dl().b(), 254 | this.d().b(), 255 | this.dr().b()) 256 | } 257 | } 258 | 259 | getUnfilteredAdjNeighbors() { return this.getUnfilteredAdjNeighborsIncSelf().filter((pt) => !this.equals(pt)) } 260 | getUnfilteredDiagNeighbors() { return this.getUnfilteredDiagNeighborsIncSelf().filter((pt) => !this.equals(pt)) } 261 | getUnfilteredAllNeighbors() { return this.getUnfilteredAllNeighborsIncSelf().filter((pt) => !this.equals(pt)) } 262 | 263 | cw90() { 264 | if (this.is3D) { 265 | throw "Point.cw90: Can't rotate 3D point" 266 | } 267 | 268 | return new Point(-this.y, this.x) 269 | } 270 | 271 | cw90Mut() { 272 | if (this.is3D) { 273 | throw "Point.cw90Mut: Can't rotate 3D point" 274 | } 275 | 276 | [this.x, this.y] = [-this.y, this.x] 277 | return this 278 | } 279 | 280 | ccw90() { 281 | if (this.is3D) { 282 | throw "Point.ccw90: Can't rotate 3D point" 283 | } 284 | 285 | return new Point(this.y, -this.x) 286 | } 287 | 288 | ccw90Mut() { 289 | if (this.is3D) { 290 | throw "Point.ccw90Mut: Can't rotate 3D point" 291 | } 292 | 293 | [this.x, this.y] = [this.y, -this.x] 294 | return this 295 | } 296 | 297 | mutate(pt) { 298 | this.is3D = pt.is3D 299 | this.x = pt.x 300 | this.y = pt.y 301 | this.z = pt.z 302 | 303 | return this 304 | } 305 | 306 | add(pt) { return new Point(this.x + pt.x, this.y + pt.y, this.is3D ? this.z + pt.z : undefined) } 307 | addMut(pt) { 308 | this.x += pt.x 309 | this.y += pt.y 310 | 311 | if (this.is3D) { 312 | this.z += pt.z 313 | } 314 | 315 | return this 316 | } 317 | 318 | sub(pt) { return new Point(this.x - pt.x, this.y - pt.y, this.is3D ? this.z - pt.z : undefined) } 319 | subMut(pt) { 320 | this.x -= pt.x 321 | this.y -= pt.y 322 | 323 | if (this.is3D) { 324 | this.z -= pt.z 325 | } 326 | 327 | return this 328 | } 329 | 330 | mult(n) { return new Point(this.x * n, this.y * n, this.is3D ? this.z * n : undefined) } 331 | multMut(n) { 332 | this.x *= n 333 | this.y *= n 334 | 335 | if (this.is3D) { 336 | this.z *= n 337 | } 338 | 339 | return this 340 | } 341 | 342 | neg(n) { return new Point(-this.x, -this.y, this.is3D ? -this.z : undefined) } 343 | negMut(n) { 344 | this.x = -this.x 345 | this.y = -this.y 346 | 347 | if (this.is3D) { 348 | this.z = -this.z 349 | } 350 | 351 | return this 352 | } 353 | 354 | wrap(w, h, d) { 355 | let x = this.x % w 356 | x = x < 0 ? x + w : x 357 | 358 | let y = this.y % h 359 | y = y < 0 ? y + h : y 360 | 361 | let z = undefined 362 | 363 | if (this.is3D) { 364 | z = this.z % d 365 | z = z < 0 ? z + d : z 366 | } 367 | 368 | return new Point(x, y, z) 369 | } 370 | 371 | wrapMut(w, h, d) { 372 | this.x %= w 373 | this.x = this.x < 0 ? this.x + w : this.x 374 | 375 | this.y %= h 376 | this.y = this.y < 0 ? this.y + h : this.y 377 | 378 | if (this.is3D) { 379 | this.z %= d 380 | this.z = this.z < 0 ? this.z + d : this.z 381 | } 382 | 383 | return this 384 | } 385 | 386 | squaredMag() { return this.x * this.x + this.y * this.y + (this.is3D ? this.z * this.z : 0) } 387 | mag() { return Math.sqrt(this.squaredMag()) } 388 | 389 | norm() { return this.mult(1 / this.mag()) } 390 | normMut() { return this.multMut(1 / this.mag()) } 391 | 392 | squaredDist(pt) { return (this.x - pt.x) * (this.x - pt.x) + (this.y - pt.y) * (this.y - pt.y) + (this.is3D ? (this.z - pt.z) * (this.z - pt.z) : 0) } 393 | dist(pt) { return Math.sqrt(this.squaredDist(pt)) } 394 | 395 | manhattanMag() { return Math.abs(this.x) + Math.abs(this.y) + (this.is3D ? Math.abs(this.z) : 0) } 396 | manhattanDist(pt) { return Math.abs(this.x - pt.x) + Math.abs(this.y - pt.y) + (this.is3D ? Math.abs(this.z - pt.z) : 0) } 397 | 398 | isAdjacent(pt) { 399 | if (this.is3D) { 400 | return this.x == pt.x && this.y == pt.y && Math.abs(this.z - pt.z) == 1 || 401 | this.y == pt.y && this.z == pt.z && Math.abs(this.x - pt.x) == 1 || 402 | this.z == pt.z && this.x == pt.x && Math.abs(this.y - pt.y) == 1 403 | } else { 404 | return this.x == pt.x && Math.abs(this.y - pt.y) == 1 || 405 | this.y == pt.y && Math.abs(this.x - pt.x) == 1 406 | } 407 | } 408 | 409 | dot(pt) { return this.x * pt.x + this.y * pt.y + (this.is3D ? this.z * pt.z : 0) } 410 | 411 | *lineTo(that, halfOpen = false) { 412 | if (this.is3D != that.is3D) { 413 | throw `Point.lineTo: Tried to make line between 2D point and 3D point: ${this.toString}; ${that.toString}` 414 | } 415 | 416 | if (this.equals(that)) { 417 | yield this.copy() 418 | return 419 | } 420 | 421 | let dir = new Point( 422 | Math.sign(that.x - this.x), 423 | Math.sign(that.y - this.y), 424 | this.is3D ? Math.sign(that.z - this.z) : undefined) 425 | 426 | let vec = that.sub(this) 427 | if (!vec.mult(1 / Math.abs(vec.x || vec.y)).equals(dir)) { 428 | throw `Point.lineTo: Line not straight: ${this.toString()}; ${that.toString()}` 429 | } 430 | 431 | let pt = this.copy() 432 | 433 | while (!that.equals(pt)) { 434 | yield pt 435 | pt = pt.add(dir) 436 | } 437 | 438 | if (!halfOpen) { 439 | yield pt 440 | } 441 | } 442 | 443 | readingOrderCompare(pt) { 444 | if (this.is3D && this.z < pt.z) { 445 | return -1 446 | } else if (this.is3D && this.z > pt.z) { 447 | return 1 448 | } else if (this.y < pt.y) { 449 | return -1 450 | } else if (this.y > pt.y) { 451 | return 1 452 | } else if (this.x < pt.x) { 453 | return -1 454 | } else if (this.x > pt.x) { 455 | return 1 456 | } else { 457 | return 0 458 | } 459 | } 460 | 461 | copy() { return new Point(this.x, this.y, this.z) } 462 | toString() { return this.x + "," + this.y + (this.is3D ? "," + this.z : "") } 463 | 464 | static fromString(str) { 465 | let [x, y, z] = str.split(",") 466 | return new Point(+x, +y, z && +z) 467 | } 468 | 469 | setFromString(str) { 470 | let [x, y, z] = str.split(",") 471 | 472 | this.is3D = z != undefined 473 | this.x = +x 474 | this.y = +y 475 | this.z = +z 476 | 477 | return this 478 | } 479 | 480 | [Symbol.toPrimitive]() { 481 | if (Point.STRING) { 482 | return this.toString() 483 | } else { 484 | return Point.encode(this) 485 | } 486 | } 487 | } 488 | 489 | Point.NONE = new Point(null, null) 490 | 491 | Point.ZERO = new Point(0, 0) 492 | Point.ORIGIN = Point.ZERO 493 | 494 | Point.NORTH = Point.UP = Point.ZERO.up() 495 | Point.WEST = Point.LEFT = Point.ZERO.left() 496 | Point.SOUTH = Point.DOWN = Point.ZERO.down() 497 | Point.EAST = Point.RIGHT = Point.ZERO.right() 498 | 499 | Point.UP.ccwConst = Point.LEFT 500 | Point.UP.cwConst = Point.RIGHT 501 | Point.UP.negConst = Point.DOWN 502 | 503 | Point.LEFT.ccwConst = Point.DOWN 504 | Point.LEFT.cwConst = Point.UP 505 | Point.LEFT.negConst = Point.RIGHT 506 | 507 | Point.DOWN.ccwConst = Point.RIGHT 508 | Point.DOWN.cwConst = Point.LEFT 509 | Point.DOWN.negConst = Point.UP 510 | 511 | Point.RIGHT.ccwConst = Point.UP 512 | Point.RIGHT.cwConst = Point.DOWN 513 | Point.RIGHT.negConst = Point.LEFT 514 | 515 | Point.DIRS = [Point.UP, Point.LEFT, Point.DOWN, Point.RIGHT] 516 | Point.ARROWS = { 517 | "^": Point.UP, 518 | "<": Point.LEFT, 519 | "v": Point.DOWN, 520 | ">": Point.RIGHT 521 | } 522 | 523 | P = function P(...args) { 524 | return new Point(...args) 525 | } 526 | 527 | 528 | -------------------------------------------------------------------------------- /push.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./cat.sh 4 | rm out.txt 5 | node out.js test | tee out.txt || exit 6 | git add . 7 | git commit -m . 8 | git push 9 | -------------------------------------------------------------------------------- /range.js: -------------------------------------------------------------------------------- 1 | Range = class Range { 2 | constructor(x, y) { 3 | this.x = x 4 | this.y = y 5 | } 6 | 7 | get l() { 8 | return this.y - this.x 9 | } 10 | 11 | set l(l) { 12 | this.y = this.x + l 13 | } 14 | 15 | copy() { 16 | return new Range(this.x, this.y) 17 | } 18 | 19 | set() { 20 | return new RangeSet([this]) 21 | } 22 | 23 | equals(that) { 24 | return this.x == that.x && this.l == that.l 25 | } 26 | 27 | has(num) { 28 | return this.x <= num && num < this.y 29 | } 30 | 31 | isValid() { 32 | return this.y > this.x 33 | } 34 | 35 | intersects(that) { 36 | return this.x < that.y && that.x < this.y 37 | } 38 | 39 | intersection(that) { 40 | if (!this.intersects(that)) { 41 | return null 42 | } 43 | 44 | return new Range(Math.max(this.x, that.x), Math.min(this.y, that.y)) 45 | } 46 | 47 | isSubset(that) { 48 | return this.x >= that.x && this.y <= that.y 49 | } 50 | 51 | isSuperset(that) { 52 | return this.x <= that.x && this.y >= that.y 53 | } 54 | 55 | *[Symbol.iterator]() { 56 | for (let i = this.x; i < this.y; i++) { 57 | yield i 58 | } 59 | } 60 | 61 | toString() { 62 | return `[${this.x}, ${this.y})` 63 | } 64 | } 65 | 66 | RangeSet = class RangeSet { 67 | constructor(ranges = []) { 68 | this.ranges = DLL.from(ranges) 69 | } 70 | 71 | get x() { 72 | return this.ranges.get(0).x 73 | } 74 | 75 | get y() { 76 | return this.ranges.get(-1).y 77 | } 78 | 79 | copy() { 80 | let res = new RangeSet() 81 | 82 | for (let range of this.ranges) { 83 | res.ranges.insValEnd(range.copy()) 84 | } 85 | 86 | return res 87 | } 88 | 89 | bounds() { 90 | return new Range(this.x, this.y - this.x) 91 | } 92 | 93 | equals(that) { 94 | if (this.ranges.length != that.ranges.length) { 95 | return false 96 | } 97 | 98 | if (this.ranges.length == 0 || that.ranges.length == 0) { 99 | return this.ranges.length == that.ranges.length 100 | } 101 | 102 | let anode = this.ranges.getNode(0) 103 | let bnode = that.ranges.getNode(0) 104 | 105 | for (let i = 0; i < this.ranges.length; i++) { 106 | if (!anode.val.equals(bnode.val)) { 107 | return false 108 | } 109 | 110 | anode = anode.next 111 | bnode = bnode.next 112 | } 113 | 114 | return true 115 | } 116 | 117 | has(num) { 118 | for (let range of this.ranges) { 119 | if (range.has(num)) { 120 | return true 121 | } 122 | } 123 | 124 | return false 125 | } 126 | 127 | intersects(that) { 128 | if (!this.ranges.length) { 129 | return false 130 | } 131 | 132 | let start = this.ranges.getNode(0) 133 | let cur = start 134 | 135 | for (let range of that.ranges) { 136 | while (cur.val.y < range.x) { 137 | cur = cur.next 138 | 139 | if (cur == start) { 140 | return false 141 | } 142 | } 143 | 144 | if (cur.val.intersects(range)) { 145 | return true 146 | } 147 | } 148 | 149 | return false 150 | } 151 | 152 | intersectionMut(that) { 153 | return this.subMut(this.sub(that)) 154 | } 155 | 156 | intersection(that) { 157 | return this.sub(this.sub(that)) 158 | } 159 | 160 | isSubset(that) { 161 | throw new Error(`lol fuck you`) 162 | } 163 | 164 | isSuperset(that) { 165 | throw new Error(`lol fuck you`) 166 | } 167 | 168 | reduceMut() { 169 | if (this.ranges.length < 2) { 170 | return this 171 | } 172 | 173 | let start = this.ranges.getNode(0) 174 | let cur = start 175 | 176 | while (cur.next != start) { 177 | if (cur.val.y >= cur.next.val.x) { 178 | cur.val = new Range(cur.val.x, Math.max(cur.val.y, cur.next.val.y)) 179 | this.ranges.removeNode(cur.next) 180 | } else { 181 | cur = cur.next 182 | } 183 | } 184 | 185 | return this 186 | } 187 | 188 | reduce() { 189 | if (this.ranges.length < 2) { 190 | return this.copy() 191 | } 192 | 193 | let res = new RangeSet() 194 | let last 195 | 196 | for (let range of this.ranges) { 197 | if (last && last.y >= range.x) { 198 | last.y = Math.max(last.y, range.y) 199 | } else { 200 | res.ranges.insValEnd(last = range.copy()) 201 | } 202 | } 203 | 204 | return res 205 | } 206 | 207 | addRangeMut(range) { 208 | if (!range.isValid()) { 209 | return this 210 | } 211 | 212 | for (let node of this.ranges.nodes()) { 213 | if (range.x < node.val.x) { 214 | this.ranges.insValBehindNode(node, range) 215 | return this 216 | } 217 | } 218 | 219 | this.ranges.insValEnd(range) 220 | return this 221 | } 222 | 223 | addRange(range) { 224 | if (!range.isValid()) { 225 | return this 226 | } 227 | 228 | let res = new RangeSet() 229 | let added = false 230 | 231 | for (let node of this.ranges.nodes()) { 232 | if (!added && range.x < node.val.x) { 233 | res.ranges.insValEnd(range.copy()) 234 | added = true 235 | } 236 | 237 | res.ranges.insValEnd(node.val.copy()) 238 | } 239 | 240 | if (!added) { 241 | res.ranges.insValEnd(range.copy()) 242 | } 243 | 244 | return res 245 | } 246 | 247 | addMut(that) { 248 | for (let range of that.ranges) { 249 | this.addRangeMut(range) 250 | } 251 | 252 | return this 253 | } 254 | 255 | add(that) { 256 | if (this.ranges.length == 0) { 257 | return that.copy() 258 | } 259 | 260 | let res = new RangeSet() 261 | 262 | let start = this.ranges.getNode(0) 263 | let cur = start 264 | let addedAll = false 265 | 266 | for (let range of that.ranges) { 267 | while (!addedAll && cur.val.x < range.x) { 268 | res.ranges.insValEnd(cur.val.copy()) 269 | cur = cur.next 270 | 271 | if (cur == start) { 272 | addedAll = true 273 | } 274 | } 275 | 276 | res.ranges.insValEnd(range.copy()) 277 | } 278 | 279 | while (cur != start) { 280 | res.ranges.insValEnd(cur.val.copy()) 281 | cur = cur.next 282 | } 283 | 284 | return res 285 | } 286 | 287 | subRangeMut(range) { 288 | let cur 289 | 290 | while (this.ranges.length) { 291 | cur = (cur ?? this.ranges.getNode(0)).prev 292 | 293 | if (range.intersects(cur.val)) { 294 | let left = new Range(cur.val.x, range.x) 295 | let right = new Range(range.y, cur.val.y) 296 | 297 | if (right.isValid()) { 298 | this.ranges.insValAheadNode(cur, right) 299 | } 300 | 301 | if (left.isValid()) { 302 | cur.val = left 303 | } else { 304 | let next = cur.next 305 | this.ranges.removeNode(cur) 306 | cur = next 307 | } 308 | } 309 | 310 | if (this.ranges.length && cur == this.ranges.getNode(0)) { 311 | break 312 | } 313 | } 314 | 315 | return this 316 | } 317 | 318 | subRange(range) { 319 | let res = new RangeSet() 320 | let cur 321 | 322 | while (this.ranges.length) { 323 | cur = (cur ?? this.ranges.getNode(0)).prev 324 | 325 | if (range.intersects(cur.val)) { 326 | let left = new Range(cur.val.x, range.x) 327 | let right = new Range(range.y, cur.val.y) 328 | 329 | if (right.isValid()) { 330 | res.addRangeMut(right) 331 | } 332 | 333 | if (left.isValid()) { 334 | res.addRangeMut(left) 335 | } 336 | } else { 337 | res.ranges.insValStart(cur.val) 338 | } 339 | 340 | if (this.ranges.length && cur == this.ranges.getNode(0)) { 341 | break 342 | } 343 | } 344 | 345 | return res 346 | } 347 | 348 | subMut(that) { 349 | for (let range of that.ranges) { 350 | this.subRangeMut(range) 351 | } 352 | 353 | return this 354 | } 355 | 356 | sub(that) { 357 | let res = this.copy() 358 | 359 | for (let range of that.ranges) { 360 | res.subRangeMut(range) 361 | } 362 | 363 | return res 364 | } 365 | 366 | count() { 367 | let sum = 0 368 | 369 | for (let range of this.reduce().ranges) { 370 | sum += range.l 371 | } 372 | 373 | return sum 374 | } 375 | 376 | *[Symbol.iterator]() { 377 | for (let range of this.reduce().ranges) { 378 | yield* range 379 | } 380 | } 381 | 382 | toString() { 383 | let str = "" 384 | 385 | for (let node of this.ranges.nodes()) { 386 | str += node.val.toString() 387 | 388 | if (node.next != this.ranges.getNode(0)) { 389 | str += "; " 390 | } 391 | } 392 | 393 | return str 394 | } 395 | } 396 | 397 | -------------------------------------------------------------------------------- /repl.js: -------------------------------------------------------------------------------- 1 | if (typeof window == "undefined") { 2 | debugger 3 | } 4 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | if (typeof window == "undefined" && process.argv[2] == "test") { 2 | const fs = require("fs") 3 | const debug = process.argv.includes("debug") 4 | 5 | const test = function(name, answer, func, ...args) { 6 | let res = func(...args) 7 | console.log(`${name}: Got ${res}, expected ${answer}`) 8 | 9 | if (res != answer) { 10 | console.log(`${name}: FAIL`) 11 | return false 12 | } 13 | 14 | if (debug) { 15 | return true 16 | } 17 | 18 | let killTime = performance.now() + 10 * 1000 19 | let avgTime = 0 20 | let i 21 | 22 | for (i = -1; i < 100; i++) { 23 | let startTime = performance.now() 24 | let newRes = func(...args) 25 | let endTime = performance.now() 26 | 27 | if (newRes != res) { 28 | console.log(`${name}: FAIL`) 29 | return false 30 | } 31 | 32 | if (i >= 0) { 33 | avgTime = ((avgTime * i) + (endTime - startTime)) / (i + 1) 34 | } 35 | 36 | if (endTime > killTime) { 37 | i++ 38 | break 39 | } 40 | } 41 | 42 | let colorCode = avgTime < 5 ? "32" : avgTime < 1000 ? "33" : "31" 43 | console.log(`${name}: \x1b[${colorCode}m${avgTime.toFixed(3)}ms\x1b[0m (avg over ${i} runs)`) 44 | 45 | return true 46 | } 47 | 48 | const year = "2024" 49 | 50 | for (let i = +process.argv[3] || 1; i <= 25; i++) { 51 | let jsPath = `./${year}/${i}.js` 52 | 53 | if (!fs.existsSync(jsPath)) { 54 | break 55 | } 56 | 57 | const func = require(jsPath) 58 | const input = fs.readFileSync(`./${year}/inputs/${i}`, "utf8").trim() 59 | const answers = fs.readFileSync(`./${year}/answers/${i}`, "utf8").trim().split("\n-----\n") 60 | 61 | if (i != 25) { 62 | if (!test(`${year} day ${i} part 1`, answers[0], func, input, false)) { 63 | break 64 | } 65 | 66 | if (!test(`${year} day ${i} part 2`, answers[1], func, input, true)) { 67 | break 68 | } 69 | } else { 70 | test(`${year} day ${i}`, answers[0], func, input) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | - permutations/combinations 2 | - partitions 3 | - levenshtein dist 4 | !!- array swaps, reverse range, etc 5 | *- Point.lineTo generator 6 | - range compare/merge/subtract/etc utils 7 | - sparse grids 8 | - 3d grids 9 | - grid expand 10 | - hex pt utils? maybe? 11 | - figure out tsp 12 | *- min/max x/y from arr of points 13 | - Point.UP Point.DOWN etc aliases 14 | - binary search on arbitrary function 15 | - rewrite grid bfs 16 | - structuredClone 17 | - test case retrieval 18 | - node utils for input retrieval and also z3 wrappers 19 | -------------------------------------------------------------------------------- /unionfind.js: -------------------------------------------------------------------------------- 1 | UnionFindNode = class UnionFindNode { 2 | constructor(val) { 3 | this.val = val 4 | this.parent = this 5 | this.numDescendants = 1 6 | } 7 | } 8 | 9 | UnionFind = class UnionFind { 10 | constructor(data = []) { 11 | this.nodes = new Map() 12 | this.numSets = 0 13 | 14 | for (let val of data) { 15 | this.add(val) 16 | } 17 | } 18 | 19 | has(val) { 20 | return this.nodes.has(val) 21 | } 22 | 23 | add(val) { 24 | if (this.nodes.has(val)) { 25 | throw "Tried to add duplicate element to UnionFind" 26 | } 27 | 28 | this.nodes.set(val, new UnionFindNode(val)) 29 | 30 | return ++this.numSets 31 | } 32 | 33 | addAndConnectIf(val, func) { 34 | this.add(val) 35 | 36 | for (let val2 of this.nodes.keys()) { 37 | if (val != val2 && func(val, val2)) { 38 | this.connect(val, val2) 39 | } 40 | } 41 | 42 | return this.numSets 43 | } 44 | 45 | getRoot(val) { 46 | let node = this.nodes.get(val) 47 | if (!node) { 48 | return null 49 | } 50 | 51 | let root = node 52 | while (root.parent != root) { 53 | root = root.parent 54 | } 55 | 56 | while (node.parent != root) { 57 | [node, node.parent] = [node.parent, root] 58 | } 59 | 60 | return root 61 | } 62 | 63 | connect(val1, val2) { 64 | let root1 = this.getRoot(val1) 65 | let root2 = this.getRoot(val2) 66 | 67 | if (root1 != root2) { 68 | let [min, max] = root1.numDescendants < root2.numDescendants ? [root1, root2] : [root2, root1] 69 | min.parent = max 70 | max.numDescendants += min.numDescendants 71 | this.numSets-- 72 | } 73 | 74 | return this.numSets 75 | } 76 | 77 | sets() { 78 | return Map.groupBy(this.nodes.keys(), (key) => this.getRoot(key).val) 79 | } 80 | 81 | [Symbol.iterator]() { 82 | return this.sets().values() 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | utils = { 2 | log: (e, ...args) => (console.log(e instanceof Grid ? e.toString() : e, ...args), e), 3 | logCopy: (e, ...args) => (console.log(e instanceof Grid ? e.toString() : e.copyDeep(), ...args), e), 4 | condLog: (e, ...args) => globalThis.a.length == globalThis.inputLength ? e : utils.log(e, ...args), 5 | condLogCopy: (e, ...args) => globalThis.a.length == globalThis.inputLength ? e : utils.logCopy(e, ...args), 6 | fetchText: (...args) => fetch(...args).then((e) => e.text()), 7 | fetchEval: (...args) => utils.fetchText(...args).then((e) => eval(e)), 8 | signAgnosticInclusiveRange: (a, b, s = Math.sign(a - b)) => Array((a - b) * s + 1).fill().map((_, i) => a - i * s), 9 | createGridArray: (w, h, fill = undefined) => { 10 | let func = functifyVal(fill) 11 | 12 | return Array(h).fill().map((_, y) => { 13 | return Array(w).fill().map((_, x) => func(new Point(x, y))) 14 | }) 15 | }, 16 | // num utils because numbers are weird 17 | mod: (a, b) => { 18 | let n = a % b 19 | return n < 0 ? n + b : n 20 | }, 21 | divmod: (a, b) => { 22 | return [Math.floor(a / b), a % b] 23 | }, 24 | powmod: (a, b, m) => { 25 | a %= m 26 | 27 | if (b == 0) { 28 | return 1 29 | } 30 | 31 | if (b == 1) { 32 | return a 33 | } 34 | 35 | let r = utils.powmod(a, Math.floor(b / 2), m) 36 | 37 | return (b % 2 ? a : 1) * r * r % m 38 | }, 39 | gcd2: (a, b) => { 40 | while (b) { 41 | [a, b] = [b, a % b] 42 | } 43 | 44 | return a 45 | }, 46 | gcd: (...args) => args.reduce(utils.gcd2, 0), 47 | lcm2: (a, b) => a && b ? a * (b / utils.gcd2(a, b)) : 0, 48 | lcm: (...args) => args.reduce(utils.lcm2, 1), 49 | isPrime: (n) => { 50 | for (let i = 2; i * i <= n; i++) { 51 | if (n % i == 0) { 52 | return false 53 | } 54 | } 55 | 56 | return true 57 | }, 58 | primeFactors: (n) => { 59 | let arr = [] 60 | 61 | for (let i = 2; n > 1;) { 62 | if (i * i > n) { 63 | arr.push(+n) 64 | break 65 | } else if (n % i == 0) { 66 | arr.push(i) 67 | n /= i 68 | } else { 69 | i++ 70 | } 71 | } 72 | 73 | return arr 74 | }, 75 | factors: (n) => { 76 | let arr = [] 77 | let arr2 = [] 78 | 79 | for (let i = 1; i * i <= n; i++) { 80 | if (n % i == 0) { 81 | arr.push(i) 82 | 83 | if (i != n / i) { 84 | arr2.unshift(n / i) 85 | } 86 | } 87 | } 88 | 89 | return arr.concat(arr2) 90 | }, 91 | lock: (obj, val) => { 92 | let proxy 93 | 94 | let func = functifyVal(val) 95 | 96 | return proxy = new Proxy(obj, { 97 | get(obj, prop) { 98 | if (prop == "obj") { 99 | return Object.assign({}, proxy) 100 | } else if (prop in obj) { 101 | return obj[prop] 102 | } else { 103 | return func(obj, prop) 104 | } 105 | } 106 | }) 107 | }, 108 | createMap: (val = undefined, obj) => utils.lock(Object.assign({ __proto__: null }, obj), val), 109 | getObject: (obj) => Object.assign({}, obj), 110 | emptyArray: (n, func = (e, i) => i) => Array(n).fill().map(func), 111 | memoize: (func, serialize = (...args) => args.join("\0")) => { 112 | let map = new Map() 113 | 114 | return (...args) => { 115 | let key = serialize(...args) 116 | 117 | if (map.has(key)) { 118 | return map.get(key) 119 | } 120 | 121 | let val = func(...args) 122 | map.set(key, val) 123 | return val 124 | } 125 | }, 126 | binarySearch: (func, start, end, searchVal = true) => { 127 | if (!(func(start) != searchVal && func(end) == searchVal)) { 128 | return null 129 | } 130 | 131 | let lastNo = start 132 | let lastYes = end 133 | 134 | while (lastYes - lastNo > 1) { 135 | let mid = Math.floor((lastNo + lastYes) / 2) 136 | 137 | if (func(mid) != searchVal) { 138 | lastNo = mid 139 | } else { 140 | lastYes = mid 141 | } 142 | } 143 | 144 | return lastYes 145 | }, 146 | manhattanDist: (arr1, arr2) => { 147 | let dist = 0 148 | 149 | for (let i = 0; i < arr1.length; i++) { 150 | dist += Math.abs(arr1[i] - arr2[i]) 151 | } 152 | 153 | return dist 154 | }, 155 | shoelaceArea: (arr) => { 156 | let area = 0 157 | 158 | for (let i = 0; i < arr.length; i++) { 159 | let a = arr[i] 160 | let b = arr[(i + 1) % arr.length] 161 | area += a.x * b.y - b.x * a.y 162 | } 163 | 164 | return area / 2 165 | }, 166 | perimeter: (arr) => { 167 | let sum = 0 168 | 169 | for (let i = 0; i < arr.length; i++) { 170 | let a = arr[i] 171 | let b = arr[(i + 1) % arr.length] 172 | sum += a.dist(b) 173 | } 174 | 175 | return sum 176 | }, 177 | manhattanPerimeter: (arr) => { 178 | let sum = 0 179 | 180 | for (let i = 0; i < arr.length; i++) { 181 | let a = arr[i] 182 | let b = arr[(i + 1) % arr.length] 183 | sum += a.manhattanDist(b) 184 | } 185 | 186 | return sum 187 | }, 188 | rectIntersects: (a1, a2, b1, b2) => { 189 | let aminx = Math.min(a1.x, a2.x) 190 | let amaxx = Math.max(a1.x, a2.x) 191 | let aminy = Math.min(a1.y, a2.y) 192 | let amaxy = Math.max(a1.y, a2.y) 193 | let bminx = Math.min(b1.x, b2.x) 194 | let bmaxx = Math.max(b1.x, b2.x) 195 | let bminy = Math.min(b1.y, b2.y) 196 | let bmaxy = Math.max(b1.y, b2.y) 197 | 198 | return aminx <= bmaxx && bminx <= amaxx && 199 | aminy <= bmaxy && bminy <= amaxy 200 | }, 201 | cuboidIntersects: (a1, a2, b1, b2) => { 202 | let aminx = Math.min(a1.x, a2.x) 203 | let amaxx = Math.max(a1.x, a2.x) 204 | let aminy = Math.min(a1.y, a2.y) 205 | let amaxy = Math.max(a1.y, a2.y) 206 | let aminz = Math.min(a1.z, a2.z) 207 | let amaxz = Math.max(a1.z, a2.z) 208 | let bminx = Math.min(b1.x, b2.x) 209 | let bmaxx = Math.max(b1.x, b2.x) 210 | let bminy = Math.min(b1.y, b2.y) 211 | let bmaxy = Math.max(b1.y, b2.y) 212 | let bminz = Math.min(b1.z, b2.z) 213 | let bmaxz = Math.max(b1.z, b2.z) 214 | 215 | return aminx <= bmaxx && bminx <= amaxx && 216 | aminy <= bmaxy && bminy <= amaxy && 217 | aminz <= bmaxz && bminz <= amaxz 218 | } 219 | } 220 | 221 | alpha = "abcdefghijklmnopqrstuvwxyz" 222 | ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 223 | 224 | utils.prime = utils.isPrime 225 | 226 | M = utils.createMap 227 | 228 | N = utils.emptyArray 229 | 230 | O = utils.getObject 231 | 232 | L = utils.log 233 | LC = utils.logCopy 234 | KL = utils.condLog 235 | KLC = utils.condLogCopy 236 | 237 | bar = function bar() { 238 | console.log("----------") 239 | } 240 | 241 | R = utils.range = utils.signAgnosticInclusiveRange 242 | 243 | U = function U(n) { 244 | return utils.emptyArray(n, (e, i) => i + 1) 245 | } 246 | 247 | Z = function Z(n) { 248 | return utils.emptyArray(n, (e, i) => i) 249 | } 250 | 251 | for (let i of Object.getOwnPropertyNames(Math)) { 252 | if (Math[i] instanceof Function) { 253 | globalThis[i] = Math[i] 254 | } 255 | } 256 | 257 | defaultPartNum = 1 258 | 259 | A = function A(ans, part, k) { 260 | if (k != undefined) { 261 | throw "Third argument in submission function." 262 | } 263 | 264 | if (typeof ans != "number") { 265 | console.warn("Tried to submit non-number; cancelled. To override, use AA or BB.") 266 | return 267 | } 268 | 269 | AA(ans, part) 270 | } 271 | 272 | AA = function AA(ans, part = 0) { 273 | let day = +location.href.match(/(\d+)\/input/)[1] 274 | 275 | if (part != 1 && part != 2) { 276 | part = defaultPartNum 277 | console.warn(`Remember to specify part number! Defaulting to ${part}`) 278 | } 279 | 280 | console.log(`Submitting ${ans} for part ${part}`) 281 | 282 | let queryString = new URLSearchParams({ 283 | "level": part.toString(), 284 | "answer": ans.toString() 285 | }).toString() 286 | 287 | utils.fetchText(location.href.replace("input", "answer"), { 288 | "headers": { 289 | "content-type": "application/x-www-form-urlencoded" 290 | }, 291 | "body": queryString, 292 | "method": "POST", 293 | "mode": "cors", 294 | "credentials": "include" 295 | }).then((text) => { 296 | if (text.includes("That's the right answer!")) { 297 | defaultPartNum = 2 298 | 299 | if (day == 25) { 300 | A(0, 2) 301 | setTimeout(() => A(0, 2), 1000) 302 | } 303 | } 304 | 305 | console.log(text.match(//)[0].replace(/<.+?>/g, "").replace(/rank \d+/g, "???").replace(/ and gained \d+ points!/g, ".")) 306 | }) 307 | 308 | return ans 309 | } 310 | 311 | B = function B(ans, part = 2) { 312 | return A(ans, part) 313 | } 314 | 315 | BB = function B(ans, part = 2) { 316 | return AA(ans, part) 317 | } 318 | 319 | T = async function T(num) { 320 | let url = location.href.match(/^(.+)\/day/)[1] + "/day/" + num 321 | 322 | let text = await utils.fetchText(url) 323 | localStorage.setItem("pre:" + location.href, JSON.stringify([...text.matchAll(/
([\s\S]+?)<\/code><\/pre>/g).map((e) => e[1].replace(/<\/?em>/g, "").trim())]))
324 | }
325 | 
326 | I = async function I(num) {
327 | 	let url = location.href.match(/^(.+)\/day/)[1] + "/day/" + num + "/input"
328 | 	history.pushState({}, "", url)
329 | 
330 | 	let text = await utils.fetchText(url)
331 | 	a = (document.body.children[0] ?? document.body).innerText = text.trimEnd()
332 | 	defaultPartNum = 1
333 | 	
334 | 	T(num)
335 | }
336 | 
337 | II = async function II(num) {
338 | 	if (window.aocTimeout) {
339 | 		clearTimeout(window.aocTimeout)
340 | 	}
341 | 
342 | 	window.aocTimeout = setTimeout(() => I(num), new Date().setHours(21, 0, 10, 0) - new Date().getTime())
343 | }
344 | 
345 | 


--------------------------------------------------------------------------------
/vm.js:
--------------------------------------------------------------------------------
  1 | Instruction = class Instruction {
  2 | 	constructor(command, types, args, varargs = false) {
  3 | 		if (types.length != args.length && !varargs) {
  4 | 			console.warn(`new Instruction: Attempted to create ${command} instruction: Expected ${types.length} arguments, got ${args.length} arguments`)
  5 | 			console.log(args)
  6 | 		}
  7 | 
  8 | 		this.command = command
  9 | 		this.args = args
 10 | 		this.types = args.map((e, i) => types[i] ?? types[types.length - 1])
 11 | 	}
 12 | }
 13 | 
 14 | // example command:
 15 | //
 16 | // add: {
 17 | //	 types: [ String, 0, 0 ],
 18 | //	 op: function(dest, a, b) {
 19 | //		 this.regs[dest] = a + b
 20 | //	 }
 21 | // }
 22 | 
 23 | VM = class VM {
 24 | 	static evalNum(val) {
 25 | 		return isNaN(val) ? this.regs[val] : Number(val)
 26 | 	}
 27 | 
 28 | 	get r() {
 29 | 		return this.regs
 30 | 	}
 31 | 
 32 | 	constructor(init = () => {}, commands = {}) {
 33 | 		if (init instanceof Function) {
 34 | 			this.init = function() {
 35 | 				this.regs = utils.createMap(0)
 36 | 				init.apply(this)
 37 | 			}
 38 | 		} else if (init) {
 39 | 			this.init = function() {
 40 | 				this.regs = utils.createMap(0, init)
 41 | 			}
 42 | 		}
 43 | 
 44 | 		this.commands = commands
 45 | 
 46 | 		this.clearProgram()
 47 | 		this.reset()
 48 | 	}
 49 | 
 50 | 	addCommand(name, command) {
 51 | 		this.commands[name] = command
 52 | 	}
 53 | 
 54 | 	removeCommand(name) {
 55 | 		return delete this.commands[name]
 56 | 	}
 57 | 
 58 | 	reset() {
 59 | 		this.init()
 60 | 		this.regs.pc = 0
 61 | 		this.halted = false
 62 | 	}
 63 | 
 64 | 	parseLine(line) {
 65 | 		let words = line.split(/\s+/)
 66 | 
 67 | 		if (!words.length) {
 68 | 			return
 69 | 		}
 70 | 
 71 | 		let command = words.shift()
 72 | 
 73 | 		if (!(command in this.commands)) {
 74 | 			console.error(`VM.parseLine: Unrecognized command: ${command}`)
 75 | 		}
 76 | 
 77 | 		return new Instruction(command, this.commands[command].types.map((e) => e.bind(this)) ?? [], words, this.commands[command].varargs)
 78 | 	}
 79 | 
 80 | 	executeInstruction(instr) {
 81 | 		return this.commands[instr.command].op.apply(this, instr.args.map((e, i) => instr.types[i](e)))
 82 | 	}
 83 | 
 84 | 	loadProgram(str) {
 85 | 		this.clearProgram()
 86 | 
 87 | 		let lines = str.split("\n")
 88 | 
 89 | 		for (let i = 0; i < lines.length; i++) {
 90 | 			let instr = this.parseLine(lines[i])
 91 | 
 92 | 			if (instr) {
 93 | 				this.program.push(instr)
 94 | 			}
 95 | 		}
 96 | 	}
 97 | 
 98 | 	clearProgram() {
 99 | 		this.program = []
100 | 	}
101 | 
102 | 	step() {
103 | 		let instr = this.program[this.regs.pc]
104 | 
105 | 		if (!instr) {
106 | 			console.warn(`VM.run: No instruction found at PC ${this.regs.pc}; stopping`)
107 | 			this.halt()
108 | 			return
109 | 		}
110 | 
111 | 		this.executeInstruction(instr)
112 | 
113 | 		if (!this.commands[instr.command].holdPc) {
114 | 			this.regs.pc++
115 | 		}
116 | 	}
117 | 
118 | 	run(limit = 100000) {
119 | 		while (!this.halted) {
120 | 			if (--limit <= 0) {
121 | 				console.error(`VM.run: Run limit reached; stopping`)
122 | 				this.halt()
123 | 				break
124 | 			}
125 | 
126 | 			this.step()
127 | 		}
128 | 	}
129 | 
130 | 	halt() {
131 | 		this.halted = true
132 | 	}
133 | }
134 | 
135 | 


--------------------------------------------------------------------------------
/z3.js:
--------------------------------------------------------------------------------
 1 | class Z3 {
 2 | 	constructor(defaultVarType = "Int") {
 3 | 		this.defaultVarType = defaultVarType
 4 | 		
 5 | 		this.vars = []
 6 | 		this.outputs = []
 7 | 		this.constraints = []
 8 | 	}
 9 | 	
10 | 	addVar(name, type = this.defaultVarType) {
11 | 		this.vars.push(`${name} = z3.${type}("${name}")`)
12 | 		return this
13 | 	}
14 | 	
15 | 	addVars(...names) {
16 | 		for (let name of names) {
17 | 			this.addVar(name)
18 | 		}
19 | 		return this
20 | 	}
21 | 	
22 | 	addOutput(expr) {
23 | 		this.outputs.push(`  ${expr}`)
24 | 		return this
25 | 	}
26 | 	
27 | 	addOutputs(...exprs) {
28 | 		for (let expr of exprs) {
29 | 			this.addOutput(expr)
30 | 		}
31 | 		return this
32 | 	}
33 | 	
34 | 	addConstraint(expr) {
35 | 		this.constraints.push(`solver.add(${expr})`)
36 | 		return this
37 | 	}
38 | 	
39 | 	addConstraints(...exprs) {
40 | 		for (let expr of exprs) {
41 | 			this.addConstraint(expr)
42 | 		}
43 | 		return this
44 | 	}
45 | 	
46 | 	compile() {
47 | 		return `import json
48 | import pyperclip
49 | import z3
50 | 
51 | def run(solver, outputs):
52 |   print("Running...")
53 | 
54 |   if solver.check() == z3.unsat:
55 |     print()
56 |     print("unsat")
57 |   else:
58 |     print()
59 |     print("sat")
60 |     print()
61 | 
62 |     model = solver.model()
63 |     dump = {}
64 |     for expr in outputs:
65 |       dump[repr(expr)] = repr(model.eval(expr))
66 | 
67 |     for key in dump:
68 |       print(f"{key}: {dump[key]}")
69 |     print()
70 |     print("-----")
71 |     print()
72 |     print("Copied!")
73 |     print()
74 | 
75 |     output = json.dumps(dump)
76 |     print(output)
77 |     pyperclip.copy(output)
78 | 
79 | solver = z3.Solver()
80 | 
81 | ${this.vars.join("\n")}
82 | 
83 | outputs = [
84 | ${this.outputs.join(",\n")}
85 | ]
86 | 
87 | ${this.constraints.sort((a, b) => Math.random() < 0.5 ? -1 : 1).join("\n")}
88 | 
89 | run(solver, outputs)`
90 | 	}
91 | }
92 | 
93 | 


--------------------------------------------------------------------------------