思澈科技solution井字棋游戏【外置应用】

文章目录

一、简介

学习思澈科技solution开发外置C应用

这里以开发一个井字棋游戏为例。

参考文档

应用APP
图形应用框架
创建一个新APP

二、应用介绍

solution 应用分为两种 1、内置应用 2、外置应用

1、内置应用:\open_source_solution_v2.4.1\solution\examples\xxx\application

2、外置应用:\open_source_solution_v2.4.1\solution\examples\_dynamic_app

其中C应用在:\open_source_solution_v2.4.1\solution\examples\_dynamic_app\c\app

三、应用开发

优先参考创建一个新APP文档

1、应用文件夹创建

直接复制粘贴 sport 文件夹,并重命名为 game

此时在butterfli下点击刷新,可以看到game出现在C app中,选中预置即可将其编译到工程中

2、文件目录结构说明

目录下包含 resourcesrc 两个目录

resource:资源目录,存放APP使用到的各种资源

src:源代码目录,存放应用C源代码

readme.ini 编译依赖配置文件 & SConscript 编译连接脚本

其中resource下包含 font_bitmap images lang thumbnails

font_bitmap:bitmap字体

images:图片资源文件,包括gif等

lang:多语言表

thumbnails:APP图标,图片名称需为tn.png

3、配置图片资源

3.1 图标放置

图标名称需为tn.png 当需要不同风格的图标时依次命名为 tn.png\tn2.png\tn3.png ,图标风格可以是方形,圆形等。在应用切换列表显示或者宫格显示时会切换不同风格的图标显示。


3.2 应用内游戏资源

这里井字棋游戏的显示可以完全使用C代码画图实现,这样可以不在这里加任何其他图片资源。也可以使用LVGL图片控件显示img图片资源。

将需要的图片资源替换掉原有的图片资源,其中empty用于清空棋盘显示。





3.3 图片资源说明

可以看到,图片资源目录下 如image下还有很多个目录。这些都有各自的用途。
图片资源说明文档 :包括如何在solution使用获取图片文件

4、编写代码文件

应用APP文档
动态应用指南

保留 sport_main_gui.c文件,修改名称如:game_main.c ,并删除其他资源文件,修改其中的代码


c 复制代码
#define DYN_APP  /* 声明此为外置APP,资源通过外置方式获取 */

#include <rtthread.h>
#include <lvgl.h>
#include "global.h"
#include "lvsf_multobj.h"
#include "lvsf_multswipe.h"
#include "lvsf_scrollbar.h"
#include "lvsf_bg_db.h"

/* 定义模块名称(必须与动态应用APP_ID相同,模拟器调试使用)且需要在 #include "app_module.h" 之前 */
#define _MODULE_NAME_ "game"
#include "app_module.h"

typedef struct
{
    lv_obj_t *bg_cont;
}game_main_t;

static void on_start(void)
{
	/*从框架获取申请的内存,必须要注册的时候填入需要的内存大小*/
   p_lane_play = APP_GET_PAGE_MEM_PTR;
}

static void on_resume(void)
{

}

static void on_pause(void)
{

}

static void on_stop(void)
{

}

/* 注册应用 */
/* app_get_strid:为应用名称设置
	NULL:第二个参数为 缩略图设置,外置动态应用中时此参数无用,可以填NULL
	"game": 应用名称,必须与对应的应用ID 相同
	size:应用全局内存大小,该内存由框架申请释放,页面可以直接使用
 */
APPLICATION_REGISTER(app_get_strid(key_sport, "Game"), NULL, "game", sizeof(game_main_t));

4.1 主体函数说明

设置APP文档

前置代码文件编辑设置完成后,后面就是应用主体的编写

应用主体分为4个函数 on_start \ on_resume \ on_pause \ on_stop

on_start

页面初始化(仅调用一次)

on_resume

页面激活(每次显示时调用)

on_pause

页面暂停(被切换至后台时调用)

on_stop

页面销毁(退出时调用)

4.2 on_start 初始化

初始化 获取框架申请的内存,全局指针所对应的结构体大小一定要和申请的大小一致
APP_GET_PAGE_MEM_PTR是框架提供的接口,用于获取预先申请的内存指针

game : static game_main_t *game = NULL;

c 复制代码
static void on_start(void)
{
		
    game = (game_main_t *)APP_GET_PAGE_MEM_PTR; 
    RT_ASSERT(game);

    jingziqi_app_gui_init(); //自定义页面初始化函数
}

4.3 on_resume 激活配置

c 复制代码
static void on_resume(void)
{
	/*
		井字棋游戏中只有简单的触摸事件,激活启动时也不需额外的配置或刷新定时器,因此这个函数为NULL。
	*/
}

4.4 on_pause 暂停

c 复制代码
static void on_pause(void)
{
	/*
		同on_resume函数。
	*/
}

4.5 on_stop

c 复制代码
static void on_stop(void)
{
	/* 将全局指针变量置空,防止其他地方使用时非空判断出现异常,该指针指向的内存会在框架执行完stop消息后释放 */
    game = NULL;
}

4.6 初始化时完成游戏代码配置

接下来就是在初始化时就完成井字棋游戏的代码编写。

【完整C代码】

c 复制代码
/*********************
 *      INCLUDES
 *********************/
#define DYN_APP

#include <rtthread.h>
#include <lvgl.h>
#include "global.h"
#include "lvsf_multobj.h"
#include "lvsf_multswipe.h"
#include "lvsf_scrollbar.h"
#include "lvsf_bg_db.h"

#define _MODULE_NAME_ "game"
#include "app_module.h"


typedef enum {
    PLAYER_X_TURN,  // X玩家回合
    PLAYER_O_TURN,  // O玩家回合
    PLAYER_X_WIN,   // X玩家获胜
    PLAYER_O_WIN,   // O玩家获胜
    GAME_DRAW       // 平局
} GameState;


// 棋盘状态
typedef enum {
    EMPTY,
    PLAYER_X,
    PLAYER_O
}  CellState;

typedef struct
{
    CellState board[3][3];      // 3x3棋盘
    GameState current_state;    // 当前游戏状态
    lv_obj_t *cell_btns[3][3];  // 按钮对象数组
    lv_obj_t *status_label;     // 状态显示标签
    lv_obj_t *restart_btn;      // 重新开始按钮
    lv_obj_t *game_board;       // 游戏棋盘对象
    lv_obj_t *bg_cont;
}game_main_t;

static game_main_t *game = NULL;


/**
 * 检查是否有获胜者
 * @return 获胜者(PLAYER_X, PLAYER_O)或EMPTY
 */
static CellState check_winner(void) {
    // 检查行
    for (int i = 0; i < 3; i++) {
        if (game->board[i][0] != EMPTY &&
            game->board[i][0] == game->board[i][1] &&
            game->board[i][1] == game->board[i][2]) {
            return game->board[i][0];
        }
    }
    
    // 检查列
    for (int j = 0; j < 3; j++) {
        if (game->board[0][j] != EMPTY &&
            game->board[0][j] == game->board[1][j] &&
            game->board[1][j] == game->board[2][j]) {
            return game->board[0][j];
        }
    }
    
    // 检查对角线
    if (game->board[0][0] != EMPTY &&
        game->board[0][0] == game->board[1][1] &&
        game->board[1][1] == game->board[2][2]) {
        return game->board[0][0];
    }
    
    if (game->board[0][2] != EMPTY &&
        game->board[0][2] == game->board[1][1] &&
        game->board[1][1] == game->board[2][0]) {
        return game->board[0][2];
    }
    
    return EMPTY;
}

/**
 * 检查是否平局
 * @return true如果平局
 */
static bool check_draw(void) {
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            if (game->board[i][j] == EMPTY) {
                return false;
            }
        }
    }
    return true;
}

/**
 * 更新按钮显示
 */
static void update_cell_display(int row, int col) {
    lv_obj_t *btn = game->cell_btns[row][col];
    lv_obj_t *img = lv_obj_get_child(btn, 0);
    
    switch (game->board[row][col]) {
        case EMPTY:
            lv_img_set_src(img, APP_GET_IMG(empty));
            break;
        case PLAYER_X:
            lv_img_set_src(img, APP_GET_IMG(cha));
            break;
        case PLAYER_O:
            lv_img_set_src(img, APP_GET_IMG(gou));
            break;
    }
}

// /**
//  * 处理单元格点击
//  */
static void cell_click_cb(lv_event_t *e) {
    //如果游戏已经结束,不处理点击
    if (game->current_state == PLAYER_X_WIN ||
        game->current_state == PLAYER_O_WIN ||
        game->current_state == GAME_DRAW) {
        return;
    }

    // 获取点击的按钮
    lv_obj_t *btn = lv_event_get_target(e);
    
    // 找到按钮对应的行列
    int row = -1, col = -1;
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            if (game->cell_btns[i][j] == btn) {
                row = i;
                col = j;
                break;
            }
        }
        if (row != -1) break;
    }
    
    // 如果单元格不为空,不处理
    if (game->board[row][col] != EMPTY) {
        return;
    }
    
    // 根据当前玩家设置单元格
    if (game->current_state == PLAYER_X_TURN) {
        game->board[row][col] = PLAYER_X;
        update_cell_display(row, col);
        
        //检查游戏状态
        CellState winner = check_winner();
        if (winner == PLAYER_X) {
            game->current_state = PLAYER_X_WIN;
            lv_label_set_text(game->status_label, "玩家 X 获胜!");
        } else if (check_draw()) {
            game->current_state = GAME_DRAW;
            lv_label_set_text(game->status_label, "平局!");

        } else {
            game->current_state = PLAYER_O_TURN;
            lv_label_set_text(game->status_label, "玩家 O 回合");
        }
    } 
    else if (game->current_state == PLAYER_O_TURN) {
        game->board[row][col] = PLAYER_O;
        update_cell_display(row, col);
        
        //检查游戏状态
        CellState winner = check_winner();
        if (winner == PLAYER_O) {
            game->current_state = PLAYER_O_WIN;
            lv_label_set_text(game->status_label, "玩家 O 获胜!");
        } else if (check_draw()) {
            game->current_state = GAME_DRAW;
            lv_label_set_text(game->status_label, "平局!");
        } else {
            game->current_state = PLAYER_X_TURN;
            lv_label_set_text(game->status_label, "玩家 X 回合");
        }
    }
}



void game_init(void) {
    // 清空棋盘
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            game->board[i][j] = EMPTY;
        }
    } 
    // 设置初始状态为X玩家回合
    game->current_state = PLAYER_X_TURN; 
    // 更新状态显示
    lv_label_set_text(game->status_label, "玩家 X 回合");
}

/**
 * 重新开始游戏回调
 */
static void restart_click_cb(lv_event_t *e) {
    (void)e; // 未使用参数
    game_init();
    
    // 清空所有按钮显示
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            update_cell_display(i, j);
        }
    }
}

static void jingziqi_app_gui_init()
{
    lv_obj_t *parent = lv_scr_act();

    lv_obj_t *bg_cont = lv_obj_create(parent);
    lv_obj_set_size(bg_cont, 390, 390);
    lv_obj_set_pos(bg_cont, 0, 0);
    lv_obj_set_style_bg_color(bg_cont, LV_COLOR_BLACK, LV_PART_MAIN);

    /* 棋盘 */
    lv_obj_t *game_board = lv_img_create(bg_cont);
    lv_img_set_src(game_board, APP_GET_IMG(qipan));
    lv_obj_set_size(game_board, 390, 390);
    lv_obj_set_pos(game_board, 0, 0);

    /* 状态标签 */
    lv_obj_t *label = lv_label_create(parent);
    lv_label_set_text(label, "玩家 X 回合");   // 初始状态
    lv_obj_set_style_text_color(label, lv_color_white(), 0);
    lv_obj_set_pos(label, 50, 400);

    game->game_board = game_board;
    game->status_label = label;


    // 创建3x3棋盘按钮
    for (int i = 0; i < 3; i++) {
        // 创建行容器
        lv_obj_t *row_cont = lv_obj_create(bg_cont);
        //lv_obj_remove_style_all(row_cont);
        lv_obj_set_size(row_cont, 300, 100);
        lv_obj_set_style_bg_opa(row_cont, 0, 0);
        lv_obj_set_pos(row_cont, 45, 45 + i * 100);
        
        for (int j = 0; j < 3; j++) {
            // 创建单元格按钮
            lv_obj_t *btn = lv_btn_create(row_cont);
            lv_obj_set_size(btn, 100, 100);
            lv_obj_set_style_bg_opa(btn, 0, 0);
            lv_obj_set_pos(btn,  j * 100,0);
            // 创建按钮标签
            lv_obj_t *img = lv_img_create(btn);
            lv_img_set_src(img, NULL);
            lv_obj_center(img);
            
            // 存储按钮引用
            game->cell_btns[i][j] = btn;
            
            // 添加点击事件
            lv_obj_add_event_cb(btn, cell_click_cb, LV_EVENT_CLICKED, NULL);
        }
    }

    /* 重新开始按钮 */
    lv_obj_t *restart_btn = lv_btn_create(parent);
    lv_obj_set_size(restart_btn, 100, 40);
    lv_obj_set_pos(restart_btn, 240, 400);
    lv_obj_t *restart_label = lv_label_create(restart_btn);
    lv_label_set_text(restart_label, "重新开始");
    lv_obj_center(restart_label);
    lv_obj_add_event_cb(restart_btn, restart_click_cb, LV_EVENT_CLICKED, NULL);

    game->restart_btn = restart_btn;

    game_init();

    lv_obj_update_layout(parent);
}


static void on_start(void)
{
    game = (game_main_t *)APP_GET_PAGE_MEM_PTR;
    RT_ASSERT(game);

    jingziqi_app_gui_init();
}

static void on_resume(void)
{

}

static void on_pause(void)
{

}

static void on_stop(void)
{

}

APPLICATION_REGISTER(app_get_strid(key_sport, "Game"), NULL, "game", sizeof(game_main_t));

5、编译下载

5.1 编译

C应用中勾选对应应用为预置

如果你和我一样只修改过C应用部分代码,且之前有关编译,可以选择部分编译,否则需要进行全编译


5.2 下载

选中对应的COM口并下载

5.3 显示




相关推荐
weilaikeqi11112 小时前
骏丰科技主动健康达人秀登上北京卫视,大健康行业迎需求重构
人工智能·科技·重构
changyunkeji2 小时前
电缆牵引专用绞磨,高效作业新选择
经验分享·科技
Deepoch3 小时前
Deepoc具身大模型机械狗:重新定义四足机器人智能交互新范式
人工智能·科技·机器人·具身智能·机器狗·deepoc·机械狗
TOWE technology3 小时前
同为科技智能桌面PDU:模块化智慧用电,智联涂鸦与小米生态
科技·涂鸦·桌面pdu·小米生态
德育处主任Pro5 小时前
『NAS』在绿联部署一个像素风宝可梦同人游戏-pokerogue
游戏·docker·群晖·nas·绿联
徐115 小时前
突破大型工件测量瓶颈:思看科技TrackScan-Sharp在风电与船舶制造中的革新应用
科技·数码相机·制造
华硕之声5 小时前
HIFI、OWS耳机,磁轴键盘等ROG外设纷纷亮相
科技·游戏·steam
xrczsjq7 小时前
非遗文化·展厅展馆立面墙面设计 | 森克思科技
科技·展馆设计·主题展馆设计·非遗展馆设计·文化展馆设计·文物展馆设计
GAOJ_K8 小时前
交叉导轨如何避免无效安装
运维·人工智能·科技·自动化·制造