clone

an offline sliding-block puzzle game in gleam and lustre, with an endless ladder of puzzles ranked by search

A door is read from its frame, not from three marks

Dami committed 14c2a9d
The aperture was the tray's rule cut across the two cells the goal block leaves
by, with three identical ticks under it. Three marks of one size have to be read
as a group and then interpreted, and the middle one — the only one carrying any
meaning — looked exactly like the two that merely marked a boundary.

So the rule turns a right angle and goes down at each side of the gap. A rule
that stops reads as a rule that ran out; a rule that turns reads as a wall built
up to an opening on purpose. Between the jambs, where the middle tick was, an
arrowhead pointing out of the tray in the goal block's own colour, which is the
one thing on the board that says which piece the door is for and says it without
a word.

The arrowhead is a new op rather than a fill, because it has to be that colour
and everything in the scene is greyscale until the threshold has run. It is
painted in the overlay pass for the reason the maker's mark is: it is a flat mark
whose whole job is to be the same shape every time, and a screen put through
something this small returns gravel. It is drawn as a stack of whole-unit runs
and not as a polygon — the rest of this board is square pixels, and a smooth
hypotenuse beside them is a picture from a different machine.

The figure is fitted to what there is between the tray's rule and the name under
the plate, rather than sized off the cell alone. That strip is the one part of
the plate that does not grow with the board.
commit
14c2a9d15ffb83ce5574c3fd4814a90e63a9de37
parent
702e7a8fa3e6a7c818151045e906e1d36eea9a48
author
Dami <dami.etoile@gmail.com>
3 files changed, +83 -7
Msrc/klotski/paint.gleam8++++
Msrc/klotski/render_ffi.mjs14++++++
Msrc/klotski/scene.gleam68+++++++++++++++++++++++++++---
M src/klotski/paint.gleam +8 -0
M src/klotski/render_ffi.mjs +14 -0
M src/klotski/scene.gleam +61 -7
@@ -160,6 +160,13 @@ pub type Op {
160160 /// through a shape whose whole job is to be constant, and at fourteen pixels161161 /// a screen of any ruling turns a ring into four dots.162162 DevLogo(x: Float, y: Float, size: Float, ink: Rgb)163+ /// The arrowhead under the aperture, pointing out of the tray, in the goal164+ /// block's colour. Painted after the threshold for the reason the maker's mark165+ /// is: it is a flat mark whose job is to be the same shape every time, and a166+ /// screen put through something this small returns gravel. `w` is its width in167+ /// units and is odd, which makes its height exactly `(w + 1) / 2` — it is a168+ /// stack of runs, each two units narrower than the one above.169+ Exit(x: Int, y: Int, w: Int, ink: Rgb)163170 /// Sprites and type are composited *after* the threshold, so they do not164171 /// respect anything drawn over them in the scene — a scrolled list ran165172 /// straight through the menu bar. Anything queued while a clip is set carries
@@ -260,14 +267,61 @@ pub fn tray(r: Rect) -> List(Op) {
260267 ]261268 }262269 263-/// The tray's rule, cut where the block is meant to leave, with three ticks264-/// under it marking the two cells of the aperture.265-pub fn aperture(board: Rect, cell: Int, goal_x: Int, tray_bottom: Int) -> List(Op) {270+/// The way out.271+///272+/// The tray's rule reaches the two cells the goal block leaves by and turns273+/// down, so the gap is a doorway with jambs rather than an absence. A rule that274+/// merely stops reads as a rule that ran out; a rule that turns a right angle275+/// reads as a wall that has been built up to an opening on purpose. Between the276+/// jambs, an arrowhead pointing out of the tray, printed in the goal block's own277+/// colour — which is the one thing on the board that says which piece the door278+/// is for, and it says it without a word.279+///280+/// It was three ticks under the rule before, evenly spaced across the gap. Three281+/// marks of one size have to be read as a group and then interpreted; the middle282+/// one carried the whole meaning and looked exactly like the two that only283+/// marked a boundary.284+///285+/// `room` is what there is between the tray's rule and the name set under the286+/// plate. The figure is fitted into it rather than sized off the cell alone,287+/// because the space below the tray is the one part of the plate that does not288+/// grow with the board.289+pub fn aperture(290+ board: Rect,291+ cell: Int,292+ goal_x: Int,293+ tray_bottom: Int,294+ room: Int,295+ ink: Result(Rgb, Nil),296+) -> List(Op) {266297 let x = board.x + goal_x * cell267- [268- Fill(x + 2, tray_bottom, cell * 2 - 4, 1, paper_tone),269- ..list.map([0, 1, 2], fn(i) { rule(x + i * cell, tray_bottom + 2, 1, 3) })270- ]298+ let w = cell * 2299+300+ // The head is a stack of runs two units narrower each time, so its height301+ // follows from its width and only one of the two is a decision. The jambs go302+ // a unit past it: a frame that stops level with what it frames stops being a303+ // frame.304+ let high =305+ int.max(2, int.min({ room - 3 } / 2, float.round(int.to_float(cell) *. 0.26)))306+ let head = int.min(high * 2 - 1, w - 6)307+ let deep = high + 2308+309+ let mark = case ink {310+ Ok(colour) -> [311+ Exit(x: x + w / 2 - head / 2, y: tray_bottom + 2, w: head, ink: colour),312+ ]313+ // No tints in this theme, so the door is read from its frame alone.314+ Error(_) -> []315+ }316+317+ list.flatten([318+ [319+ Fill(x, tray_bottom, w, 1, paper_tone),320+ rule(x - 1, tray_bottom, 1, deep),321+ rule(x + w, tray_bottom, 1, deep),322+ ],323+ mark,324+ ])271325 }272326 273327 /// A plain label, at its natural size.