# 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 wrong import Base import ./grid.bend as G import ./cell.bend as C # A colour above 0xFFFFFF means "draw the background here". def Art.clear() -> U32: 16777216 def Art.pick(c: Bool, a: U32, b: U32) -> U32: match c: case True{}: a case False{}: b def 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}: v def 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))