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 个雷 应是显示层问题
This commit is contained in:
2026-09-23 20:54:28 +08:00
parent cfcf2f4923
commit 7e13571744
4 changed files with 1236 additions and 1191 deletions
+121 -81
View File
@@ -18,13 +18,24 @@
* ---------------------------------------------------------------------------*/
#include "mmio.h"
#define BW 16u /* 棋盘宽(格) */
#define BH 16u /* 棋盘高(格) */
#define NCELLS (BW * BH) /* 256 */
#define BM_WORDS (NCELLS / 32u) /* 8 */
/* 三档难度完全照微软扫雷:初级 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 */
#define BOARD_C0 12u /* 棋盘左上角在网格里的列 */
#define BOARD_R0 7u /* 棋盘左上角在网格里的行 */
/* 当前难度下的棋盘参数(由 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
@@ -32,9 +43,10 @@
#define ST_LOSE 2u
/* ---- 棋盘状态:3 个位图,每个 8 个字(256 位) ---- */
static unsigned mine_bm[BM_WORDS];
static unsigned open_bm[BM_WORDS];
static unsigned flag_bm[BM_WORDS];
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;
@@ -46,7 +58,7 @@ 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]; /* 揭开用的队列 */
static unsigned bfs_q[NCELLS_MAX]; /* 揭开用的队列(按最大棋盘分配) */
/* ============================ 小工具 ============================ */
@@ -74,7 +86,7 @@ static void draw_cell(unsigned gr, unsigned gc, unsigned glyph, unsigned color)
/* 把棋盘格 (r,c) 画出来 */
static void draw_tile(unsigned r, unsigned c, unsigned glyph, unsigned color)
{
draw_cell(BOARD_R0 + r, BOARD_C0 + c, glyph, color);
draw_cell(g_board_r0 + r, g_board_c0 + c, glyph, color);
}
static unsigned num_color(unsigned n)
@@ -96,20 +108,20 @@ static unsigned num_color(unsigned n)
static unsigned in_board(void)
{
if (cur_r < BOARD_R0)
if (cur_r < g_board_r0)
return 0u;
if (cur_r >= (BOARD_R0 + BH))
if (cur_r >= (g_board_r0 + g_bh))
return 0u;
if (cur_c < BOARD_C0)
if (cur_c < g_board_c0)
return 0u;
if (cur_c >= (BOARD_C0 + BW))
if (cur_c >= (g_board_c0 + g_bw))
return 0u;
return 1u;
}
static unsigned cur_index(void)
{
return ((cur_r - BOARD_R0) << 4) + (cur_c - BOARD_C0); /* *BW(=16) 用移位 */
return ((cur_r - g_board_r0) * g_bw) + (cur_c - g_board_c0);
}
/* LCG 随机数:只用 mul/add/移位 */
@@ -119,13 +131,32 @@ static unsigned rnd_next(void)
return rnd_state >> 16;
}
static unsigned difficulty_mines(unsigned difficulty)
/* 设定难度:棋盘尺寸 + 雷数 + 居中位置,三档完全照微软扫雷。 */
static void set_difficulty(unsigned difficulty)
{
if (difficulty == 0u)
return 20u;
if (difficulty == 2u)
return 60u;
return 40u;
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;
}
/* ============================ 画面 ============================ */
@@ -142,24 +173,24 @@ static void clear_screen(void)
static void draw_frame(void)
{
unsigned gc, gr;
for (gc = (BOARD_C0 - 1u); gc <= (BOARD_C0 + BW); gc = gc + 1u)
for (gc = (g_board_c0 - 1u); gc <= (g_board_c0 + g_bw); gc = gc + 1u)
{
draw_cell(BOARD_R0 - 1u, gc, G_SOLID, C_CYAN);
draw_cell(BOARD_R0 + BH, gc, G_SOLID, C_CYAN);
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 = (BOARD_R0 - 1u); gr <= (BOARD_R0 + BH); gr = gr + 1u)
for (gr = (g_board_r0 - 1u); gr <= (g_board_r0 + g_bh); gr = gr + 1u)
{
draw_cell(gr, BOARD_C0 - 1u, G_SOLID, C_CYAN);
draw_cell(gr, BOARD_C0 + BW, G_SOLID, C_CYAN);
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 < BH; r = r + 1u)
for (r = 0u; r < g_bh; r = r + 1u)
{
for (c = 0u; c < BW; c = c + 1u)
for (c = 0u; c < g_bw; c = c + 1u)
{
draw_tile(r, c, G_COVER, C_GRAY);
}
@@ -180,11 +211,11 @@ static void set_bg(unsigned color)
static unsigned count_adj(unsigned i)
{
unsigned r = i >> 4, c = i & 15u, n = 0u, rr, cc;
unsigned r = IDX_R(i), c = IDX_C(i), n = 0u, rr, cc;
unsigned r0 = (r == 0u) ? 0u : (r - 1u);
unsigned r1 = (r + 1u >= BH) ? (BH - 1u) : (r + 1u);
unsigned r1 = (r + 1u >= g_bh) ? (g_bh - 1u) : (r + 1u);
unsigned c0 = (c == 0u) ? 0u : (c - 1u);
unsigned c1 = (c + 1u >= BW) ? (BW - 1u) : (c + 1u);
unsigned c1 = (c + 1u >= g_bw) ? (g_bw - 1u) : (c + 1u);
for (rr = r0; rr <= r1; rr = rr + 1u)
{
@@ -192,7 +223,7 @@ static unsigned count_adj(unsigned i)
{
if ((rr == r) && (cc == c))
continue;
n = n + bget(mine_bm, (rr << 4) + cc);
n = n + bget(mine_bm, (rr * g_bw) + cc);
}
}
return n;
@@ -201,17 +232,17 @@ static unsigned count_adj(unsigned i)
static void lose(unsigned hit)
{
unsigned k;
for (k = 0u; k < NCELLS; k = k + 1u)
for (k = 0u; k < g_ncells; k = k + 1u)
{
if (bget(mine_bm, k))
{
if (k == hit)
{
draw_tile(k >> 4, k & 15u, G_BOOM, C_RED);
draw_tile(IDX_R(k), IDX_C(k), G_BOOM, C_RED);
}
else if (!bget(flag_bm, k))
{
draw_tile(k >> 4, k & 15u, G_MINE, C_RED);
draw_tile(IDX_R(k), IDX_C(k), G_MINE, C_RED);
}
}
}
@@ -222,11 +253,11 @@ static void lose(unsigned hit)
static void win(void)
{
unsigned k;
for (k = 0u; k < NCELLS; k = k + 1u)
for (k = 0u; k < g_ncells; k = k + 1u)
{
if (bget(mine_bm, k))
{
draw_tile(k >> 4, k & 15u, G_FLAG, C_YELLOW);
draw_tile(IDX_R(k), IDX_C(k), G_FLAG, C_YELLOW);
}
}
REG_W(LED_ADDR) = 0xF00Du;
@@ -236,7 +267,7 @@ static void win(void)
static void reveal_at(unsigned i)
{
unsigned qh = 0u, qt = 0u, cur, n, r, c, rr, cc, ni;
unsigned r0, r1, c0, c1;
unsigned r0, r1, c0, c1, k;
if (bget(open_bm, i) || bget(flag_bm, i))
return;
@@ -246,8 +277,12 @@ static void reveal_at(unsigned 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)
{
@@ -260,27 +295,30 @@ static void reveal_at(unsigned i)
opened_cnt = opened_cnt + 1u;
n = count_adj(cur);
draw_tile(cur >> 4, cur & 15u, n, num_color(n));
draw_tile(IDX_R(cur), IDX_C(cur), n, num_color(n));
if (n != 0u)
continue;
/* 空白格:把周围 8 格入队(队列有界,满了就丢,不会越界) */
r = cur >> 4;
c = cur & 15u;
/* 空白格:把周围 8 格入队(已入过队的跳过,队列不会溢出) */
r = IDX_R(cur);
c = IDX_C(cur);
r0 = (r == 0u) ? 0u : (r - 1u);
r1 = (r + 1u >= BH) ? (BH - 1u) : (r + 1u);
r1 = (r + 1u >= g_bh) ? (g_bh - 1u) : (r + 1u);
c0 = (c == 0u) ? 0u : (c - 1u);
c1 = (c + 1u >= BW) ? (BW - 1u) : (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 << 4) + cc;
if (qt < NCELLS)
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;
}
@@ -288,21 +326,21 @@ static void reveal_at(unsigned i)
}
}
if (opened_cnt == (NCELLS - g_mines))
if (opened_cnt == (g_ncells - g_mines))
win();
}
static void place_mines(unsigned safe)
{
unsigned placed = 0u, i, r, c;
unsigned sr = safe >> 4;
unsigned sc = safe & 15u;
unsigned sr = IDX_R(safe);
unsigned sc = IDX_C(safe);
while (placed < g_mines)
{
i = rnd_next() & 255u; /* NCELLS = 256,正好 2 的幂,不需要取模 */
r = i >> 4;
c = i & 15u;
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)))
@@ -342,8 +380,8 @@ static void toggle_flag(void)
if (bget(open_bm, i))
return;
r = cur_r - BOARD_R0;
c = cur_c - BOARD_C0;
r = cur_r - g_board_r0;
c = cur_c - g_board_c0;
if (bget(flag_bm, i))
{
@@ -368,14 +406,14 @@ static void move_cursor(int dr, int dc)
int nr = (int)cur_r + dr;
int nc = (int)cur_c + dc;
if (nr < (int)(BOARD_R0 - 1u))
nr = (int)(BOARD_R0 - 1u);
if (nr > (int)(BOARD_R0 + BH))
nr = (int)(BOARD_R0 + BH);
if (nc < (int)(BOARD_C0 - 1u))
nc = (int)(BOARD_C0 - 1u);
if (nc > (int)(BOARD_C0 + BW))
nc = (int)(BOARD_C0 + BW);
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;
@@ -437,19 +475,19 @@ static void handle_event(unsigned ev)
{
if (cur_r == DIFF_EASY_R)
{
g_difficulty = 0u;
set_difficulty(0u);
draw_start_screen();
return;
}
if (cur_r == DIFF_NORMAL_R)
{
g_difficulty = 1u;
set_difficulty(1u);
draw_start_screen();
return;
}
if (cur_r == DIFF_HARD_R)
{
g_difficulty = 2u;
set_difficulty(2u);
draw_start_screen();
return;
}
@@ -457,11 +495,10 @@ static void handle_event(unsigned ev)
if ((cur_r == START_R) && (cur_c >= START_C0) && (cur_c <= START_C1))
{
g_in_level = 1u; /* 关卡程序返回时自己清 0 */
g_mines = difficulty_mines(g_difficulty);
REG_W(REG_LEVEL) = 1u;
}
}
else if ((cur_r == (BOARD_R0 - 1u)) && (cur_c == (BOARD_C0 - 1u)))
else if ((cur_r == (g_board_r0 - 1u)) && (cur_c == (g_board_c0 - 1u)))
{
g_exit = 1u; /* 关卡:点左上角黄格 = 返回上一层 */
}
@@ -568,11 +605,14 @@ static void draw_start_screen(void)
draw_cell(START_R, i, G_SOLID, C_GREEN);
draw_cell(START_R + 1u, i, G_BAR, C_YELLOW);
}
draw_cell(START_R, 17u, G_S, C_BLACK);
draw_cell(START_R, 18u, G_T, C_BLACK);
draw_cell(START_R, 19u, G_A, C_BLACK);
draw_cell(START_R, 20u, G_R, C_BLACK);
draw_cell(START_R, 21u, G_T, C_BLACK);
/* 按钮上的字母不能用 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)
@@ -598,19 +638,19 @@ static void draw_start_screen(void)
* -> 点左上角黄格 = 返回:清 LEVEL、return -> isr1_entry 恢复现场并 uret
* -> 回到背景程序被打断的那条指令,继续等下一次"开始"。
* ========================================================================== */
#define RET_R (BOARD_R0 - 1u)
#define RET_C (BOARD_C0 - 1u)
/* 关卡里“返回上一层”的黄格 = 棋盘左上角外面一格(随难度尺寸移动) */
void level_main(void)
{
unsigned r;
/* ---- 关卡初始化 ---- */
for (r = 0u; r < BM_WORDS; r = r + 1u)
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;
@@ -619,13 +659,13 @@ void level_main(void)
g_exit = 0u;
acc_x = 0;
acc_y = 0;
cur_r = BOARD_R0 + 8u;
cur_c = BOARD_C0 + 8u;
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(RET_R, RET_C, G_BAR, C_YELLOW); /* 左上角黄格 = 返回 */
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;
@@ -680,7 +720,7 @@ int main(void)
crt_zero_bss(); /* 清 .bss(上电 RAM 本来就是 0,保险起见) */
rnd_state = REG_R(REG_TIME) | 1u; /* 用自由计数器当随机种子 */
g_difficulty = 1u; /* 默认普通:40 颗雷 */
set_difficulty(1u); /* 默认中级:16x16,40 颗雷 */
REG_W(REG_MCTRL) = 1u; /* 打开鼠标中断(IRQ2,入口 0x200) */
REG_W(REG_LEVEL) = 0u; /* 清关卡软中断 */