style: 引入 ruff,474 项告警清零
配置见 pyproject.toml:
- line-length=100:贴着现有代码定。若用默认的 88 会重排 122 行,
噪音淹没真正有价值的修复。
- 启用 E/W/F/I/UP/B/C4/SIM/RET/PERF/RUF;**不**启用 PTH。因为本项目的路径
多是"字符串身份"——要塞进 subprocess 命令行、写进 JSON 存档、打印给用户,
全换 pathlib 只是包一层再拆一层,换来 70 处纯风格改动。
- 忽略 RUF001/002/003:中文全角标点是正确写法,不是"歧义字符"。
- 忽略 PERF203:剩下两处都是"逐项容错"语义(逐个牧场存档要立刻上报失败、
收获全部要跳过失败地块)。把 try 提到循环外会毁掉"一项失败不影响其它项"
这个行为,是错的优化。
自动修复 358 项:Dict/List/Optional 换成 PEP 585/604 写法、导入排序、
失效的 noqa、空占位 f-string 等。
手工修复的(这些是真问题,不是风格偏好):
- B904 x9:except 中 raise 补异常链。刻意区分——网络/IO 失败用 from exc 保留
根因;参数解析失败用 from None,因为底层 ValueError 对玩家只是噪音。
- RUF012 x2:StateView 的 plots/animals 原本是**类级共享可变列表**,
改成在 __init__ 里初始化(apply({}) 会提前返回,所以必须显式摆好)。
- F841:TUI 里一个赋值后再没用过的死变量。
- SIM105 x11:try/except/pass 统一改成 contextlib.suppress。
- SIM115:日志句柄改用 with。Popen 已把 fd 复制给子进程,父进程可安全关闭。
- E501 x7:拆超长行;main.py 那处顺手提取变量,避免超长嵌套表达式。
- E741:把 l/r 改成 left_line/right_line。
- 其余:RUF046 / PERF401 / RET504 / RUF005 / UP031 / B007。
ruff format 重排 24 个文件。
验证:97 项测试全绿。
This commit is contained in:
1 parent
c7c8a6308a
commit
fcd4cb4649
25 files changed
+1204
-794
No files matched your search
+120
-73
@@ -13,13 +13,14 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import time
|
||||
import unicodedata
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
from typing import Any
|
||||
|
||||
from . import config
|
||||
from .engine import GameError
|
||||
@@ -41,11 +42,11 @@ C_PURPLE = "\x1b[38;5;176m"
|
||||
C_BOLD = "\x1b[1m"
|
||||
|
||||
ANSI_RE = re.compile(r"\x1b\[[0-9;]*m")
|
||||
CELL_WIDTH = 17 # 单块农田格子的显示宽度
|
||||
CELL_WIDTH = 17 # 单块农田格子的显示宽度
|
||||
CELL_INNER = CELL_WIDTH - 2
|
||||
SIDE_WIDTH = 36 # 右侧栏宽度
|
||||
SIDE_WIDTH = 36 # 右侧栏宽度
|
||||
MIN_TERM_WIDTH = 76
|
||||
MAX_VISIBLE_ANIMALS = 5 # 侧栏同时最多展示的牲畜数量
|
||||
MAX_VISIBLE_ANIMALS = 5 # 侧栏同时最多展示的牲畜数量
|
||||
|
||||
|
||||
def strip_ansi(text: str) -> str:
|
||||
@@ -103,7 +104,7 @@ def bar(filled: int, total: int, width: int, on: str = "█", off: str = "░")
|
||||
"""生成进度条字符串(不含颜色)。"""
|
||||
if total <= 0:
|
||||
total = 1
|
||||
filled = max(0, min(width, int(round(width * filled / total))))
|
||||
filled = max(0, min(width, round(width * filled / total)))
|
||||
return on * filled + off * (width - filled)
|
||||
|
||||
|
||||
@@ -127,14 +128,15 @@ def enable_ansi() -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def terminal_size() -> Tuple[int, int]:
|
||||
def terminal_size() -> tuple[int, int]:
|
||||
size = shutil.get_terminal_size(fallback=(110, 30))
|
||||
return max(MIN_TERM_WIDTH, size.columns), max(18, size.lines)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- 绘制工具
|
||||
def draw_box(title: str, body: List[str], width: int, accent: Optional[str] = None,
|
||||
use_color: bool = True) -> List[str]:
|
||||
def draw_box(
|
||||
title: str, body: list[str], width: int, accent: str | None = None, use_color: bool = True
|
||||
) -> list[str]:
|
||||
"""画一个带标题的方框,返回行列表(宽度精确等于 ``width``)。"""
|
||||
inner = max(0, width - 2)
|
||||
head = f" {title} "
|
||||
@@ -144,30 +146,34 @@ def draw_box(title: str, body: List[str], width: int, accent: Optional[str] = No
|
||||
top = "┌─" + head + "─" * fill_len + "┐"
|
||||
bottom = "└" + "─" * inner + "┘"
|
||||
lines = [top]
|
||||
for row in body:
|
||||
lines.append("│" + pad(truncate(row, inner), inner) + "│")
|
||||
lines.extend("│" + pad(truncate(row, inner), inner) + "│" for row in body)
|
||||
lines.append(bottom)
|
||||
return lines
|
||||
|
||||
|
||||
def hstack(left: List[str], right: List[str], gap: int = 1) -> List[str]:
|
||||
def hstack(left: list[str], right: list[str], gap: int = 1) -> list[str]:
|
||||
"""把两列行按行并排,短的一侧用空格补齐。"""
|
||||
height = max(len(left), len(right))
|
||||
left_width = max((display_width(line) for line in left), default=0)
|
||||
out = []
|
||||
for i in range(height):
|
||||
l = left[i] if i < len(left) else ""
|
||||
r = right[i] if i < len(right) else ""
|
||||
out.append(pad(l, left_width) + " " * gap + r)
|
||||
left_line = left[i] if i < len(left) else ""
|
||||
right_line = right[i] if i < len(right) else ""
|
||||
out.append(pad(left_line, left_width) + " " * gap + right_line)
|
||||
return out
|
||||
|
||||
|
||||
class TuiApp:
|
||||
"""交互式界面主程序。"""
|
||||
|
||||
def __init__(self, engine: Any, save_path: Optional[str] = None,
|
||||
use_color: bool = True, autosave: bool = True,
|
||||
server_label: str = "") -> None:
|
||||
def __init__(
|
||||
self,
|
||||
engine: Any,
|
||||
save_path: str | None = None,
|
||||
use_color: bool = True,
|
||||
autosave: bool = True,
|
||||
server_label: str = "",
|
||||
) -> None:
|
||||
# engine 既可以是本地 FarmEngine,也可以是远程 RemoteFarm——只要接口一致
|
||||
self.engine = engine
|
||||
self.save_path = save_path or default_save_path()
|
||||
@@ -177,11 +183,11 @@ class TuiApp:
|
||||
self.reader = KeyReader()
|
||||
|
||||
self.running = True
|
||||
self.mode = "main" # main / help / select_crop / select_animal / confirm_reset
|
||||
self.mode = "main" # main / help / select_crop / select_animal / confirm_reset
|
||||
self.plot_cursor = 0
|
||||
self.animal_cursor = 0
|
||||
self.grid_cols = 3
|
||||
self.menu: List[Dict[str, Any]] = []
|
||||
self.menu: list[dict[str, Any]] = []
|
||||
self.menu_title = ""
|
||||
self.pending_action = ""
|
||||
self.message = ""
|
||||
@@ -192,20 +198,18 @@ class TuiApp:
|
||||
# ================================================================ 生命周期
|
||||
def _enter(self) -> None:
|
||||
if enable_ansi() and os.name == "nt":
|
||||
try:
|
||||
# 个别终端不支持重新配置,失败就按原编码继续
|
||||
with contextlib.suppress(Exception):
|
||||
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
||||
except Exception:
|
||||
pass
|
||||
sys.stdout.write("\x1b[?25l\x1b[2J\x1b[H")
|
||||
sys.stdout.write(C_RESET)
|
||||
sys.stdout.flush()
|
||||
|
||||
def _exit(self) -> None:
|
||||
if self.autosave:
|
||||
try:
|
||||
self.engine.save(self.save_path) # 本地引擎写文件;远程引擎让服务器落盘
|
||||
except Exception:
|
||||
pass
|
||||
# 本地引擎写文件;远程引擎让服务器落盘
|
||||
with contextlib.suppress(Exception):
|
||||
self.engine.save(self.save_path)
|
||||
self.reader.close()
|
||||
sys.stdout.write(C_RESET + "\x1b[?25h\n")
|
||||
sys.stdout.flush()
|
||||
@@ -348,7 +352,7 @@ class TuiApp:
|
||||
self._flash(f"{plot.index + 1} 号地已种了作物,先收获再说", C_RED)
|
||||
return
|
||||
|
||||
items: List[Dict[str, Any]] = []
|
||||
items: list[dict[str, Any]] = []
|
||||
catalog = {c["key"]: c for c in self.engine.catalog()["crops"]}
|
||||
for key, info in config.CROPS.items():
|
||||
entry = catalog.get(key, {})
|
||||
@@ -470,7 +474,7 @@ class TuiApp:
|
||||
middle = hstack(farm_col, side_col, gap=1)
|
||||
bottom = self._render_bottom(snapshot, width)
|
||||
|
||||
lines: List[str] = []
|
||||
lines: list[str] = []
|
||||
lines.append(self._render_header(snapshot, width))
|
||||
lines.extend(self._render_status(snapshot, width))
|
||||
lines.extend(middle)
|
||||
@@ -480,15 +484,14 @@ class TuiApp:
|
||||
lines = lines[: height - 1]
|
||||
|
||||
out = ["\x1b[H"]
|
||||
for line in lines:
|
||||
out.append(truncate(line, width) + "\x1b[K\r\n")
|
||||
out.extend(truncate(line, width) + "\x1b[K\r\n" for line in lines)
|
||||
# 清掉本轮剩余行
|
||||
out.append("\x1b[J")
|
||||
sys.stdout.write("".join(out))
|
||||
sys.stdout.flush()
|
||||
|
||||
# ---------------------------------------------------------- header
|
||||
def _render_header(self, snap: Dict[str, Any], width: int) -> str:
|
||||
def _render_header(self, snap: dict[str, Any], width: int) -> str:
|
||||
title = color(" SimpleFarm", C_BOLD + C_GREEN, self.use_color)
|
||||
if self.server_label:
|
||||
title += color(f" {self.server_label} ", C_BOLD + C_CYAN, self.use_color)
|
||||
@@ -514,14 +517,12 @@ class TuiApp:
|
||||
)
|
||||
|
||||
gap = max(1, width - display_width(left) - display_width(right) - 1)
|
||||
line = left + "─" * gap + right + "╮"
|
||||
return line
|
||||
return left + "─" * gap + right + "╮"
|
||||
|
||||
# ---------------------------------------------------------- 状态区
|
||||
def _render_status(self, snap: Dict[str, Any], width: int) -> List[str]:
|
||||
def _render_status(self, snap: dict[str, Any], width: int) -> list[str]:
|
||||
net_worth = snap["net_worth"]
|
||||
goal = snap["next_goal"]
|
||||
reached = snap["goal_index"]
|
||||
|
||||
ap_ratio = snap["ap"] / max(1, snap["max_ap"])
|
||||
ap_color = C_GREEN if ap_ratio > 0.4 else (C_ORANGE if ap_ratio > 0 else C_RED)
|
||||
@@ -568,14 +569,14 @@ class TuiApp:
|
||||
return [pad(line1, width), pad(line2, width)]
|
||||
|
||||
# ---------------------------------------------------------- 农田
|
||||
def _render_farm(self, snap: Dict[str, Any], width: int) -> List[str]:
|
||||
def _render_farm(self, snap: dict[str, Any], width: int) -> list[str]:
|
||||
inner = width - 2
|
||||
# 格子之间留 1 列间隙,避免相邻边框粘成 "││"
|
||||
cols = max(1, (inner + 1) // (CELL_WIDTH + 1))
|
||||
self.grid_cols = cols
|
||||
plots = snap["plots"]
|
||||
|
||||
rows: List[str] = []
|
||||
rows: list[str] = []
|
||||
for start in range(0, len(plots), cols):
|
||||
chunk = plots[start : start + cols]
|
||||
parts = [self._render_cell(cell, width).split("\n") for cell in chunk]
|
||||
@@ -590,7 +591,7 @@ class TuiApp:
|
||||
title = f"农田 {snap['plot_count']}/{snap['max_plots']} 块"
|
||||
return draw_box(title, rows, width, accent=C_GREEN, use_color=self.use_color)
|
||||
|
||||
def _render_cell(self, plot: Dict[str, Any], farm_width: int) -> str:
|
||||
def _render_cell(self, plot: dict[str, Any], farm_width: int) -> str:
|
||||
selected = plot["index"] == self.plot_cursor
|
||||
border = C_BOLD + C_GREEN if selected else C_DIM
|
||||
|
||||
@@ -611,9 +612,12 @@ class TuiApp:
|
||||
name_text = color(f" {plot['position']} {info['name']}", C_WHITE, self.use_color)
|
||||
|
||||
ratio = plot["growth"] / max(1, plot["grow_days"])
|
||||
filled_count = max(0, min(10, int(round(ratio * 10))))
|
||||
progress = color("█" * filled_count + "░" * (10 - filled_count),
|
||||
C_GREEN if plot["is_ripe"] else C_CYAN, self.use_color)
|
||||
filled_count = max(0, min(10, round(ratio * 10)))
|
||||
progress = color(
|
||||
"█" * filled_count + "░" * (10 - filled_count),
|
||||
C_GREEN if plot["is_ripe"] else C_CYAN,
|
||||
self.use_color,
|
||||
)
|
||||
|
||||
water_level = plot["moisture"]
|
||||
water = color(
|
||||
@@ -629,30 +633,40 @@ class TuiApp:
|
||||
top = color("╭" + "─" * CELL_INNER + "╮", border, self.use_color)
|
||||
bottom = color("╰" + "─" * CELL_INNER + "╯", border, self.use_color)
|
||||
body = [
|
||||
color("│", border, self.use_color) + pad(line1, CELL_INNER) + color("│", border, self.use_color),
|
||||
color("│", border, self.use_color) + pad(line2, CELL_INNER) + color("│", border, self.use_color),
|
||||
color("│", border, self.use_color)
|
||||
+ pad(line1, CELL_INNER)
|
||||
+ color("│", border, self.use_color),
|
||||
color("│", border, self.use_color)
|
||||
+ pad(line2, CELL_INNER)
|
||||
+ color("│", border, self.use_color),
|
||||
]
|
||||
return "\n".join([top] + body + [bottom])
|
||||
return "\n".join([top, *body, bottom])
|
||||
|
||||
# ---------------------------------------------------------- 侧栏
|
||||
def _render_side(self, snap: Dict[str, Any], side_width: int = SIDE_WIDTH) -> List[str]:
|
||||
def _render_side(self, snap: dict[str, Any], side_width: int = SIDE_WIDTH) -> list[str]:
|
||||
animals = snap["animals"]
|
||||
rows: List[str] = []
|
||||
rows: list[str] = []
|
||||
if not animals:
|
||||
rows.append(color(" 牧场空空的,按 B 购买", C_DIM, self.use_color))
|
||||
rows.append(color(" 鸡/羊/牛需每日喂食才产出", C_DIM, self.use_color))
|
||||
else:
|
||||
# 只渲染一个跟随光标的窗口,避免牲畜变多后把界面撑爆
|
||||
start = max(0, min(max(0, len(animals) - MAX_VISIBLE_ANIMALS),
|
||||
self.animal_cursor - MAX_VISIBLE_ANIMALS // 2))
|
||||
start = max(
|
||||
0,
|
||||
min(
|
||||
max(0, len(animals) - MAX_VISIBLE_ANIMALS),
|
||||
self.animal_cursor - MAX_VISIBLE_ANIMALS // 2,
|
||||
),
|
||||
)
|
||||
for index in range(start, min(len(animals), start + MAX_VISIBLE_ANIMALS)):
|
||||
animal = animals[index]
|
||||
focused = index == self.animal_cursor
|
||||
marker = color("▸", C_BOLD + C_GREEN, self.use_color) if focused else " "
|
||||
|
||||
if animal["pending"] > 0:
|
||||
status = color(f"{animal['product']}x{animal['pending']}",
|
||||
C_BOLD + C_GOLD, self.use_color)
|
||||
status = color(
|
||||
f"{animal['product']}x{animal['pending']}", C_BOLD + C_GOLD, self.use_color
|
||||
)
|
||||
elif animal["is_sick"]:
|
||||
status = color("虚弱!", C_RED, self.use_color)
|
||||
elif not animal["fed"]:
|
||||
@@ -662,18 +676,22 @@ class TuiApp:
|
||||
else:
|
||||
status = color(f"再{animal['days_to_product']}天", C_GRAY, self.use_color)
|
||||
|
||||
head = marker + color(f" #{animal['id']} {animal['name']}",
|
||||
C_WHITE, self.use_color)
|
||||
head = marker + color(f" #{animal['id']} {animal['name']}", C_WHITE, self.use_color)
|
||||
rows.append(pad(head, 13) + status)
|
||||
|
||||
hidden = len(animals) - start - MAX_VISIBLE_ANIMALS
|
||||
if hidden > 0:
|
||||
rows.append(color(f" …还有 {hidden} 只(Tab 切换)", C_DIM, self.use_color))
|
||||
pasture = draw_box(f"牧场 {snap['animal_count']}/{snap['max_animals']}", rows,
|
||||
side_width, accent=C_ORANGE, use_color=self.use_color)
|
||||
pasture = draw_box(
|
||||
f"牧场 {snap['animal_count']}/{snap['max_animals']}",
|
||||
rows,
|
||||
side_width,
|
||||
accent=C_ORANGE,
|
||||
use_color=self.use_color,
|
||||
)
|
||||
|
||||
# ---- 市场行情(中国习惯:涨红跌绿)
|
||||
market_rows: List[str] = []
|
||||
market_rows: list[str] = []
|
||||
name_by_key = {k: v["name"] for k, v in config.CROPS.items()}
|
||||
ranked = sorted(
|
||||
((k, v) for k, v in snap["market"].items() if not k.startswith("animal:")),
|
||||
@@ -694,19 +712,20 @@ class TuiApp:
|
||||
market_rows.append(
|
||||
color(f" 饲料 {config.FEED_UNIT_PRICE} 金币/份(按 K 购买)", C_DIM, self.use_color)
|
||||
)
|
||||
market = draw_box("市场行情", market_rows, side_width,
|
||||
accent=C_CYAN, use_color=self.use_color)
|
||||
market = draw_box(
|
||||
"市场行情", market_rows, side_width, accent=C_CYAN, use_color=self.use_color
|
||||
)
|
||||
|
||||
return pasture + market
|
||||
|
||||
# ---------------------------------------------------------- 底部
|
||||
def _render_bottom(self, snap: Dict[str, Any], width: int) -> List[str]:
|
||||
def _render_bottom(self, snap: dict[str, Any], width: int) -> list[str]:
|
||||
if self.mode == "help":
|
||||
return self._render_help(width)
|
||||
if self.mode in ("select_crop", "select_animal"):
|
||||
return self._render_menu(width)
|
||||
|
||||
rows: List[str] = []
|
||||
rows: list[str] = []
|
||||
now = time.time()
|
||||
if self.message and now < self.message_until:
|
||||
rows.append(color(" " + self.message, self.message_color, self.use_color))
|
||||
@@ -737,8 +756,8 @@ class TuiApp:
|
||||
]
|
||||
return log_box + [pad(h, width) for h in hint]
|
||||
|
||||
def _render_menu(self, width: int) -> List[str]:
|
||||
rows: List[str] = []
|
||||
def _render_menu(self, width: int) -> list[str]:
|
||||
rows: list[str] = []
|
||||
rows.append(color(" " + self.menu_title + "(按 Esc 取消)", C_CYAN, self.use_color))
|
||||
for i, item in enumerate(self.menu, start=1):
|
||||
if item["locked"]:
|
||||
@@ -752,32 +771,60 @@ class TuiApp:
|
||||
rows.append(label + pad("", max(1, 20 - display_width(label))) + detail)
|
||||
return draw_box("菜单", rows, width, accent=C_GOLD, use_color=self.use_color)
|
||||
|
||||
def _render_help(self, width: int) -> List[str]:
|
||||
def _render_help(self, width: int) -> list[str]:
|
||||
rows = [
|
||||
color(" 玩法目标:靠种地和养殖把净资产做到 10000 金币,解锁全部 5 个里程碑。", C_WHITE, self.use_color),
|
||||
color(
|
||||
" 玩法目标:靠种地和养殖把净资产做到 10000 金币,解锁全部 5 个里程碑。",
|
||||
C_WHITE,
|
||||
self.use_color,
|
||||
),
|
||||
"",
|
||||
color(" 时间", C_BOLD + C_GOLD, self.use_color)
|
||||
+ color(" 每 15 秒自动推进一天,作物成长、牲畜产奶、市场价格都在这一天结算。", C_GRAY, self.use_color),
|
||||
+ color(
|
||||
" 每 15 秒自动推进一天,作物成长、牲畜产奶、市场价格都在这一天结算。",
|
||||
C_GRAY,
|
||||
self.use_color,
|
||||
),
|
||||
color(" 水分", C_BOLD + C_GOLD, self.use_color)
|
||||
+ color(" 作物每天消耗 1 点水分,水为 0 就停止生长。按 W 浇水恢复到 3 点。", C_GRAY, self.use_color),
|
||||
+ color(
|
||||
" 作物每天消耗 1 点水分,水为 0 就停止生长。按 W 浇水恢复到 3 点。",
|
||||
C_GRAY,
|
||||
self.use_color,
|
||||
),
|
||||
color(" 行动点", C_BOLD + C_GOLD, self.use_color)
|
||||
+ color(" 每天 10 点,播种/浇水/喂食各消耗 1 点,收获和收取免费。", C_GRAY, self.use_color),
|
||||
+ color(
|
||||
" 每天 10 点,播种/浇水/喂食各消耗 1 点,收获和收取免费。", C_GRAY, self.use_color
|
||||
),
|
||||
color(" 牲畜", C_BOLD + C_GOLD, self.use_color)
|
||||
+ color(" 必须每天喂食才会产出;连续 3 天没吃东西会离开农场。", C_GRAY, self.use_color),
|
||||
+ color(
|
||||
" 必须每天喂食才会产出;连续 3 天没吃东西会离开农场。", C_GRAY, self.use_color
|
||||
),
|
||||
color(" 市场", C_BOLD + C_GOLD, self.use_color)
|
||||
+ color(" 作物和畜产品价格每天波动 ±18%,高点卖出更赚。", C_GRAY, self.use_color),
|
||||
"",
|
||||
color(" 快捷键", C_BOLD + C_GOLD, self.use_color)
|
||||
+ color(" P 播种 / W 浇水 / H 收获 / E 一键收获 / F 喂食 / C 收取产出", C_GRAY, self.use_color),
|
||||
+ color(
|
||||
" P 播种 / W 浇水 / H 收获 / E 一键收获 / F 喂食 / C 收取产出",
|
||||
C_GRAY,
|
||||
self.use_color,
|
||||
),
|
||||
color(" ", C_GRAY, self.use_color)
|
||||
+ color(" B 买动物 / L 开垦土地 / K 买饲料 / N 休息 / 空格 暂停 / S 存档 / R 重置", C_GRAY, self.use_color),
|
||||
+ color(
|
||||
" B 买动物 / L 开垦土地 / K 买饲料 / N 休息 / 空格 暂停 / S 存档 / R 重置",
|
||||
C_GRAY,
|
||||
self.use_color,
|
||||
),
|
||||
"",
|
||||
color(" AI 接入", C_BOLD + C_GOLD, self.use_color)
|
||||
+ color(" 游戏跑在服务器进程里,HTTP 接口与 WebSocket 通道同时开放,",
|
||||
C_GRAY, self.use_color),
|
||||
+ color(
|
||||
" 游戏跑在服务器进程里,HTTP 接口与 WebSocket 通道同时开放,",
|
||||
C_GRAY,
|
||||
self.use_color,
|
||||
),
|
||||
color(" ", C_GRAY, self.use_color)
|
||||
+ color(" AI 与你在同一个农场里操作(见 README「给 AI 的接口」)。",
|
||||
C_GRAY, self.use_color),
|
||||
+ color(
|
||||
" AI 与你在同一个农场里操作(见 README「给 AI 的接口」)。", C_GRAY, self.use_color
|
||||
),
|
||||
"",
|
||||
color(" 按任意键返回", C_DIM, self.use_color),
|
||||
]
|
||||
|
||||
Reference in new issue
Block a user