|
| 1 | +module main |
| 2 | + |
| 3 | +import gg |
| 4 | +import gx |
| 5 | +import rand |
| 6 | +import time |
| 7 | + |
| 8 | +const cover = gx.rgba(85, 200, 85, 255) |
| 9 | +const csize = 120 // cell size in pixels |
| 10 | +const letters = 'AABBOOCCVVXXYYZZMMKKHHTT'.runes().map(it.str()) |
| 11 | +const header_size = 30 |
| 12 | + |
| 13 | +struct Cell { |
| 14 | +mut: |
| 15 | + is_open bool |
| 16 | + letter string |
| 17 | +} |
| 18 | + |
| 19 | +struct Game { |
| 20 | +mut: |
| 21 | + ctx &gg.Context = unsafe { nil } |
| 22 | + cells []Cell |
| 23 | + card1_idx ?int |
| 24 | + card2_idx ?int |
| 25 | + size int // in cells |
| 26 | + remaining int |
| 27 | + sw time.StopWatch = time.new_stopwatch() |
| 28 | + revert_sw time.StopWatch = time.new_stopwatch(auto_start: false) |
| 29 | +} |
| 30 | + |
| 31 | +fn (mut g Game) restart() { |
| 32 | + ncells := g.size * g.size |
| 33 | + g.remaining = ncells |
| 34 | + g.cells = []Cell{len: ncells, init: Cell{ |
| 35 | + letter: letters[index % letters.len] |
| 36 | + }} |
| 37 | + rand.shuffle(mut g.cells) or {} |
| 38 | + g.sw = time.new_stopwatch() |
| 39 | + g.card1_idx = none |
| 40 | + g.card2_idx = none |
| 41 | +} |
| 42 | + |
| 43 | +fn (mut g Game) draw_cell(i int, cell Cell) { |
| 44 | + x, y := i % g.size, i / g.size |
| 45 | + rect_x, rect_y := x * csize, header_size + y * csize |
| 46 | + dt := g.sw.elapsed().milliseconds() |
| 47 | + if g.cells[i].is_open || dt <= 1000 { |
| 48 | + lsize := 96 |
| 49 | + g.ctx.draw_rect_empty(rect_x + 6, rect_y + 6, csize - 10, csize - 10, gx.light_gray) |
| 50 | + g.ctx.draw_text(rect_x + csize / 2 - lsize / 3, rect_y + csize / 2 - lsize / 2, |
| 51 | + g.cells[i].letter, color: gx.yellow, size: lsize) |
| 52 | + } else { |
| 53 | + g.ctx.draw_rect_filled(rect_x + 6, rect_y + 6, csize - 10, csize - 10, cover) |
| 54 | + } |
| 55 | +} |
| 56 | + |
| 57 | +fn on_frame(mut g Game) { |
| 58 | + ws := gg.window_size() |
| 59 | + g.ctx.begin() |
| 60 | + message := '(r)estart (esc)ape | remaining: ${g.remaining:02} | time: ${f64(g.sw.elapsed().milliseconds()) / 1000.0:06.1f}s' |
| 61 | + g.ctx.draw_text(ws.width / 2, 7, message, color: gx.light_gray, size: 22, align: .center) |
| 62 | + for i, cell in g.cells { |
| 63 | + g.draw_cell(i, cell) |
| 64 | + } |
| 65 | + dt := g.revert_sw.elapsed().milliseconds() |
| 66 | + if dt > 750 { |
| 67 | + g.revert_sw = time.new_stopwatch(auto_start: false) |
| 68 | + if g.card1_idx != none { |
| 69 | + if g.card2_idx != none { |
| 70 | + g.cells[g.card1_idx].is_open = false |
| 71 | + g.cells[g.card2_idx].is_open = false |
| 72 | + g.card1_idx = none |
| 73 | + g.card2_idx = none |
| 74 | + g.remaining = g.cells.count(!it.is_open) |
| 75 | + } |
| 76 | + } |
| 77 | + } |
| 78 | + g.ctx.end() |
| 79 | +} |
| 80 | + |
| 81 | +fn on_event(e &gg.Event, mut g Game) { |
| 82 | + if e.typ == .key_down { |
| 83 | + match e.key_code { |
| 84 | + .escape { g.ctx.quit() } |
| 85 | + .r { g.restart() } |
| 86 | + else {} |
| 87 | + } |
| 88 | + return |
| 89 | + } |
| 90 | + if e.typ != .mouse_down { |
| 91 | + return |
| 92 | + } |
| 93 | + x, y := int(e.mouse_x / csize), int((e.mouse_y - header_size) / csize) |
| 94 | + if e.mouse_button == .left && g.card2_idx == none { |
| 95 | + if g.remaining == 0 { |
| 96 | + g.restart() |
| 97 | + return |
| 98 | + } |
| 99 | + i := y * g.size + x |
| 100 | + if !g.cells[i].is_open { |
| 101 | + g.cells[i].is_open = true |
| 102 | + if g.card1_idx == none { |
| 103 | + g.card1_idx = i |
| 104 | + } else { |
| 105 | + g.card2_idx = i |
| 106 | + if g.cells[g.card1_idx].letter == g.cells[i].letter { |
| 107 | + g.card1_idx = none |
| 108 | + g.card2_idx = none |
| 109 | + } else { |
| 110 | + // start a timer, that will be checked in the on_frame callback |
| 111 | + g.revert_sw.start() |
| 112 | + } |
| 113 | + } |
| 114 | + } |
| 115 | + } |
| 116 | + g.remaining = g.cells.count(!it.is_open) |
| 117 | + if g.remaining == 0 { |
| 118 | + g.sw.stop() |
| 119 | + } |
| 120 | +} |
| 121 | + |
| 122 | +mut g := &Game{ |
| 123 | + // the CLI argument should be number of pairs, so `size` is even, and the puzzle can be solved: |
| 124 | + size: arguments()[1] or { '3' }.int() * 2 |
| 125 | +} |
| 126 | +g.restart() |
| 127 | +g.ctx = gg.new_context( |
| 128 | + bg_color: gx.black |
| 129 | + width: g.size * csize |
| 130 | + height: header_size + g.size * csize |
| 131 | + window_title: 'V Memory ${g.size} x ${g.size}' |
| 132 | + user_data: g |
| 133 | + frame_fn: on_frame |
| 134 | + event_fn: on_event |
| 135 | + sample_count: 2 |
| 136 | +) |
| 137 | +g.ctx.run() |
0 commit comments