-- 贪吃蛇 —— 用 **Lua** 写的手机游戏 -- Snake — a phone game written in **Lua** -- -- 规则:方向键(或触摸)控制蛇头,吃到食物变长、加 1 分并提一档速度。 -- Rules: steer the head with the arrow keys (or by touch); eating food makes the snake longer, -- adding 1 point and one notch of speed. -- 撞墙或咬到自己就结束;蛇长到 40 节算通关。 -- Hitting a wall or biting yourself ends the run; reaching 40 cells wins. -- 那个 40 是「场地大小」与「表的容量」两条约束的交点(见下)。 -- That 40 is where the field size and the **table capacity** constraint meet (see below). -- 操作:方向键 / WASD 转向 · SELECT 暂停 · 回车重开 · 触摸 = 朝那边拐 · ESC 退出。 -- Controls: arrows / WASD to steer · SELECT pauses · Enter restarts · touch = turn that way · ESC exits. -- -- ◆ 这份例程在练什么 -- ◆ What this example exercises -- -- 生命游戏(`life.lua`)把状态摊成两个 480 格的扁平数组、一帧只做「遍历 + 条件求和」。 -- The Life example flattens its state into two 480-cell arrays and does nothing but "walk + conditional sum" per frame. -- 这一份走另一条路:**蛇身是一张会增删的表**,每走一步 `table.insert` / `table.remove` 加一次整条挪位。 -- This one takes another path: the **snake is a table that grows and shrinks**; every step does -- `table.insert` / `table.remove` plus one full shift of the body. -- HUD 的分数与长度走一个**自己画的 3×5 点阵数字渲染器**,不靠字符串。 -- The HUD's score and length go through a **hand-rolled 3×5 dot-matrix number renderer**, not through strings. -- -- ◆ 为什么 HUD 要自己画点阵数字 -- ◆ Why the HUD renders its own dot-matrix digits -- -- `tostring(1234)` + `..` 拼串在本前端**不可靠**:实测 `#tostring(1234)` 得 **0**、 -- `tostring(1234)` plus `..` string building is **unreliable** here: measured, `#tostring(1234)` gives **0**, -- `print(tostring(1234))` 打进 stdout 的是 **5**、`#("score=" .. tostring(9))` 得 **7**(应为 10)。 -- `print(tostring(1234))` prints **5**, and `#("score=" .. tostring(9))` gives **7** (it should be 10). -- 所以数字改走**查表 + 位掩码**:`FONT[d+1]` 是 15 位掩码(3 宽 × 5 高,位序 = 行*3+列)。 -- So numbers go through a **table + bitmask** instead: `FONT[d+1]` is a 15-bit mask (3 wide × 5 tall, bit index = row*3+col). -- 逐格测位、亮格画一个 `ui_2d_rect` —— 零字符串、零浮点,画出来的是确定的矩形(`--trace-draw` 能逐条核)。 -- Each bit is tested and one `ui_2d_rect` is drawn per lit cell — no strings, no floats, and every pixel is -- verifiable line by line with `--trace-draw`. -- -- ◆ 本语言的五条**实测约束**(这份例程按它们写) -- ◆ Five **measured constraints** of this language (this example is written around them) -- -- · **顶层 `local` 在函数里读不到**(读到野地址 ⇒ 内存错误);**函数也看不见外层函数的局部**。 -- · **A top-level `local` is not visible inside a function** (wild address ⇒ memory fault), and **a function -- cannot see the enclosing function's locals either** (no closure capture). -- ⇒ 跨函数共用的状态**一律写成顶层全局**,逻辑也写在顶层函数里。 -- ⇒ Everything shared across functions is a **top-level global**, and the logic lives in top-level functions. -- · **空表 `{}` 只有 8 个键位**:`capacity = 字段数 + max(8, 字段数/2 + 4)`,**写满之后新键被静默丢弃**。 -- · **An empty `{}` table holds only 8 keys**: `capacity = fields + max(8, fields/2 + 4)`, and once it is full -- **new keys are silently dropped**. -- 实测:`A = {}` 连写 90 个下标只有 8 个留下;`B = {0,0}` 留 10 个;`C = {16 个 0}` 留 28 个。 -- Measured: `A = {}` — 8 of 90 indices stick; `B = {0,0}` — 10; `C = {16 zeros}` — 28. -- ✅ 2026-10-04 复验仍成立(重测:`{}` 连写 90 个 ⇒ `#A` = 8、`{0,0}` ⇒ 10、`{16 个 0}` ⇒ 28)。 -- ✅ Still true as of 2026-10-04 (re-measured: `{}` + 90 writes => `#A` = 8; `{0,0}` => 10; `{16 zeros}` => 28). -- ⇒ 蛇身表先按 `{40 个 0}` 建出来、**再逐个弹空**(弹空只动 count、容量还在),之后最多放 64 节。 -- ⇒ The body table is built as `{40 zeros}` and then **popped empty** (popping only moves `count`; the -- capacity stays), which supports up to 64 cells. -- · **别写 `t[#t + 1] = v`**(内联的 `#` 当下标)—— 下标算错(实测写 3 次只剩 1 个元素)。 -- · **Do not write `t[#t + 1] = v`** (inline `#` as a subscript) — the index is miscomputed -- (measured: three writes leave one element). -- 要么先 `local n = #t` 再 `t[n + 1] = v`,要么直接 `table.insert`。 -- Either take `local n = #t` first and then `t[n + 1] = v`, or just use `table.insert`. -- · **`and` / `or` 求的是布尔 0/1**,不是操作数的值(见 `life.lua`)⇒ 选择一律 `if/else`。 -- · **`and` / `or` evaluate to boolean 0/1**, not the operand's value (see `life.lua`) ⇒ always use `if/else`. -- · `pairs` / `tostring` / `math.random` / `math.sin` 这些路没通,别用;随机数走 `ui_math_rand(n)`。 -- · `pairs` / `tostring` / `math.random` / `math.sin` are not wired up — use `ui_math_rand(n)` for randomness. -- ── 显式引用 UI 库的声明文件 ──────────────────────────────────────────────── -- Explicit reference to the UI library's declaration file. -- `Lib/lua/vml_ui.lua` 由 `GenLib -n -c vmlui -l lua` 从 `Lib/shared/src/vmlui.c` 生成(155 个 `ui_*`)。 -- `Lib/lua/vml_ui.lua` is produced by `GenLib -n -c vmlui -l lua` from `Lib/shared/src/vmlui.c` (155 `ui_*`). -- **不要手写、也不要手改生成物**(要改就改生成器 `tools/GenLib`)。 -- Never hand-write it and never edit the generated file (change the generator `tools/GenLib` instead). -- ⚠ 这条 `require` 对 Lua 是**文档级**引用:本前端按名字解析库函数,删掉它照样编得过、跑得对。 -- ⚠ For Lua this `require` is a **documentation-level** reference: the front end resolves library -- functions by name, so removing it still compiles and runs. -- 实测结论与命令见 `docs/UI库导入约定.md` 的「lua 实测结论」一节。 -- Measured results and the exact commands are in the UI-library import convention doc (Lua section). require("vml_ui") -- ── 顶层全局:常量、点阵字体、一局的全部状态 ──────────────────────────────── -- ── Top-level globals: constants, the dot-matrix font, and all the state of one round ── -- 开局每步 220ms;吃到食物减 6ms -- one step every 220 ms at the start; each food shaves 6 ms off that SPEED0 = 220 -- 速度下限(再快就只是"看不清"了) -- the speed floor (anything faster is just a blur) SPEED_MIN = 96 -- 通关长度 -- the winning length: reach this many cells and the round is won WIN_LEN = 40 COLS = 18 ROWS = 22 FONT = {31599, 29850, 29671, 31207, 18925, 31183, 31695, 18727, 31727, 31215} -- 数字 0..9 的 15 位点阵掩码(位序 = 行*3 + 列): -- 15-bit dot-matrix masks for 0..9 (bit index = row*3 + col): -- "0" = 111/101/101/101/111 ⇒ 低 15 位里的 0、1、2、4、6、8、10、12、13、14 位置 1 -- "0" = 111/101/101/101/111 ⇒ bits 0,1,2,4,6,8,10,12,13,14 set BIT = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384} -- 蛇身:**先按 40 个元素建表拿到容量、再逐个弹空**(弹的是 count,容量留在 [p-8])。 -- The body: **built as a 40-element table to grab the capacity, then popped empty** (popping moves `count`; -- the capacity lives in `[p-8]`). -- 只写 `{}` 的话容量只有 8,蛇长到第 9 节起新下标会被 lua_table_set 静默丢掉(2026-10-04 复验仍成立)。 -- With a bare `{}` the capacity is 8 and every index past the eighth is silently dropped by `lua_table_set` (still true as of 2026-10-04). BODY = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -- 0 上 / 1 右 / 2 下 / 3 左 -- current heading: 0 up / 1 right / 2 down / 3 left DIR = 1 FOOD = 0 SCORE = 0 -- 0 进行 / 1 结束 / 2 通关 -- round state: 0 playing / 1 over / 2 won STATE = 0 PAUSED = 0 SPEED = SPEED0 TIMER_SPEED = SPEED0 TID = 0 CELL = 8 OX = 0 OY = 48 W = 360 -- 界面文字:开局按 `ui_scr_get_language()` 选一次(见 main) -- UI strings: chosen once from `ui_scr_get_language()` at startup (see main) T_TITLE = "Snake" T_SCORE = "SCORE" T_LEN = "LEN" T_OVER = "GAME OVER" T_WIN = "YOU WIN!" T_AGAIN = "Enter = restart" T_PAUSED = "PAUSED (SELECT)" -- 画一个 3×5 点阵数字:`d` 是数字、`px` 是一格边长、`color` 是真彩色 -- Draw one 3×5 dot-matrix digit: `d` the digit, `px` the cell side, `color` a truecolor value -- (多参函数 + 函数内调库:坐标在这里算,`ui_2d_rect` 也在这里调) -- (multi-argument function calling into the library: coordinates computed here, `ui_2d_rect` called here too) function draw_digit(x, y, d, px, color) if d < 0 then d = 0 end if d > 9 then d = 9 end local mask = FONT[d + 1] local r = 0 while r < 5 do local c = 0 while c < 3 do -- 位序与生成掩码时一致:idx = 行*3 + 列;掩码表 1 基 ⇒ +1。 -- Same bit order as when the masks were generated: idx = row*3 + col; the table is 1-based ⇒ +1. -- 整数除 + 取余就能测位,不必用 `math.pow`(它对 Lua 走 8 字节 double 那条路) -- Integer division plus modulo tests the bit; no `math.pow` (that goes down the 8-byte double path). if mask / BIT[r * 3 + c + 1] % 2 == 1 then ui_2d_rect(x + c * px, y + r * px, px, px, color, 1, 0, 0) end c = c + 1 end r = r + 1 end end -- 画一个非负整数:先数位数、再从高位往低位画(每位占 4 格宽 = 3 格字形 + 1 格间距) -- Draw a non-negative integer: count the digits, then draw from the most significant down -- (each digit takes four cells = three glyph + one gap) function draw_number(x, y, v, px, color) if v < 0 then v = 0 end if v > 999999 then v = 999999 end local digits = 1 local n = v while n >= 10 do n = n / 10 digits = digits + 1 end local i = digits local rest = v while i >= 1 do local d = rest % 10 rest = rest / 10 draw_digit(x + (i - 1) * px * 4, y, d, px, color) i = i - 1 end end -- 字符串宽度(方法调用形态:`s:len()` 被归一成 `string.len(s)`,实测可用) -- String width (the method-call form: `s:len()` normalises to `string.len(s)` — measured working) -- ⚠ 它数的是**字节**:中文一个字 3 字节(`"贪吃蛇":len()` = 9)—— 拿它排版要按字节算。 -- ⚠ It counts **bytes**: one CJK character is three bytes (a three-character title measures 9) — lay out by bytes. function text_w(s) return s:len() end -- 复位一局:蛇身三格、方向向右、分数归零、速度回开局值 -- Reset a round: a three-cell snake heading right, score zero, speed back to its opening value function reset_round() local i = 0 while i < 64 do table.remove(BODY) i = i + 1 end table.insert(BODY, 11 * COLS + 6) table.insert(BODY, 11 * COLS + 5) table.insert(BODY, 11 * COLS + 4) DIR = 1 SCORE = 0 STATE = 0 PAUSED = 0 SPEED = SPEED0 FOOD = 11 * COLS + 12 end -- 找一个空格放食物:先随机试 400 次,试不出来(场地快满了)就线性扫。返回 -1 = 一个空格都没有 = 通关 -- Pick an empty cell for food: 400 random tries first, then a linear scan when the field is nearly full -- Returns -1 meaning "no empty cell left" = the player won function free_cell() local total = COLS * ROWS local tries = 0 while tries < 400 do local idx = ui_math_rand(total) local taken = 0 local k = 1 while k <= #BODY do if BODY[k] == idx then taken = 1 end k = k + 1 end if taken == 0 then return idx end tries = tries + 1 end local j = 0 while j < total do local taken = 0 local k = 1 while k <= #BODY do if BODY[k] == j then taken = 1 end k = k + 1 end if taken == 0 then return j end j = j + 1 end return -1 end -- 记下「下一步往哪走」,不允许 180° 掉头 -- Record the next heading; a 180-degree reversal is refused function turn(nd) if nd ~= (DIR + 2) % 4 then DIR = nd end end -- 走一步:算新头 → 判撞墙 / 判自咬 → 吃或不吃 -- Take one step: compute the new head → test walls / self → eat or not -- 返回 1 表示这一步改了画面(喂给主循环的「要不要重画」) -- Returns 1 when the step changed the picture (fed to the main loop's "should we redraw") function step() local r = BODY[1] / COLS local c = BODY[1] % COLS if DIR == 0 then r = r - 1 end if DIR == 1 then c = c + 1 end if DIR == 2 then r = r + 1 end if DIR == 3 then c = c - 1 end if r < 0 or r >= ROWS or c < 0 or c >= COLS then STATE = 1 -- 撞墙:最低音 + 最长(§4.5「输取最低」,不看屏幕也分得出) -- wall hit: lowest and longest tone (4.5: take the lowest for a loss, audible without looking) ui_audio_beep(131, 320) return 1 end local nh = r * COLS + c local eating = 0 if nh == FOOD then eating = 1 end -- 自咬判定:不吃食物时尾巴会挪开,所以尾巴那一格不算撞上 -- Self-bite test: when not eating the tail moves away, so the tail cell does not count as a hit local last = #BODY if eating == 0 then last = last - 1 end local k = 1 while k <= last do if BODY[k] == nh then STATE = 1 ui_audio_beep(131, 320) return 1 end k = k + 1 end if eating == 0 then -- 表增删之「删」:不吃就把尾巴弹掉(`table.remove(t)` 弹的是最后一个 = 尾巴) -- Table insert/remove, the "remove" half: `table.remove(t)` pops the last element, which is the tail table.remove(BODY) end -- 整条蛇往后挪一格,给新头腾出 body[1](整条挪位走的是**覆盖已有下标**,不动容量)。 -- Shift the whole snake one slot back to free body[1] (the shift only **overwrites existing indices**, -- so it does not touch the capacity). -- ⚠ 本前端没有「插到表头」:`table.insert(t, 1, v)` 那条 3 实参形态没实现,弹的永远是最后一个。 -- ⚠ No "insert at front" here: the 3-argument `table.insert(t, 1, v)` is not implemented, and popping -- always takes the last element. -- 2026-10-04 复验仍成立(`local T = {1,2,3}; table.insert(T, 1, 99)` ⇒ `#T` = 4、99 落在 T[4])。 -- Still true as of 2026-10-04 (`local T = {1,2,3}; table.insert(T, 1, 99)` => `#T` = 4, with 99 landing at T[4]). local i = #BODY while i >= 1 do BODY[i + 1] = BODY[i] i = i - 1 end BODY[1] = nh if eating ~= 0 then SCORE = SCORE + 1 -- 吃到:清亮的一声(一个事件配一个音,§4.5) -- ate food: one bright tone (one sound per event, 4.5) ui_audio_beep(880, 60) if SPEED > SPEED_MIN then SPEED = SPEED - 6 end if #BODY >= WIN_LEN then STATE = 2 -- 通关:最高音 + 最长 -- won: highest and longest tone ui_audio_beep(1047, 320) else local nx = free_cell() if nx < 0 then STATE = 2 ui_audio_beep(1047, 320) else FOOD = nx end end end return 1 end -- 画一整帧 -- Draw one whole frame function draw() local cell = CELL local ox = OX local oy = OY ui_2d_clear(-15724520) -- 0xFF101018 -- 标题 + 与标题等宽的下划线(宽度由 `s:len()` 数出来 —— 方法调用那条路) -- Title plus an underline as wide as the title (the width comes from `s:len()` — the method-call path) ui_2d_text(ox, 4, T_TITLE, -1513232, 12, VML_ANCHOR_LEFT) ui_2d_rect(ox, 18, text_w(T_TITLE) * 2, 2, -11409298, 1, 0, 0) -- 右侧:得分 / 长度(数字走点阵渲染器) -- Right side: score / length (numbers through the dot-matrix renderer) ui_2d_text(W - ox - 132, 6, T_SCORE, -6643536, 11, VML_ANCHOR_LEFT) draw_number(W - ox - 132, 20, SCORE, 3, -2509750) ui_2d_text(W - ox - 54, 6, T_LEN, -6643536, 11, VML_ANCHOR_LEFT) draw_number(W - ox - 54, 20, #BODY, 3, -11409298) -- 场地底 + 边框 -- Field floor plus border -- 0xFF29292A 边框 -- 0xFF29292A border ui_2d_rect(ox - 2, oy - 2, cell * COLS + 4, cell * ROWS + 4, -14079702, 1, 0, 4) ui_2d_rect(ox, oy, cell * COLS, cell * ROWS, -15066588, 1, 0, 2) -- 食物 -- Food local fr = FOOD / COLS local fc = FOOD % COLS ui_2d_circle(ox + fc * cell + cell / 2, oy + fr * cell + cell / 2, cell / 2 - 2, -2069424, 1, 0) -- 蛇:头一格亮、其余暗一点(长蛇一眼看得出头在哪) -- Snake: the head cell is bright, the rest dimmer (so the head is obvious on a long snake) local i = #BODY while i >= 1 do local r = BODY[i] / COLS local c = BODY[i] % COLS local col = -11483016 if i == 1 then col = -11409298 end ui_2d_rect(ox + c * cell + 1, oy + r * cell + 1, cell - 2, cell - 2, col, 1, 0, 3) i = i - 1 end -- 暂停 / 结束 / 通关横幅(结束与通关一定伴随音效,见 step) -- Paused / over / won banner (the over and won cases always come with a sound — see step) if PAUSED ~= 0 then ui_2d_text(W / 2, oy + cell * ROWS / 2, T_PAUSED, -1513232, 14, VML_ANCHOR_CENTER) end if STATE ~= 0 then local cy = oy + cell * ROWS / 2 -- 0xFF1F2422 半透明面板 -- 0xFF1F2422 panel ui_2d_rect(ox, cy - 44, cell * COLS, 88, -14736350, 1, 0, 6) if STATE == 2 then ui_2d_text(W / 2, cy - 30, T_WIN, -11409298, 18, VML_ANCHOR_CENTER) else ui_2d_text(W / 2, cy - 30, T_OVER, -2069424, 18, VML_ANCHOR_CENTER) end ui_2d_text(W / 2, cy + 4, T_AGAIN, -1513232, 13, VML_ANCHOR_CENTER) draw_number(W / 2 - 30, cy + 24, SCORE, 3, -2509750) end ui_2d_present() end function main() -- 界面语言:开局问一次宿主要中文还是英文(0=中文 1=英文),之后整局按它分支。 -- UI language: ask the host once (0=Chinese 1=English), then branch on it for the whole session. -- ⚠ 别在每帧里调(一次 syscall),也**不能**用 `and/or` 选串(见文件头)。 -- ⚠ Not per frame (it is a syscall), and do not pick strings with `and/or` (see the header). local lang = ui_scr_get_language() if lang == 0 then T_TITLE = "贪吃蛇" end if lang == 0 then T_SCORE = "得分" end if lang == 0 then T_LEN = "长度" end if lang == 0 then T_OVER = "游戏结束" end if lang == 0 then T_WIN = "通关了!" end if lang == 0 then T_AGAIN = "回车 = 重开" end if lang == 0 then T_PAUSED = "已暂停(SELECT 继续)" end -- 开窗时传 0:尺寸交给宿主(用真实可用区)。**开窗之后**再问一次真实画布 —— 开窗前问到的只是估算值。 -- Pass 0 when opening: the size is the host's to decide (the real usable area). Ask the **real** canvas only after the window is open -- before that the host can only estimate. ui_win_open(T_TITLE, 0, 0) ui_scr_set_keep_on(1) local w = ui_scr_get_w() local h = ui_scr_get_h() if w <= 0 then w = 360 end if h <= 0 then h = 620 end W = w -- 场地:格子边长取「按宽」「按高」两个上界的小者(手机可用绘图区实测约 395×578) -- Field: the cell side is the smaller of the two bounds (the measured phone drawing area is about 395×578) local cell = (w - 12) / COLS local avail = h - 84 if avail / ROWS < cell then cell = avail / ROWS end if cell < 4 then cell = 4 end CELL = cell OX = (w - cell * COLS) / 2 OY = 48 reset_round() -- 定时器:每 `SPEED` 毫秒投一条 VML_MSG_TIMER(A=定时器 id、B=tag);tag 用 7 认出它 -- Timer: one VML_MSG_TIMER every `SPEED` ms (A = timer id, B = tag); tag 7 identifies it TID = ui_timer_set(SPEED, 7) TIMER_SPEED = SPEED local quit = 0 local need_draw = 1 -- 墙钟补步累加器(按墙钟补步,用户 2026-10-04 口径:游戏时钟取系统毫秒数) -- Wall-clock catch-up accumulator (steps are caught up from the wall clock; user's 2026-10-04 rule: all game clocks read system milliseconds) local acc = 0 local lastms = ui_time_get_ms() -- 墙钟起点(进主循环时取) -- wall-clock origin, sampled when entering the main loop while ui_win_is_closed() == 0 and quit == 0 do -- 先画后等:只在画面真会变时才画(一帧要把整幅场景光栅化一遍,手机上不便宜) -- Draw before waiting, and only when the picture really changes (rasterising a whole frame is not cheap) if need_draw ~= 0 then draw() need_draw = 0 end -- 按墙钟补步,用户 2026-10-04 口径:游戏时钟取系统毫秒数。 -- steps are caught up from the wall clock; user's 2026-10-04 rule: all game clocks read system milliseconds. -- 定时器只做唤醒:欠账时不等消息、立刻补跑一步;欠账还清才回去等。 -- the timer only wakes us up: while in deficit it does not wait for a message and runs one step immediately; it goes back to waiting once the deficit is cleared. local now2 = ui_time_get_ms() acc = acc + (now2 - lastms) lastms = now2 if acc > SPEED * 8 then acc = 0 -- 卡太久别一次补一大串 -- do not catch up in one huge burst after a long stall end local t = 0 if acc < SPEED then t = ui_msg_fetch_wait(0) if t == 10 then -- 用户把窗口关了 -- the user closed the window quit = 1 end end if acc >= SPEED then acc = acc - SPEED -- 定时器只做唤醒;真正跑几步由上面的墙钟欠账决定 -- The timer only wakes us; how many steps run is decided by the wall-clock debt above if PAUSED == 0 and STATE == 0 then need_draw = step() end end if t == 1 then local k = ui_msg_get_a() if k == 27 then quit = 1 end if k == 13 then -- 回车:任何状态下都重开(结束画面里这是唯一要按的键) -- Enter: restart from any state (on the end screen this is the only key to press) reset_round() ui_audio_beep(660, 70) need_draw = 1 end if k == 16 then if STATE == 0 then if PAUSED == 0 then PAUSED = 1 else PAUSED = 0 end need_draw = 1 end end -- 转向键:方向键 37-40、WASD 65/68/83/87 -- Steering keys: arrows 37-40 and WASD 65/68/83/87 if STATE == 0 and PAUSED == 0 then if k == 38 or k == 87 then turn(0) end if k == 39 or k == 68 then turn(1) end if k == 40 or k == 83 then turn(2) end if k == 37 or k == 65 then turn(3) end end end if t == 6 then -- 触摸:朝触点相对蛇头的**主轴**方向拐(手机上没键盘也能玩) -- Touch: turn toward the **dominant axis** from the head to the touch point (playable with no keyboard) if STATE == 0 and PAUSED == 0 then local tx = ui_msg_get_a() local ty = ui_msg_get_b() local hr = BODY[1] / COLS local hc = BODY[1] % COLS local hx = OX + hc * CELL + CELL / 2 local hy = OY + hr * CELL + CELL / 2 local dx = tx - hx local dy = ty - hy if dx < 0 then dx = 0 - dx end if dy < 0 then dy = 0 - dy end if dx > dy then if tx > hx then turn(1) else turn(3) end else if ty > hy then turn(2) else turn(0) end end end end -- 吃完食物会提速:定时器要换掉(tag 仍是 7:杀掉旧的重开一个) -- Eating speeds the game up, so the timer is replaced (same tag 7: kill the old one, arm a new one) if SPEED ~= TIMER_SPEED then TIMER_SPEED = SPEED ui_timer_kill(TID) TID = ui_timer_set(SPEED, 7) end end ui_timer_kill(TID) ui_scr_set_keep_on(0) ui_sfx_panic() ui_win_close() end main()