KARL VINCENT REMOJOHN WIEL JAYME
Published

RENESAS RA6M3 Grid Game

Sweet Sabotage: A clean, interactive RA6M3 HMI game where strategy decides who avoids the poisoned treat.

BeginnerFull instructions provided2 days16
RENESAS RA6M3 Grid Game

Things used in this project

Hardware components

RT-Thread Renesas RA6M3
×1

Software apps and online services

RT-Thread IoT OS
RT-Thread IoT OS

Story

Read more

Schematics

GAME FLOW CHART

Code

(source) lv_rt_thread_port.c

C/C++
#include "game.h"
#include <stdio.h>

static game_phase_t current_phase;
static int current_player;
static int8_t poison_p1_idx;
static int8_t poison_p2_idx;
static bool taken[GRID_SIZE];
static int turn_count;
static int winner;

/* Initialize game state */
void game_init(void)
{
    game_reset();
}

void game_reset(void)
{
    current_phase = GAME_PHASE_SETUP_P1;
    current_player = 1;
    poison_p1_idx = -1;
    poison_p2_idx = -1;
    turn_count = 0;
    winner = 0;

    for (int i = 0; i < GRID_SIZE; i++) {
        taken[i] = false;
    }

    ui_update_view();
    ui_show_modal("Setup Phase", "Player 1:\nSelect a candy to hide your poison!");
}

/* Main Interaction Logic */
void game_on_cell_tap(uint8_t idx)
{
    if (idx >= GRID_SIZE) return;

    /* --- Setup Phase Player 1 --- */
    if (current_phase == GAME_PHASE_SETUP_P1) {
        poison_p1_idx = (int8_t)idx;

        /* Transition to P2 */
        current_phase = GAME_PHASE_SETUP_P2;
        current_player = 2;

        ui_update_view(); /* Clears board so P2 can't see P1's pick */
        ui_show_modal("Pass Device", "Pass device to Player 2.\nPlayer 2: Hide your poison!");
        return;
    }

    /* --- Setup Phase Player 2 --- */
    if (current_phase == GAME_PHASE_SETUP_P2) {
        /* FIX: Removed the check 'if (idx == poison_p1_idx)'
           Now P2 CAN pick the same spot as P1 (Double Poison Strategy) */

        poison_p2_idx = (int8_t)idx;

        /* Start Game */
        current_phase = GAME_PHASE_PLAY;
        current_player = 1; /* P1 starts */

        ui_update_view();
        ui_show_modal("Game Start!", "Grid is armed.\nPlayer 1, eat a candy... if you dare.");
        return;
    }

    /* --- Main Gameplay Phase --- */
    if (current_phase == GAME_PHASE_PLAY) {
        if (taken[idx]) return; /* Already eaten */

        taken[idx] = true;
        turn_count++;

        /* Check for Death (Poison) */
        bool is_poison = (idx == poison_p1_idx || idx == poison_p2_idx);

        if (is_poison) {
            /* Current player ate poison -> Opponent wins */
            winner = (current_player == 1) ? 2 : 1;
            current_phase = GAME_PHASE_GAME_OVER;

            char msg[64];
            snprintf(msg, sizeof(msg), "Player %d ate poison!", current_player);

            ui_update_view(); /* Reveal poisons */
            ui_show_modal(winner == 1 ? "Player 1 Wins!" : "Player 2 Wins!", msg);
        }
        else {
            /* Check Draw Condition */
            int safe_count = 0;

            /* Calculate total safe candies dynamically based on overlap */
            int total_poisons = (poison_p1_idx == poison_p2_idx) ? 1 : 2;
            int target_safe_eaten = GRID_SIZE - total_poisons;

            for (int i = 0; i < GRID_SIZE; i++) {
                if (taken[i] && i != poison_p1_idx && i != poison_p2_idx) {
                    safe_count++;
                }
            }

            /* If all safe candies are eaten */
            if (safe_count >= target_safe_eaten) {
                winner = 0;
                current_phase = GAME_PHASE_GAME_OVER;
                ui_update_view();
                ui_show_modal("DRAW!", "All safe candies eaten.\nYou both survived!");
            } else {
                /* Switch Turn */
                current_player = (current_player == 1) ? 2 : 1;
                ui_update_view();
            }
        }
    }
}

/* Getters */
game_phase_t game_get_phase(void) { return current_phase; }
int game_get_current_player(void) { return current_player; }
bool game_is_cell_taken(uint8_t idx) { return taken[idx]; }
bool game_is_p1_poison(uint8_t idx) { return (int8_t)idx == poison_p1_idx; }
bool game_is_p2_poison(uint8_t idx) { return (int8_t)idx == poison_p2_idx; }
int game_get_score_p1(void) { return (winner == 1) ? 1 : 0; }
int game_get_score_p2(void) { return (winner == 2) ? 1 : 0; }

(headers) game.h

C/C++
Variables for the game
#ifndef GAME_H
#define GAME_H

#include <stdint.h>
#include <stdbool.h>

/* 5x5 Grid settings */
#define GRID_ROWS 5
#define GRID_COLS 5
#define GRID_SIZE (GRID_ROWS * GRID_COLS)

/* Game Phases */
typedef enum {
    GAME_PHASE_SETUP_P1,    // Player 1 hiding poison
    GAME_PHASE_SETUP_P2,    // Player 2 hiding poison
    GAME_PHASE_PLAY,        // Main gameplay loop
    GAME_PHASE_GAME_OVER    // Match finished
} game_phase_t;

/* Core API */
void game_init(void);
void game_reset(void);
void game_on_cell_tap(uint8_t idx);

/* State Getters for UI */
game_phase_t game_get_phase(void);
int game_get_current_player(void);
bool game_is_cell_taken(uint8_t idx);
bool game_is_p1_poison(uint8_t idx);
bool game_is_p2_poison(uint8_t idx);
int game_get_score_p1(void); // Used as win counter or dummy
int game_get_score_p2(void);

/* Callbacks implemented in UI */
extern void ui_show_modal(const char *title, const char *msg);
extern void ui_update_view(void);

#endif

(headers) ui.h

C/C++
variables for ui
#ifndef UI_H__
#define UI_H__

#include <lvgl.h>
#include "game.h"

/* Initialize UI (create screens, widgets) */
void ui_init(void);

/* Call frequently from main loop */
void ui_task_handler(void);

/* Refresh a single cell's visuals (call after state changes) */
void ui_refresh_cell(uint8_t idx);

/* Show confirm popup during poison selection (player 1 or 2) */
void ui_confirm_selection(uint8_t player, uint8_t idx);

/* Show final game over overlay */
void ui_show_game_over(int winner);

/* Request restart (from UI or popup) */
void ui_request_restart(void);

#endif /* UI_H__ */

(source) game.c

C/C++
main code
#include "game.h"
#include <stdio.h>

static game_phase_t current_phase;
static int current_player;
static int8_t poison_p1_idx;
static int8_t poison_p2_idx;
static bool taken[GRID_SIZE];
static int turn_count;
static int winner;

/* Initialize game state */
void game_init(void)
{
    game_reset();
}

void game_reset(void)
{
    current_phase = GAME_PHASE_SETUP_P1;
    current_player = 1;
    poison_p1_idx = -1;
    poison_p2_idx = -1;
    turn_count = 0;
    winner = 0;

    for (int i = 0; i < GRID_SIZE; i++) {
        taken[i] = false;
    }

    ui_update_view();
    ui_show_modal("Setup Phase", "Player 1:\nSelect a candy to hide your poison!");
}

/* Main Interaction Logic */
void game_on_cell_tap(uint8_t idx)
{
    if (idx >= GRID_SIZE) return;

    /* --- Setup Phase Player 1 --- */
    if (current_phase == GAME_PHASE_SETUP_P1) {
        poison_p1_idx = (int8_t)idx;

        /* Transition to P2 */
        current_phase = GAME_PHASE_SETUP_P2;
        current_player = 2;

        ui_update_view(); /* Clears board so P2 can't see P1's pick */
        ui_show_modal("Pass Device", "Pass device to Player 2.\nPlayer 2: Hide your poison!");
        return;
    }

    /* --- Setup Phase Player 2 --- */
    if (current_phase == GAME_PHASE_SETUP_P2) {
        /* FIX: Removed the check 'if (idx == poison_p1_idx)'
           Now P2 CAN pick the same spot as P1 (Double Poison Strategy) */

        poison_p2_idx = (int8_t)idx;

        /* Start Game */
        current_phase = GAME_PHASE_PLAY;
        current_player = 1; /* P1 starts */

        ui_update_view();
        ui_show_modal("Game Start!", "Grid is armed.\nPlayer 1, eat a candy... if you dare.");
        return;
    }

    /* --- Main Gameplay Phase --- */
    if (current_phase == GAME_PHASE_PLAY) {
        if (taken[idx]) return; /* Already eaten */

        taken[idx] = true;
        turn_count++;

        /* Check for Death (Poison) */
        bool is_poison = (idx == poison_p1_idx || idx == poison_p2_idx);

        if (is_poison) {
            /* Current player ate poison -> Opponent wins */
            winner = (current_player == 1) ? 2 : 1;
            current_phase = GAME_PHASE_GAME_OVER;

            char msg[64];
            snprintf(msg, sizeof(msg), "Player %d ate poison!", current_player);

            ui_update_view(); /* Reveal poisons */
            ui_show_modal(winner == 1 ? "Player 1 Wins!" : "Player 2 Wins!", msg);
        }
        else {
            /* Check Draw Condition */
            int safe_count = 0;

            /* Calculate total safe candies dynamically based on overlap */
            int total_poisons = (poison_p1_idx == poison_p2_idx) ? 1 : 2;
            int target_safe_eaten = GRID_SIZE - total_poisons;

            for (int i = 0; i < GRID_SIZE; i++) {
                if (taken[i] && i != poison_p1_idx && i != poison_p2_idx) {
                    safe_count++;
                }
            }

            /* If all safe candies are eaten */
            if (safe_count >= target_safe_eaten) {
                winner = 0;
                current_phase = GAME_PHASE_GAME_OVER;
                ui_update_view();
                ui_show_modal("DRAW!", "All safe candies eaten.\nYou both survived!");
            } else {
                /* Switch Turn */
                current_player = (current_player == 1) ? 2 : 1;
                ui_update_view();
            }
        }
    }
}

/* Getters */
game_phase_t game_get_phase(void) { return current_phase; }
int game_get_current_player(void) { return current_player; }
bool game_is_cell_taken(uint8_t idx) { return taken[idx]; }
bool game_is_p1_poison(uint8_t idx) { return (int8_t)idx == poison_p1_idx; }
bool game_is_p2_poison(uint8_t idx) { return (int8_t)idx == poison_p2_idx; }
int game_get_score_p1(void) { return (winner == 1) ? 1 : 0; }
int game_get_score_p2(void) { return (winner == 2) ? 1 : 0; }

(source) lvgl_port.c

C/C++
#include <lvgl.h>
#include "ui.h"
#include "game.h"

/* * The display and input drivers (lv_port_disp_init, lv_port_indev_init)
 * are provided by the board drivers in the "board/lvgl" folder.
 * * This file only needs to define the user GUI entry point.
 */

void lv_user_gui_init(void)
{
    /* * FIX: Only call ui_init().
     * * We DO NOT call game_init() here because ui_init() inside 'src/ui.c'
     * already calls game_init() at the very end.
     * * Calling it twice here was causing the crash (black screen) because
     * the game tried to update buttons that hadn't been created yet.
     */
    ui_init();
}

(source) main.c

C/C++
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>

/* * NOTE: We do NOT include lvgl.h or ui.h here.
 * The LVGL thread starts automatically in the background
 * and calls lv_user_gui_init() in src/lvgl_port.c
 */

int main(void)
{
    /* LED setup (optional, just to verify board is alive) */
    #ifdef LED_BLUE_PIN
    rt_pin_mode(LED_BLUE_PIN, PIN_MODE_OUTPUT);
    #endif

    while (1)
    {
        /* Optional: Blink LED to show system is running */
        #ifdef LED_BLUE_PIN
        rt_pin_write(LED_BLUE_PIN, PIN_HIGH);
        rt_thread_mdelay(500);
        rt_pin_write(LED_BLUE_PIN, PIN_LOW);
        rt_thread_mdelay(500);
        #else
        /* Just sleep to let the idle task run */
        rt_thread_mdelay(1000);
        #endif
    }

    return 0;
}

(source) ui.c

C/C++
#include "ui.h"
#include "game.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/* --- EMBEDDED CANDY ICON (Alpha Map) --- */
/* This creates a "Candy" shape directly in code so you don't need external files/fonts */
#if LV_COLOR_DEPTH == 16 || LV_COLOR_DEPTH == 32
static const uint8_t candy_icon_map[] = {
  0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00,
  0x00, 0x00, 0xff, 0xcc, 0xff, 0x00, 0x00, 0x00, 0xff, 0xcc, 0xff, 0x00,
  0x00, 0xff, 0xcc, 0x00, 0xcc, 0xff, 0xff, 0xff, 0x00, 0xcc, 0xff, 0x00,
  0xff, 0xcc, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0xcc, 0xff,
  0x00, 0xff, 0xcc, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xcc, 0xff,
  0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
  0x00, 0x00, 0xff, 0xff, 0xff, 0xe0, 0xe0, 0xff, 0xff, 0xff, 0x00, 0x00,
  0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00,
  0x00, 0x00, 0xff, 0xff, 0xff, 0xe0, 0xe0, 0xff, 0xff, 0xff, 0x00, 0x00,
  0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
  0x00, 0xff, 0xcc, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xcc, 0xff,
  0xff, 0xcc, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0xcc, 0xff,
  0x00, 0xff, 0xcc, 0x00, 0xcc, 0xff, 0xff, 0xff, 0x00, 0xcc, 0xff, 0x00,
  0x00, 0x00, 0xff, 0xcc, 0xff, 0x00, 0x00, 0x00, 0xff, 0xcc, 0xff, 0x00,
  0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00,
};

const lv_img_dsc_t candy_icon = {
  .header.cf = LV_IMG_CF_ALPHA_8BIT,
  .header.always_zero = 0,
  .header.reserved = 0,
  .header.w = 12,
  .header.h = 15,
  .data_size = 180,
  .data = candy_icon_map,
};
#endif

/* Layout for 480x272 - 5x5 Grid */
#define CELL_W 54
#define CELL_H 38
#define CELL_GAP 6
#define GRID_X 93
#define GRID_Y 40

/* UI objects */
static lv_obj_t *screen_obj;
static lv_obj_t *title_screen_obj;
static lv_obj_t *game_screen_obj;
static lv_obj_t *cells[GRID_SIZE];
static lv_obj_t *lbl_status;
static lv_obj_t *btn_restart;
static lv_obj_t *modal_obj = NULL;

/* Storage for visual state */
static lv_color_t cell_save_colors[GRID_SIZE];

/* Styles */
static lv_style_t style_bg;
static lv_style_t style_cell;
static lv_style_t style_text;
static lv_style_t style_btn;
static lv_style_t style_title_text;
static lv_style_t style_subtitle_text;

/* Candy Colors Palette */
static uint32_t candy_colors[] = {
    0xFF5252, // Red
    0x448AFF, // Blue
    0x69F0AE, // Green
    0xFFD740, // Yellow
    0xE040FB, // Purple
    0xFFAB40, // Orange
    0xFF4081  // Pink
};

/* Forward declarations */
static void cell_event_cb(lv_event_t * e);
static void restart_event_cb(lv_event_t * e);
static void start_setup_event_cb(lv_event_t * e);
static void modal_close_cb(lv_event_t * e);
void ui_show_game_screen(void);

static void update_labels(void)
{
    char buf[64];
    game_phase_t phase = game_get_phase();
    int p = game_get_current_player();

    if (phase == GAME_PHASE_SETUP_P1) {
        snprintf(buf, sizeof(buf), "Setup: Player 1 Hide Poison");
    } else if (phase == GAME_PHASE_SETUP_P2) {
        snprintf(buf, sizeof(buf), "Setup: Player 2 Hide Poison");
    } else if (phase == GAME_PHASE_PLAY) {
        snprintf(buf, sizeof(buf), "Turn: Player %d (Eat!)", p);
    } else {
        snprintf(buf, sizeof(buf), "Game Over");
    }

    if (lbl_status) lv_label_set_text(lbl_status, buf);
}

/* Modal functions */
void ui_show_modal(const char *title, const char *msg)
{
    if (modal_obj) lv_obj_del(modal_obj);

    modal_obj = lv_obj_create(lv_scr_act());
    lv_obj_set_size(modal_obj, 300, 180);
    lv_obj_center(modal_obj);
    lv_obj_set_style_bg_color(modal_obj, lv_color_hex(0xFFFFFF), LV_PART_MAIN);
    lv_obj_set_style_radius(modal_obj, 12, LV_PART_MAIN);
    lv_obj_set_style_border_width(modal_obj, 4, LV_PART_MAIN);
    lv_obj_set_style_border_color(modal_obj, lv_color_hex(0xFF80AB), LV_PART_MAIN);

    lv_obj_t *l_title = lv_label_create(modal_obj);
    lv_label_set_text(l_title, title);
    lv_obj_set_style_text_font(l_title, &lv_font_montserrat_14, 0);
    lv_obj_set_style_text_color(l_title, lv_color_hex(0xC51162), 0);
    lv_obj_align(l_title, LV_ALIGN_TOP_MID, 0, 5);

    lv_obj_t *l_msg = lv_label_create(modal_obj);
    lv_label_set_text(l_msg, msg);
    lv_label_set_long_mode(l_msg, LV_LABEL_LONG_WRAP);
    lv_obj_set_width(l_msg, 260);
    lv_obj_set_style_text_align(l_msg, LV_TEXT_ALIGN_CENTER, 0);
    lv_obj_align(l_msg, LV_ALIGN_CENTER, 0, -10);

    lv_obj_t *btn = lv_btn_create(modal_obj);
    lv_obj_set_size(btn, 100, 40);
    lv_obj_align(btn, LV_ALIGN_BOTTOM_MID, 0, -10);
    lv_obj_set_style_bg_color(btn, lv_color_hex(0xFF4081), LV_PART_MAIN);
    lv_obj_add_event_cb(btn, modal_close_cb, LV_EVENT_CLICKED, NULL);

    lv_obj_t *l_btn = lv_label_create(btn);
    lv_label_set_text(l_btn, "OK");
    lv_obj_center(l_btn);
}

static void modal_close_cb(lv_event_t * e)
{
    if (modal_obj) {
        lv_obj_del(modal_obj);
        modal_obj = NULL;
    }
}

void ui_update_view(void)
{
    if (!game_screen_obj) return;

    update_labels();
    game_phase_t phase = game_get_phase();

    for (uint8_t i = 0; i < GRID_SIZE; i++)
    {
        lv_obj_t *btn = cells[i];
        if (!btn) continue;

        /* Get the Image Object (Child 0) which replaced the label/circle */
        lv_obj_t *img_icon = lv_obj_get_child(btn, 0);

        /* 1. Reset State */
        lv_obj_clear_state(btn, LV_STATE_DISABLED);

        /* 2. Reset Visuals */
        lv_color_t color = cell_save_colors[i];

        lv_obj_set_style_bg_opa(btn, LV_OPA_COVER, LV_PART_MAIN);
        lv_obj_set_style_bg_color(btn, color, LV_PART_MAIN);
        lv_obj_set_style_bg_grad_color(btn, lv_color_darken(color, 50), LV_PART_MAIN);
        lv_obj_set_style_border_color(btn, lv_color_lighten(color, 50), LV_PART_MAIN);
        lv_obj_set_style_border_width(btn, 2, LV_PART_MAIN);

        /* Restore Candy Icon: White, Visible */
        if(img_icon) {
            lv_obj_set_style_img_recolor(img_icon, lv_color_white(), LV_PART_MAIN);
            lv_obj_set_style_img_recolor_opa(img_icon, LV_OPA_COVER, LV_PART_MAIN);
            lv_obj_clear_flag(img_icon, LV_OBJ_FLAG_HIDDEN);
        }

        /* 3. Apply Game Logic */
        if (phase == GAME_PHASE_GAME_OVER) {
            bool p1_trap = game_is_p1_poison(i);
            bool p2_trap = game_is_p2_poison(i);

            if (p1_trap || p2_trap) {
                /* Reveal Poison: Turn button dark grey */
                lv_obj_set_style_bg_color(btn, lv_color_hex(0x222222), LV_PART_MAIN);
                lv_obj_set_style_bg_grad_color(btn, lv_color_hex(0x000000), LV_PART_MAIN);
                lv_obj_set_style_border_color(btn, lv_color_hex(0xFF0000), LV_PART_MAIN);

                /* Turn Icon Black to look like a "skull/hole" */
                if(img_icon) {
                    lv_obj_set_style_img_recolor(img_icon, lv_color_black(), LV_PART_MAIN);
                }
            } else if (game_is_cell_taken(i)) {
                lv_obj_set_style_bg_opa(btn, LV_OPA_20, LV_PART_MAIN);
            }
            lv_obj_add_state(btn, LV_STATE_DISABLED);
            if (btn_restart) lv_obj_clear_flag(btn_restart, LV_OBJ_FLAG_HIDDEN);
        }
        else {
            /* Play/Setup Phase */
            if (btn_restart) lv_obj_add_flag(btn_restart, LV_OBJ_FLAG_HIDDEN);

            if (game_is_cell_taken(i)) {
                /* Eaten candy - Make invisible */
                lv_obj_set_style_bg_opa(btn, LV_OPA_0, LV_PART_MAIN);
                lv_obj_set_style_border_width(btn, 0, LV_PART_MAIN);
                /* Hide Icon */
                if(img_icon) lv_obj_add_flag(img_icon, LV_OBJ_FLAG_HIDDEN);
            }
        }
    }
}

static void cell_event_cb(lv_event_t * e)
{
    lv_obj_t *target = lv_event_get_target(e);
    uintptr_t ud = (uintptr_t)lv_event_get_user_data(e);
    uint8_t idx = (uint8_t)(ud & 0xFF);

    lv_obj_set_style_transform_zoom(target, 280, LV_PART_MAIN);
    game_on_cell_tap(idx);
    lv_obj_set_style_transform_zoom(target, 256, LV_PART_MAIN);
}

static void restart_event_cb(lv_event_t * e)
{
    game_reset();
}

static void start_setup_event_cb(lv_event_t * e)
{
    if (title_screen_obj) lv_obj_add_flag(title_screen_obj, LV_OBJ_FLAG_HIDDEN);
    if (game_screen_obj) {
        lv_obj_clear_flag(game_screen_obj, LV_OBJ_FLAG_HIDDEN);
    } else {
        ui_show_game_screen();
    }
    game_init();
}

void ui_show_title_screen(void)
{
    title_screen_obj = lv_obj_create(screen_obj);
    lv_obj_set_size(title_screen_obj, LV_PCT(100), LV_PCT(100));
    lv_obj_set_style_bg_opa(title_screen_obj, LV_OPA_TRANSP, 0);
    lv_obj_clear_flag(title_screen_obj, LV_OBJ_FLAG_SCROLLABLE);

    /* Overlay */
    lv_obj_t *overlay = lv_obj_create(title_screen_obj);
    lv_obj_set_size(overlay, LV_PCT(100), LV_PCT(100));
    lv_obj_set_style_bg_color(overlay, lv_color_hex(0x5E1224), 0);
    lv_obj_set_style_bg_opa(overlay, LV_OPA_60, 0);
    lv_obj_clear_flag(overlay, LV_OBJ_FLAG_SCROLLABLE);

    /* Title */
    lv_obj_t *l_title = lv_label_create(title_screen_obj);
    lv_label_set_text(l_title, "Candy Poison");
    lv_obj_add_style(l_title, &style_title_text, 0);
    lv_obj_align(l_title, LV_ALIGN_CENTER, 0, -40);

    /* Subtitle */
    lv_obj_t *l_sub = lv_label_create(title_screen_obj);
    lv_label_set_text(l_sub, "5x5 Grid. 2 Poisons. 2 Players.");
    lv_obj_add_style(l_sub, &style_subtitle_text, 0);
    lv_obj_align(l_sub, LV_ALIGN_CENTER, 0, -10);

    /* Start Button */
    lv_obj_t *btn_start = lv_btn_create(title_screen_obj);
    lv_obj_add_style(btn_start, &style_btn, LV_PART_MAIN);
    lv_obj_set_size(btn_start, 160, 50);
    lv_obj_align(btn_start, LV_ALIGN_CENTER, 0, 50);
    lv_obj_add_event_cb(btn_start, start_setup_event_cb, LV_EVENT_CLICKED, NULL);

    lv_obj_t *l_btn = lv_label_create(btn_start);
    lv_label_set_text(l_btn, "Start Setup");
    lv_obj_set_style_text_font(l_btn, &lv_font_montserrat_14, 0);
    lv_obj_center(l_btn);
}

void ui_show_game_screen(void)
{
    game_screen_obj = lv_obj_create(screen_obj);
    lv_obj_set_size(game_screen_obj, LV_PCT(100), LV_PCT(100));
    lv_obj_set_style_bg_opa(game_screen_obj, LV_OPA_TRANSP, 0);
    lv_obj_clear_flag(game_screen_obj, LV_OBJ_FLAG_SCROLLABLE);

    lbl_status = lv_label_create(game_screen_obj);
    lv_obj_add_style(lbl_status, &style_text, 0);
    lv_obj_align(lbl_status, LV_ALIGN_TOP_MID, 0, 12);

    /* 5x5 Grid */
    for (int r = 0; r < GRID_ROWS; r++) {
        for (int c = 0; c < GRID_COLS; c++) {
            uint8_t idx = (uint8_t)(r * GRID_COLS + c);
            lv_obj_t *btn = lv_btn_create(game_screen_obj);

            lv_obj_add_style(btn, &style_cell, LV_PART_MAIN);

            /* Randomize & Save Color */
            int color_idx = rand() % (sizeof(candy_colors)/sizeof(candy_colors[0]));
            lv_color_t color = lv_color_hex(candy_colors[color_idx]);
            cell_save_colors[idx] = color;

            lv_obj_set_style_bg_color(btn, color, LV_PART_MAIN);
            lv_obj_set_style_bg_grad_color(btn, lv_color_darken(color, 50), LV_PART_MAIN);
            lv_obj_set_style_bg_grad_dir(btn, LV_GRAD_DIR_VER, LV_PART_MAIN);
            lv_obj_set_style_border_color(btn, lv_color_lighten(color, 50), LV_PART_MAIN);

            lv_obj_set_size(btn, CELL_W, CELL_H);
            lv_obj_set_pos(btn, GRID_X + c * (CELL_W + CELL_GAP), GRID_Y + r * (CELL_H + CELL_GAP));
            lv_obj_add_event_cb(btn, cell_event_cb, LV_EVENT_CLICKED, (void*)(uintptr_t)idx);

            /* REPLACED CIRCLE WITH IMAGE OBJECT */
            /* Uses the embedded 'candy_icon' array defined at top of file */
            #if LV_COLOR_DEPTH == 16 || LV_COLOR_DEPTH == 32
            lv_obj_t *img_icon = lv_img_create(btn);
            lv_img_set_src(img_icon, &candy_icon);
            lv_obj_center(img_icon);
            /* Tint the icon white initially so it looks like a generic candy */
            lv_obj_set_style_img_recolor(img_icon, lv_color_white(), LV_PART_MAIN);
            lv_obj_set_style_img_recolor_opa(img_icon, LV_OPA_COVER, LV_PART_MAIN);
            #endif

            cells[idx] = btn;
        }
    }

    /* Restart Button */
    btn_restart = lv_btn_create(game_screen_obj);
    lv_obj_add_style(btn_restart, &style_btn, LV_PART_MAIN);
    lv_obj_set_size(btn_restart, 120, 42);
    lv_obj_align(btn_restart, LV_ALIGN_BOTTOM_MID, 0, -10);
    lv_obj_add_event_cb(btn_restart, restart_event_cb, LV_EVENT_CLICKED, NULL);
    lv_obj_add_flag(btn_restart, LV_OBJ_FLAG_HIDDEN);

    lv_obj_t *lr = lv_label_create(btn_restart);
    lv_label_set_text(lr, "Play Again");
    lv_obj_set_style_text_font(lr, &lv_font_montserrat_14, 0);
    lv_obj_center(lr);
}

void ui_init(void)
{
    screen_obj = lv_scr_act();

    /* Background Style */
    lv_style_init(&style_bg);
    lv_style_set_bg_color(&style_bg, lv_color_hex(0x880E4F));
    lv_style_set_bg_grad_color(&style_bg, lv_color_hex(0x2C041C));
    lv_style_set_bg_grad_dir(&style_bg, LV_GRAD_DIR_VER);
    lv_obj_add_style(screen_obj, &style_bg, 0);

    lv_style_init(&style_text);
    lv_style_set_text_color(&style_text, lv_color_hex(0xFFFFFF));
    lv_style_set_text_font(&style_text, &lv_font_montserrat_14);

    lv_style_init(&style_title_text);
    lv_style_set_text_color(&style_title_text, lv_color_hex(0xFFFFFF));
    lv_style_set_text_font(&style_title_text, &lv_font_montserrat_14);

    lv_style_init(&style_subtitle_text);
    lv_style_set_text_color(&style_subtitle_text, lv_color_hex(0xE0E0E0));
    lv_style_set_text_font(&style_subtitle_text, &lv_font_montserrat_14);

    /* Cell Style */
    lv_style_init(&style_cell);
    lv_style_set_radius(&style_cell, 12);
    lv_style_set_border_width(&style_cell, 2);
    lv_style_set_border_opa(&style_cell, LV_OPA_80);
    lv_style_set_shadow_width(&style_cell, 8);
    lv_style_set_shadow_ofs_y(&style_cell, 3);
    lv_style_set_shadow_color(&style_cell, lv_color_hex(0x000000));
    lv_style_set_shadow_opa(&style_cell, LV_OPA_50);

    /* Button Style */
    lv_style_init(&style_btn);
    lv_style_set_bg_color(&style_btn, lv_color_hex(0xFFFFFF));
    lv_style_set_text_color(&style_btn, lv_color_hex(0xC51162));
    lv_style_set_radius(&style_btn, 25);
    lv_style_set_shadow_width(&style_btn, 10);
    lv_style_set_shadow_ofs_y(&style_btn, 4);
    lv_style_set_shadow_color(&style_btn, lv_color_hex(0x000000));
    lv_style_set_shadow_opa(&style_btn, LV_OPA_30);

    ui_show_title_screen();
}

void ui_task_handler(void) {
    lv_timer_handler();
}

Credits

KARL VINCENT REMO
1 project • 0 followers
JOHN WIEL JAYME
1 project • 0 followers

Comments