Hardware components | ||||||
| × | 1 | ||||
Software apps and online services | ||||||
![]() |
| |||||
| ||||||
We wanted to build something highly interactive on the HMI board, moving beyond static displays. Our core idea was simple: simulate a coin toss. This project, Lucky 3, evolved from that concept, demonstrating how an HMI board can deliver engaging, real-time feedback for a simple, universal game of chance.
We aimed to showcase the capabilities of RT-Thread for managing system processes and LVGL for dynamic, responsive graphics, proving that even a straightforward interaction can be made polished and professional on embedded hardware.
How the Game WorksLucky 3 simulates a game of chance, much like a digital coin toss, but with an added competitive element.
The Setup: Two players, represented by Blue (Player 1) and Red (Player 2).
- The Setup: Two players, represented by Blue (Player 1) and Red (Player 2).
The Input: A single "Toss" button initiates the random event.
- The Input: A single "Toss" button initiates the random event.
The Logic: When the button is tapped, the system digitally "tosses a coin" to generate a random outcome:
Blue Card: If the "coin" lands on blue, Player 1 scores +1 Point.
Blue Card: If the "coin" lands on blue, Player 1 scores +1 Point.
Red Card: If the "coin" lands on red, Player 2 scores +1 Point.
Red Card: If the "coin" lands on red, Player 2 scores +1 Point.
Joker/Fun Card: A neutral outcome, awarding 0 points, adding a twist to the toss.
Joker/Fun Card: A neutral outcome, awarding 0 points, adding a twist to the toss.
The Logic: When the button is tapped, the system digitally "tosses a coin" to generate a random outcome:
Blue Card: If the "coin" lands on blue, Player 1 scores +1 Point.Red Card: If the "coin" lands on red, Player 2 scores +1 Point.Joker/Fun Card: A neutral outcome, awarding 0 points, adding a twist to the toss.
The Win: The first player to reach 3 Points is declared the winner. After a brief celebration, the game automatically resets for the next round.
- The Win: The first player to reach 3 Points is declared the winner. After a brief celebration, the game automatically resets for the next round.
#ifndef _SQUARELINE_PROJECT_UI_H
#define _SQUARELINE_PROJECT_UI_H
#ifdef __cplusplus
extern "C" {
#endif
#include <lvgl.h>
#include "ui_helpers.h"
#include "components/ui_comp.h"
#include "components/ui_comp_hook.h"
#include "ui_events.h"
///////////////////// SCREENS ////////////////////
#include "screens/ui_Screen1.h"
///////////////////// VARIABLES ////////////////////
extern lv_anim_t * pop_Animation(lv_obj_t * TargetObject, int delay);
// EVENTS
extern lv_obj_t * ui____initial_actions0;
// IMAGES AND IMAGE SETS
LV_IMG_DECLARE(ui_img_untitled_design_png); // assets/Untitled design.png
LV_IMG_DECLARE(ui_img_1570748651); // assets/Untitled design (2).png
LV_IMG_DECLARE(ui_img_1570745578); // assets/Untitled design (1).png
LV_IMG_DECLARE(ui_img_player_1_win_png); // assets/PLAYER 1 WIN.png
LV_IMG_DECLARE(ui_img_player_2_win_png); // assets/PLAYER 2 WIN.png
LV_IMG_DECLARE(ui_img_974432646); // assets/haha 0-0 (1).png
// UI INIT
void ui_init(void);
void ui_destroy(void);
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif
#include "ui.h"
#include "ui_helpers.h"
///////////////////// VARIABLES ////////////////////
lv_anim_t * pop_Animation(lv_obj_t * TargetObject, int delay);
// EVENTS
lv_obj_t * ui____initial_actions0;
// IMAGES AND IMAGE SETS
///////////////////// TEST LVGL SETTINGS ////////////////////
#if LV_COLOR_DEPTH != 16
#error "LV_COLOR_DEPTH should be 16bit to match SquareLine Studio's settings"
#endif
#if LV_COLOR_16_SWAP !=0
#error "LV_COLOR_16_SWAP should be 0 to match SquareLine Studio's settings"
#endif
///////////////////// ANIMATIONS ////////////////////
lv_anim_t * pop_Animation(lv_obj_t * TargetObject, int delay)
{
lv_anim_t * out_anim;
ui_anim_user_data_t * PropertyAnimation_0_user_data = lv_mem_alloc(sizeof(ui_anim_user_data_t));
PropertyAnimation_0_user_data->target = TargetObject;
PropertyAnimation_0_user_data->val = -1;
lv_anim_t PropertyAnimation_0;
lv_anim_init(&PropertyAnimation_0);
lv_anim_set_time(&PropertyAnimation_0, 200);
lv_anim_set_user_data(&PropertyAnimation_0, PropertyAnimation_0_user_data);
lv_anim_set_custom_exec_cb(&PropertyAnimation_0, _ui_anim_callback_set_opacity);
lv_anim_set_values(&PropertyAnimation_0, 0, 255);
lv_anim_set_path_cb(&PropertyAnimation_0, lv_anim_path_linear);
lv_anim_set_delay(&PropertyAnimation_0, delay + 0);
lv_anim_set_deleted_cb(&PropertyAnimation_0, _ui_anim_callback_free_user_data);
lv_anim_set_playback_time(&PropertyAnimation_0, 0);
lv_anim_set_playback_delay(&PropertyAnimation_0, 0);
lv_anim_set_repeat_count(&PropertyAnimation_0, 0);
lv_anim_set_repeat_delay(&PropertyAnimation_0, 0);
lv_anim_set_early_apply(&PropertyAnimation_0, false);
lv_anim_set_get_value_cb(&PropertyAnimation_0, &_ui_anim_callback_get_opacity);
out_anim = lv_anim_start(&PropertyAnimation_0);
ui_anim_user_data_t * PropertyAnimation_1_user_data = lv_mem_alloc(sizeof(ui_anim_user_data_t));
PropertyAnimation_1_user_data->target = TargetObject;
PropertyAnimation_1_user_data->val = -1;
lv_anim_t PropertyAnimation_1;
lv_anim_init(&PropertyAnimation_1);
lv_anim_set_time(&PropertyAnimation_1, 250);
lv_anim_set_user_data(&PropertyAnimation_1, PropertyAnimation_1_user_data);
lv_anim_set_custom_exec_cb(&PropertyAnimation_1, _ui_anim_callback_set_image_zoom);
lv_anim_set_values(&PropertyAnimation_1, 307, 256);
lv_anim_set_path_cb(&PropertyAnimation_1, lv_anim_path_overshoot);
lv_anim_set_delay(&PropertyAnimation_1, delay + 0);
lv_anim_set_deleted_cb(&PropertyAnimation_1, _ui_anim_callback_free_user_data);
lv_anim_set_playback_time(&PropertyAnimation_1, 0);
lv_anim_set_playback_delay(&PropertyAnimation_1, 0);
lv_anim_set_repeat_count(&PropertyAnimation_1, 0);
lv_anim_set_repeat_delay(&PropertyAnimation_1, 0);
lv_anim_set_early_apply(&PropertyAnimation_1, false);
out_anim = lv_anim_start(&PropertyAnimation_1);
return out_anim;
}
///////////////////// FUNCTIONS ////////////////////
///////////////////// SCREENS ////////////////////
void ui_init(void)
{
LV_EVENT_GET_COMP_CHILD = lv_event_register_id();
lv_disp_t * dispp = lv_disp_get_default();
lv_theme_t * theme = lv_theme_default_init(dispp, lv_palette_main(LV_PALETTE_BLUE), lv_palette_main(LV_PALETTE_RED),
false, LV_FONT_DEFAULT);
lv_disp_set_theme(dispp, theme);
ui_Screen1_screen_init();
ui____initial_actions0 = lv_obj_create(NULL);
lv_disp_load_scr(ui_Screen1);
}
void ui_destroy(void)
{
ui_Screen1_screen_destroy();
}
#include "ui.h"
#include <stdlib.h> // For rand()
#include <stdio.h> // For sprintf()
// --- Global Game Variables ---
int score_p1 = 0;
int score_p2 = 0;
int game_over_flag = 0;
// --- Helper: Hide everything ---
void hide_all_cards(void) {
lv_obj_add_flag(ui_uiimgblue, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(ui_uiimgred, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(ui_uiimgfun, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(ui_uiimgp1win, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(ui_uiimgp2win, LV_OBJ_FLAG_HIDDEN);
}
// --- LOGIC: Reset Game to 0-0 ---
void perform_reset(void) {
score_p1 = 0;
score_p2 = 0;
game_over_flag = 0;
// Reset Text
lv_label_set_text(ui_uilblscore1, "0");
lv_label_set_text(ui_uilblscore2, "0");
// Hide everything
hide_all_cards();
// Unlock Button
lv_obj_clear_state(ui_uibtntoss, LV_STATE_DISABLED);
}
// --- TIMER 1: Normal Turn Callback ---
void auto_hide_callback(lv_timer_t * timer)
{
lv_obj_add_flag(ui_uiimgblue, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(ui_uiimgred, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(ui_uiimgfun, LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_state(ui_uibtntoss, LV_STATE_DISABLED);
}
// --- TIMER 2: Win Reset Callback ---
void auto_reset_callback(lv_timer_t * timer)
{
perform_reset();
}
// --- ANIMATION: The "Pop" Effect ---
void play_pop_animation(lv_obj_t * target_img)
{
lv_img_set_zoom(target_img, 0); // Start tiny
lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_var(&a, target_img);
lv_anim_set_values(&a, 0, 256); // Zoom to normal
lv_anim_set_time(&a, 300); // Speed
lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_img_set_zoom);
lv_anim_set_path_cb(&a, lv_anim_path_overshoot); // Bounce effect
lv_anim_start(&a);
}
// =======================================================
// MAIN GAME EVENT
// =======================================================
// Assign this to: uibtntoss (Event: Clicked)
void OnTossClicked(lv_event_t * e)
{
if (game_over_flag == 1) return; // Stop if game is over
// Disable button immediately
lv_obj_add_state(ui_uibtntoss, LV_STATE_DISABLED);
hide_all_cards();
int outcome = rand() % 3;
char buffer[10];
// --- DETERMINE RESULT ---
if (outcome == 0) {
// BLUE
lv_obj_clear_flag(ui_uiimgblue, LV_OBJ_FLAG_HIDDEN);
play_pop_animation(ui_uiimgblue);
score_p1++;
sprintf(buffer, "%d", score_p1);
lv_label_set_text(ui_uilblscore1, buffer);
}
else if (outcome == 1) {
// RED
lv_obj_clear_flag(ui_uiimgred, LV_OBJ_FLAG_HIDDEN);
play_pop_animation(ui_uiimgred);
score_p2++;
sprintf(buffer, "%d", score_p2);
lv_label_set_text(ui_uilblscore2, buffer);
}
else {
// FUN / JOKER
lv_obj_clear_flag(ui_uiimgfun, LV_OBJ_FLAG_HIDDEN);
play_pop_animation(ui_uiimgfun);
}
// --- CHECK FOR WINNER ---
if (score_p1 >= 3 || score_p2 >= 3) {
game_over_flag = 1;
// Show the correct Win Popup
if (score_p1 >= 3) {
lv_obj_clear_flag(ui_uiimgp1win, LV_OBJ_FLAG_HIDDEN);
play_pop_animation(ui_uiimgp1win);
} else {
lv_obj_clear_flag(ui_uiimgp2win, LV_OBJ_FLAG_HIDDEN);
play_pop_animation(ui_uiimgp2win);
}
// TIMING CHANGE: 5 Seconds (5000ms) for Win Screen
lv_timer_t * t = lv_timer_create(auto_reset_callback, 5000, NULL);
lv_timer_set_repeat_count(t, 1);
}
else {
// --- TIMING LOGIC FOR NORMAL TURNS ---
if (outcome == 2) {
// Fun Image = 2.0 Seconds (2000ms)
lv_timer_t * t = lv_timer_create(auto_hide_callback, 2000, NULL);
lv_timer_set_repeat_count(t, 1);
} else {
// Blue or Red Image = 1.5 Seconds (1500ms)
lv_timer_t * t = lv_timer_create(auto_hide_callback, 1500, NULL);
lv_timer_set_repeat_count(t, 1);
}
}
}
// Optional: Logic for a manual restart button
void OnRestartGame(lv_event_t * e) {
perform_reset();
}
// This file was generated by SquareLine Studio
// SquareLine Studio version: SquareLine Studio 1.5.4
// LVGL version: 8.3.11
// Project name: SquareLine_Project
#include "ui_helpers.h"
void _ui_bar_set_property(lv_obj_t * target, int id, int val)
{
if(id == _UI_BAR_PROPERTY_VALUE_WITH_ANIM) lv_bar_set_value(target, val, LV_ANIM_ON);
if(id == _UI_BAR_PROPERTY_VALUE) lv_bar_set_value(target, val, LV_ANIM_OFF);
}
void _ui_basic_set_property(lv_obj_t * target, int id, int val)
{
if(id == _UI_BASIC_PROPERTY_POSITION_X) lv_obj_set_x(target, val);
if(id == _UI_BASIC_PROPERTY_POSITION_Y) lv_obj_set_y(target, val);
if(id == _UI_BASIC_PROPERTY_WIDTH) lv_obj_set_width(target, val);
if(id == _UI_BASIC_PROPERTY_HEIGHT) lv_obj_set_height(target, val);
}
void _ui_dropdown_set_property(lv_obj_t * target, int id, int val)
{
if(id == _UI_DROPDOWN_PROPERTY_SELECTED) lv_dropdown_set_selected(target, val);
}
void _ui_image_set_property(lv_obj_t * target, int id, uint8_t * val)
{
if(id == _UI_IMAGE_PROPERTY_IMAGE) lv_img_set_src(target, val);
}
void _ui_label_set_property(lv_obj_t * target, int id, const char * val)
{
if(id == _UI_LABEL_PROPERTY_TEXT) lv_label_set_text(target, val);
}
void _ui_roller_set_property(lv_obj_t * target, int id, int val)
{
if(id == _UI_ROLLER_PROPERTY_SELECTED_WITH_ANIM) lv_roller_set_selected(target, val, LV_ANIM_ON);
if(id == _UI_ROLLER_PROPERTY_SELECTED) lv_roller_set_selected(target, val, LV_ANIM_OFF);
}
void _ui_slider_set_property(lv_obj_t * target, int id, int val)
{
if(id == _UI_SLIDER_PROPERTY_VALUE_WITH_ANIM) lv_slider_set_value(target, val, LV_ANIM_ON);
if(id == _UI_SLIDER_PROPERTY_VALUE) lv_slider_set_value(target, val, LV_ANIM_OFF);
}
void _ui_screen_change(lv_obj_t ** target, lv_scr_load_anim_t fademode, int spd, int delay, void (*target_init)(void))
{
if(*target == NULL)
target_init();
lv_scr_load_anim(*target, fademode, spd, delay, false);
}
void _ui_arc_increment(lv_obj_t * target, int val)
{
int old = lv_arc_get_value(target);
lv_arc_set_value(target, old + val);
lv_event_send(target, LV_EVENT_VALUE_CHANGED, 0);
}
void _ui_bar_increment(lv_obj_t * target, int val, int anm)
{
int old = lv_bar_get_value(target);
lv_bar_set_value(target, old + val, anm);
}
void _ui_slider_increment(lv_obj_t * target, int val, int anm)
{
int old = lv_slider_get_value(target);
lv_slider_set_value(target, old + val, anm);
lv_event_send(target, LV_EVENT_VALUE_CHANGED, 0);
}
void _ui_keyboard_set_target(lv_obj_t * keyboard, lv_obj_t * textarea)
{
lv_keyboard_set_textarea(keyboard, textarea);
}
void _ui_flag_modify(lv_obj_t * target, int32_t flag, int value)
{
if(value == _UI_MODIFY_FLAG_TOGGLE) {
if(lv_obj_has_flag(target, flag)) lv_obj_clear_flag(target, flag);
else lv_obj_add_flag(target, flag);
}
else if(value == _UI_MODIFY_FLAG_ADD) lv_obj_add_flag(target, flag);
else lv_obj_clear_flag(target, flag);
}
void _ui_state_modify(lv_obj_t * target, int32_t state, int value)
{
if(value == _UI_MODIFY_STATE_TOGGLE) {
if(lv_obj_has_state(target, state)) lv_obj_clear_state(target, state);
else lv_obj_add_state(target, state);
}
else if(value == _UI_MODIFY_STATE_ADD) lv_obj_add_state(target, state);
else lv_obj_clear_state(target, state);
}
void _ui_textarea_move_cursor(lv_obj_t * target, int val)
{
if(val == UI_MOVE_CURSOR_UP) lv_textarea_cursor_up(target);
if(val == UI_MOVE_CURSOR_RIGHT) lv_textarea_cursor_right(target);
if(val == UI_MOVE_CURSOR_DOWN) lv_textarea_cursor_down(target);
if(val == UI_MOVE_CURSOR_LEFT) lv_textarea_cursor_left(target);
lv_obj_add_state(target, LV_STATE_FOCUSED);
}
typedef void (*screen_destroy_cb_t)(void);
void scr_unloaded_delete_cb(lv_event_t * e)
{
// Get the destroy callback from user_data
screen_destroy_cb_t destroy_cb = lv_event_get_user_data(e);
if(destroy_cb) {
destroy_cb(); // call the specific screen destroy function
}
}
void _ui_opacity_set(lv_obj_t * target, int val)
{
lv_obj_set_style_opa(target, val, 0);
}
void _ui_anim_callback_free_user_data(lv_anim_t * a)
{
lv_mem_free(a->user_data);
a->user_data = NULL;
}
void _ui_anim_callback_set_x(lv_anim_t * a, int32_t v)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
lv_obj_set_x(usr->target, v);
}
void _ui_anim_callback_set_y(lv_anim_t * a, int32_t v)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
lv_obj_set_y(usr->target, v);
}
void _ui_anim_callback_set_width(lv_anim_t * a, int32_t v)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
lv_obj_set_width(usr->target, v);
}
void _ui_anim_callback_set_height(lv_anim_t * a, int32_t v)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
lv_obj_set_height(usr->target, v);
}
void _ui_anim_callback_set_opacity(lv_anim_t * a, int32_t v)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
lv_obj_set_style_opa(usr->target, v, 0);
}
void _ui_anim_callback_set_image_zoom(lv_anim_t * a, int32_t v)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
lv_img_set_zoom(usr->target, v);
}
void _ui_anim_callback_set_image_angle(lv_anim_t * a, int32_t v)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
lv_img_set_angle(usr->target, v);
}
void _ui_anim_callback_set_image_frame(lv_anim_t * a, int32_t v)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
usr->val = v;
if(v < 0) v = 0;
if(v >= usr->imgset_size) v = usr->imgset_size - 1;
lv_img_set_src(usr->target, usr->imgset[v]);
}
int32_t _ui_anim_callback_get_x(lv_anim_t * a)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
return lv_obj_get_x_aligned(usr->target);
}
int32_t _ui_anim_callback_get_y(lv_anim_t * a)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
return lv_obj_get_y_aligned(usr->target);
}
int32_t _ui_anim_callback_get_width(lv_anim_t * a)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
return lv_obj_get_width(usr->target);
}
int32_t _ui_anim_callback_get_height(lv_anim_t * a)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
return lv_obj_get_height(usr->target);
}
int32_t _ui_anim_callback_get_opacity(lv_anim_t * a)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
return lv_obj_get_style_opa(usr->target, 0);
}
int32_t _ui_anim_callback_get_image_zoom(lv_anim_t * a)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
return lv_img_get_zoom(usr->target);
}
int32_t _ui_anim_callback_get_image_angle(lv_anim_t * a)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
return lv_img_get_angle(usr->target);
}
int32_t _ui_anim_callback_get_image_frame(lv_anim_t * a)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
return usr->val;
}
void _ui_arc_set_text_value(lv_obj_t * trg, lv_obj_t * src, const char * prefix, const char * postfix)
{
char buf[_UI_TEMPORARY_STRING_BUFFER_SIZE];
lv_snprintf(buf, sizeof(buf), "%s%d%s", prefix, (int)lv_arc_get_value(src), postfix);
lv_label_set_text(trg, buf);
}
void _ui_slider_set_text_value(lv_obj_t * trg, lv_obj_t * src, const char * prefix, const char * postfix)
{
char buf[_UI_TEMPORARY_STRING_BUFFER_SIZE];
lv_snprintf(buf, sizeof(buf), "%s%d%s", prefix, (int)lv_slider_get_value(src), postfix);
lv_label_set_text(trg, buf);
}
void _ui_checked_set_text_value(lv_obj_t * trg, lv_obj_t * src, const char * txt_on, const char * txt_off)
{
if(lv_obj_has_state(src, LV_STATE_CHECKED)) lv_label_set_text(trg, txt_on);
else lv_label_set_text(trg, txt_off);
}
void _ui_spinbox_step(lv_obj_t * target, int val)
{
if(val > 0) lv_spinbox_increment(target);
else lv_spinbox_decrement(target);
lv_event_send(target, LV_EVENT_VALUE_CHANGED, 0);
}
void _ui_switch_theme(int val)
{
#ifdef UI_THEME_ACTIVE
ui_theme_set(val);
#endif
}





Comments