python/app/wordle.py · 30215 字节 · 下载原文
# wordle.py —— 猜词(VML 的 Python 前端)
# wordle.py — Wordle (VML's Python frontend)
#
# 5 个字母的词、6 次机会、逐位三色反馈(绿=位置也对 / 黄=字母有但位置错 / 灰=没有这个字母),
# A 5-letter word, 6 tries, per-position three-color feedback (green = right letter right spot /
# 内置词表,胜负都有提示;**触摸屏上字母键盘为主,物理键盘兜底**。
# yellow = right letter wrong spot / gray = no such letter), a built-in word list, win and lose
# messages; **the on-screen alphabet keyboard is the main input, a physical keyboard is the fallback**.
#
# 界面走**共享调用库** `Lib/shared/src/vmlui.c`(编成 `vmlui.vml`,由 `vmltool.config.xml`
# The UI goes through the **shared call library** `Lib/shared/src/vmlui.c` (compiled into `vmlui.vml`
# 挂到 python 的 `Libs` 上):Python 前端不支持内联 `asm()`,只能按标签调 C 函数。
# and attached to python's `Libs` by `vmltool.config.xml`): the Python frontend has no inline asm(),
# so it can only call C functions by label.
# ↳ 2026-10-04 复验仍成立:`asm("SYSCALL 6")` 实测 `error: 未定义的函数 'asm'(引用 2 次)`(`asm` 被当普通调用)。
# ↳ Still true, re-checked 2026-10-04: `asm("SYSCALL 6")` measures `error: undefined function 'asm' (referenced 2 times)` (`asm` is treated as an ordinary call).
#
# ## 为什么整份"模块级状态 + 只读函数"
# ## Why it is "module-level state + read-only functions"
#
# 本程序**用到了**这一轮刚修好的三条前端能力(写这个游戏时就是被它们挡住才去修的,
# This program **uses** three frontend capabilities fixed in the same round (writing this game is
# 详见 `FRONTEND_DEFECTS.md` 的 Python 节):
# exactly what exposed them; see the Python section of `FRONTEND_DEFECTS.md`):
#
# · **函数体里能读到模块级变量**了(修之前 `W = 10` 在函数里读出来是 0)⇒ 棋盘尺寸、词表、
# · **Function bodies can read module-level variables** — before the fix, `W = 10` read back 0
# 局面这些都可以放模块级,函数直接读,不必一路当参数传。
# inside a function — so the board geometry, the word list and the game state all live at module
# level and the functions just read them instead of taking everything as parameters.
#
# · **多参函数收得到第 2 个及以后的实参**了(修之前第 2 个形参拿到的是寄存器残留)
# · **Multi-argument functions receive their 2nd argument onward** — before the fix the 2nd
# ⇒ `draw_cell(r, c, x, y, w, h)` 这种六参助手可以正常写。
# parameter was register garbage — so a six-argument helper like `draw_cell(r, c, x, y, w, h)`
# can be written normally.
#
# · **`or` 的短路方向**修对了(修之前 `A or B` 恒等于 `B`)⇒ `if k == 13 or k == 32:` 这类
# · **`or` short-circuits in the right direction** — before the fix `A or B` was simply `B` —
# 判断才真的生效。
# so conditions like `if k == 13 or k == 32:` actually work.
#
# ⚠ 但**状态的修改一律留在模块级**:函数体里的赋值在 Python 语义下是**新的局部量**
# ⚠ State changes stay at module level, though: inside a function an assignment creates a **new
# (本前端现在也守这条),要改模块级状态得写 `global`,绕来绕去不如把"改状态"那几段
# local** (Python semantics, and this frontend now honours it), so mutating module state would
# 直接平铺在顶层 —— 函数只做**只读**的事(算颜色、算命中哪个键、画一格)。
# need `global` everywhere — simpler to keep the mutating parts flat at the top level and let
# the functions be **read-only** (pick a color, hit-test a key, draw one cell).
#
# ## 两条绕开前端短板的写法
# ## Two workarounds for frontend gaps
#
# · **字母/数字都不能 int→字符串**(`str()` 是恒等、`chr()` 返回的是栈上地址)⇒
# · **Neither letters nor numbers can be turned into strings** (`str()` is the identity and
# 字母一律走**字形表** `ALPH[下标]`(26 个元素的字符串列表),
# `chr()` returns a stack address) ⇒ letters go through a **glyph table** `ALPH[index]` (a
# 词表也只存**字母下标**(`0..25`),比较全是整数比较 —— 正好绕开"字符串相等恒为 0"。
# 26-element string list) and the word list stores **letter indices** (`0..25`) only, so every
# comparison is an integer comparison — which also sidesteps "string equality is always 0".
#
# · **没有 `set`/`dict` 可用**(`set()` 未定义、字典取值恒 0)⇒ 词表用**平铺的整数列表**,
# · **No `set`/`dict` either** (`set()` is undefined and dictionary lookups always return 0) ⇒
# "用过没"用另一个同长的 `USED` 列表记。
# the word list is a **flat integer list** and "already consumed" is tracked in a parallel
# `USED` list.
#
# ## 操作
# ## Controls
#
# 开窗声明了 `VML_WIN_PORTRAIT`(坐标空间锁竖屏)+ `VML_WIN_NO_GAMEPAD`(画布吃满整屏),
# The window declares `VML_WIN_PORTRAIT` (coordinate space locked to portrait) plus
# 另配 `ui_scr_lock_orientation(VML_LOCK_PORTRAIT)` 让**系统**也不转屏。
# `VML_WIN_NO_GAMEPAD` (the canvas fills the screen), with `ui_scr_lock_orientation(VML_LOCK_PORTRAIT)`
# so the **system** will not rotate either.
#
# **触摸**:直接点屏上键盘(三行 QWERTY + 回车 + 退格)。**键盘兜底**:A–Z 输入、回车提交、
# **Touch**: tap the on-screen keyboard (three QWERTY rows + Enter + Backspace). **Keyboard
# 退格删除、ESC 退出。结束后点屏幕 / 回车重开。
# fallback**: A–Z to type, Enter to submit, Backspace to delete, ESC to quit. After the game
# ends, tap the screen or press Enter to restart.
#
# ## 跑法
# ## How to run
#
# 手机:`vml run examples/python/wordle.py`
# Phone: `vml run examples/python/wordle.py`
# 桌面:`vmlcli Examples/python/wordle.py --screen 411x726 --frames /tmp/w`
# Desktop: `vmlcli Examples/python/wordle.py --screen 411x726 --frames /tmp/w`
# ── 窗口:开窗时传 0(尺寸交给宿主),**开窗之后**再量一次真实画布 ──────────
# ── window: open with 0 (the host decides the size), measure the real canvas **after** it opens ──
# ── 显式引用:下面用到的 `ui_*` 全部来自共享库 vmlui.vml ────────────
# ── explicit reference: every `ui_*` below comes from the shared library vmlui.vml ──
# `Lib/python/vml_ui.py` 是 GenLib 从 `Lib/shared/src/vmlui.c` 生成的声明文件(勿手写),
# `Lib/python/vml_ui.py` is the declaration file GenLib generates from `Lib/shared/src/vmlui.c` (do not hand-write);
# 前端读它登记名字与形参个数 —— 有了声明表,实参个数写错会在**编译期**报错。
# the frontend reads it to register the names and their arities — with that table a wrong argument count is a **compile-time** error.
import vml_ui
LANG = ui_scr_get_language()
ui_scr_lock_orientation(VML_LOCK_PORTRAIT) # VML_LOCK_PORTRAIT
# 开窗时传 0:尺寸交给宿主(用真实可用区)。**开窗之后**再问一次真实画布 —— 开窗前问到的只是估算值。
# Open with 0: the host decides the size (the real usable area). Ask the **real** canvas only after
# the window is open -- before that the host can only estimate.
ui_win_open_ex("猜词" if LANG == 0 else "Wordle", 0, 0, VML_WIN_PORTRAIT, VML_WIN_NO_GAMEPAD)
ui_scr_set_keep_on(1)
SW = ui_scr_get_w()
SH = ui_scr_get_h()
if SW <= 0:
SW = 411
if SH <= 0:
SH = 726
# ── 颜色(真彩 0xAARRGGBB)────────────────────────────────────────────
# ── colors (true color 0xAARRGGBB) ───────────────────────────────────
C_BG = 0xFF0B1020
C_TEXT = 0xFFE8EAF2
C_DIM = 0xFF8A90A4
C_EMPTY = 0xFF1C2233
C_EDGE = 0xFF39415C
C_HIT = 0xFF4ADE80 # 位置也对
# right letter, right spot
C_NEAR = 0xFFF0B429 # 字母有、位置错
# right letter, wrong spot
C_GONE = 0xFF4A5169 # 没这个字母
# no such letter
C_KEY = 0xFF2A3145
C_ACT = 0xFF5AC8FA # 当前行的边框色
# border color of the active row
C_DARK = 0xFF101018 # 亮底上的字色
# text color on light fills
# ── 字母字形表(本前端没有 int→字符串,字母只能查表)──────────────────
# ── letter glyph table (no int→string in this frontend, so letters are looked up) ──
ALPH = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "Z"]
# 屏上键盘的三行字母(下标 0..25 = A..Z)
# the three on-screen keyboard rows (index 0..25 = A..Z)
KROW0 = [16, 22, 4, 17, 19, 24, 20, 8, 14, 15] # QWERTYUIOP(Q=16 W=22 E=4 R=17 …)
KROW1 = [0, 18, 3, 5, 6, 7, 9, 10, 11, 12] # ASDFGHJKL(第 10 格空着)
# ASDFGHJKL (the 10th slot is left blank)
KROW2 = [25, 23, 2, 21, 1, 13, 12] # ZXCVBNM
# ── 几何(模块级;函数里读得到)──────────────────────────────────────
# ── geometry (module level; readable from inside the functions) ───────
PAD = 8
CELLW = (SW - PAD * 2 - 4 * 5) // 5
CELLH = 46
GT = 60 # 猜词格子区顶
# top of the guess grid
UGAP = 4
KBTOP = GT + 6 * (CELLH + UGAP) + 14
UNIT = (SW - PAD * 2) // 10 # 屏上键盘的一格宽
# one key unit of the on-screen keyboard
KH = (SH - KBTOP - 46) // 3
if KH > 46:
KH = 46
if KH < 24:
KH = 24
KSTEP = KH + 6
MSG_Y = KBTOP + 3 * KSTEP + 8
# ── 局面 ──────────────────────────────────────────────────────────────
# ── game state ────────────────────────────────────────────────────────
# 6 行 × 5 列,-1 = 这一格还没填
# 6 rows × 5 columns, -1 = cell not filled yet
GRID = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]
MARK = [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]
USED = [0, 0, 0, 0, 0] # 判定某个答案字母有没有被"绿"用掉
# which answer letters the greens consumed
LSTAT = [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]
ROW = 0 # 当前行
# current row
COL = 0 # 当前行里已填几格
# how many cells are filled in the current row
STATE = 0 # 0 进行中 / 1 胜 / 2 负
# 0 = playing, 1 = won, 2 = lost
ANS = 0 # 答案在词表里的下标
# index of the answer in the word list
DOWN0 = 0 # 上一帧手指按着没有(做"点一下"的边沿检测)
# was the finger down last frame (edge detection for a single tap)
TID = 0
# ── 内置词表:220 个常见 5 字母单词,每个词 5 个字母下标(0=A … 25=Z)──
# ── built-in word list: 220 common 5-letter words; five letter indices per word (0=A … 25=Z) ──
WLEN = 220
WD = [
0, 1, 14, 20, 19, 0, 1, 14, 21, 4, 0, 2, 19, 14, 17, 0, 3, 14, 15, 19, 0, 11, 8, 4, 13,
0, 11, 8, 21, 4, 0, 11, 11, 14, 22, 0, 11, 14, 13, 4, 0, 11, 14, 13, 6, 0, 11, 19, 4, 17,
0, 12, 1, 4, 17, 0, 12, 14, 13, 6, 0, 13, 6, 11, 4, 0, 13, 10, 11, 4, 0, 15, 0, 17, 19,
0, 15, 15, 11, 4, 0, 15, 15, 11, 24, 0, 17, 4, 13, 0, 0, 17, 6, 20, 4, 0, 17, 8, 18, 4,
0, 17, 17, 0, 24, 0, 18, 8, 3, 4, 0, 18, 18, 4, 19, 0, 20, 3, 8, 14, 0, 21, 14, 8, 3,
0, 22, 0, 10, 4, 0, 22, 0, 17, 3, 0, 22, 0, 17, 4, 1, 0, 2, 14, 13, 1, 0, 3, 6, 4,
1, 0, 10, 4, 17, 1, 0, 18, 8, 2, 1, 0, 18, 8, 13, 1, 4, 0, 2, 7, 1, 4, 6, 0, 13,
1, 4, 6, 8, 13, 1, 4, 8, 13, 6, 1, 4, 11, 14, 22, 1, 4, 13, 2, 7, 1, 8, 17, 19, 7,
1, 11, 0, 2, 10, 1, 11, 0, 3, 4, 1, 11, 0, 12, 4, 1, 11, 0, 13, 10, 1, 11, 0, 18, 19,
1, 11, 4, 13, 3, 1, 11, 8, 13, 3, 1, 11, 14, 2, 10, 1, 11, 14, 14, 3, 1, 14, 0, 17, 3,
1, 14, 14, 18, 19, 1, 14, 20, 13, 3, 1, 17, 0, 8, 13, 1, 17, 0, 13, 3, 1, 17, 0, 21, 4,
1, 17, 4, 0, 3, 1, 17, 4, 0, 10, 1, 17, 4, 4, 3, 1, 17, 8, 2, 10, 1, 17, 8, 3, 4,
1, 17, 8, 4, 5, 1, 17, 8, 13, 6, 1, 17, 14, 0, 3, 1, 17, 14, 10, 4, 1, 17, 14, 22, 13,
1, 17, 20, 18, 7, 1, 20, 8, 11, 3, 1, 20, 8, 11, 19, 1, 20, 24, 4, 17, 2, 0, 1, 8, 13,
2, 0, 1, 11, 4, 2, 0, 12, 4, 11, 2, 0, 13, 3, 24, 2, 0, 13, 14, 4, 2, 0, 17, 17, 24,
2, 0, 17, 21, 4, 2, 0, 19, 2, 7, 2, 0, 20, 18, 4, 2, 7, 0, 8, 13, 2, 7, 0, 8, 17,
2, 7, 0, 11, 10, 2, 7, 0, 17, 12, 2, 7, 0, 17, 19, 2, 7, 0, 18, 4, 2, 7, 4, 0, 15,
2, 7, 4, 2, 10, 2, 7, 4, 18, 18, 2, 7, 8, 4, 5, 2, 7, 8, 11, 3, 2, 7, 14, 18, 4,
2, 8, 21, 8, 11, 2, 11, 0, 8, 12, 2, 11, 0, 18, 7, 2, 11, 0, 18, 18, 2, 11, 4, 0, 13,
2, 11, 4, 0, 17, 2, 11, 4, 17, 10, 2, 11, 8, 2, 10, 2, 11, 8, 5, 5, 2, 11, 8, 12, 1,
2, 11, 14, 2, 10, 2, 11, 14, 18, 4, 2, 11, 14, 19, 7, 2, 11, 14, 20, 3, 2, 14, 0, 2, 7,
2, 14, 0, 18, 19, 2, 14, 2, 14, 0, 2, 14, 11, 14, 13, 2, 14, 11, 14, 17, 2, 14, 17, 0, 11,
2, 14, 20, 2, 7, 2, 14, 20, 11, 3, 2, 14, 20, 13, 19, 2, 14, 20, 17, 19, 2, 14, 21, 4, 17,
2, 17, 0, 2, 10, 2, 17, 0, 5, 19, 2, 17, 0, 18, 7, 2, 17, 0, 13, 4, 2, 17, 0, 25, 24,
2, 17, 4, 0, 12, 2, 17, 8, 18, 15, 2, 17, 14, 18, 18, 2, 17, 14, 22, 3, 2, 17, 14, 22, 13,
2, 17, 20, 3, 4, 2, 17, 20, 4, 11, 2, 17, 20, 18, 7, 2, 20, 17, 21, 4, 2, 24, 2, 11, 4,
3, 0, 8, 11, 24, 3, 0, 8, 17, 24, 3, 0, 13, 2, 4, 3, 0, 19, 4, 3, 3, 4, 0, 11, 19,
3, 4, 0, 19, 7, 3, 4, 1, 20, 19, 3, 4, 2, 0, 24, 3, 4, 2, 14, 24, 3, 4, 11, 0, 24,
3, 4, 11, 19, 0, 3, 4, 13, 18, 4, 3, 4, 15, 19, 7, 3, 8, 0, 17, 24, 3, 8, 17, 19, 24,
3, 8, 19, 2, 7, 3, 8, 21, 4, 17, 3, 8, 25, 25, 24, 3, 14, 3, 6, 4, 3, 14, 13, 14, 17,
3, 14, 20, 1, 19, 3, 14, 25, 4, 13, 3, 17, 0, 12, 0, 3, 17, 0, 13, 10, 3, 17, 4, 0, 12,
3, 17, 4, 18, 18, 3, 17, 8, 5, 19, 3, 17, 8, 11, 11, 3, 17, 8, 13, 10, 3, 17, 8, 21, 4,
3, 17, 14, 13, 4, 3, 17, 14, 22, 13, 3, 17, 24, 4, 17, 3, 22, 4, 11, 11, 4, 0, 6, 11, 4,
4, 0, 17, 11, 24, 4, 0, 17, 19, 7, 4, 0, 19, 4, 13, 4, 1, 14, 13, 24, 4, 11, 1, 14, 22,
4, 11, 3, 4, 17, 4, 11, 4, 2, 19, 4, 11, 8, 19, 4, 4, 12, 15, 19, 24, 4, 13, 3, 4, 3,
4, 13, 4, 12, 24, 4, 13, 9, 14, 24, 4, 13, 19, 4, 17, 4, 13, 19, 17, 24, 4, 16, 20, 0, 11,
4, 16, 20, 8, 15, 4, 17, 17, 14, 17, 4, 18, 18, 0, 24, 4, 21, 4, 13, 19, 4, 21, 4, 17, 24,
4, 23, 0, 2, 19, 4, 23, 8, 11, 4, 4, 23, 8, 18, 19, 4, 23, 19, 17, 0, 5, 0, 1, 11, 4,
5, 0, 2, 4, 3, 5, 0, 8, 13, 19, 5, 0, 8, 17, 24, 5, 0, 8, 19, 7, 5, 0, 11, 18, 4,
5, 0, 13, 2, 24, 5, 0, 19, 0, 11, 5, 0, 20, 11, 19, 5, 0, 21, 14, 17, 5, 4, 0, 18, 19,
5, 4, 13, 2, 4, 5, 4, 17, 17, 24, 5, 4, 19, 2, 7, 5, 4, 21, 4, 17, 5, 8, 1, 4, 17,
5, 8, 4, 11, 3, 5, 8, 4, 17, 24, 5, 8, 5, 19, 7, 5, 8, 5, 19, 24, 5, 8, 6, 7, 19,
5, 8, 13, 0, 11, 5, 8, 13, 2, 7, 5, 8, 17, 18, 19, 5, 11, 0, 12, 4, 5, 11, 0, 18, 7,
5, 11, 4, 4, 19, 5, 11, 4, 18, 7, 5, 11, 14, 2, 10, 5, 11, 14, 14, 3, 5, 11, 14, 14, 17,
]
# ── 只读小函数(算颜色 / 命中哪个键 / 画一格)─────────────────────────
# ── small read-only helpers (pick a color / hit-test a key / draw one cell) ──
# ⚠ 这几个函数**只读**模块级状态(现在读得到了),一个字节都不写 —— 写状态留在顶层。
# ⚠ These functions only **read** module-level state (which now works) and never write it —
# mutation stays flat at the top level.
def cell_face(m):
# 已判定的格用实色;没判定的是空槽
# judged cells get a solid color; unjudged ones are empty slots
if m == 3:
return C_HIT
if m == 2:
return C_NEAR
if m == 1:
return C_GONE
return C_EMPTY
def on_face(m):
# 亮底上用深色字,空槽用亮色字
# dark text on the light fills, bright text on empty slots
if m == 0:
return C_TEXT
return C_DARK
def key_face(li):
s = LSTAT[li]
if s == 3:
return C_HIT
if s == 2:
return C_NEAR
if s == 1:
return C_GONE
return C_KEY
def key_at(tx, ty):
# 触摸点 → 键。返回 0..25 = 字母,26 = 回车,27 = 退格,-1 = 没点到键盘
# touch point → key. 0..25 = a letter, 26 = Enter, 27 = Backspace, -1 = missed the keyboard
if ty < KBTOP:
return 0 - 1
kr = (ty - KBTOP) // KSTEP
if kr < 0 or kr > 2:
return 0 - 1
if tx < PAD:
return 0 - 1
u = (tx - PAD) // UNIT
if u > 9:
u = 9
if kr == 0:
return KROW0[u]
if kr == 1:
if u > 8:
return 0 - 1
return KROW1[u]
if u < 7:
return KROW2[u]
if u < 9:
return 26
return 27
def draw_cell(r, c, x, y, w, h):
li = GRID[r * 5 + c]
m = MARK[r * 5 + c]
ui_2d_rect(x, y, w, h, cell_face(m), 1, 0, 6)
if r == ROW and STATE == 0:
ui_2d_rect(x, y, w, h, C_ACT, 0, 2, 6)
else:
ui_2d_rect(x, y, w, h, C_EDGE, 0, 2, 6)
if li >= 0:
ui_2d_text_v(x + w // 2, y + h // 2, ALPH[li], on_face(m), 26, VML_ANCHOR_CENTER, VML_VANCHOR_MIDDLE, VML_FONT_BOLD)
def draw_key(x, y, w, h, li, act):
if act == 0:
ui_2d_rect(x, y, w, h, key_face(li), 1, 0, 6)
ui_2d_text_v(x + w // 2, y + h // 2, ALPH[li], on_face(LSTAT[li]), 18, VML_ANCHOR_CENTER, VML_VANCHOR_MIDDLE, VML_FONT_BOLD)
if act == 1:
ui_2d_rect(x, y, w, h, C_KEY, 1, 0, 6)
ui_2d_text_v(x + w // 2, y + h // 2, "回车" if LANG == 0 else "ENTER", C_TEXT, 13, VML_ANCHOR_CENTER, VML_VANCHOR_MIDDLE, VML_FONT_BOLD)
if act == 2:
ui_2d_rect(x, y, w, h, C_KEY, 1, 0, 6)
ui_2d_text_v(x + w // 2, y + h // 2, "删" if LANG == 0 else "DEL", C_TEXT, 13, VML_ANCHOR_CENTER, VML_VANCHOR_MIDDLE, VML_FONT_BOLD)
# ── 开局:抽一个答案 ──────────────────────────────────────────────────
# ── start: pick an answer ────────────────────────────────────────────
ANS = ui_math_rand(WLEN)
TID = ui_timer_set(40, 1)
# ⚠ 提示音:**单通道**,一次事件只发一个音。频率是刻意区分的:
# ⚠ Beeps: **single channel**, one beep per event. The frequencies are deliberately distinct:
# 字母键 440+8n / 回车 620 / 回车但没填满 200 / 胜利 990 / 失败 160 / 重开 880。
# letter key 440+8n / Enter 620 / Enter with an incomplete row 200 / win 990 / lose 160 / restart 880.
# letter key 440+8n / submit 620 / submit with a short row 200 / win 990 / lose 160 / restart 880.
# ── 主循环 ────────────────────────────────────────────────────────────
# ── main loop ────────────────────────────────────────────────────────
while ui_win_is_closed() == 0:
# 触摸是"追最新位置"的语义 ⇒ 每帧丢掉积压的触摸消息,直接轮询当前值
# touches mean "follow the latest position", so drop the backlog each frame and poll instead
ui_msg_drop(1)
T = ui_msg_fetch_wait(0)
if T == 10: # WindowClose
break
HIT = 0 - 1
# ── 物理键盘兜底:A–Z / 回车 / 退格 / ESC ──
# ── physical keyboard fallback: A–Z / Enter / Backspace / ESC ──
if T == 1:
K = ui_msg_get_a()
if K == 27:
break
if K == 13:
HIT = 26
if K == 8:
HIT = 27
if K >= 65 and K <= 90:
HIT = K - 65
# ── 触摸:只在"按下的那一沿"算一次点击 ──
# ── touch: only the rising edge counts as a tap ──
if ui_touch_fetch(0) == 1:
if ui_touch_is_down() == 1 and DOWN0 == 0:
HIT = key_at(ui_touch_get_x(), ui_touch_get_y())
if STATE != 0:
HIT = 26 # 结束后点屏幕任意处 = 重开
# after the game, any tap restarts
if ui_touch_is_down() == 1:
DOWN0 = 1
else:
DOWN0 = 0
# ── 处理这次输入 ──
# ── handle this input ──
if HIT >= 0:
if STATE != 0:
if HIT == 26:
i = 0
while i < 30:
GRID[i] = 0 - 1
MARK[i] = 0
i = i + 1
i = 0
while i < 26:
LSTAT[i] = 0
i = i + 1
ROW = 0
COL = 0
STATE = 0
ANS = ui_math_rand(WLEN)
ui_audio_beep(880, 70)
elif HIT == 26:
if COL == 5:
# ① 先全部按"没有这个字母"起底,再把绿、黄标上
# ① start every cell at "absent", then stamp the greens and the yellows
i = 0
while i < 5:
USED[i] = 0
MARK[ROW * 5 + i] = 1
i = i + 1
# ② 绿:字母和位置都对
# ② green: right letter, right spot
i = 0
while i < 5:
if GRID[ROW * 5 + i] == WD[ANS * 5 + i]:
MARK[ROW * 5 + i] = 3
USED[i] = 1
i = i + 1
# ③ 黄:字母在答案里、但这一格没被绿吃掉 —— 每个答案字母只喂一格
# ③ yellow: the letter is in the answer and not consumed by a green —
# each answer letter feeds at most one cell
i = 0
while i < 5:
if MARK[ROW * 5 + i] != 3:
j = 0
GOT = 0
while j < 5:
if GOT == 0 and USED[j] == 0 and WD[ANS * 5 + j] == GRID[ROW * 5 + i]:
MARK[ROW * 5 + i] = 2
USED[j] = 1
GOT = 1
j = j + 1
i = i + 1
# ④ 屏上键盘:每键取"见过的最好一次"(3 > 2 > 1)
# ④ on-screen keyboard: keep the best status each key has ever had (3 > 2 > 1)
i = 0
while i < 5:
LI = GRID[ROW * 5 + i]
if MARK[ROW * 5 + i] > LSTAT[LI]:
LSTAT[LI] = MARK[ROW * 5 + i]
i = i + 1
# ⑤ 五个全绿 = 赢;否则下一行;填满 6 行还没中 = 输
# ⑤ five greens = win; otherwise the next row; six full rows = lose
G = 0
i = 0
while i < 5:
if MARK[ROW * 5 + i] == 3:
G = G + 1
i = i + 1
if G == 5:
STATE = 1
ui_audio_beep(990, 160)
else:
ROW = ROW + 1
COL = 0
if ROW >= 6:
ROW = 5
STATE = 2
ui_audio_beep(160, 260)
else:
ui_audio_beep(620, 45)
else:
ui_audio_beep(200, 60) # 这一行还没填满
# the row is not full yet
elif HIT == 27:
if COL > 0:
COL = COL - 1
GRID[ROW * 5 + COL] = 0 - 1
ui_audio_beep(330, 25)
else:
if COL < 5:
GRID[ROW * 5 + COL] = HIT
COL = COL + 1
ui_audio_beep(440 + HIT * 8, 20)
# ── 画这一帧(每个位置都由 SW / SH 推出来,没有一个写死的画布尺寸)──
# ── draw this frame (every position derived from SW / SH; no hard-coded canvas size) ──
ui_2d_clear(C_BG)
ui_text_set_font(22, 1, C_TEXT, 1)
ui_2d_text_cur(SW // 2, 6, "猜词" if LANG == 0 else "WORDLE")
ui_text_set_font(12, 0, C_DIM, 1)
if STATE == 0:
# 剩余机会画成 6 个小点(**不写数字**:本前端没有 int→字符串,见文件头)
# remaining tries as six pips (**no digits**: this frontend has no int→string, see the header)
ui_text_set_font(12, 0, C_DIM, 1)
ui_2d_text_cur(SW // 2 - 40, 40, "机会" if LANG == 0 else "TRIES")
i = 0
while i < 6:
if i < ROW:
ui_2d_circle(SW // 2 + 4 + i * 16, 47, 5, C_GONE, 1, 0)
else:
ui_2d_circle(SW // 2 + 4 + i * 16, 47, 5, C_ACT, 1, 0)
i = i + 1
if STATE == 1:
ui_text_set_font(13, 1, C_HIT, 1)
ui_2d_text_cur(SW // 2, 40, "猜对了!" if LANG == 0 else "You got it!")
if STATE == 2:
ui_text_set_font(13, 1, C_NEAR, 1)
ui_2d_text_cur(SW // 2, 40, "机会用完了" if LANG == 0 else "Out of tries")
# 6×5 的猜词格
# the 6×5 guess grid
r = 0
while r < 6:
c = 0
while c < 5:
draw_cell(r, c, PAD + c * (CELLW + 4), GT + r * (CELLH + UGAP), CELLW, CELLH)
c = c + 1
r = r + 1
# 屏上键盘:三行 QWERTY,第三行再挂"回车"和"退格"(合计仍是 10 格宽)
# on-screen keyboard: three QWERTY rows, with Enter and Backspace on the third
# (still ten units wide in total)
i = 0
while i < 10:
draw_key(PAD + i * UNIT, KBTOP, UNIT - 3, KH, KROW0[i], 0)
i = i + 1
i = 0
while i < 9:
draw_key(PAD + UNIT // 2 + i * UNIT, KBTOP + KSTEP, UNIT - 3, KH, KROW1[i], 0)
i = i + 1
i = 0
while i < 7:
draw_key(PAD + i * UNIT, KBTOP + 2 * KSTEP, UNIT - 3, KH, KROW2[i], 0)
i = i + 1
EW = (UNIT * 3) // 2
draw_key(PAD + 7 * UNIT, KBTOP + 2 * KSTEP, EW - 3, KH, 0 - 1, 1)
draw_key(PAD + 7 * UNIT + EW, KBTOP + 2 * KSTEP, EW - 3, KH, 0 - 1, 2)
# 提示 / 结果
# hint / result
if STATE == 0:
ui_text_set_font(12, 0, C_DIM, 1)
ui_2d_text_cur(SW // 2, MSG_Y, "点屏上键盘输入,回车提交" if LANG == 0 else "Tap keys, Enter to submit")
if STATE == 1:
ui_text_set_font(15, 1, C_HIT, 1)
ui_2d_text_cur(SW // 2, MSG_Y, "点屏幕或按回车再来一局" if LANG == 0 else "Tap / Enter for a new word")
if STATE == 2:
ui_text_set_font(15, 1, C_NEAR, 1)
ui_2d_text_cur(SW // 2, MSG_Y, "点屏幕或按回车再来一局" if LANG == 0 else "Tap / Enter for a new word")
# 输的时候把答案亮出来(点屏幕重开之后就换了新词)
# on a loss, reveal the answer (it changes as soon as you restart)
ui_text_set_font(13, 0, C_TEXT, 1)
ui_2d_text_cur(SW // 2, MSG_Y + 20, "答案是 " if LANG == 0 else "Answer: ")
ui_2d_text_v(SW // 2 + 34, MSG_Y + 20, ALPH[WD[ANS * 5 + 0]], C_HIT, 16, VML_ANCHOR_CENTER, VML_VANCHOR_MIDDLE, VML_FONT_BOLD)
ui_2d_text_v(SW // 2 + 46, MSG_Y + 20, ALPH[WD[ANS * 5 + 1]], C_HIT, 16, VML_ANCHOR_CENTER, VML_VANCHOR_MIDDLE, VML_FONT_BOLD)
ui_2d_text_v(SW // 2 + 58, MSG_Y + 20, ALPH[WD[ANS * 5 + 2]], C_HIT, 16, VML_ANCHOR_CENTER, VML_VANCHOR_MIDDLE, VML_FONT_BOLD)
ui_2d_text_v(SW // 2 + 70, MSG_Y + 20, ALPH[WD[ANS * 5 + 3]], C_HIT, 16, VML_ANCHOR_CENTER, VML_VANCHOR_MIDDLE, VML_FONT_BOLD)
ui_2d_text_v(SW // 2 + 82, MSG_Y + 20, ALPH[WD[ANS * 5 + 4]], C_HIT, 16, VML_ANCHOR_CENTER, VML_VANCHOR_MIDDLE, VML_FONT_BOLD)
# ⚠ 每帧末尾:`ui_2d_present()` 是"这一帧画完了"的唯一信号
# ⚠ at the end of every frame: `ui_2d_present()` is the only "this frame is done" signal
ui_2d_present()
# ── 收尾 ──────────────────────────────────────────────────────────────
# ── wrap-up ──────────────────────────────────────────────────────────
ui_timer_kill(TID)
ui_scr_set_keep_on(0)
ui_win_close()
print("wordle: done row=", ROW, " state=", STATE)