KEVIN CHRISTIAN VILLAREALSEAN MARIE RABAGO
Published © GPL3+

Building a Wordle Game on the Renesas HMI Board

We created Wordle game which tests how well the processor handles graphics and game logic at the same time while responding to touchscreen.

BeginnerFull instructions provided1 hour73
Building a Wordle Game on the Renesas HMI Board

Things used in this project

Hardware components

IoT Enabler
Renesas IoT Enabler
×1
Flash Memory Card, SD Card
Flash Memory Card, SD Card
×1
Memory Controller, Nonvolatile SRAM
Memory Controller, Nonvolatile SRAM
×1
Nextion NX8048T070 - Generic 7.0" HMI TFT LCD Touch Display
Itead Nextion NX8048T070 - Generic 7.0" HMI TFT LCD Touch Display
×1

Software apps and online services

RT-Thread IoT OS
RT-Thread IoT OS

Story

Read more

Schematics

System Block Diagram (High Level)

This diagram shows the "Big Picture" of the hardware architecture.

Visual Representation

Code

The Build Script (src/SConscript)

Python
This tells the compiler to look at the files in your src folder.
from building import *

cwd = GetCurrentDir()
src = Glob('*.c')

# Defines that headers are in the same folder as source code
CPPPATH = [cwd]

group = DefineGroup('GameSrc', src, depend = [''], CPPPATH = CPPPATH)

Return('group')

Game Header (src/game.h)

C/C++
Defines the structure of the game data.
#ifndef GAME_H
#define GAME_H

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

#define WORD_LENGTH 5
#define MAX_ATTEMPTS 6

typedef enum {
    CELL_EMPTY,
    CELL_FILLED,
    CELL_ABSENT,    // Gray
    CELL_PRESENT,   // Yellow
    CELL_CORRECT    // Green
} cell_status_t;

typedef enum {
    GAME_PLAYING,
    GAME_WON,
    GAME_LOST
} game_state_t;

typedef struct {
    char letter;
    cell_status_t status;
} game_cell_t;

typedef struct {
    game_cell_t grid[MAX_ATTEMPTS][WORD_LENGTH];
    char target_word[WORD_LENGTH + 1];
    int current_row;
    int current_col;
    game_state_t state;
} wordle_game_t;

void game_logic_init(void);
void game_logic_input_char(char c);
void game_logic_backspace(void);
void game_logic_enter(void);
void game_logic_reset(void);
wordle_game_t* game_get_context(void);

#endif /* GAME_H */

Game Logic (src/game.c)

C/C++
Contains the rules, the 50-word dictionary, and the "True Random" fix.
#include "game.h"
#include <string.h>
#include <stdlib.h>
#include <rtthread.h> 

static wordle_game_t game;
static bool has_seeded_random = false;

/* Dictionary of 50 words */
static const char* word_list[] = {
    "ROBOT", "BOARD", "RESET", "FLASH", "STACK",
    "CLOCK", "POWER", "DEBUG", "LOGIC", "INPUT",
    "PIXEL", "TOUCH", "DRIVER", "BUILD", "LAYER",
    "SMART", "WATCH", "APPLE", "SMILE", "BRAIN",
    "ALARM", "BEACH", "CANDY", "DREAM", "EAGLE",
    "FLAME", "GRAPE", "HEART", "IMAGE", "JUICE",
    "KNIFE", "LEMON", "MOUSE", "NIGHT", "OCEAN",
    "PIANO", "QUEEN", "RADIO", "SHEEP", "TIGER",
    "UNCLE", "VIDEO", "WATER", "XEROX", "YACHT",
    "ZEBRA", "ALPHA", "BREAD", "CHAIR", "DANCE"
};

#define WORD_COUNT (sizeof(word_list) / sizeof(word_list[0]))

void game_logic_init(void) {
    game_logic_reset();
}

void game_logic_reset(void) {
    memset(&game, 0, sizeof(wordle_game_t));
    game.state = GAME_PLAYING;
    
    // Pick temporary word until user provides random seed
    int idx = rand() % WORD_COUNT;
    strcpy(game.target_word, word_list[idx]);
    
    rt_kprintf("Target Word: %s\n", game.target_word);
}

wordle_game_t* game_get_context(void) {
    return &game;
}

void game_logic_input_char(char c) {
    // --- TRUE RANDOM FIX ---
    if (!has_seeded_random) {
        srand(rt_tick_get()); 
        has_seeded_random = true;
        
        int idx = rand() % WORD_COUNT;
        strcpy(game.target_word, word_list[idx]);
        rt_kprintf("True Random Word: %s\n", game.target_word);
    }
    // -----------------------

    if (game.state != GAME_PLAYING) return;
    if (game.current_col >= WORD_LENGTH) return; 

    game.grid[game.current_row][game.current_col].letter = c;
    game.grid[game.current_row][game.current_col].status = CELL_FILLED;
    game.current_col++;
}

void game_logic_backspace(void) {
    if (game.state != GAME_PLAYING) return;
    if (game.current_col <= 0) return;

    game.current_col--;
    game.grid[game.current_row][game.current_col].letter = '\0';
    game.grid[game.current_row][game.current_col].status = CELL_EMPTY;
}

void game_logic_enter(void) {
    if (game.state != GAME_PLAYING) return;
    if (game.current_col < WORD_LENGTH) return; 

    char target_copy[WORD_LENGTH + 1];
    strcpy(target_copy, game.target_word);
    
    // 1. Check Green
    for (int i = 0; i < WORD_LENGTH; i++) {
        char guess = game.grid[game.current_row][i].letter;
        if (guess == target_copy[i]) {
            game.grid[game.current_row][i].status = CELL_CORRECT;
            target_copy[i] = 0; 
        } else {
            game.grid[game.current_row][i].status = CELL_ABSENT; 
        }
    }

    // 2. Check Yellow
    for (int i = 0; i < WORD_LENGTH; i++) {
        if (game.grid[game.current_row][i].status == CELL_CORRECT) continue;

        char guess = game.grid[game.current_row][i].letter;
        for (int j = 0; j < WORD_LENGTH; j++) {
            if (target_copy[j] == guess) {
                game.grid[game.current_row][i].status = CELL_PRESENT;
                target_copy[j] = 0; 
                break;
            }
        }
    }

    // Check Win
    int correct_count = 0;
    for (int i = 0; i < WORD_LENGTH; i++) {
        if (game.grid[game.current_row][i].status == CELL_CORRECT) correct_count++;
    }

    if (correct_count == WORD_LENGTH) {
        game.state = GAME_WON;
    } else {
        game.current_row++;
        game.current_col = 0;
        if (game.current_row >= MAX_ATTEMPTS) {
            game.state = GAME_LOST;
        }
    }
}

UI Header (src/ui.h)

C/C++
#ifndef UI_H
#define UI_H

void ui_init(void);

#endif /* UI_H */

UI Implementation (src/ui.c)

C/C++
Contains the graphics code and the fix for the disappearing borders.
#include "ui.h"
#include "game.h"
#include "lvgl.h"
#include <stdio.h>

static lv_obj_t * scr;
static lv_obj_t * grid_container;
static lv_obj_t * cell_objs[MAX_ATTEMPTS][WORD_LENGTH];
static lv_obj_t * kb_matrix;
static lv_obj_t * msg_label;

#define COLOR_CORRECT lv_color_hex(0x6aaa64) // Green
#define COLOR_PRESENT lv_color_hex(0xc9b458) // Yellow
#define COLOR_ABSENT  lv_color_hex(0x787c7e) // Gray
#define COLOR_EMPTY   lv_color_hex(0xffffff) // White
#define COLOR_BORDER  lv_color_hex(0xd3d6da) // Light Gray

static const char * kb_map[] = {
    "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "\n",
    "A", "S", "D", "F", "G", "H", "J", "K", "L", "\n",
    "Enter", "Z", "X", "C", "V", "B", "N", "M", "Bksp", ""
};

static void update_ui_grid(void) {
    wordle_game_t *g = game_get_context();
    
    for(int r=0; r<MAX_ATTEMPTS; r++) {
        for(int c=0; c<WORD_LENGTH; c++) {
            lv_obj_t * btn = cell_objs[r][c];
            lv_obj_t * label = lv_obj_get_child(btn, 0);
            
            /* --- FIX: RESET DEFAULTS --- */
            lv_obj_set_style_border_width(btn, 2, 0);
            lv_obj_set_style_border_color(btn, COLOR_BORDER, 0);
            lv_obj_set_style_bg_color(btn, COLOR_EMPTY, 0);
            lv_obj_set_style_text_color(label, lv_color_black(), 0);

            /* Text */
            if(g->grid[r][c].letter != 0) {
                lv_label_set_text_fmt(label, "%c", g->grid[r][c].letter);
            } else {
                lv_label_set_text(label, "");
            }

            /* Colors */
            lv_color_t bg_color = COLOR_EMPTY;
            if (g->grid[r][c].status == CELL_CORRECT) bg_color = COLOR_CORRECT;
            else if (g->grid[r][c].status == CELL_PRESENT) bg_color = COLOR_PRESENT;
            else if (g->grid[r][c].status == CELL_ABSENT) bg_color = COLOR_ABSENT;

            /* Final Style Apply */
            if (g->grid[r][c].status >= CELL_ABSENT) {
                lv_obj_set_style_bg_color(btn, bg_color, 0);
                lv_obj_set_style_border_width(btn, 0, 0); 
                lv_obj_set_style_text_color(label, lv_color_white(), 0);
            } 
            else if (g->grid[r][c].status == CELL_FILLED) {
                lv_obj_set_style_border_color(btn, lv_color_hex(0x878a8c), 0); 
            }
        }
    }
    
    if (g->state == GAME_WON) lv_label_set_text(msg_label, "SPLENDID! PRESS ENTER");
    else if (g->state == GAME_LOST) lv_label_set_text_fmt(msg_label, "LOST! WORD: %s", g->target_word);
    else lv_label_set_text(msg_label, "GUESS THE WORD");
}

static void kb_event_cb(lv_event_t * e) {
    lv_obj_t * obj = lv_event_get_target(e);
    uint32_t id = lv_btnmatrix_get_selected_btn(obj);
    const char * txt = lv_btnmatrix_get_btn_text(obj, id);
    wordle_game_t *g = game_get_context();

    if(txt == NULL) return;

    if (g->state != GAME_PLAYING) {
        if(strcmp(txt, "Enter") == 0) {
            game_logic_reset();
            update_ui_grid();
        }
        return;
    }

    if(strcmp(txt, "Enter") == 0) game_logic_enter();
    else if(strcmp(txt, "Bksp") == 0) game_logic_backspace();
    else game_logic_input_char(txt[0]);
    
    update_ui_grid();
}

void ui_init(void) {
    game_logic_init();
    scr = lv_scr_act();
    lv_obj_set_style_bg_color(scr, lv_color_white(), 0);

    /* Grid Container */
    grid_container = lv_obj_create(scr);
    lv_obj_set_size(grid_container, 230, 272);
    lv_obj_set_pos(grid_container, 0, 0);
    lv_obj_set_style_border_width(grid_container, 0, 0);
    lv_obj_set_style_bg_opa(grid_container, 0, 0);
    
    int cell_size = 38;
    int gap = 4;
    
    for(int r=0; r<MAX_ATTEMPTS; r++) {
        for(int c=0; c<WORD_LENGTH; c++) {
            lv_obj_t * btn = lv_btn_create(grid_container);
            lv_obj_set_size(btn, cell_size, cell_size);
            lv_obj_set_pos(btn, c * (cell_size + gap), r * (cell_size + gap) + 10);
            lv_obj_set_style_radius(btn, 0, 0);
            
            lv_obj_t * label = lv_label_create(btn);
            lv_obj_center(label);
            lv_obj_set_style_text_font(label, &lv_font_montserrat_14, 0);
            cell_objs[r][c] = btn;
        }
    }

    /* Message Label */
    msg_label = lv_label_create(scr);
    lv_obj_set_width(msg_label, 240);
    lv_obj_align(msg_label, LV_ALIGN_TOP_RIGHT, -10, 10);
    lv_obj_set_style_text_align(msg_label, LV_TEXT_ALIGN_CENTER, 0);
    lv_obj_set_style_text_font(msg_label, &lv_font_montserrat_14, 0);

    /* Keyboard */
    kb_matrix = lv_btnmatrix_create(scr);
    lv_obj_set_size(kb_matrix, 240, 200);
    lv_obj_align(kb_matrix, LV_ALIGN_BOTTOM_RIGHT, -5, -5);
    lv_btnmatrix_set_map(kb_matrix, kb_map);
    lv_btnmatrix_set_btn_width(kb_matrix, 21, 2); 
    lv_btnmatrix_set_btn_width(kb_matrix, 29, 2); 
    lv_obj_add_event_cb(kb_matrix, kb_event_cb, LV_EVENT_VALUE_CHANGED, NULL);
    
    update_ui_grid();
}

Entry Point (src/lvgl_port.c)

C/C++
Turns on the screen backlight and starts the UI.
#include <rtthread.h>
#include <rtdevice.h>
#include <drv_gpio.h>
#include "lvgl.h"
#include "ui.h"

#define LCD_BACKLIGHT_PIN    BSP_IO_PORT_06_PIN_00

void lv_user_gui_init(void)
{
    /* Force Backlight ON */
    rt_pin_mode(LCD_BACKLIGHT_PIN, PIN_MODE_OUTPUT);
    rt_pin_write(LCD_BACKLIGHT_PIN, PIN_HIGH);

    /* Start Game */
    ui_init();
}

Credits

KEVIN CHRISTIAN VILLAREAL
1 project • 0 followers
SEAN MARIE RABAGO
1 project • 0 followers

Comments