← 都来码 / Dolaima← 例程索引python/app/wordle.py原文

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)