~/bend-docscommunity

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))