中文
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
#ifndef GAME_H
|
||||
#define GAME_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "ui.h" // for DisplayList and colors
|
||||
|
||||
namespace esp32 {
|
||||
namespace game {
|
||||
|
||||
using namespace esp32::ui;
|
||||
|
||||
// 棋盘尺寸(经典 10x20)
|
||||
static const int COLS = 10;
|
||||
static const int ROWS = 20;
|
||||
|
||||
enum class State : uint8_t { Playing = 0, GameOver = 1 };
|
||||
|
||||
struct Piece {
|
||||
uint8_t type; // 0..6
|
||||
int8_t x; // 左上角/参考点
|
||||
int8_t y;
|
||||
// 仅使用一个朝向(简化:不提供旋转)
|
||||
int8_t blocks[4][2]; // 4 个方块偏移
|
||||
uint16_t color;
|
||||
};
|
||||
|
||||
struct Tetris {
|
||||
uint8_t board[ROWS][COLS]; // 0=空,其它=颜色索引或标记
|
||||
uint16_t colors[8]; // 颜色表(index->RGB565)
|
||||
Piece current;
|
||||
uint32_t lastDropMs = 0;
|
||||
uint32_t dropInterval = 700; // ms
|
||||
State state = State::Playing;
|
||||
uint32_t score = 0;
|
||||
uint32_t rng = 1;
|
||||
};
|
||||
|
||||
void init(Tetris& g);
|
||||
void reset(Tetris& g);
|
||||
void update(Tetris& g, uint32_t nowMs);
|
||||
|
||||
// 输入
|
||||
void moveLeft(Tetris& g);
|
||||
void moveRight(Tetris& g);
|
||||
void hardDrop(Tetris& g);
|
||||
|
||||
// UI 构建
|
||||
void buildScene(const Tetris& g, DisplayList& out,
|
||||
int16_t screenW, int16_t screenH,
|
||||
int16_t cell, int16_t originX, int16_t originY);
|
||||
|
||||
} // namespace game
|
||||
} // namespace esp32
|
||||
|
||||
#endif // GAME_H
|
||||
@@ -0,0 +1,30 @@
|
||||
#ifndef TIMER_H
|
||||
#define TIMER_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "ui.h"
|
||||
|
||||
namespace esp32 {
|
||||
namespace timer {
|
||||
|
||||
struct ClockState {
|
||||
uint32_t startMs = 0; // 起始时间
|
||||
uint32_t elapsedMs = 0; // 当前经过时间
|
||||
static const int MAX_LAPS = 12;
|
||||
uint16_t lapCount = 0;
|
||||
float lapAngles[MAX_LAPS]; // 存储圈记录的角度(秒针位置)
|
||||
uint32_t lapMs[MAX_LAPS]; // 每次记录的时间戳 (ms)
|
||||
bool running = true;
|
||||
};
|
||||
|
||||
void init(ClockState& s);
|
||||
void reset(ClockState& s);
|
||||
void update(ClockState& s, uint32_t nowMs);
|
||||
void addLap(ClockState& s);
|
||||
void buildScene(const ClockState& s, esp32::ui::DisplayList& out,
|
||||
int16_t screenW, int16_t screenH);
|
||||
|
||||
} // namespace timer
|
||||
} // namespace esp32
|
||||
|
||||
#endif // TIMER_H
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
#ifndef UI_H
|
||||
#define UI_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Adafruit_ST7735.h>
|
||||
#include <Adafruit_GFX.h>
|
||||
#include <LittleFS.h>
|
||||
#include <U8g2_for_Adafruit_GFX.h>
|
||||
#include "utils.h"
|
||||
|
||||
namespace esp32 {
|
||||
namespace ui {
|
||||
|
||||
// 简易图标:RGB565 像素数组,内存持有
|
||||
struct Icon {
|
||||
uint16_t width = 0;
|
||||
uint16_t height = 0;
|
||||
uint16_t* pixels = nullptr; // width*height 大小的 RGB565 数组
|
||||
};
|
||||
|
||||
// 显示操作类型
|
||||
enum class OpType : uint8_t { FillRect = 0, DrawText = 1, DrawIcon = 2, Line = 3, Circle = 4 };
|
||||
|
||||
// 显示项(Display List 条目)
|
||||
struct DisplayItem {
|
||||
OpType op;
|
||||
uint16_t key; // 用于跨帧识别(简化版,当前全量重放)
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
int16_t w; // FillRect 使用 或 Line/Circle 半径/终点 x
|
||||
int16_t h; // FillRect 使用 或 Line 终点 y
|
||||
uint16_t color; // FillRect/DrawText 使用
|
||||
const char* text; // 指向 textBuf 或字符串字面量
|
||||
const Icon* icon; // DrawIcon 使用
|
||||
char textBuf[64]; // 小型文本缓冲,存放动态生成的字符串(例如时间、lap)
|
||||
};
|
||||
|
||||
// 固定容量的显示列表
|
||||
class DisplayList {
|
||||
public:
|
||||
static const int MAX_ITEMS = 256;
|
||||
DisplayList() : _count(0) {}
|
||||
|
||||
void clear() { _count = 0; }
|
||||
int size() const { return _count; }
|
||||
const DisplayItem& at(int i) const { return _items[i]; }
|
||||
|
||||
bool addFillRect(uint16_t key, int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) {
|
||||
if (_count >= MAX_ITEMS) return false;
|
||||
auto& it = _items[_count++];
|
||||
it.op = OpType::FillRect;
|
||||
it.key = key;
|
||||
it.x = x; it.y = y; it.w = w; it.h = h; it.color = color;
|
||||
it.text = nullptr; it.icon = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool addText(uint16_t key, int16_t x, int16_t y, uint16_t color, const char* text, bool centered = false) {
|
||||
if (_count >= MAX_ITEMS) return false;
|
||||
auto& it = _items[_count++];
|
||||
it.op = OpType::DrawText;
|
||||
it.key = key;
|
||||
it.x = x; it.y = y;
|
||||
it.w = centered ? 1 : 0; // w=1 表示需要居中
|
||||
it.h = 0;
|
||||
it.color = color;
|
||||
// 复制文本到缓冲,保证作用域结束后仍可用
|
||||
if (text) {
|
||||
strncpy(it.textBuf, text, sizeof(it.textBuf) - 1);
|
||||
it.textBuf[sizeof(it.textBuf) - 1] = '\0';
|
||||
it.text = it.textBuf;
|
||||
} else {
|
||||
it.textBuf[0] = '\0';
|
||||
it.text = it.textBuf;
|
||||
}
|
||||
it.icon = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool addIcon(uint16_t key, int16_t x, int16_t y, const Icon* icon) {
|
||||
if (_count >= MAX_ITEMS) return false;
|
||||
auto& it = _items[_count++];
|
||||
it.op = OpType::DrawIcon;
|
||||
it.key = key;
|
||||
it.x = x; it.y = y; it.w = 0; it.h = 0; it.color = 0;
|
||||
it.text = nullptr; it.icon = icon;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool addLine(uint16_t key, int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint16_t color) {
|
||||
if (_count >= MAX_ITEMS) return false;
|
||||
auto& it = _items[_count++];
|
||||
it.op = OpType::Line;
|
||||
it.key = key;
|
||||
it.x = x1; it.y = y1; it.w = x2; it.h = y2; it.color = color;
|
||||
it.text = nullptr; it.icon = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool addCircle(uint16_t key, int16_t cx, int16_t cy, int16_t radius, uint16_t color) {
|
||||
if (_count >= MAX_ITEMS) return false;
|
||||
auto& it = _items[_count++];
|
||||
it.op = OpType::Circle;
|
||||
it.key = key;
|
||||
it.x = cx; it.y = cy; it.w = radius; it.h = 0; it.color = color;
|
||||
it.text = nullptr; it.icon = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
int _count;
|
||||
DisplayItem _items[MAX_ITEMS];
|
||||
};
|
||||
|
||||
// Tween(补间动画)
|
||||
struct Tween {
|
||||
float* target = nullptr;
|
||||
float from = 0.0f;
|
||||
float to = 0.0f;
|
||||
uint32_t startMs = 0;
|
||||
uint32_t durationMs = 0;
|
||||
float x1 = 0.42f, y1 = 0.0f, x2 = 0.58f, y2 = 1.0f; // 贝塞尔参数
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
// UI 框架主体
|
||||
class UI {
|
||||
public:
|
||||
UI() : _tft(nullptr), _u8g2(nullptr), _builder(nullptr), _builderCtx(nullptr) {}
|
||||
|
||||
void begin(Adafruit_ST7735* tft, U8G2_FOR_ADAFRUIT_GFX* u8g2 = nullptr) {
|
||||
_tft = tft;
|
||||
_u8g2 = u8g2;
|
||||
}
|
||||
|
||||
// builder: 负责把状态转为显示列表
|
||||
using BuilderFn = void (*)(void* ctx, DisplayList& out);
|
||||
void setScene(BuilderFn builder, void* ctx) {
|
||||
_builder = builder;
|
||||
_builderCtx = ctx;
|
||||
}
|
||||
|
||||
// 每帧调用:更新动画 + 生成显示列表 + 渲染
|
||||
void tick(uint32_t nowMs);
|
||||
|
||||
// 添加一个 float 补间(从当前值到目标值)
|
||||
int addTweenFloat(float* target, float to, uint32_t durationMs,
|
||||
float x1 = 0.42f, float y1 = 0.0f, float x2 = 0.58f, float y2 = 1.0f);
|
||||
|
||||
bool hasActiveTweens() const;
|
||||
bool isTweenActive(int id) const;
|
||||
|
||||
// 图标加载:文件格式 (little-endian): [2字节宽][2字节高][像素: width*height*2]
|
||||
static bool loadIconFromFS(const char* path, Icon& out);
|
||||
static void freeIcon(Icon& icon);
|
||||
|
||||
// 启用内存帧缓冲(双缓冲),第一次调用会分配两块 16 位色的画布
|
||||
bool enableBackBuffer(uint16_t width, uint16_t height);
|
||||
bool isBackBufferEnabled() const { return _useBackBuffer; }
|
||||
|
||||
private:
|
||||
void _updateTweens(uint32_t nowMs);
|
||||
void _render(const DisplayList& list);
|
||||
void _drawIcon(int16_t x, int16_t y, const Icon& icon);
|
||||
void _renderToCanvas(const DisplayList& list); // 使用内存画布绘制
|
||||
void _flushCanvas(); // 将当前画布推送到屏幕
|
||||
void _swapCanvas(); // 交换前后缓冲
|
||||
|
||||
Adafruit_ST7735* _tft;
|
||||
U8G2_FOR_ADAFRUIT_GFX* _u8g2;
|
||||
BuilderFn _builder;
|
||||
void* _builderCtx;
|
||||
DisplayList _list;
|
||||
|
||||
static const int MAX_TWEENS = 8;
|
||||
Tween _tweens[MAX_TWEENS];
|
||||
|
||||
// 双缓冲相关
|
||||
bool _useBackBuffer = false;
|
||||
GFXcanvas16* _canvasA = nullptr; // 当前绘制目标
|
||||
GFXcanvas16* _canvasB = nullptr; // 上一帧
|
||||
GFXcanvas16* _canvasCurrent = nullptr;
|
||||
GFXcanvas16* _canvasPrev = nullptr;
|
||||
uint16_t _bufW = 0;
|
||||
uint16_t _bufH = 0;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace esp32
|
||||
|
||||
#endif // UI_H
|
||||
Reference in New Issue
Block a user