From d7061cbfefe9a8a1f6a799931707f54d370e4052 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Mon, 2 Jun 2025 23:02:48 +0300 Subject: [PATCH] examples: add a small memory game (#24643) --- examples/gg/memory.v | 137 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 examples/gg/memory.v diff --git a/examples/gg/memory.v b/examples/gg/memory.v new file mode 100644 index 0000000000..9a00f0a676 --- /dev/null +++ b/examples/gg/memory.v @@ -0,0 +1,137 @@ +module main + +import gg +import gx +import rand +import time + +const cover = gx.rgba(85, 200, 85, 255) +const csize = 120 // cell size in pixels +const letters = 'AABBOOCCVVXXYYZZMMKKHHTT'.runes().map(it.str()) +const header_size = 30 + +struct Cell { +mut: + is_open bool + letter string +} + +struct Game { +mut: + ctx &gg.Context = unsafe { nil } + cells []Cell + card1_idx ?int + card2_idx ?int + size int // in cells + remaining int + sw time.StopWatch = time.new_stopwatch() + revert_sw time.StopWatch = time.new_stopwatch(auto_start: false) +} + +fn (mut g Game) restart() { + ncells := g.size * g.size + g.remaining = ncells + g.cells = []Cell{len: ncells, init: Cell{ + letter: letters[index % letters.len] + }} + rand.shuffle(mut g.cells) or {} + g.sw = time.new_stopwatch() + g.card1_idx = none + g.card2_idx = none +} + +fn (mut g Game) draw_cell(i int, cell Cell) { + x, y := i % g.size, i / g.size + rect_x, rect_y := x * csize, header_size + y * csize + dt := g.sw.elapsed().milliseconds() + if g.cells[i].is_open || dt <= 1000 { + lsize := 96 + g.ctx.draw_rect_empty(rect_x + 6, rect_y + 6, csize - 10, csize - 10, gx.light_gray) + g.ctx.draw_text(rect_x + csize / 2 - lsize / 3, rect_y + csize / 2 - lsize / 2, + g.cells[i].letter, color: gx.yellow, size: lsize) + } else { + g.ctx.draw_rect_filled(rect_x + 6, rect_y + 6, csize - 10, csize - 10, cover) + } +} + +fn on_frame(mut g Game) { + ws := gg.window_size() + g.ctx.begin() + message := '(r)estart (esc)ape | remaining: ${g.remaining:02} | time: ${f64(g.sw.elapsed().milliseconds()) / 1000.0:06.1f}s' + g.ctx.draw_text(ws.width / 2, 7, message, color: gx.light_gray, size: 22, align: .center) + for i, cell in g.cells { + g.draw_cell(i, cell) + } + dt := g.revert_sw.elapsed().milliseconds() + if dt > 750 { + g.revert_sw = time.new_stopwatch(auto_start: false) + if g.card1_idx != none { + if g.card2_idx != none { + g.cells[g.card1_idx].is_open = false + g.cells[g.card2_idx].is_open = false + g.card1_idx = none + g.card2_idx = none + g.remaining = g.cells.count(!it.is_open) + } + } + } + g.ctx.end() +} + +fn on_event(e &gg.Event, mut g Game) { + if e.typ == .key_down { + match e.key_code { + .escape { g.ctx.quit() } + .r { g.restart() } + else {} + } + return + } + if e.typ != .mouse_down { + return + } + x, y := int(e.mouse_x / csize), int((e.mouse_y - header_size) / csize) + if e.mouse_button == .left && g.card2_idx == none { + if g.remaining == 0 { + g.restart() + return + } + i := y * g.size + x + if !g.cells[i].is_open { + g.cells[i].is_open = true + if g.card1_idx == none { + g.card1_idx = i + } else { + g.card2_idx = i + if g.cells[g.card1_idx].letter == g.cells[i].letter { + g.card1_idx = none + g.card2_idx = none + } else { + // start a timer, that will be checked in the on_frame callback + g.revert_sw.start() + } + } + } + } + g.remaining = g.cells.count(!it.is_open) + if g.remaining == 0 { + g.sw.stop() + } +} + +mut g := &Game{ + // the CLI argument should be number of pairs, so `size` is even, and the puzzle can be solved: + size: arguments()[1] or { '3' }.int() * 2 +} +g.restart() +g.ctx = gg.new_context( + bg_color: gx.black + width: g.size * csize + height: header_size + g.size * csize + window_title: 'V Memory ${g.size} x ${g.size}' + user_data: g + frame_fn: on_frame + event_fn: on_event + sample_count: 2 +) +g.ctx.run()