Hardware components | ||||||
| × | 1 | ||||
Software apps and online services | ||||||
![]() |
| |||||
In a grid-based digital frontier powered by RT-Thread, hidden mines threaten system stability. You are the Grid Guardian, tasked with clearing safe zones and neutralizing threats. Each tap is a logic decision: reveal a number, flag a danger, or risk a system fault.
What began decades ago as a Windows classic puzzle game has now been reborn on the Renesas HMI board. Minesweeper, once played with a mouse and keyboard, is transformed into a touch-driven embedded showcase. The familiar tension of uncovering squares and avoiding hidden mines is now paired with the responsiveness and multitasking power of RT-Thread, proving that even nostalgic challenges can evolve into modern demonstrations of embedded logic.
This project bridges past and present: a timeless puzzle adapted to highlight the speed, efficiency, and clarity of real-time operating systems on dedicated hardware.
How It Works- Touch Input: Tap to reveal a cell
- MODE DIG / FLAG Toggle: Switch between revealing cells and flagging suspected mines
- Restart Button: Resets the board and game state
Grid Initialization
- A 2D array of cells is generated
- Mines are randomly placed
- Each non-mine cell stores a number that represents the count of adjacent mines
TapBehavior
- Tap a cell with to reveal a number, a zero which reveals adjacent squares without a number, or a mine to trigger Game Over
- Win Condition
- All non-mine cells are revealed
- All mines are correctly flagged and all non-mine cells are revealed
Loss Condition:
- A mine is tapped in DIG mode
#ifndef GAME_H
#define GAME_H
#include <stdint.h>
#include <stdbool.h>
// Grid Dimensions (Fit within 480x232 area)
#define GAME_COLS 10
#define GAME_ROWS 6
#define TOTAL_MINES 8
typedef enum {
CELL_HIDDEN,
CELL_REVEALED,
CELL_FLAGGED
} CellState;
typedef struct {
bool is_mine;
int neighbor_mines; // 0-8
CellState state;
} Cell;
typedef enum {
GAME_PLAYING,
GAME_WON,
GAME_LOST
} GameStatus;
void game_logic_init(void);
void game_logic_reset(void);
GameStatus game_click_cell(int x, int y, bool flag_mode);
Cell* game_get_cell(int x, int y);
GameStatus game_get_status(void);
#endif // GAME_H
#include "ui.h"
#include "game.h"
#include "lvgl.h"
#include <stdio.h>
// UI Objects
static lv_obj_t *lbl_status;
static lv_obj_t *btn_mode;
static lv_obj_t *lbl_mode;
static lv_obj_t *game_container;
static lv_obj_t *tile_btns[GAME_ROWS][GAME_COLS];
static bool is_flag_mode = false;
// Styles
static lv_style_t style_tile_hidden;
static lv_style_t style_tile_revealed;
static lv_style_t style_tile_mine;
// --- Helper Functions ---
static void update_ui_grid(void) {
for (int y = 0; y < GAME_ROWS; y++) {
for (int x = 0; x < GAME_COLS; x++) {
Cell *c = game_get_cell(x, y);
lv_obj_t *btn = tile_btns[y][x];
if (c->state == CELL_HIDDEN) {
lv_obj_add_style(btn, &style_tile_hidden, 0);
lv_obj_remove_style(btn, &style_tile_revealed, 0);
lv_obj_remove_style(btn, &style_tile_mine, 0);
lv_label_set_text(lv_obj_get_child(btn, 0), "");
}
else if (c->state == CELL_FLAGGED) {
lv_obj_add_style(btn, &style_tile_hidden, 0); // Keep hidden style base
lv_label_set_text(lv_obj_get_child(btn, 0), "F");
lv_obj_set_style_text_color(lv_obj_get_child(btn, 0), lv_palette_main(LV_PALETTE_YELLOW), 0);
}
else if (c->state == CELL_REVEALED) {
if (c->is_mine) {
lv_obj_add_style(btn, &style_tile_mine, 0);
lv_label_set_text(lv_obj_get_child(btn, 0), "X");
lv_obj_set_style_text_color(lv_obj_get_child(btn, 0), lv_color_white(), 0);
} else {
lv_obj_remove_style(btn, &style_tile_hidden, 0);
lv_obj_add_style(btn, &style_tile_revealed, 0);
if (c->neighbor_mines > 0) {
char buf[2];
snprintf(buf, sizeof(buf), "%d", c->neighbor_mines);
lv_label_set_text(lv_obj_get_child(btn, 0), buf);
// Color code numbers
lv_color_t num_color;
switch(c->neighbor_mines) {
case 1: num_color = lv_palette_main(LV_PALETTE_BLUE); break;
case 2: num_color = lv_palette_main(LV_PALETTE_GREEN); break;
case 3: num_color = lv_palette_main(LV_PALETTE_RED); break;
default: num_color = lv_palette_main(LV_PALETTE_PURPLE); break;
}
lv_obj_set_style_text_color(lv_obj_get_child(btn, 0), num_color, 0);
} else {
lv_label_set_text(lv_obj_get_child(btn, 0), "");
}
}
}
}
}
GameStatus status = game_get_status();
if (status == GAME_WON) lv_label_set_text(lbl_status, "YOU WIN!");
else if (status == GAME_LOST) lv_label_set_text(lbl_status, "GAME OVER");
else lv_label_set_text(lbl_status, "Minesweeper");
}
// --- Event Handlers ---
static void evt_tile_click(lv_event_t *e) {
if (game_get_status() != GAME_PLAYING) return;
// Retrieve coordinates stored in user_data
intptr_t coords = (intptr_t)lv_event_get_user_data(e);
int x = (coords >> 8) & 0xFF;
int y = coords & 0xFF;
game_click_cell(x, y, is_flag_mode);
update_ui_grid();
}
static void evt_reset(lv_event_t *e) {
game_logic_reset();
lv_label_set_text(lbl_status, "Minesweeper");
update_ui_grid();
}
static void evt_mode_toggle(lv_event_t *e) {
is_flag_mode = !is_flag_mode;
if (is_flag_mode) {
lv_label_set_text(lbl_mode, "MODE: FLAG");
lv_obj_set_style_bg_color(btn_mode, lv_palette_main(LV_PALETTE_ORANGE), 0);
} else {
lv_label_set_text(lbl_mode, "MODE: DIG");
lv_obj_set_style_bg_color(btn_mode, lv_palette_main(LV_PALETTE_BLUE), 0);
}
}
// --- Init ---
void ui_init(void) {
game_logic_init();
// 1. Initialize Styles
lv_style_init(&style_tile_hidden);
lv_style_set_bg_color(&style_tile_hidden, lv_palette_main(LV_PALETTE_GREY));
lv_style_set_border_width(&style_tile_hidden, 2);
lv_style_set_border_color(&style_tile_hidden, lv_palette_darken(LV_PALETTE_GREY, 2));
lv_style_init(&style_tile_revealed);
lv_style_set_bg_color(&style_tile_revealed, lv_color_white());
lv_style_set_border_width(&style_tile_revealed, 1);
lv_style_set_border_color(&style_tile_revealed, lv_palette_lighten(LV_PALETTE_GREY, 2));
lv_style_init(&style_tile_mine);
lv_style_set_bg_color(&style_tile_mine, lv_palette_main(LV_PALETTE_RED));
// 2. Top Bar
lv_obj_t *top_bar = lv_obj_create(lv_scr_act());
lv_obj_set_size(top_bar, 480, 50);
lv_obj_set_pos(top_bar, 0, 0);
lv_obj_set_flex_flow(top_bar, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(top_bar, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_clear_flag(top_bar, LV_OBJ_FLAG_SCROLLABLE);
// Status Label
lbl_status = lv_label_create(top_bar);
lv_label_set_text(lbl_status, "Minesweeper");
// Mode Button
btn_mode = lv_btn_create(top_bar);
lv_obj_add_event_cb(btn_mode, evt_mode_toggle, LV_EVENT_CLICKED, NULL);
lv_obj_set_width(btn_mode, 120);
lbl_mode = lv_label_create(btn_mode);
lv_label_set_text(lbl_mode, "MODE: DIG");
lv_obj_center(lbl_mode);
// Reset Button
lv_obj_t *btn_rst = lv_btn_create(top_bar);
lv_obj_add_event_cb(btn_rst, evt_reset, LV_EVENT_CLICKED, NULL);
lv_obj_t *l = lv_label_create(btn_rst);
lv_label_set_text(l, "Restart");
// 3. Game Grid Container
game_container = lv_obj_create(lv_scr_act());
lv_obj_set_size(game_container, 480, 222);
lv_obj_set_pos(game_container, 0, 50);
lv_obj_set_style_pad_all(game_container, 2, 0);
lv_obj_clear_flag(game_container, LV_OBJ_FLAG_SCROLLABLE);
// Calculate tile size
// Width: (480 - padding) / 10 cols approx 46px
// Height: (222 - padding) / 6 rows approx 35px
int w = 46;
int h = 34;
for (int y = 0; y < GAME_ROWS; y++) {
for (int x = 0; x < GAME_COLS; x++) {
tile_btns[y][x] = lv_btn_create(game_container);
lv_obj_set_size(tile_btns[y][x], w, h);
lv_obj_set_pos(tile_btns[y][x], x * w + 5, y * h + 5); // Simple manual grid placement
lv_obj_add_style(tile_btns[y][x], &style_tile_hidden, 0);
// Store coordinates in user_data (Upper byte X, Lower byte Y)
intptr_t coords = (x << 8) | y;
lv_obj_add_event_cb(tile_btns[y][x], evt_tile_click, LV_EVENT_CLICKED, (void*)coords);
// Label for number/icon
lv_obj_t *lbl = lv_label_create(tile_btns[y][x]);
lv_label_set_text(lbl, "");
lv_obj_center(lbl);
}
}
update_ui_grid();
}
#include "game.h"
#include <stdlib.h>
#include <rtthread.h> // For rt_tick_get() seeding
static Cell grid[GAME_ROWS][GAME_COLS];
static GameStatus current_status;
static int cells_revealed;
// Helper: Check bounds
static bool is_valid(int x, int y) {
return (x >= 0 && x < GAME_COLS && y >= 0 && y < GAME_ROWS);
}
// Calculate neighbor mines for a cell
static void calculate_neighbors(void) {
for (int y = 0; y < GAME_ROWS; y++) {
for (int x = 0; x < GAME_COLS; x++) {
if (grid[y][x].is_mine) continue;
int count = 0;
for (int dy = -1; dy <= 1; dy++) {
for (int dx = -1; dx <= 1; dx++) {
if (dx == 0 && dy == 0) continue;
if (is_valid(x + dx, y + dy) && grid[y + dy][x + dx].is_mine) {
count++;
}
}
}
grid[y][x].neighbor_mines = count;
}
}
}
void game_logic_init(void) {
srand(rt_tick_get()); // Seed using RT-Thread tick
game_logic_reset();
}
void game_logic_reset(void) {
current_status = GAME_PLAYING;
cells_revealed = 0;
// Reset grid
for (int y = 0; y < GAME_ROWS; y++) {
for (int x = 0; x < GAME_COLS; x++) {
grid[y][x].is_mine = false;
grid[y][x].state = CELL_HIDDEN;
grid[y][x].neighbor_mines = 0;
}
}
// Place Mines
int mines_placed = 0;
while (mines_placed < TOTAL_MINES) {
int rx = rand() % GAME_COLS;
int ry = rand() % GAME_ROWS;
if (!grid[ry][rx].is_mine) {
grid[ry][rx].is_mine = true;
mines_placed++;
}
}
calculate_neighbors();
}
// Recursive flood fill for empty cells
static void reveal_recursive(int x, int y) {
if (!is_valid(x, y) || grid[y][x].state != CELL_HIDDEN) return;
grid[y][x].state = CELL_REVEALED;
cells_revealed++;
if (grid[y][x].neighbor_mines > 0) return; // Stop at number border
// Recurse neighbors
for (int dy = -1; dy <= 1; dy++) {
for (int dx = -1; dx <= 1; dx++) {
reveal_recursive(x + dx, y + dy);
}
}
}
GameStatus game_click_cell(int x, int y, bool flag_mode) {
if (current_status != GAME_PLAYING || !is_valid(x, y)) return current_status;
Cell *c = &grid[y][x];
if (flag_mode) {
if (c->state == CELL_HIDDEN) c->state = CELL_FLAGGED;
else if (c->state == CELL_FLAGGED) c->state = CELL_HIDDEN;
return GAME_PLAYING;
}
// Dig Mode
if (c->state == CELL_FLAGGED || c->state == CELL_REVEALED) return GAME_PLAYING;
if (c->is_mine) {
c->state = CELL_REVEALED;
current_status = GAME_LOST;
// Reveal all mines
for(int i=0; i<GAME_ROWS; i++) {
for(int j=0; j<GAME_COLS; j++) {
if(grid[i][j].is_mine) grid[i][j].state = CELL_REVEALED;
}
}
} else {
reveal_recursive(x, y);
// Check win condition
if (cells_revealed >= (GAME_COLS * GAME_ROWS - TOTAL_MINES)) {
current_status = GAME_WON;
}
}
return current_status;
}
Cell* game_get_cell(int x, int y) {
if (!is_valid(x, y)) return NULL;
return &grid[y][x];
}
GameStatus game_get_status(void) {
return current_status;
}
#include <rtthread.h>
#include "lvgl.h"
#include "ui.h"
/* * This function is called by the board initialization process
* (usually in board/lvgl/lv_port_disp.c or similar)
*/
void lv_user_gui_init(void)
{
ui_init();
}
#include <rtthread.h>
#include <rtdevice.h>
#include "hal_data.h"
/* defined in Renesas FSP BSP, but re-defining just in case to match your context */
#ifndef BSP_IO_PORT_06_PIN_00
#define BSP_IO_PORT_06_PIN_00 (0x0600)
#endif
#define LCD_BACKLIGHT_PIN BSP_IO_PORT_06_PIN_00
int main(void)
{
/* 1. Hardware Setup: Turn on LCD Backlight
* The display driver initializes the screen data, but the
* physical backlight often requires this manual GPIO high.
*/
rt_pin_mode(LCD_BACKLIGHT_PIN, PIN_MODE_OUTPUT);
rt_pin_write(LCD_BACKLIGHT_PIN, PIN_HIGH);
rt_kprintf("System Started: LCD Backlight ON\n");
/* * 2. Main Loop
* Since LVGL runs in its own thread (initialized by board/lvgl drivers),
* and your game logic is hooked via lv_user_gui_init,
* the main thread just needs to stay alive or yield.
* * We blink the built-in LED (if available) or just sleep
* to indicate the system is running without freezing.
*/
while (1)
{
rt_thread_mdelay(1000);
}
return RT_EOK;
}




Comments