Files
risc-v-cpu/saolei/sw/saolei.c
T
Alpha 7e13571744 fix(saolei): 修显示/难度/数字三处问题(只本地提交)
1) 显示:VGA 的像素色 = 字模点 ? 字符色 : 背景色,而背景是黑的,所以 START 按钮上
   那几个 C_BLACK 字母的字形点和空白处全黑,整格看不见。改成黄色。
2) 难度:三档改成微软扫雷的真实尺寸 9x9/10、16x16/40、30x16/99(原来三档都是
   16x16,只有雷数不同)。棋盘尺寸由编译期常量改成运行期变量
   (g_bw/g_bh/g_board_r0/g_board_c0),所有索引改用 mul/divu/remu(这颗核都有),
   棋盘自动居中;BFS 加 seen_bm 去重,避免重复入队把队列撑满丢格。
3) RTL:cpu21_riscv_redirect_int_bpb 例化 cpu21_mmio 时漏传 FB_WORDS,mmio 用默认
   300,fb_ok 把显存写卡在前 300 字(前 15 行),屏幕下半部分永远刷不出来。
   补上 .FB_WORDS(FB_WORDS) 并给顶层加参数,mmio 默认值也改成 600。

验证:
- build.ps1:编译 + check_isa 指令白名单全部通过(新增的 mul/divu/remu 都合法)
- 棋盘逻辑(PC 上 gcc 跑 _tmp/logic,不改仓库):三档 x 40 局 x 全部格子,
  count_adj 与独立重算逐格一致,雷数、首点 3x3 安全区都正确
  -> 数字与雷位没有逻辑矛盾,看到的 3 旁边 4 个雷 应是显示层问题
2026-09-23 20:54:28 +08:00

742 lines
22 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* -----------------------------------------------------------------------------
* saolei.c —— 纯 C 扫雷(RV32IM 子集:只用这颗核实现了的指令)
*
* 显示:把每个格子写成显存里的 2 个字节(MMIO 0x2000 起)
* 输入:鼠标事件(中断驱动)——中断里搬到软件队列,主循环在下面处理
*
* 针对这颗 CPU 的硬约束(很重要,改代码时别踩):
* 1) 存储只有 lw / sw / sb,**没有 lb/lh/lbu/lhu** ——
* 所以只按字节"写"显存,从不按字节"读";所有状态都用 32 位字保存。
* 2) **没有 auipc** —— 编译时必须 -fno-pic -mcmodel=medlow,并且不能出现
* 只读数据(.rodata),所以本文件没有任何字符串/常量表(字模在 RTL 里)。
* 3) 乘除只有 mul/mulh/mulhsu/mulhu 和**无符号**的 divu/remu ——
* 所以本文件所有索引运算都用"2 的幂 + 移位/掩码",一次除法都不用。
* 4) 没有 fence/ebreak 等;中断返回是 uret(在 isr.c 里)。
*
* 棋盘:16 x 16,雷数可选 20/40/60(16 是 2 的幂 => 索引全用移位)
* 网格:40 列 x 30 行,棋盘居中放在 (行 7..22, 列 12..27)
* ---------------------------------------------------------------------------*/
#include "mmio.h"
/* 三档难度完全照微软扫雷:初级 9x9/10 雷、中级 16x16/40 雷、高级 30x16/99 雷。
* 棋盘尺寸是运行期变量(三档各不相同),所以位图与队列都按最大尺寸分配。 */
#define BW_MAX 30u /* 最大棋盘宽(高级) */
#define BH_MAX 16u /* 最大棋盘高 */
#define NCELLS_MAX (BW_MAX * BH_MAX) /* 480 */
#define BM_WORDS_MAX ((NCELLS_MAX + 31u) / 32u) /* 15 */
/* 当前难度下的棋盘参数(由 set_difficulty() 设定) */
static unsigned g_bw, g_bh; /* 棋盘宽、高(格) */
static unsigned g_ncells; /* g_bw * g_bh */
static unsigned g_bm_words; /* 位图字数 */
static unsigned g_board_r0; /* 棋盘左上角在网格里的行(居中) */
static unsigned g_board_c0; /* 棋盘左上角在网格里的列(居中) */
/* 格索引 <-> (行, 列)。这颗核有无符号除法 divu/remu,直接用;虽然多周期,
* 但一局游戏这点开销无所谓。 */
#define IDX_R(i) ((i) / g_bw)
#define IDX_C(i) ((i) % g_bw)
/* 游戏状态 */
#define ST_PLAY 0u
#define ST_WIN 1u
#define ST_LOSE 2u
/* ---- 棋盘状态:3 个位图,每个 8 个字(256 位) ---- */
static unsigned mine_bm[BM_WORDS_MAX];
static unsigned open_bm[BM_WORDS_MAX];
static unsigned flag_bm[BM_WORDS_MAX];
static unsigned seen_bm[BM_WORDS_MAX]; /* BFS:已经入过队的格子,避免重复入队把队列撑满 */
/* 其它全局状态(全部进 .bss,靠 start.c 清零) */
static unsigned opened_cnt;
static unsigned flags_cnt;
static unsigned g_state;
static unsigned g_first_done;
static unsigned g_mines;
static unsigned g_difficulty;
static unsigned rnd_state;
static unsigned cur_r, cur_c; /* 光标所在的网格格 */
static int acc_x, acc_y; /* 指针累积量(1/8 格,有符号) */
static unsigned bfs_q[NCELLS_MAX]; /* 揭开用的队列(按最大棋盘分配) */
/* ============================ 小工具 ============================ */
/* 位图取位/置位/清位:i = r*16 + c,全部用移位和掩码 */
static unsigned bget(const unsigned *bm, unsigned i)
{
return (bm[i >> 5] >> (i & 31u)) & 1u;
}
static void bset(unsigned *bm, unsigned i)
{
bm[i >> 5] |= (1u << (i & 31u));
}
static void bclr(unsigned *bm, unsigned i)
{
bm[i >> 5] &= ~(1u << (i & 31u));
}
static void draw_cell(unsigned gr, unsigned gc, unsigned glyph, unsigned color)
{
fb_write_cell(gr * GRID_COLS + gc, glyph, color);
}
/* 把棋盘格 (r,c) 画出来 */
static void draw_tile(unsigned r, unsigned c, unsigned glyph, unsigned color)
{
draw_cell(g_board_r0 + r, g_board_c0 + c, glyph, color);
}
static unsigned num_color(unsigned n)
{
if (n == 1u)
return C_BLUE;
if (n == 2u)
return C_GREEN;
if (n == 3u)
return C_RED;
if (n == 4u)
return C_MAGENTA;
if (n == 5u)
return C_YELLOW;
if (n == 6u)
return C_CYAN;
return C_GRAY;
}
static unsigned in_board(void)
{
if (cur_r < g_board_r0)
return 0u;
if (cur_r >= (g_board_r0 + g_bh))
return 0u;
if (cur_c < g_board_c0)
return 0u;
if (cur_c >= (g_board_c0 + g_bw))
return 0u;
return 1u;
}
static unsigned cur_index(void)
{
return ((cur_r - g_board_r0) * g_bw) + (cur_c - g_board_c0);
}
/* LCG 随机数:只用 mul/add/移位 */
static unsigned rnd_next(void)
{
rnd_state = rnd_state * 1103515245u + 12345u;
return rnd_state >> 16;
}
/* 设定难度:棋盘尺寸 + 雷数 + 居中位置,三档完全照微软扫雷。 */
static void set_difficulty(unsigned difficulty)
{
g_difficulty = difficulty;
if (difficulty == 0u) /* 初级:9x9,10 雷 */
{
g_bw = 9u;
g_bh = 9u;
g_mines = 10u;
}
else if (difficulty == 2u) /* 高级:30x16,99 雷 */
{
g_bw = 30u;
g_bh = 16u;
g_mines = 99u;
}
else /* 中级:16x16,40 雷 */
{
g_bw = 16u;
g_bh = 16u;
g_mines = 40u;
}
g_ncells = g_bw * g_bh;
g_bm_words = (g_ncells + 31u) / 32u;
g_board_c0 = (GRID_COLS - g_bw) / 2u; /* 棋盘居中 */
g_board_r0 = (GRID_ROWS - g_bh) / 2u;
}
/* ============================ 画面 ============================ */
static void clear_screen(void)
{
unsigned i;
for (i = 0u; i < (GRID_COLS * GRID_ROWS); i = i + 1u)
{
fb_write_cell(i, G_BLANK, C_BLACK);
}
}
static void draw_frame(void)
{
unsigned gc, gr;
for (gc = (g_board_c0 - 1u); gc <= (g_board_c0 + g_bw); gc = gc + 1u)
{
draw_cell(g_board_r0 - 1u, gc, G_SOLID, C_CYAN);
draw_cell(g_board_r0 + g_bh, gc, G_SOLID, C_CYAN);
}
for (gr = (g_board_r0 - 1u); gr <= (g_board_r0 + g_bh); gr = gr + 1u)
{
draw_cell(gr, g_board_c0 - 1u, G_SOLID, C_CYAN);
draw_cell(gr, g_board_c0 + g_bw, G_SOLID, C_CYAN);
}
}
static void draw_all_covered(void)
{
unsigned r, c;
for (r = 0u; r < g_bh; r = r + 1u)
{
for (c = 0u; c < g_bw; c = c + 1u)
{
draw_tile(r, c, G_COVER, C_GRAY);
}
}
}
static void update_cursor_reg(void)
{
REG_W(REG_CURSOR) = (cur_c & 31u) | ((cur_r & 31u) << 8);
}
static void set_bg(unsigned color)
{
REG_W(REG_CTRL) = (color << 4) | 1u; /* bit0 = 光标显示 */
}
/* ============================ 游戏逻辑 ============================ */
static unsigned count_adj(unsigned i)
{
unsigned r = IDX_R(i), c = IDX_C(i), n = 0u, rr, cc;
unsigned r0 = (r == 0u) ? 0u : (r - 1u);
unsigned r1 = (r + 1u >= g_bh) ? (g_bh - 1u) : (r + 1u);
unsigned c0 = (c == 0u) ? 0u : (c - 1u);
unsigned c1 = (c + 1u >= g_bw) ? (g_bw - 1u) : (c + 1u);
for (rr = r0; rr <= r1; rr = rr + 1u)
{
for (cc = c0; cc <= c1; cc = cc + 1u)
{
if ((rr == r) && (cc == c))
continue;
n = n + bget(mine_bm, (rr * g_bw) + cc);
}
}
return n;
}
static void lose(unsigned hit)
{
unsigned k;
for (k = 0u; k < g_ncells; k = k + 1u)
{
if (bget(mine_bm, k))
{
if (k == hit)
{
draw_tile(IDX_R(k), IDX_C(k), G_BOOM, C_RED);
}
else if (!bget(flag_bm, k))
{
draw_tile(IDX_R(k), IDX_C(k), G_MINE, C_RED);
}
}
}
REG_W(LED_ADDR) = 0xDEADu;
g_state = ST_LOSE;
}
static void win(void)
{
unsigned k;
for (k = 0u; k < g_ncells; k = k + 1u)
{
if (bget(mine_bm, k))
{
draw_tile(IDX_R(k), IDX_C(k), G_FLAG, C_YELLOW);
}
}
REG_W(LED_ADDR) = 0xF00Du;
g_state = ST_WIN;
}
static void reveal_at(unsigned i)
{
unsigned qh = 0u, qt = 0u, cur, n, r, c, rr, cc, ni;
unsigned r0, r1, c0, c1, k;
if (bget(open_bm, i) || bget(flag_bm, i))
return;
if (bget(mine_bm, i))
{
lose(i);
return;
}
for (k = 0u; k < g_bm_words; k = k + 1u)
seen_bm[k] = 0u;
bfs_q[qt] = i;
qt = qt + 1u;
bset(seen_bm, i);
while (qh < qt)
{
cur = bfs_q[qh];
qh = qh + 1u;
if (bget(open_bm, cur))
continue;
bset(open_bm, cur);
opened_cnt = opened_cnt + 1u;
n = count_adj(cur);
draw_tile(IDX_R(cur), IDX_C(cur), n, num_color(n));
if (n != 0u)
continue;
/* 空白格:把周围 8 格入队(已入过队的跳过,队列不会溢出) */
r = IDX_R(cur);
c = IDX_C(cur);
r0 = (r == 0u) ? 0u : (r - 1u);
r1 = (r + 1u >= g_bh) ? (g_bh - 1u) : (r + 1u);
c0 = (c == 0u) ? 0u : (c - 1u);
c1 = (c + 1u >= g_bw) ? (g_bw - 1u) : (c + 1u);
for (rr = r0; rr <= r1; rr = rr + 1u)
{
for (cc = c0; cc <= c1; cc = cc + 1u)
{
if ((rr == r) && (cc == c))
continue;
ni = (rr * g_bw) + cc;
if (bget(seen_bm, ni) || bget(open_bm, ni) || bget(flag_bm, ni))
continue;
if (qt < NCELLS_MAX)
{
bset(seen_bm, ni);
bfs_q[qt] = ni;
qt = qt + 1u;
}
}
}
}
if (opened_cnt == (g_ncells - g_mines))
win();
}
static void place_mines(unsigned safe)
{
unsigned placed = 0u, i, r, c;
unsigned sr = IDX_R(safe);
unsigned sc = IDX_C(safe);
while (placed < g_mines)
{
i = rnd_next() % g_ncells; /* 尺寸不再是 2 的幂,用 remu 取模 */
r = IDX_R(i);
c = IDX_C(i);
/* 首点及其 8 邻居不放雷,保证第一次点击一定能展开 */
if ((r + 1u >= sr) && (r <= (sr + 1u)) &&
(c + 1u >= sc) && (c <= (sc + 1u)))
continue;
if (bget(mine_bm, i))
continue;
bset(mine_bm, i);
placed = placed + 1u;
}
}
static void click_here(void)
{
unsigned i;
if (!in_board())
return;
i = cur_index();
if (bget(flag_bm, i))
return;
if (!g_first_done)
{
place_mines(i);
g_first_done = 1u;
}
reveal_at(i);
}
static void toggle_flag(void)
{
unsigned i, r, c;
if (!in_board())
return;
i = cur_index();
if (bget(open_bm, i))
return;
r = cur_r - g_board_r0;
c = cur_c - g_board_c0;
if (bget(flag_bm, i))
{
bclr(flag_bm, i);
flags_cnt = flags_cnt - 1u;
draw_tile(r, c, G_COVER, C_GRAY);
}
else
{
bset(flag_bm, i);
flags_cnt = flags_cnt + 1u;
draw_tile(r, c, G_FLAG, C_YELLOW);
}
REG_W(LED_ADDR) = g_mines - flags_cnt;
}
/* ============================ 鼠标事件 ============================ */
/* 关卡内:光标允许落在盘面外那一圈边框上(左上角那格是"返回") */
static void move_cursor(int dr, int dc)
{
int nr = (int)cur_r + dr;
int nc = (int)cur_c + dc;
if (nr < (int)(g_board_r0 - 1u))
nr = (int)(g_board_r0 - 1u);
if (nr > (int)(g_board_r0 + g_bh))
nr = (int)(g_board_r0 + g_bh);
if (nc < (int)(g_board_c0 - 1u))
nc = (int)(g_board_c0 - 1u);
if (nc > (int)(g_board_c0 + g_bw))
nc = (int)(g_board_c0 + g_bw);
cur_r = (unsigned)nr;
cur_c = (unsigned)nc;
update_cursor_reg();
}
/* ======================= 运行环境状态(背景 / 关卡) =======================
* 程序分成两层,正好对应"伪操作系统 + 关卡"的设想:
* 背景程序 main() 从头跑到尾:画开始界面、等鼠标;
* 关卡程序 level_main() 跑在 IRQ1(0x100) 的中断服务程序里:点"开始"进入,
* 点"返回"就 uret 回到背景被打断的那条指令。
* ========================================================================= */
static unsigned g_in_level; /* 0 = 背景界面,1 = 关卡内 */
static unsigned g_exit; /* 关卡内请求返回 */
/* 背景界面上"开始"按钮的位置(行 20,列 17..22) */
#define START_R 20u
#define START_C0 17u
#define START_C1 22u
#define DIFF_EASY_R 14u
#define DIFF_NORMAL_R 16u
#define DIFF_HARD_R 18u
static void draw_start_screen(void);
/* 背景用:光标可以在整个画面上移动 */
static void move_cursor_screen(int dr, int dc)
{
int nr = (int)cur_r + dr;
int nc = (int)cur_c + dc;
if (nr < 0)
nr = 0;
if (nr > (int)(GRID_ROWS - 1u))
nr = (int)(GRID_ROWS - 1u);
if (nc < 0)
nc = 0;
if (nc > (int)(GRID_COLS - 1u))
nc = (int)(GRID_COLS - 1u);
cur_r = (unsigned)nr;
cur_c = (unsigned)nc;
update_cursor_reg();
}
/* 处理一个鼠标事件:ev = {btn[20:16], dy[15:8], dx[7:0]} */
static void handle_event(unsigned ev)
{
unsigned ux = ev & 0xFFu;
unsigned uy = (ev >> 8) & 0xFFu;
unsigned btn = (ev >> 16) & 31u;
int dx = (ux & 0x80u) ? ((int)ux - 256) : (int)ux;
int dy = (uy & 0x80u) ? ((int)uy - 256) : (int)uy;
unsigned want_flag = (REG_R(REG_INPUT) & 9u) != 0u; /* SW0 或 SW3 = 插旗模式 */
if ((btn & 1u) != 0u)
{ /* 左键 */
if (g_in_level == 0u)
{
if (cur_r == DIFF_EASY_R)
{
set_difficulty(0u);
draw_start_screen();
return;
}
if (cur_r == DIFF_NORMAL_R)
{
set_difficulty(1u);
draw_start_screen();
return;
}
if (cur_r == DIFF_HARD_R)
{
set_difficulty(2u);
draw_start_screen();
return;
}
/* 背景:点到"开始"按钮 -> 拉起软件中断,跳到 IRQ1 里的关卡程序 */
if ((cur_r == START_R) && (cur_c >= START_C0) && (cur_c <= START_C1))
{
g_in_level = 1u; /* 关卡程序返回时自己清 0 */
REG_W(REG_LEVEL) = 1u;
}
}
else if ((cur_r == (g_board_r0 - 1u)) && (cur_c == (g_board_c0 - 1u)))
{
g_exit = 1u; /* 关卡:点左上角黄格 = 返回上一层 */
}
else if (want_flag != 0u)
{
toggle_flag();
}
else
{
click_here();
}
return;
}
if ((btn & 2u) != 0u)
{ /* 右键(PS/2 鼠标有;按键没有) */
if (g_in_level != 0u)
toggle_flag();
return;
}
/* 移动:单位是 1/8 格,累计到 8 就动一格(纯加减比较,没有除法) */
acc_x += dx;
acc_y += dy;
while (acc_x >= 8)
{
if (g_in_level != 0u)
move_cursor(0, 1);
else
move_cursor_screen(0, 1);
acc_x -= 8;
}
while (acc_x <= -8)
{
if (g_in_level != 0u)
move_cursor(0, -1);
else
move_cursor_screen(0, -1);
acc_x += 8;
}
while (acc_y >= 8)
{
if (g_in_level != 0u)
move_cursor(1, 0);
else
move_cursor_screen(1, 0);
acc_y -= 8;
}
while (acc_y <= -8)
{
if (g_in_level != 0u)
move_cursor(-1, 0);
else
move_cursor_screen(-1, 0);
acc_y += 8;
}
}
/* ============================ 背景程序(伪操作系统)============================
* 从头跑到尾:画开始界面 -> 等鼠标 -> 点到"开始"按钮就拉起软件中断。
* ========================================================================== */
static void draw_start_screen(void)
{
unsigned i, selected_color;
clear_screen();
/* 标题和难度菜单使用 RTL 字模中的大写字母。 */
draw_cell(6u, 18u, G_M, C_RED);
draw_cell(6u, 19u, G_I, C_RED);
draw_cell(6u, 20u, G_N, C_RED);
draw_cell(6u, 21u, G_E, C_RED);
draw_cell(6u, 22u, G_S, C_RED);
selected_color = C_YELLOW;
if (g_difficulty != 0u)
selected_color = C_GRAY;
draw_cell(DIFF_EASY_R, 18u, G_E, selected_color);
draw_cell(DIFF_EASY_R, 19u, G_A, selected_color);
draw_cell(DIFF_EASY_R, 20u, G_S, selected_color);
draw_cell(DIFF_EASY_R, 21u, G_Y, selected_color);
selected_color = C_YELLOW;
if (g_difficulty != 1u)
selected_color = C_GRAY;
draw_cell(DIFF_NORMAL_R, 16u, G_N, selected_color);
draw_cell(DIFF_NORMAL_R, 17u, G_O, selected_color);
draw_cell(DIFF_NORMAL_R, 18u, G_R, selected_color);
draw_cell(DIFF_NORMAL_R, 19u, G_M, selected_color);
draw_cell(DIFF_NORMAL_R, 20u, G_A, selected_color);
draw_cell(DIFF_NORMAL_R, 21u, G_L, selected_color);
selected_color = C_YELLOW;
if (g_difficulty != 2u)
selected_color = C_GRAY;
draw_cell(DIFF_HARD_R, 18u, G_H, selected_color);
draw_cell(DIFF_HARD_R, 19u, G_A, selected_color);
draw_cell(DIFF_HARD_R, 20u, G_R, selected_color);
draw_cell(DIFF_HARD_R, 21u, G_D, selected_color);
/* "START" 按钮:绿色实心条,上下各一条黄线框住它。 */
for (i = START_C0; i <= START_C1; i = i + 1u)
{
draw_cell(START_R - 1u, i, G_BAR, C_YELLOW);
draw_cell(START_R, i, G_SOLID, C_GREEN);
draw_cell(START_R + 1u, i, G_BAR, C_YELLOW);
}
/* 按钮上的字母不能用 C_BLACK:VGA 里像素色 = 字模点 ? 字符色 : 背景色,
* 而背景是黑的,所以“黑字”的字形点和空白处全黑 —— 整格完全看不见。
* 这里用黄色:黑底黄字,和上下的黄线框也更呼应。 */
draw_cell(START_R, 17u, G_S, C_YELLOW);
draw_cell(START_R, 18u, G_T, C_YELLOW);
draw_cell(START_R, 19u, G_A, C_YELLOW);
draw_cell(START_R, 20u, G_R, C_YELLOW);
draw_cell(START_R, 21u, G_T, C_YELLOW);
/* 下方提示条 */
for (i = 12u; i <= 27u; i = i + 1u)
{
draw_cell(24u, i, G_BAR, C_CYAN);
}
set_bg(C_BLACK);
cur_r = START_R;
cur_c = (START_C0 + START_C1) / 2u;
update_cursor_reg();
REG_W(LED_ADDR) = 0x0001u; /* LED = 背景状态码 */
REG_W(REG_SEG) = 0u;
}
/* ============================ 关卡(游戏)程序 ============================
* **运行在 IRQ1 的中断服务程序里**(入口 isr1_entry,固定地址 0x100):
* 背景 main() 点"开始" -> 写 LEVEL 寄存器 -> 硬件拉起 irq_i[1]
* -> CPU 跳到 0x100 -> isr1_entry 保存现场、打开 MIE、调用本函数
* -> 这里跑完整一局(鼠标中断 0x200 优先级更高,会嵌套进来把事件放进队列,
* 所以本循环只要轮询队列)
* -> 点左上角黄格 = 返回:清 LEVEL、return -> isr1_entry 恢复现场并 uret
* -> 回到背景程序被打断的那条指令,继续等下一次"开始"。
* ========================================================================== */
/* 关卡里“返回上一层”的黄格 = 棋盘左上角外面一格(随难度尺寸移动) */
void level_main(void)
{
unsigned r;
/* ---- 关卡初始化 ---- */
for (r = 0u; r < g_bm_words; r = r + 1u)
{
mine_bm[r] = 0u;
open_bm[r] = 0u;
flag_bm[r] = 0u;
seen_bm[r] = 0u;
}
opened_cnt = 0u;
flags_cnt = 0u;
g_first_done = 0u;
g_state = ST_PLAY;
g_exit = 0u;
acc_x = 0;
acc_y = 0;
cur_r = g_board_r0 + (g_bh / 2u); /* 光标从棋盘中心开始 */
cur_c = g_board_c0 + (g_bw / 2u);
clear_screen();
draw_frame();
draw_all_covered();
draw_cell(g_board_r0 - 1u, g_board_c0 - 1u, G_BAR, C_YELLOW); /* 左上角黄格 = 返回 */
set_bg(C_BLACK);
update_cursor_reg();
REG_W(LED_ADDR) = g_mines;
REG_W(REG_SEG) = g_mines; /* 数码管显示剩余雷数 */
/* ---- 关卡主循环 ---- */
while (g_exit == 0u)
{
if (g_evq_head != g_evq_tail)
{
unsigned ev = g_evq[g_evq_head];
g_evq_head = (g_evq_head + 1u) & EVQ_MASK;
if (g_state == ST_PLAY)
{
handle_event(ev);
}
else if ((ev >> 16) & 1u)
{
g_exit = 1u; /* 分出胜负后任意点击 = 返回 */
}
}
else if (g_state != ST_PLAY)
{
/* 分出胜负后让背景闪动(用 VGA 帧计数做时间基准,不需要定时器) */
static unsigned phase;
if ((REG_R(REG_FRAME) & 31u) == 0u)
{
phase = phase ^ 1u;
if (phase != 0u)
{
set_bg((g_state == ST_WIN) ? C_GREEN : C_RED);
}
else
{
set_bg(C_BLACK);
}
}
}
}
REG_W(REG_LEVEL) = 0u; /* 清软件中断:否则下次点"开始"没有新的上升沿 */
g_in_level = 0u; /* 回到背景状态 */
draw_start_screen(); /* 恢复开始界面 */
}
/* ============================ 背景主程序 ============================ */
extern void crt_zero_bss(void);
int main(void)
{
crt_zero_bss(); /* 清 .bss(上电 RAM 本来就是 0,保险起见) */
rnd_state = REG_R(REG_TIME) | 1u; /* 用自由计数器当随机种子 */
set_difficulty(1u); /* 默认中级:16x16,40 颗雷 */
REG_W(REG_MCTRL) = 1u; /* 打开鼠标中断(IRQ2,入口 0x200) */
REG_W(REG_LEVEL) = 0u; /* 清关卡软中断 */
g_in_level = 0u;
draw_start_screen();
/* 背景循环:只服务鼠标事件;点"开始"后会被 IRQ1 里的关卡程序"接管",
* 关卡返回时又回到这个循环里继续。 */
for (;;)
{
if (g_evq_head != g_evq_tail)
{
unsigned ev = g_evq[g_evq_head];
g_evq_head = (g_evq_head + 1u) & EVQ_MASK;
handle_event(ev);
}
}
}