art.bend source
art.bend on the hub · documented module
# Art: the tile set, and the board as one Image.# ==============================================## Bend's Image is a quadtree over a 2^k square, and so is the board.# A 16x16 board of 32x32 tiles is a depth-9 image: four levels pick the# cell, five more pick the pixel inside its tile.## There are only fourteen tiles on screen at once, so the tile set is# drawn once when the game starts and kept in the state. A frame then# costs 256 reference counts, not 262144 pixels.## 0 hidden 1 flag 2 open, blank# 3..10 open, 1 to 8 mines next to it# 11 mine 12 the mine that was clicked 13 a flag that was wrongimport Baseimport ./grid.bend as Gimport ./cell.bend as C# A colour above 0xFFFFFF means "draw the background here".def Art.clear() -> U32: 16777216def Art.pick(c: Bool, a: U32, b: U32) -> U32: match c: case True{}: a case False{}: bdef Art.gap(a: U32, b: U32) -> U32: +p = a +q = b U32.sub(U32.max(p, q), U32.min(p, q))# The 4x6 digit font, one row of four bits per nibble, row 0 lowest.def Art.font(+d: U32) -> U32: Art.pick(U32.is_eq(d, 1), 7479858, Art.pick(U32.is_eq(d, 2), 15803542, Art.pick(U32.is_eq(d, 3), 6915719, Art.pick(U32.is_eq(d, 4), 4519268, Art.pick(U32.is_eq(d, 5), 6915871, Art.pick(U32.is_eq(d, 6), 6919958, Art.pick(U32.is_eq(d, 7), 2237583, Art.pick(U32.is_eq(d, 8), 6919830, 0))))))))# The colours Minesweeper has used since 1990.def Art.ink(+d: U32) -> U32: Art.pick(U32.is_eq(d, 1), 255, Art.pick(U32.is_eq(d, 2), 32768, Art.pick(U32.is_eq(d, 3), 16711680, Art.pick(U32.is_eq(d, 4), 128, Art.pick(U32.is_eq(d, 5), 8388608, Art.pick(U32.is_eq(d, 6), 32896, Art.pick(U32.is_eq(d, 7), 0, Art.pick(U32.is_eq(d, 8), 8421504, 0))))))))# An unclicked tile: light on the top and left, dark on the bottom right.def Art.raised(+x: U32, +y: U32) -> U32: dark = Bool.or(U32.is_ge(x, 29), U32.is_ge(y, 29)) lite = Bool.or(U32.is_lt(x, 3), U32.is_lt(y, 3)) Art.pick(dark, 8092539, Art.pick(lite, 16777215, 12434877))# A clicked tile: flat, with a grid line along its top and left edge.def Art.flat(x: U32, y: U32) -> U32: Art.pick(Bool.or(U32.is_eq(x, 0), U32.is_eq(y, 0)), 8092539, 12434877)# The glyph box is 16x24 at (8, 4), four screen pixels per font pixel.# The column and row are clamped before the shift, because an x below# 8 wraps to a huge number and U32.shrn counts down its Nat one step# at a time.def Art.digit_on(+d: U32, +x: U32, +y: U32) -> Bool: box = Bool.and( Bool.and(U32.is_ge(x, 8), U32.is_lt(x, 24)), Bool.and(U32.is_ge(y, 4), U32.is_lt(y, 28))) +gx = U32.min(U32.shrn((x - 8 : U32), 2n), 3) +gy = U32.min(U32.shrn((y - 4 : U32), 2n), 5) row = U32.and(U32.shrn(Art.font(d), U32.to_nat((gy * 4 : U32))), 15) on = U32.is_ne(U32.and(U32.shrn(row, U32.to_nat(gx)), 1), 0) Bool.and(box, on)# A ball with two bars through it, and a highlight on its upper left.def Art.mine_on(+x: U32, +y: U32) -> Bool: +dx = Art.gap(x, 16) +dy = Art.gap(y, 16) ball = U32.is_le((dx * dx + dy * dy : U32), 49) barh = Bool.and(U32.is_le(dy, 1), U32.is_le(dx, 10)) barv = Bool.and(U32.is_le(dx, 1), U32.is_le(dy, 10)) Bool.or(ball, Bool.or(barh, barv))def Art.spark_on(+x: U32, +y: U32) -> Bool: Bool.and( Bool.and(U32.is_ge(x, 11), U32.is_le(x, 13)), Bool.and(U32.is_ge(y, 11), U32.is_le(y, 13)))# The cross drawn over a flag that guarded nothing.def Art.cross_on(+x: U32, +y: U32) -> Bool: +dx = Art.gap(x, 16) +dy = Art.gap(y, 16) Bool.and(U32.is_le(Art.gap(dx, dy), 1), U32.is_le(U32.max(dx, dy), 12))def Art.pole_on(+x: U32, +y: U32) -> Bool: pole = Bool.and( Bool.and(U32.is_ge(x, 17), U32.is_le(x, 18)), Bool.and(U32.is_ge(y, 7), U32.is_le(y, 24))) foot = Bool.and( Bool.and(U32.is_ge(x, 10), U32.is_le(x, 22)), Bool.and(U32.is_ge(y, 22), U32.is_le(y, 24))) Bool.or(pole, foot)# The cloth: widest at the top, down to two pixels at the pole.def Art.cloth_on(+x: U32, +y: U32) -> Bool: Bool.and( Bool.and(U32.is_ge(y, 7), U32.is_le(y, 14)), Bool.and(U32.is_ge(x, (y + 2 : U32)), U32.is_le(x, 17)))def Art.flag_px(+x: U32, +y: U32) -> U32: Art.pick(Art.pole_on(x, y), 0, Art.pick(Art.cloth_on(x, y), 16711680, Art.clear()))def Art.mine_px(+x: U32, +y: U32) -> U32: Art.pick(Art.spark_on(x, y), 16777215, Art.pick(Art.mine_on(x, y), 0, Art.clear()))def Art.wrong_px(+x: U32, +y: U32) -> U32: Art.pick(Art.cross_on(x, y), 16711680, Art.mine_px(x, y))def Art.digit_px(+code: U32, +x: U32, +y: U32) -> U32: +d = (code - 2 : U32) Art.pick(Art.digit_on(d, x, y), Art.ink(d), Art.clear())def Art.bg(+code: U32, +x: U32, +y: U32) -> U32: up = Bool.or(U32.is_eq(code, 0), U32.is_eq(code, 1)) Art.pick(up, Art.raised(x, y), Art.pick(U32.is_eq(code, 12), 16711680, Art.flat(x, y)))def Art.fg(+code: U32, +x: U32, +y: U32) -> U32: num = Bool.and(U32.is_ge(code, 3), U32.is_le(code, 10)) bomb = Bool.or(U32.is_eq(code, 11), U32.is_eq(code, 12)) Art.pick(U32.is_eq(code, 1), Art.flag_px(x, y), Art.pick(num, Art.digit_px(code, x, y), Art.pick(bomb, Art.mine_px(x, y), Art.pick(U32.is_eq(code, 13), Art.wrong_px(x, y), Art.clear()))))# One pixel of one tile.def Art.pixel(+code: U32, +x: U32, +y: U32) -> U32: +ink = Art.fg(code, x, y) Art.pick(U32.is_gt(ink, 16777215), Art.bg(code, x, y), ink)# Draws one 2^d square. The four quadrants run in parallel, which is# also how the quadtree wants to be built.def Art.draw(d: Nat, +code: U32, +x: U32, +y: U32) -> Image: match d: case 0n: Pix{Art.pixel(code, x, y)} case Succ{+k}: +h = U32.shln(1, k) a b c e = Art.draw(k, code, x, y) Art.draw(k, code, (x + h : U32), y) Art.draw(k, code, x, (y + h : U32)) Art.draw(k, code, (x + h : U32), (y + h : U32)) Qua{a, b, c, e}# The whole tile set, drawn once. `scale` is the tile depth: 5n is 32x32.def Art.tiles(+scale: Nat) -> List<&2, Image>: [ Art.draw(scale, 0, 0, 0) , Art.draw(scale, 1, 0, 0) , Art.draw(scale, 2, 0, 0) , Art.draw(scale, 3, 0, 0) , Art.draw(scale, 4, 0, 0) , Art.draw(scale, 5, 0, 0) , Art.draw(scale, 6, 0, 0) , Art.draw(scale, 7, 0, 0) , Art.draw(scale, 8, 0, 0) , Art.draw(scale, 9, 0, 0) , Art.draw(scale, 10, 0, 0) , Art.draw(scale, 11, 0, 0) , Art.draw(scale, 12, 0, 0) , Art.draw(scale, 13, 0, 0) ]def Art.take(m: Maybe<&2, Image>) -> Image: match m: case None{}: Pix{16711680} case Some{v}: vdef Art.tile(ts: List<&2, Image>, code: U32) -> Image: Art.take(List.get(&2, Image, ts, U32.to_nat(code)))# Which tile a cell shows. `lost` turns unflagged mines and bad flags# face up; `boom` is the one mine the player actually clicked.def Art.face(c: C.Cell, lost: Bool, boom: Bool) -> U32: match c: case C.Cell{m, n, k}: match k: case C.Hidden{}: Art.pick(Bool.and(lost, m), 11, 0) case C.Flagged{}: Art.pick(Bool.and(lost, Bool.not(m)), 13, 1) case C.Shown{}: Art.pick(m, Art.pick(boom, 12, 11), (2 + n : U32))