diff --git a/content/programming/blog/tairu.tree b/content/programming/blog/tairu.tree index 147f195..c02d3a8 100644 --- a/content/programming/blog/tairu.tree +++ b/content/programming/blog/tairu.tree @@ -161,6 +161,8 @@ styles = ["tairu.css"] } ``` + TODO this should be literate code + % template = true id = "01HPMVT9BM9CS9375MX4H9WKW8" - and that gives us this result: @@ -406,6 +408,98 @@ styles = ["tairu.css"] 4 ``` + + for my own (and your) convenience, here's a complete list of *all* the possible combinations in order. + + - ```javascript ordinal-directions + function toString(bitset) { + if (bitset == 0) return "0"; + + let directions = []; + if (isSet(bitset, E)) directions.push("E"); + if (isSet(bitset, SE)) directions.push("SE"); + if (isSet(bitset, S)) directions.push("S"); + if (isSet(bitset, SW)) directions.push("SW"); + if (isSet(bitset, W)) directions.push("W"); + if (isSet(bitset, NW)) directions.push("NW"); + if (isSet(bitset, N)) directions.push("N"); + if (isSet(bitset, NE)) directions.push("NE"); + return directions.join(" | "); + } + + for (let x in xToConnectionBitSet) { + console.log(`${x} => ${toString(xToConnectionBitSet[x])}`); + } + ``` + ```output ordinal-directions + 0 => 0 + 1 => E + 2 => S + 3 => E | S + 4 => E | SE | S + 5 => W + 6 => E | W + 7 => S | W + 8 => E | S | W + 9 => E | SE | S | W + 10 => S | SW | W + 11 => E | S | SW | W + 12 => E | SE | S | SW | W + 13 => N + 14 => E | N + 15 => S | N + 16 => E | S | N + 17 => E | SE | S | N + 18 => W | N + 19 => E | W | N + 20 => S | W | N + 21 => E | S | W | N + 22 => E | SE | S | W | N + 23 => S | SW | W | N + 24 => E | S | SW | W | N + 25 => E | SE | S | SW | W | N + 26 => W | NW | N + 27 => E | W | NW | N + 28 => S | W | NW | N + 29 => E | S | W | NW | N + 30 => E | SE | S | W | NW | N + 31 => S | SW | W | NW | N + 32 => E | S | SW | W | NW | N + 33 => E | SE | S | SW | W | NW | N + 34 => E | N | NE + 35 => E | S | N | NE + 36 => E | SE | S | N | NE + 37 => E | W | N | NE + 38 => E | S | W | N | NE + 39 => E | SE | S | W | N | NE + 40 => E | S | SW | W | N | NE + 41 => E | SE | S | SW | W | N | NE + 42 => E | W | NW | N | NE + 43 => E | S | W | NW | N | NE + 44 => E | SE | S | W | NW | N | NE + 45 => E | S | SW | W | NW | N | NE + 46 => E | SE | S | SW | W | NW | N | NE + ``` + + - with the lookup table generated, we are now able to prepare a tile strip like before - except now it's even more tedious work arranging the pieces together :ralsei_dead: + + anyways I spent like 20 minutes doing that by hand, and now we have a neat tile strip just like before, except way longer: + + ![horizontal tile strip of 47 8x8 pixel metal tiles][pic:01HPW47SHMSVAH7C0JR9HWXWCM] + + - now let's hook it up to our tileset renderer! TODO literate program. + + % template = true + - with the capability to render with 47-tile tilesets, our examples suddenly look a whole lot better! + + + Your browser does not support <canvas>. + + + % id = "01HPD4XQPWT9N8X9BD9GKWD78F" - bitwise autotiling is a really cool technique that I've used in plenty of games in the past. diff --git a/static/css/main.css b/static/css/main.css index 9e783ee..989a802 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -186,6 +186,7 @@ strong { i, em { --recursive-slnt: -16.0; + font-style: normal; } /* Lay out elements a bit more compactly */ @@ -240,18 +241,28 @@ th-literate-program { transition: background-color var(--transition-duration); } -th-literate-program { - white-space: pre; -} +@media (prefers-color-scheme: light) { -.tree summary:hover { - - & pre, - & th-literate-program { - background-color: var(--shaded-against-background-twice); + pre, + th-literate-program { + background-color: transparent; + border: 1px solid var(--border-1); } } + +@media (prefers-color-scheme: dark) { + .tree summary:hover { + + & pre, + & th-literate-program { + background-color: var(--shaded-against-background-twice); + } + } +} + + + pre>code, th-literate-program>code { padding: 0; @@ -259,6 +270,10 @@ th-literate-program>code { border-radius: 0px; } +th-literate-program { + white-space: pre; +} + /* And don't let code examples fly off and overflow the window */ pre { @@ -283,10 +298,12 @@ img.pic { /* Image hints for tweaking rendering */ img { &[src*='+pixel'] { - image-rendering: crisp-edges; + image-rendering: pixelated; border-radius: 0; } + /* TODO: These could be autogenerated! */ + &[src*='+width160'] { width: 160px; height: auto; @@ -297,6 +314,11 @@ img { height: auto; } + &[src*='+width752'] { + width: 752px; + height: auto; + } + /* Resources for use in JavaScript. */ &.resource { display: none; @@ -532,11 +554,6 @@ th-literate-program[data-mode="output"] { color: #e39393; } - & code .return-value { - content: 'Return value: '; - opacity: 50%; - } - &::after { content: 'Output'; @@ -553,7 +570,15 @@ th-literate-program[data-mode="output"] { /* Syntax highlighting */ :root { - /* TODO: Light mode syntax highlighting */ + --syntax-comment: #9b8580; + --syntax-identifier: var(--text-color); + --syntax-keyword1: #e15e2b; + --syntax-keyword2: #199aca; + --syntax-operator: #e3755b; + --syntax-function: #d57b07; + --syntax-literal: #a64fb3; + --syntax-string: #79ac3b; + --syntax-punct: #a28680; } @media (prefers-color-scheme: dark) { @@ -607,4 +632,21 @@ th-literate-program[data-mode="output"] { &.punct { color: var(--syntax-punct); } + +} + +.th-syntax-highlighting { + & .export { + text-decoration: underline dotted; + cursor: help; + text-decoration-color: transparent; + transition: text-decoration-color var(--transition-duration); + } + + &:hover, + &:focus { + & .export { + text-decoration-color: var(--syntax-keyword1); + } + } } diff --git a/static/css/tairu.css b/static/css/tairu.css index 533e2dd..18e562d 100644 --- a/static/css/tairu.css +++ b/static/css/tairu.css @@ -46,7 +46,7 @@ margin: 8px; background-image: url('../pic/01HPHVDRV0F0251MD0A2EG66C4-tilemap-heavy-metal-16+pixel+width160.png'); background-size: 400%; - image-rendering: crisp-edges; + image-rendering: pixelated; position: relative; } @@ -155,7 +155,7 @@ background-image: url('../pic/01HPHVDRV0F0251MD0A2EG66C4-tilemap-heavy-metal-16+pixel+width160.png'); background-size: 400%; background-position: 100% 100%; - image-rendering: crisp-edges; + image-rendering: pixelated; position: relative; diff --git a/static/js/components/literate-programming.js b/static/js/components/literate-programming.js index ba7bbda..8837da4 100644 --- a/static/js/components/literate-programming.js +++ b/static/js/components/literate-programming.js @@ -121,7 +121,14 @@ class InputMode { } static highlight(frame) { - highlight(frame, InputMode.JAVASCRIPT); + highlight(frame, InputMode.JAVASCRIPT, (token, span) => { + if (token.kind == "keyword1" && token.string == "export") { + // This is something a bit non-obvious about the treehouse's literate programs + // so let's document it. + span.classList.add("export"); + span.title = "This item is exported and visible in code blocks that follow"; + } + }); } } @@ -145,7 +152,7 @@ class OutputMode { if (this.worker != null) { this.worker.terminate(); } - this.worker = new Worker(`${TREEHOUSE_SITE}/static/js/components/literate-programming/worker.js`, { + this.worker = new Worker(import.meta.resolve("./literate-programming/worker.js"), { type: "module", name: `evaluate LiterateOutput ${this.frame.programName}` }); @@ -187,13 +194,6 @@ class OutputMode { }) .join(" "); - if (output.kind == "result") { - let returnValueText = document.createElement("span"); - returnValueText.classList.add("return-value"); - returnValueText.textContent = "Return value: "; - line.insertBefore(returnValueText, line.firstChild); - } - this.frame.appendChild(line); } diff --git a/static/js/components/literate-programming/highlight.js b/static/js/components/literate-programming/highlight.js index 20e2cfe..d3ec00d 100644 --- a/static/js/components/literate-programming/highlight.js +++ b/static/js/components/literate-programming/highlight.js @@ -58,7 +58,7 @@ function tokenize(text, syntax) { return tokens; } -export function highlight(element, syntax) { +export function highlight(element, syntax, customize = null) { let tokens = tokenize(element.textContent, syntax); element.textContent = ""; @@ -67,6 +67,9 @@ export function highlight(element, syntax) { let span = document.createElement("span"); span.textContent = token.string; span.classList.add(token.kind); + if (customize != null) { + customize(token, span); + } element.appendChild(span); } } diff --git a/static/js/tairu/tairu.js b/static/js/tairu/tairu.js index cb3e256..6cd35a2 100644 --- a/static/js/tairu/tairu.js +++ b/static/js/tairu/tairu.js @@ -9,6 +9,52 @@ export function shouldConnect(a, b) { return a == b; } +const dirs47 = { + E: 0b0000_0001, + SE: 0b0000_0010, + S: 0b0000_0100, + SW: 0b0000_1000, + W: 0b0001_0000, + NW: 0b0010_0000, + N: 0b0100_0000, + NE: 0b1000_0000, +}; + +function isSet(integer, bit) { + return (integer & bit) == bit; +} + +function removeRedundancies(t) { + if (isSet(t, dirs47.SE) && (!isSet(t, dirs47.S) || !isSet(t, dirs47.E))) { + t &= ~dirs47.SE; + } + if (isSet(t, dirs47.SW) && (!isSet(t, dirs47.S) || !isSet(t, dirs47.W))) { + t &= ~dirs47.SW; + } + if (isSet(t, dirs47.NW) && (!isSet(t, dirs47.N) || !isSet(t, dirs47.W))) { + t &= ~dirs47.NW; + } + if (isSet(t, dirs47.NE) && (!isSet(t, dirs47.N) || !isSet(t, dirs47.E))) { + t &= ~dirs47.NE; + } + return t; +} + +function ordinalDirections() { + let unique = new Set(); + for (let i = 0; i <= 0b1111_1111; ++i) { + unique.add(removeRedundancies(i)); + } + return Array.from(unique).sort((a, b) => a - b); +} + +let xToConnectionBitSet = ordinalDirections(); +let connectionBitSetToX = new Uint8Array(256); +for (let i = 0; i < xToConnectionBitSet.length; ++i) { + connectionBitSetToX[xToConnectionBitSet[i]] = i; +} +console.log(connectionBitSetToX); + export class TileEditor extends Frame { constructor() { super(); @@ -44,12 +90,16 @@ export class TileEditor extends Frame { // 0st element is explicitly null because it represents the empty tile. this.tilesets = [null]; + this.tilesets47 = [null]; let attachedImages = this.getElementsByTagName("img"); for (let image of attachedImages) { if (image.hasAttribute("data-tairu-tileset")) { let tilesetIndex = parseInt(image.getAttribute("data-tairu-tileset")); this.tilesets[tilesetIndex] = image; + } else if (image.hasAttribute("data-tairu-tileset-47")) { + let tilesetIndex = parseInt(image.getAttribute("data-tairu-tileset-47")); + this.tilesets47[tilesetIndex] = image; } } @@ -105,7 +155,7 @@ export class TileEditor extends Frame { get hasTilesets() { // Remember that tile 0 represents emptiness. - return this.tilesets.length > 1; + return this.tilesets.length > 1 || this.tilesets47.length > 1; } drawTiles() { @@ -135,22 +185,34 @@ export class TileEditor extends Frame { for (let x = 0; x < this.tilemap.width; ++x) { let tile = this.tilemap.at(x, y); if (tile != 0) { - let tileset = this.tilesets[tile]; + let tileset16 = this.tilesets[tile]; + let tileset47 = this.tilesets47[tile]; + let tileset = tileset47 != null ? tileset47 : tileset16; - let connectedWithEast = shouldConnect(tile, this.tilemap.at(x + 1, y)) ? 0b0001 : 0; - let connectedWithSouth = shouldConnect(tile, this.tilemap.at(x, y + 1)) ? 0b0010 : 0; - let connectedWithWest = shouldConnect(tile, this.tilemap.at(x - 1, y)) ? 0b0100 : 0; - let connectedWithNorth = shouldConnect(tile, this.tilemap.at(x, y - 1)) ? 0b1000 : 0; - let tileIndex = connectedWithNorth - | connectedWithWest - | connectedWithSouth - | connectedWithEast; + let tileIndex = 0; + if (tileset47 != null) { + let rawTileIndex = 0; + rawTileIndex |= shouldConnect(tile, this.tilemap.at(x + 1, y)) ? dirs47.E : 0; + rawTileIndex |= shouldConnect(tile, this.tilemap.at(x + 1, y + 1)) ? dirs47.SE : 0; + rawTileIndex |= shouldConnect(tile, this.tilemap.at(x, y + 1)) ? dirs47.S : 0; + rawTileIndex |= shouldConnect(tile, this.tilemap.at(x - 1, y + 1)) ? dirs47.SW : 0; + rawTileIndex |= shouldConnect(tile, this.tilemap.at(x - 1, y)) ? dirs47.W : 0; + rawTileIndex |= shouldConnect(tile, this.tilemap.at(x - 1, y - 1)) ? dirs47.NW : 0; + rawTileIndex |= shouldConnect(tile, this.tilemap.at(x, y - 1)) ? dirs47.N : 0; + rawTileIndex |= shouldConnect(tile, this.tilemap.at(x + 1, y - 1)) ? dirs47.NE : 0; + tileIndex = connectionBitSetToX[removeRedundancies(rawTileIndex)]; + } else { + tileIndex |= shouldConnect(tile, this.tilemap.at(x + 1, y)) ? 0b0001 : 0; + tileIndex |= shouldConnect(tile, this.tilemap.at(x, y + 1)) ? 0b0010 : 0; + tileIndex |= shouldConnect(tile, this.tilemap.at(x - 1, y)) ? 0b0100 : 0; + tileIndex |= shouldConnect(tile, this.tilemap.at(x, y - 1)) ? 0b1000 : 0; + } let tilesetTileSize = tileset.height; let tilesetX = tileIndex * tilesetTileSize; let tilesetY = 0; this.ctx.drawImage( - this.tilesets[tile], + tileset, tilesetX, tilesetY, tilesetTileSize, tilesetTileSize, x * this.tileSize, y * this.tileSize, this.tileSize, this.tileSize, ); diff --git a/static/js/tairu/tilemap-registry.js b/static/js/tairu/tilemap-registry.js index f66bcbc..680d278 100644 --- a/static/js/tairu/tilemap-registry.js +++ b/static/js/tairu/tilemap-registry.js @@ -36,5 +36,12 @@ export default { " x x ", " ", ]), + bitwiseAutotiling47: parseTilemap([ + " x ", + " x ", + " xx xx ", + " xxxx ", + " x ", + ]), }; diff --git a/static/pic/01HPW47SHMSVAH7C0JR9HWXWCM-tilemap-heavy-metal-bitwise-48+pixel+width752.png b/static/pic/01HPW47SHMSVAH7C0JR9HWXWCM-tilemap-heavy-metal-bitwise-48+pixel+width752.png new file mode 100644 index 0000000..e8d7944 Binary files /dev/null and b/static/pic/01HPW47SHMSVAH7C0JR9HWXWCM-tilemap-heavy-metal-bitwise-48+pixel+width752.png differ