csharp/app/g2048.cs · 35903 字节 · 下载原文
// 2048 —— 用 **C#** 写的手机游戏(不是 C)
// 2048 — a phone game written in **C#** (not C)
//
// 玩法:4×4 棋盘,**手指滑动**(上/下/左/右)让所有方块朝那个方向压紧并合并,每次有效
// Gameplay: a 4×4 board. **Swipe** (up/down/left/right) to pack and merge every tile that way;
// 移动后随机空位冒出 2(90%)或 4(10%)。两块同值相撞合成一块、分值相加并计入得分。
// after each effective move a random empty cell spawns a 2 (90%) or a 4 (10%). Two equal tiles
// 出现 2048 即获胜(本局结束),棋盘塞满且相邻无可合并者即失败。
// that collide merge into one, and their value is added to the score. Reaching 2048 wins the
// 键盘兜底:方向键同义,R / 回车重开,ESC 退出。底部「重开」按钮点一下也重开。
// round; a full board with no adjacent equal pair loses. Keyboard fallback: the arrow keys do
// the same thing, R / Enter restart, ESC quits. The on-screen Restart button does the same.
//
// ## 为什么写得这么"平"
// ## Why it is written so "flat"
//
// C# 前端只支持**一个子集**(实测,见下),所以这里只用了被验证过的写法:
// The C# front end only supports **a subset** (measured; see below), so this only uses verified constructs:
// · 一个类 + `static` 字段 / `static` 方法 / `const int`(**不需要实例**)
// · one class with `static` fields / `static` methods / `const int` (**no instances needed**)
// · `static int[]` 定长数组 + 局部定长数组(读、写、当实参传都对)
// · `static int[]` fixed arrays plus local fixed arrays (read, write and pass-as-argument all work)
// · `while` / `do-while` / `if-else` / `switch-case` / `?:` / `&&` / `||`
// · 不带参的 `for(;;)`、~~`foreach`~~、`List<T>`、属性、继承、struct、`$"…"`、~~字符串 `+`~~、
// · `for(;;)` with no condition, ~~`foreach`~~, `List<T>`, properties, inheritance, structs, `$"…"`,
// `?.` / `??`、LINQ、委托、`out`/`ref`、`try/finally`、运算符重载 —— **这条前端都不支持**
// ~~string `+`~~, `?.` / `??`, LINQ, delegates, `out`/`ref`, `try/finally` and operator overloading —
// (有的是编译报错,有的是**编得过、跑起来是错的**),本文件一个都不碰。
// **none of which this front end supports** (some fail to compile, some compile and then
// misbehave). This file touches none of them.
// ⚠ 2026-10-04 复验更正:`foreach` 与字符串 `+` **现在能用了**(实测 `foreach` 求和 = 6;`string c = a + b;` ⇒ `"ABCD"`),
// ⚠ Corrected 2026-10-04: `foreach` and string `+` **now work** (measured `foreach` sum = 6; `string c = a + b;` ⇒ `"ABCD"`),
// 其余各项仍不支持(`for(;;)`/`List<T>`/属性/继承/struct/`$"…"`/`?.`/LINQ/委托/`out`·`ref`/`try/finally`/运算符重载 —— 逐条实测仍编译不过或跑错)。
// the rest are still unsupported (`for(;;)` / `List<T>` / properties / inheritance / struct / `$"…"` / `?.` / LINQ / delegates / `out`·`ref` / `try/finally` / operator overloading — each re-measured, still failing to compile or misbehaving).
//
// ## 两条绕开子集缺口的写法(不是风格,是必需)
// ## Two workarounds forced by the subset (not style — necessity)
//
// ① ~~**字符串拼不起来**(`a + b` 编得过,但结果是空的/垃圾内存)⇒ 数字**不能拼成字符串**,~~
// ① ~~**Strings cannot be concatenated** (`a + b` compiles but yields an empty string or garbage) ⇒~~
// ~~只能:数字**逐位画**(`drawNum`),或者查一张**字面量表**(`numText`)。~~
// ~~numbers cannot be turned into strings; they are either **drawn digit by digit** (`drawNum`)~~
// ~~⚠ 这条也是全文件没有 `numToStr` 这种函数的原因 —— 那种函数在这里必然是坏的。~~
// ~~or looked up in a **table of literals** (`numText`). ⚠ That is also why this file has no~~
// ~~`numToStr`-style helper: such a function is necessarily broken here.~~
// ✅ **已修好(2026-10-04 复验)** —— 字符串拼接正常:`string c = a + b;`(`"AB"`,`"CD"`)实测得 `"ABCD"`;
// ✅ **Fixed (re-measured 2026-10-04)** — string concatenation works: `string c = a + b;` (`"AB"`,`"CD"`) measured `"ABCD"`;
// `string s = "" + n;`(n=12345)得 `"12345"`,所以 `numToStr` 这种函数**现在是能写的**(`static string NumToStr(int n){ string s = "" + n; return s; }` 实测得 `"12345"`)。
// `string s = "" + n;` (n=12345) gives `"12345"`, so a `numToStr`-style helper **can be written now** (`static string NumToStr(int n){ string s = "" + n; return s; }` measured `"12345"`).
// ⚠ 唯一残留:**同名重载方法**收到字符串实参时仍会被解成 `int` 重载(`Show(string)`+`Show(int)` 下 `Show(a+b)`/`Show(c)` 打印地址)—— 与拼接本身无关。
// ⚠ One residual: an **overloaded** method still resolves a string argument to the `int` overload (`Show(a+b)`/`Show(c)` print addresses under `Show(string)`+`Show(int)`) — unrelated to concatenation itself.
// ② **数字的字宽得自己算**(宿主没有"量文字"的接口)⇒ `dw = 字号 × 14/25` 是**实测**出来的
// ② **Digit advance must be computed by hand** (the host has no text-measuring call) ⇒
// (用 `ui_2d_get_pixel` 扫 size=24 的 "8888":墨迹 20..72,3 个推进 = 53−13 ⇒ 13.33/24 = 0.556)。
// `dw = size × 14/25` was **measured** by scanning "8888" at size 24 with `ui_2d_get_pixel`
// (ink spans 20..72; 3 advances = 53−13 ⇒ 13.33/24 = 0.556).
//
// ## 操作
// ## Controls
//
// 滑动棋盘(或方向键)移动;「重开」按钮 / 回车 / R 重开;ESC 或返回箭头退出。
// Swipe the board (or use the arrow keys) to move; the Restart button / Enter / R restart;
// ESC or the back arrow quits.
//
// 数值的唯一真源仍是 `Lib/c/vml_ui.h`。
// The single source of truth for these numbers is still `Lib/c/vml_ui.h`.
// 显式引用 UI 库:`ui_*` 的声明在 `Lib/csharp/VmlUi.cs`(tools/GenLib 生成,别手改)。
// Explicit reference to the UI library: `ui_*` is declared in `Lib/csharp/VmlUi.cs` (generated by tools/GenLib; don't hand-edit).
using Vml.Ui;
class G2048
{
// ── 消息类型(源:Lib/c/vml_ui.h)──
// ── Message types (source: Lib/c/vml_ui.h) ──
const int MSG_NONE = 0;
const int MSG_KEYDOWN = 1;
const int MSG_TOUCHDOWN = 6;
const int MSG_TOUCHMOVE = 7;
const int MSG_TOUCHUP = 8;
const int MSG_TIMER = 9;
const int MSG_WINDOWCLOSE = 10;
const int MSG_WINDOWRESIZE = 11;
// ── 键码 ──
// ── Key codes ──
const int KEY_ENTER = 13;
const int KEY_ESCAPE = 27;
const int KEY_SPACE = 32;
const int KEY_LEFT = 37;
const int KEY_UP = 38;
const int KEY_RIGHT = 39;
const int KEY_DOWN = 40;
const int KEY_PAD_A = 65;
const int KEY_PAD_B = 66;
const int KEY_R = 82;
// ── 文字样式 / 锚点 / 竖对齐 ──
// ── Text style / anchors / vertical alignment ──
const int ANCHOR_LEFT = 0;
const int ANCHOR_CENTER = 1;
const int ANCHOR_RIGHT = 2;
const int FONT_BOLD = 1;
const int VANCHOR_MIDDLE = 1;
// ── 界面语言 / 对话框 ──
// ── UI language / dialog ──
const int LANG_ZH = 0;
const int DLG_INFO = 0;
// ── 窗口声明(开窗之前生效)──
// ── Window declarations (take effect before the window opens) ──
const int WIN_PORTRAIT = 0;
const int WIN_NO_GAMEPAD = 0;
// ── 规则常量 ──
// ── Rule constants ──
const int N = 4; // 4×4
const int CELLS = 16;
const int DIR_L = 0;
const int DIR_R = 1;
const int DIR_U = 2;
const int DIR_D = 3;
const int TARGET = 2048;
const int SWIPE_MIN = 24; // 滑动多少像素才算一次滑动
// How many pixels count as one swipe
const int GAP_MIN = 2;
// ── 颜色(0xAARRGGBB 的十进制负数形式,与仓库里其它语言例程一致)──
// ── Colors (the decimal-negative form of 0xAARRGGBB, consistent with the other examples) ──
const int BG = -15724520; // 0xFF101018 底色
// 0xFF101018 background
const int HUD_C = -6643536; // 0xFF9AA0B0 次要文字
// 0xFF9AA0B0 secondary text
const int WHITE = -1; // 0xFFFFFFFF
const int PANEL = -15066588; // 0xFF1A1A24 空格子
// 0xFF1A1A24 empty cell
const int ACCENT = -11409298; // 0xFF51E86E
const int DARKTXT = -15724520; // 0xFF101018 亮块上的字
// 0xFF101018 text drawn on a bright tile
const int BOARD_C = -15263713; // 0xFF17181F 棋盘底板
// 0xFF17181F board plate
// 各方块的颜色(2/4/8… 越往后越亮)
// Per-tile colors (2/4/8… brighter as the value grows)
const int C2 = -13618602; // 0xFF30323C
const int C4 = -12632212; // 0xFF3F424E
const int C8 = -11890471; // 0xFF4A90D9
const int C16 = -12623695; // 0xFF3F7CB1
const int C32 = -2069424; // 0xFFE06C50
const int C64 = -2523025; // 0xFFD9534F
const int C128 = -2509750; // 0xFFD9B44A
const int C256 = -3690693; // 0xFFC79B3B
const int C512 = -11483016; // 0xFF50C878
const int C1024 = -12933028; // 0xFF3AA85C
const int C2048 = -11409298; // 0xFF51E86E
const int CBIG = -7623434; // 0xFF8B5CF6
// ── 状态 ──
// ── State ──
static int[] bd = new int[CELLS]; // 棋盘(行优先:r*4+c)
// the board (row-major: r*4+c)
static int[] ln = new int[N]; // 单行/单列的暂存(压紧与合并都在这上面做)
// scratch row/column (both compaction and merging happen on it)
static int score;
static int best;
static int lang;
static int over; // 1 = 这一局已经结束(等重开)
// 1 = this round is over (waiting for a restart)
static int won; // 1 = 这一局已经达成 2048
// 1 = this round already reached 2048
static int moves;
static int flash; // 合并后的高亮还剩几拍
// how many ticks the post-merge highlight still lasts
static int flashIdx;
static int lastGain; // 上一步 slide 的合并得分
// merge score of the last slide
// ── 版面(开窗时算一次;画与命中判定共用同一套数)──
// ── Layout (computed once at open; drawing and hit-testing share the same numbers) ──
static int sw;
static int sh;
static int pad;
static int cell;
static int board;
static int bx;
static int by;
static int hudH;
static int btnX;
static int btnY;
static int btnW;
static int btnH;
// ── 触摸 ──
// ── Touch ──
static int sx;
static int sy;
static int ex;
static int ey;
static int down;
// ── 每帧图元数:宿主侧的 `ui_res_get_count(0)` 报"场景里有几个图元",
// ── Primitives per frame: the host's `ui_res_get_count(0)` reports how many primitives the
// 每帧画完查一次、记最大值,**只在新高时打印**(被超时杀掉的运行也能留下这个数)。
// scene holds. It is queried once per frame and the max is printed **only when it grows**,
// so even a run killed by the timeout leaves the number in the log.
static int primMax;
// ── 界面语言(开局问一次宿主,之后整局按它分支)──
// ── UI language (asked once at startup, then branched on for the whole round) ──
static string T(string zh, string en)
{
if (lang == LANG_ZH) return zh;
return en;
}
// ════════════════════════════════════════════════════════════════════
// 版面
// Layout
// ════════════════════════════════════════════════════════════════════
static void layout()
{
pad = sw / 24;
if (pad < 8) pad = 8;
hudH = 104;
btnH = 44;
// 棋盘是正方形:宽高两个方向都要够,取小的那个(只缩一个维度会把棋盘切掉)
// The board is a square, so both axes must fit — take the smaller (scaling one axis
// only would clip the board).
int maxw = sw - pad * 2;
btnH = 44;
btnY = sh - btnH - 18;
int maxh = btnY - 34 - hudH;
board = maxw;
if (maxh < board) board = maxh;
cell = board / N;
board = cell * N;
bx = (sw - board) / 2;
// 竖直方向在「HUD 下沿」到「按钮上沿」之间居中(只按宽度摆会留下一大块空白)
// Centre vertically between the HUD's bottom edge and the button's top edge (laying out by
// width alone leaves a large empty band)
by = hudH + (maxh - board) / 2;
btnW = sw / 3;
if (btnW < 120) btnW = 120;
if (btnW > sw - pad * 2) btnW = sw - pad * 2;
btnX = (sw - btnW) / 2;
}
// ════════════════════════════════════════════════════════════════════
// 数字文本
// Number text
//
// ⚠ 字符串拼不起来(见文件头)⇒ 方块上的数字走**字面量表**(正好都是 2 的幂),
// ⚠ Strings cannot be concatenated (see the header) ⇒ tile numbers use a **table of literals**
// 得分那种任意数走**逐位画**。
// (they are exactly the powers of two); arbitrary numbers such as the score are **drawn digit
// by digit**.
// ════════════════════════════════════════════════════════════════════
static string numText(int v)
{
if (v == 2) return "2";
if (v == 4) return "4";
if (v == 8) return "8";
if (v == 16) return "16";
if (v == 32) return "32";
if (v == 64) return "64";
if (v == 128) return "128";
if (v == 256) return "256";
if (v == 512) return "512";
if (v == 1024) return "1024";
if (v == 2048) return "2048";
if (v == 4096) return "4096";
if (v == 8192) return "8192";
if (v == 16384) return "16384";
if (v == 32768) return "32768";
if (v == 65536) return "65536";
return "?";
}
static string digitText(int d)
{
if (d == 0) return "0";
if (d == 1) return "1";
if (d == 2) return "2";
if (d == 3) return "3";
if (d == 4) return "4";
if (d == 5) return "5";
if (d == 6) return "6";
if (d == 7) return "7";
if (d == 8) return "8";
return "9";
}
static int numDigits(int v)
{
if (v < 10) return 1;
if (v < 100) return 2;
if (v < 1000) return 3;
if (v < 10000) return 4;
if (v < 100000) return 5;
if (v < 1000000) return 6;
if (v < 10000000) return 7;
return 8;
}
static int pow10(int n)
{
if (n <= 0) return 1;
if (n == 1) return 10;
if (n == 2) return 100;
if (n == 3) return 1000;
if (n == 4) return 10000;
if (n == 5) return 100000;
if (n == 6) return 1000000;
if (n == 7) return 10000000;
return 100000000;
}
// 逐位画一个数。anchor:0=左 1=中 2=右(按**整串**的宽度算,不是按单字)
// Draw a number digit by digit. anchor: 0=left 1=centre 2=right (based on the **whole string's**
// width, not a single digit's).
static void drawNum(int x, int y, int v, int color, int size, int anchor)
{
int n = numDigits(v);
int dw = size * 14 / 25; // 实测字宽(见文件头)
// measured glyph width (see the file header)
int w = n * dw;
int startX = x;
if (anchor == ANCHOR_CENTER) startX = x - w / 2;
if (anchor == ANCHOR_RIGHT) startX = x - w;
int i = 0;
while (i < n)
{
int d = (v / pow10(n - 1 - i)) % 10;
ui_2d_text(startX + i * dw, y, digitText(d), color, size, ANCHOR_LEFT);
i = i + 1;
}
}
static int tileColor(int v)
{
if (v == 2) return C2;
if (v == 4) return C4;
if (v == 8) return C8;
if (v == 16) return C16;
if (v == 32) return C32;
if (v == 64) return C64;
if (v == 128) return C128;
if (v == 256) return C256;
if (v == 512) return C512;
if (v == 1024) return C1024;
if (v == 2048) return C2048;
return CBIG;
}
static int tileSize(int v)
{
int n = numDigits(v);
if (n <= 2) return cell * 32 / 100;
if (n == 3) return cell * 25 / 100;
if (n == 4) return cell * 19 / 100;
return cell * 15 / 100;
}
// ════════════════════════════════════════════════════════════════════
// 绘制
// Drawing
// ════════════════════════════════════════════════════════════════════
static void draw()
{
int i = 0;
ui_2d_clear(BG);
// ── 顶部:标题 / 分数 / 最高分 ──
// ── Top: title / score / best ──
ui_2d_text(sw / 2, 26, T("2048 · 都来码", "2048 · Dolaima"), WHITE, 18, ANCHOR_CENTER);
ui_2d_text(pad, 62, T("得分", "Score"), HUD_C, 12, ANCHOR_LEFT);
drawNum(pad, 84, score, ACCENT, 20, ANCHOR_LEFT);
ui_2d_text(sw / 2, 62, T("最高", "Best"), HUD_C, 12, ANCHOR_CENTER);
drawNum(sw / 2, 84, best, WHITE, 20, ANCHOR_LEFT);
ui_2d_text(sw - pad, 62, T("步数", "Moves"), HUD_C, 12, ANCHOR_RIGHT);
drawNum(sw - pad, 84, moves, WHITE, 20, ANCHOR_RIGHT);
// ── 棋盘底板 ──
// ── Board backing plate ──
ui_2d_rect(bx - 4, by - 4, board + 8, board + 8, BOARD_C, 1, 0, 10);
int r = 0;
while (r < N)
{
int c = 0;
while (c < N)
{
int idx = r * N + c;
int v = bd[idx];
int gap = GAP_MIN;
int tx = bx + c * cell + gap;
int ty = by + r * cell + gap;
int tw = cell - gap * 2;
int color = PANEL;
if (v != 0) color = tileColor(v);
// 刚合并出来的那一块闪一下(白色描边)
// The tile that just merged flashes (a white outline)
ui_2d_rect(tx, ty, tw, tw, color, 1, 0, 6);
if (flash > 0 && idx == flashIdx) ui_2d_rect(tx, ty, tw, tw, WHITE, 0, 3, 6);
if (v != 0)
{
int txtc = WHITE;
if (v >= 8 && v <= 16) txtc = WHITE;
ui_2d_text_v(tx + tw / 2, ty + tw / 2, numText(v), txtc, tileSize(v),
ANCHOR_CENTER, VANCHOR_MIDDLE, FONT_BOLD);
}
c = c + 1;
}
r = r + 1;
}
// ── 底部:提示 / 重开按钮 / 终局横幅 ──
// ── Bottom: hint / Restart button / end-of-round banner ──
ui_2d_text(sw / 2, btnY - 14, T("滑动屏幕(或方向键)移动", "Swipe the board (or use the arrow keys)"),
HUD_C, 12, ANCHOR_CENTER);
ui_2d_rect(btnX, btnY, btnW, btnH, ACCENT, 1, 0, 8);
ui_2d_text_v(btnX + btnW / 2, btnY + btnH / 2, T("重开", "Restart"), DARKTXT, 15,
ANCHOR_CENTER, VANCHOR_MIDDLE, FONT_BOLD);
if (over != 0)
{
int bwd = sw - pad * 2 - 20;
ui_2d_rect(pad + 10, by + board / 2 - 40, bwd, 80, -16777216, 1, 0, 12);
ui_2d_text_v(sw / 2, by + board / 2 - 12, T("无处可动,这一局结束", "No moves left — round over"),
WHITE, 15, ANCHOR_CENTER, VANCHOR_MIDDLE, FONT_BOLD);
ui_2d_text_v(sw / 2, by + board / 2 + 16, T("点屏幕或按回车重开", "Tap or press Enter to restart"),
ACCENT, 13, ANCHOR_CENTER, VANCHOR_MIDDLE, 0);
}
if (won != 0 && over == 0)
{
ui_2d_text(sw / 2, by - 18, T("达成 2048!", "You reached 2048!"), ACCENT, 14, ANCHOR_CENTER);
}
// 每帧图元数(宿主侧真值)——**只在新高时打印**,这样被超时杀掉的运行也留得下数
// Primitives this frame (the host's own count) — printed **only when it grows**, so a run
// killed by the timeout still leaves the number behind.
int np = ui_res_get_count(0);
if (np > primMax)
{
primMax = np;
print_str("PRIMAX ");
println_int(primMax);
}
ui_2d_present();
}
// ════════════════════════════════════════════════════════════════════
// 棋盘逻辑
// Board logic
// ════════════════════════════════════════════════════════════════════
// 某方向第 line 条第 k 格在棋盘数组里的下标
// Index into the board array of the k-th cell of line #line along the given direction
static int cellIdx(int dir, int line, int k)
{
if (dir == DIR_L) return line * N + k;
if (dir == DIR_R) return line * N + (N - 1 - k);
if (dir == DIR_U) return k * N + line;
return (N - 1 - k) * N + line;
}
static int emptyCount()
{
int n = 0;
int i = 0;
while (i < CELLS)
{
if (bd[i] == 0) n = n + 1;
i = i + 1;
}
return n;
}
static int randEmpty(int nth)
{
int seen = 0;
int i = 0;
while (i < CELLS)
{
if (bd[i] == 0)
{
if (seen == nth) return i;
seen = seen + 1;
}
i = i + 1;
}
return -1;
}
static void spawn()
{
int e = emptyCount();
if (e == 0) return;
int idx = randEmpty(ui_math_rand(e));
if (idx < 0) return;
int v = 2;
if (ui_math_rand(10) == 0) v = 4; // 10% 出 4
// 10% of the time a 4 appears
bd[idx] = v;
print_str("SPAWN ");
print_int(idx);
print_str(" ");
println_int(v);
}
static void dumpState()
{
int i = 0;
print_str("STATE");
while (i < CELLS)
{
print_str(" ");
print_int(bd[i]);
i = i + 1;
}
print_str(" SCORE ");
println_int(score);
}
// 朝 dir 压紧并合并。返回 1 = 棋盘变了(所以要出新的块)
// Pack and merge towards `dir`. Returns 1 if the board changed (so a new tile must spawn).
static int slide(int dir)
{
int moved = 0;
int gain = 0;
int line = 0;
while (line < N)
{
int k = 0;
while (k < N)
{
ln[k] = bd[cellIdx(dir, line, k)];
k = k + 1;
}
// ① 压紧:非零的往前面挤(读 ln[k] 写 ln[w],w ≤ k,不会读脏)
// ① Pack: push the non-zeros forward (read ln[k], write ln[w]; w ≤ k, so no stale reads)
int w = 0;
k = 0;
while (k < N)
{
if (ln[k] != 0) { ln[w] = ln[k]; w = w + 1; }
k = k + 1;
}
while (w < N) { ln[w] = 0; w = w + 1; }
// ② 合并:同值相邻合成一块;**合并过的那一格不再参与**(k = k + 2)
// ② Merge: equal neighbours combine; a merged cell does **not** take part again (k += 2)
k = 0;
while (k < N - 1)
{
if (ln[k] != 0 && ln[k] == ln[k + 1])
{
ln[k] = ln[k] * 2;
ln[k + 1] = 0;
gain = gain + ln[k];
if (ln[k] == TARGET) won = 1;
k = k + 2;
}
else
{
k = k + 1;
}
}
// ③ 再压紧一次(合并产生的空格)
// ③ Pack once more (the merges left holes)
w = 0;
k = 0;
while (k < N)
{
if (ln[k] != 0) { ln[w] = ln[k]; w = w + 1; }
k = k + 1;
}
while (w < N) { ln[w] = 0; w = w + 1; }
// ④ 写回,并和原来的比一比(有没有变)
// ④ Write back and compare with what was there (did anything change?)
k = 0;
while (k < N)
{
int idx = cellIdx(dir, line, k);
if (bd[idx] != ln[k]) moved = 1;
bd[idx] = ln[k];
k = k + 1;
}
line = line + 1;
}
lastGain = gain;
if (moved == 0) return 0;
moves = moves + 1;
if (gain > 0)
{
score = score + gain;
if (score > best) best = score;
flash = 3;
// 闪哪一块:合并后棋盘上**最大的那一块**(合并出来的必然是局部最大值)
// Which tile flashes: the **largest one** on the board after merging (a merge always
// produces a local maximum).
int mt = 0;
int mi = 0;
int j = 0;
while (j < CELLS)
{
if (bd[j] > mt) { mt = bd[j]; mi = j; }
j = j + 1;
}
flashIdx = mi;
}
print_str("MOVE ");
print_int(dir);
print_str(" GAIN ");
println_int(gain);
return 1;
}
static int canMove()
{
if (emptyCount() > 0) return 1;
int r = 0;
while (r < N)
{
int c = 0;
while (c < N)
{
int v = bd[r * N + c];
if (c + 1 < N && bd[r * N + c + 1] == v) return 1;
if (r + 1 < N && bd[(r + 1) * N + c] == v) return 1;
c = c + 1;
}
r = r + 1;
}
return 0;
}
static void reset()
{
int i = 0;
while (i < CELLS) { bd[i] = 0; i = i + 1; }
score = 0;
moves = 0;
over = 0;
won = 0;
flash = 0;
flashIdx = -1;
spawn();
spawn();
println_str("INIT");
dumpState();
draw();
}
// 走一步(dir),返回 1 = 要来一次重画
// One move in `dir`; returns 1 if a redraw is needed
static int doMove(int dir)
{
if (over != 0) return 0;
int m = slide(dir);
if (m == 0)
{
ui_audio_beep(196, 30); // 这一步没动:低音提示
// the move changed nothing: low-tone cue
print_str("NOMOVE ");
println_int(dir);
return 1;
}
int gain = lastGain;
if (gain == 0) { flash = 0; flashIdx = -1; } // 没合并 ⇒ 不闪(合并时 slide 已经设好了)
// no merge => no flash (slide already set it up when merging)
spawn();
dumpState();
if (won != 0)
{
ui_audio_beep(1318, 220);
showWin();
return 1;
}
if (gain > 0) ui_audio_beep(1047, 90); // 合并了:高音
// merged: high tone
else ui_audio_beep(660, 60); // 只是挪了:中音
// just moved: mid tone
if (canMove() == 0)
{
over = 1;
ui_audio_beep(131, 320);
gameOver();
}
return 1;
}
static void showWin()
{
draw();
int r = ui_dlg_msg(T("2048", "2048"),
T("达成 2048,你赢了!\n再来一局?", "You reached 2048 — you win!\nPlay again?"),
DLG_INFO);
if (r != 0) { ui_win_close(); }
reset();
}
static void gameOver()
{
draw();
int r = ui_dlg_msg(T("2048", "2048"),
T("无处可动了,这一局结束。\n再来一局?", "No moves left — round over.\nPlay again?"),
DLG_INFO);
if (r != 0) { ui_win_close(); }
reset();
}
// ════════════════════════════════════════════════════════════════════
// 输入
// Input
// ════════════════════════════════════════════════════════════════════
static int inBtn(int x, int y)
{
if (x < btnX || x > btnX + btnW) return 0;
if (y < btnY || y > btnY + btnH) return 0;
return 1;
}
// 一次完整滑动(起终点都在手里)→ 方向;认不出返回 -1
// A complete swipe (both ends known) → a direction; -1 if it is not a swipe
static int swipeDir(int x0, int y0, int x1, int y1)
{
int dx = x1 - x0;
int dy = y1 - y0;
// `Math.Abs` = 前端新增的“标准方法 → 共享库 abs”映射(v0.96.592 修)
// `Math.Abs` = the frontend's new "standard method -> shared-library abs" mapping (fixed in v0.96.592)
int ax = Math.Abs(dx);
int ay = Math.Abs(dy);
if (ax < SWIPE_MIN && ay < SWIPE_MIN) return -1;
if (ax >= ay)
{
if (dx > 0) return DIR_R;
return DIR_L;
}
if (dy > 0) return DIR_D;
return DIR_U;
}
static void Main()
{
// 界面语言:开局问一次宿主(0=中文 1=英文),之后整局按它分支
// UI language: asked once of the host (0=Chinese 1=English), then branched on all round
lang = ui_scr_get_language();
// 开窗:尺寸传 0 ⇒ 交给宿主给真实可用区(开窗**之前**问到的只是估算值)
// Opening: pass size 0 ⇒ the host supplies the real available area (a **pre-open** read is only an estimate)
ui_win_open_ex("2048", 0, 0, WIN_PORTRAIT, WIN_NO_GAMEPAD);
ui_scr_set_keep_on(1);
// 尺寸:开窗**之后**再问一次 —— 这时 `ui_scr_get_w/h` 给的是实测画布(真值)
// Size: re-read **after** the open — `ui_scr_get_w/h` now report the measured canvas (the real value)
sw = ui_scr_get_w();
sh = ui_scr_get_h();
if (sw <= 0) sw = 411;
if (sh <= 0) sh = 726;
layout();
reset();
int tid = ui_timer_set(60, 0);
int running = 1;
while (running != 0 && ui_win_is_closed() == 0)
{
int t = ui_msg_fetch_wait(80);
if (t == MSG_NONE) continue;
if (t == MSG_WINDOWCLOSE) break;
if (t == MSG_WINDOWRESIZE)
{
// 视口变了:用**消息自带的** A/B 重排(别回头问 ui_scr_get_w/h)
// Viewport changed: re-lay out with the **message's own** A/B (don't re-ask ui_scr_get_w/h)
sw = ui_msg_get_a();
sh = ui_msg_get_b();
if (sw <= 0) sw = 411;
if (sh <= 0) sh = 726;
layout();
draw();
continue;
}
if (t == MSG_TIMER)
{
if (flash > 0) { flash = flash - 1; draw(); }
continue;
}
if (t == MSG_TOUCHDOWN)
{
sx = ui_msg_get_a();
sy = ui_msg_get_b();
ex = sx;
ey = sy;
down = 1;
if (over != 0) { reset(); continue; }
if (inBtn(sx, sy) != 0) { ui_audio_beep(880, 60); reset(); continue; }
continue;
}
if (t == MSG_TOUCHMOVE)
{
// 拖动类消息只追最新位置 ⇒ 把积压的那一类丢掉(不然队列只涨不落)
// Drag messages only care about the newest position ⇒ drop the backlog of that
// kind (otherwise the queue only grows)
ui_msg_drop(1);
ex = ui_msg_get_a();
ey = ui_msg_get_b();
continue;
}
if (t == MSG_TOUCHUP)
{
ex = ui_msg_get_a();
ey = ui_msg_get_b();
if (down != 0 && over == 0)
{
int d = swipeDir(sx, sy, ex, ey);
if (d >= 0) doMove(d);
}
down = 0;
continue;
}
if (t == MSG_KEYDOWN)
{
int k = ui_msg_get_a();
if (k == KEY_ESCAPE) break;
if (k == KEY_ENTER || k == KEY_R || k == KEY_PAD_A) { reset(); continue; }
if (k == KEY_LEFT) doMove(DIR_L);
else if (k == KEY_RIGHT) doMove(DIR_R);
else if (k == KEY_UP) doMove(DIR_U);
else if (k == KEY_DOWN) doMove(DIR_D);
continue;
}
}
ui_timer_kill(tid);
ui_scr_set_keep_on(0);
ui_win_close();
print_str("PRIMAX-FINAL ");
println_int(primMax);
println_str("g2048: done");
}
}