--- url: /app/install.md description: 安装 Tuack-NG 到你的系统。 --- Tuack-NG 目前支持 Linux 和 Windows,其他操作系统可能可用,但不保证兼容性。 目前支持 x86\_64 架构,Linux 上 Arm64 架构,Windows 上 x86 架构,以及 Nix 包管理器,其他架构可能需要自行编译安装,且不保证兼容性。 ## Windows 在 [Tuack-NG 的 Release 界面](https://github.com/tuack-ng/tuack-ng/releases) 下载 `tuack-ng-windows-x86_64.zip`(32 位请选择 `tuack-ng-windows-x86.zip`),解压到任意目录即可使用。 为了方便使用,推荐将解压出来的目录加入 `PATH` 环境变量,请自行搜索教程。 ## Debian 及衍生版(如 Ubuntu) 在 [Tuack-NG 的 Release 界面](https://github.com/tuack-ng/tuack-ng/releases) 下载 `tuack-ng-linux-x86_64.deb`(Arm 架构请选择 `tuack-ng-linux-arm64.deb`),并使用以下方法安装: ```bash apt install [下载下来的安装包名字].deb ``` ## Arch Linux 及衍生版(AUR) ::: tabs \== yay ```bash yay -S tuack-ng-bin ``` \== paru ```bash paru -S tuack-ng-bin ``` \== 手动安装 > \[!warning] 警告 > 仅限 Arch Linux 及衍生版可用,其他发行版不可以使用以下方法。 ```bash git clone https://aur.archlinux.org/tuack-ng-bin.git cd https://aur.archlinux.org/tuack-ng-bin.git makepkg -si ``` ::: ## Nix / NixOS > \[!note] 附注 > > 这种方法也许兼容 MacOS,但开发者没有设备测试,仅供参考。 本仓库附带一个 Nix Flake。你可以使用任何方式使用该 flake,比如: ```bash nix profile add github:tuack-ng/tuack-ng ``` ## 其他发行版 暂时没有官方支持的安装方式。 如果你愿意为你的发行版贡献一个安装包/源,欢迎提交贡献。 --- --- url: /app/project/overview.md description: Tuack-NG **比赛 - 比赛日 - 题目** 的三层工程结构。 --- ## 比赛 这是整个工程的**根目录**,代表着整个比赛(比如 NOIP 2025),其下包括三个组成部分: * `conf.json`:工程的配置文件。 * `precaution.md`:如果使用了渲染 PDF 功能,此文件的内容将可能被作为注意信息显示。 * 文件夹:**比赛日**的文件夹,包含各个比赛日。 ## 比赛日 这是工程的**第二层级**,代表着整个比赛日(比如省选的 Day1, Day2)。当然,如果只有一天,只建立一个比赛日也可以。其下包括两个组成部分: * `conf.json`:比赛日的配置文件。 * 文件夹:**题目**的文件夹,包含各个题目。 ## 题目 这是工程的**第三层级**,代表着比赛题(比如 NOIP 2025 的糖果店 (candy))。当然,如果只有一天,只建立一个文件夹也可以。其下可能包括多个组成部分: * `conf.json`:题目的配置文件。 * `statement.md`:这道题的题面。 * `data`:这道题的数据。 * `sample`:这道题的样例。 > 以下目录均为建议做法,我们推荐您这样做。 * `tests`:这道题的测试用例,包括 std。 * `gen`:这道题的数据生成器。 * `chk`:这道题的 Special Judge。 * `interactive`:这道题的交互库和交互头文件。 * `tables`:这道题使用的 Lua 表格。 * `solution`:这道题的题解。 ## 输出产物 这些产物可能因为调用的位置不同,而在不同层级的目录被创建。 * `statements`:这道题/比赛日/比赛的渲染产物。 * `dump`:这道题/比赛日的导出产物。 三层结构并不是唯一可行的组织结构。如果你想,可以将所有题目整合到同一个文件夹等等,但是 Tuack-NG 不保证在这些使用场景下会正常工作。 --- --- url: /app/project/config.md description: 关于 Tuack-NG 三层工程的配置文件。 --- ## 比赛配置文件 ```json { "version": 7, "folder": "contest", "name": "myoi", "subdir": [], "title": "试题标题", "short title": "试题副标题" } ``` | 字段名 | 值类型 | 描述 | | ------------- | ---------- | ------------------------------------------------------------- | | `version` | `integer` | 配置文件版本 | | `folder` | `string` | 工程层级标识,比赛配置文件必须为 `contest` | | `name` | `string` | 比赛的英文名称(仅 Tuack-NG 使用,不会体现在渲染/导出产物中) | | `subdir` | `string[]` | 比赛子目录(比赛日)名称列表,将会按照列表顺序组织比赛日 | | `title` | `string` | 比赛标题 | | `short title` | `string` | 比赛副标题 | | `use-pretest` | `boolean?` | 是否启用预测试点(目前没有用途) | | `noi-style` | `boolean?` | 是否启用 NOI 风格,详见 [渲染目标](../ren/targets) | | `file-io` | `boolean?` | 是否启用文件 IO | 其中 `use-pretest`,`noi-style`,`file-io` 会向下继承,并且如果未指定,则会在渲染/测试/导出时,使用模板默认值。 ## 比赛日配置文件 ```json { "version": 7, "folder": "day", "name": "day1", "subdir": [], "title": "场次标题", "compile": { "cpp": "-O2 -std=c++14 -static" }, "start time": [ 1970, 1, 1, 0, 0, 0 ], "end time": [ 1970, 1, 1, 0, 0, 0 ] } ``` | 字段名 | 值类型 | 描述 | | ------------- | ------------------ | --------------------------------------------------------------- | | `version` | `integer` | 配置文件版本 | | `folder` | `string` | 工程层级标识,比赛日配置文件必须为 `day` | | `name` | `string` | 比赛日的英文名称(仅 Tuack-NG 使用,不会体现在渲染/导出产物中) | | `subdir` | `string[]` | 比赛子目录(题目)名称列表,将会按照列表顺序组织题目 | | `title` | `string` | 场次标题 | | `compile` | `{string: string}` | 某种语言的编译选项,键应为这门语言的文件名后缀 | | `start time` | `integer[6]?` | 比赛的开始时间,格式为 `[年, 月, 日, 时, 分, 秒]` | | `end time` | `integer[6]?` | 比赛的结束时间,格式为 `[年, 月, 日, 时, 分, 秒]` | | `use-pretest` | `boolean?` | 是否启用预测试点(目前没有用途) | | `noi-style` | `boolean?` | 是否启用 NOI 风格,详见 [渲染目标](../ren/targets) | | `file-io` | `boolean?` | 是否启用文件 IO | 其中: * `start time` 和 `end time` 可以不指定,如果不指定,渲染产物中将不显示时间。 * `compile`,目前可以给 `cpp`,`c`,`rs`(Rust),`py`(Python),`java` 设置编译选项,并且理论上可以自行扩充。 * `use-pretest`,`noi-style`,`file-io` 会向下继承,并且如果未指定,则会在渲染/测试/导出时,使用模板默认值。 ## 题目配置文件 ```json { "version": 7, "folder": "problem", "type": "program", "name": "aplusb", "title": "题目名称", "time limit": 1.0, "memory limit": "512 MiB", // ... } ``` | 字段名 | 值类型 | 描述 | | -------------- | ---------- | ------------------------------------------------------------------------------- | | `version` | `integer` | 配置文件版本 | | `folder` | `string` | 工程层级标识,题目配置文件必须为 `problem` | | `name` | `string` | 题目的英文名称(仅 Tuack-NG 使用,不会体现在渲染/导出产物中) | | `title` | `string` | 题目标题 | | `type` | `string` | 题目类型:`program`(传统型)、`interactive`(交互型)或 `output`(提交答案型) | | `time limit` | `number` | 时间限制,单位为秒 | | `memory limit` | `string` | 空间限制,支持 SI 或 IEC 标准,详见 [ByteSize](https://github.com/bytesize-rs/) | | `samples` | `object[]` | 样例数据点列表,详见 [数据点配置](./data/configure#样例数据点) | | `data` | `object[]` | 正式数据点列表,详见 [数据点配置](./data/configure#正式数据点) | | `subtasks` | `object` | 子任务评分策略,详见 [数据点配置](./data/configure#子任务) | | `dmk` | `string` | 数据生成行为默认值,详见 [生成配置](../dmk/config#dmk) | | `args` | `object?` | 数据生成器全局参数,详见 [生成配置](../dmk/config#args) | | `generator` | `object?` | 数据生成器配置,详见 [数据生成器规范](../dmk/generator) | | `checker` | `object?` | SPJ 配置,详见 [SPJ 编写参考](../test/spj) | | `validator` | `object?` | Validator 配置,详见 [校验配置](../validate/config) | | `tests` | `object?` | 测试用例程序配置,详见 [测试配置](../test/config) | | `interactive` | `object?` | 交互题配置,详见 [交互题](../special/interactive/overview) | ### 相关章节 * [数据与测试用例](./data/overview) — 测试用例、数据与标准程序的基本概念 * [数据点配置](./data/configure) — 数据点、样例、子任务的完整配置参考 * [造数据](../dmk/overview) — 使用生成器和标程自动生成数据 * [数据生成器规范](../dmk/generator) — 生成器编写、参数传递、依赖管理 * [测试题目](../test/overview) — 运行测试并验证预期分数 * [测试配置](../test/config) — 测试用例程序、expected 表达式、SPJ 配置 * [SPJ 编写参考](../test/spj) — Special Judge 编写规范 * [校验](../validate/overview) — 校验输入数据的合法性 * [校验配置](../validate/config) — `validator` 字段配置 * [校验规范](../validate/spec) — Validator 的编写规范 * [题面格式](../ren/statement) — MiniJinja 模板、sample/tools 函数 * [渲染目标](../ren/targets) — 支持的渲染格式及依赖 --- --- url: /app/project/precaution.md description: Tuack-NG 比赛目录下的注意事项文件。 --- 在 Tuack-NG 比赛目录下,有一个 `precaution.md` 文件。 在部分渲染模板开头,会打印这个文件的内容,作为注意事项。 这个文件的默认内容(CCF 官方内容)如下: ```md **注意事项(请仔细阅读)** 1. 文件名(程序名和输入输出文件名)必须使用英文小写。 2. `main` 函数的返回值类型必须是 `int`,程序正常结束时的返回值必须是 0。 3. 提交的程序代码文件的放置位置请参考各省的具体要求。 4. 因违反以上三点而出现的错误或问题,申诉时一律不予受理。 5. 若无特殊说明,结果的比较方式为全文比较(过滤行末空格及文末回车)。 6. 选手提交的程序源文件必须不大于 100KB。 7. 程序可使用的栈空间内存限制与题目的内存限制一致。 8. 全国统一评测时采用的机器配置为:Intel(R) Core(TM) i7-8700K CPU @3.70GHz,内存 32GB。上述时限以此配置为准。 9. 只提供 Linux 格式附加样例文件。 10. 评测在当前最新公布的 NOI Linux 下进行,各语言的编译器版本以此为准。 ``` 你可以自行修改,这个文件支持 Markdown 语法。 如果不想留下注意事项,**请清空而非删除**。 --- --- url: /app/project/data/overview.md description: Tuack-NG 中测试用例、数据与标准程序的基本概念。 --- ## 测试用例 测试用例是用于解决这道题的一系列程序,其中可能包括暴力程序、不应通过的错误解法和标准程序。 你可以配置每个测试用例的预期分数,Tuack-NG 会在没有达到预期时发出警告。详见 [测试配置](../../test/config)。 ## 数据 数据是用于测试测试用例(在开发时)与测试用例代码(在评测时)的输入/输出文件。 Tuack-NG 主要注重前者,后者使用 `dump` 命令交给评测机完成。详见 [数据点配置](./configure)。 ## 标准程序 标准程序是测试用例中正确、标准且应当通过所有数据的代码。 ## 关系图 ```mermaid graph LR subgraph 出题工程 direction TD subgraph 测试用例 direction LR STD[标准程序] subgraph 非标准程序 direction LR BF[暴力程序] WA1[错误解法] end end D[Tuack-NG 评测 数据
输入/输出文件 .in / .out] STD -->|AC| D BF -->|预期部分分/WA| D WA1 -->|预期部分分/WA| D end subgraph 测试用例测评 direction TD TC[评测机测试 测试点
输入/输出文件 .in / .out] subgraph 测试用例程序 direction LR AC2[正解] WA2[错解] end AC2 -->|AC| TC WA2 -->|部分分/WA| TC end D -->|导出数据| TC STD <--> |对应| AC2 非标准程序 <--> |对应| WA2 ``` --- --- url: /app/project/data/configure.md description: 关于 Tuack-NG 中数据点、样例、子任务及相关功能的配置。 --- 要使用 Tuack-NG 的测试、生成数据等功能,必须先配置数据点。支持自动搜索和手动配置两种方式,两者互补。 ## 自动搜索 ```bash # 自动搜索样例 tuack-ng gen samples # 自动搜索正式数据 tuack-ng gen data # 自动搜索所有(样例 + 正式数据 + 测试代码) tuack-ng gen all ``` > \[!important] 注意 > 上述命令会将对应配置**覆盖**且不可恢复,建议在执行前进行备份。 自动搜索的详细行为见 [生成工程 - gen data / samples / code / all](../../gen/overview#gen-data--samples--code--all)。 ## 手动配置 数据点通过题目 `conf.json` 中的以下三个字段配置: ```json { "samples": [ /* ... */ ], "data": [ /* ... */ ], "subtasks": { /* ... */ } } ``` ## 样例数据点 `samples` 数组中的每个元素定义一个样例: ```json { "id": 1, "input": "1.in", "output": "1.ans", "dmk": "skip", "args": {} } ``` | 字段 | 类型 | 说明 | | -------- | --------- | ------------------------------------------------------------ | | `id` | `integer` | 样例编号,一般从 1 开始 | | `input` | `string?` | 输入文件名(相对于 `sample/` 目录)。未设置时默认 `{id}.in` | | `output` | `string?` | 输出文件名(相对于 `sample/` 目录)。未设置时默认 `{id}.ans` | | `dmk` | `string?` | 数据生成行为,详见 [生成配置](../../dmk/config#dmk) | | `args` | `object?` | 生成器参数,详见 [生成配置](../../dmk/config#args) | 样例在题面中的显示方式见 [题面格式 - sample](../../ren/statement#sample)。 ## 正式数据点 `data` 数组中的每个元素定义一个正式数据点或数据点组。 ### 单个数据点 ```json { "id": 1, "score": 5, "input": "1.in", "output": "1.ans", "subtask": 0, "args": {}, "dmk": "on" } ``` | 字段 | 类型 | 说明 | | --------- | --------- | ---------------------------------------------------------- | | `id` | `integer` | 测试点编号 | | `score` | `integer` | 测试点分值 | | `subtask` | `integer` | 所属子任务编号,默认 `0` | | `input` | `string?` | 输入文件名(相对于 `data/` 目录)。未设置时默认 `{id}.in` | | `output` | `string?` | 输出文件名(相对于 `data/` 目录)。未设置时默认 `{id}.ans` | | `args` | `object?` | 生成器参数,详见 [生成配置](../../dmk/config#args) | | `dmk` | `string?` | 数据生成行为,详见 [生成配置](../../dmk/config#dmk) | ### 数据点组 当多个测试点共享相同配置时,可用数组形式合并: ```json { "id": [2, 3, 4, 5], "score": 5, "subtask": 0, "args": {} } ``` | 字段 | 类型 | 说明 | | --------- | ----------- | ------------------------------ | | `id` | `integer[]` | 测试点编号列表 | | `score` | `integer` | **每个**测试点的分值(非总分) | | `subtask` | `integer` | 所属子任务编号 | > \[!note] > 数据点组不可设置 `input` 和 `output` 字段,文件名默认使用 `{id}.in` / `{id}.ans`。 ## 子任务 `subtasks` 字段使用键值对配置评分策略: ```json { "0": "sum", "1": "min", "2": "max" } ``` | 值 | 说明 | | ----- | ----------------------------------------------------------------------------------------- | | `sum` | 子任务总分为各测试点分数之和。最常用的评分方式 | | `min` | 子任务总分为各测试点分数的最小值。适用于**捆绑测试/打包评测**:一个点错则整个子任务不得分 | | `max` | 子任务总分为各测试点分数的最大值。较少使用 | ## 相关章节 * [生成配置](../../dmk/config) — 数据生成行为与参数 * [数据生成器规范](../../dmk/generator) — 生成器编写、依赖管理 * [测试配置](../../test/config) — 测试用例程序、expected 表达式 * [SPJ 编写参考](../../test/spj) — Special Judge 编写规范 * [题面格式 - sample](../../ren/statement#sample) — 样例在题面中的显示方式 * [Lua 表格](../../ren/format/lua) — Lua 表格生成 --- --- url: /app/gen/overview.md description: 生成 Tuack-NG 的三层工程结构,以及自动检测样例、数据与测试用例。 --- ## 命令用法 ```txt 生成工程文件夹 Usage: tuack-ng gen [OPTIONS] Commands: contest 生成竞赛文件夹 day 生成竞赛日文件夹 problem 生成题目文件夹 data 自动检测数据 samples 自动检测样例 code 自动检测题解 all 自动检测所有(data + samples + code) lfs 生成 .gitattributes(Git LFS) complete 生成补全脚本 Options: -v, --verbose... 详细模式 ``` Tuack-NG 在执行 `gen` 时会自动修改配置文件中的相应字段(如 `subdir`),保持工程结构的一致性。 ## `gen contest` 使用 `tuack-ng gen contest` 以生成竞赛文件夹。 在当前目录下生成一个比赛文件夹,包含 `conf.json` 和 `precaution.md`。 ## `gen day` 使用 `tuack-ng gen day` 以生成比赛日文件夹。 在比赛目录下生成比赛日文件夹,自动将目录名加入父级 `conf.json` 的 `subdir`。支持同时生成多个比赛日。 ## `gen problem` 使用 `tuack-ng gen problem` 以生成题目文件夹。 在比赛日目录下生成题目文件夹,包含 `conf.json` 和 `statement.md`。支持同时生成多道题。生成的 `conf.json` 包含默认的数据点结构、样例配置和字段占位符,需根据题目实际内容修改。 ## `gen data` / `samples` / `code` / `all` 使用 `tuack-ng gen data`、`tuack-ng gen samples`、`tuack-ng gen code` 或 `tuack-ng gen all` 以自动检测文件。 自动检测工程中的已有文件并写入配置文件。 | 子命令 | 检测内容 | | --------- | ------------------------------------------ | | `data` | `data/` 目录下的 `.in` / `.ans` 配对文件 | | `samples` | `sample/` 目录下的 `.in` / `.ans` 配对文件 | | `code` | 递归查找源码文件,排除常见非题解目录 | | `all` | 依次执行 data、samples、code | 自动检测使用符合人类直觉的自然排序(`natord`)对文件排序后写入配置。 > \[!note] 注意 > `gen code` 会递归查询并排除不应查找的文件夹(如 `data/`、`sample/`、`gen/`、`chk/` 等),只检测常见的题解代码文件。 ## `gen lfs` 使用 `tuack-ng gen lfs` 以生成 Git LFS 配置。 为工程下的数据目录生成 `.gitattributes`,配置 Git LFS 跟踪规则。 ## `gen complete` 使用 `tuack-ng gen complete` 以生成 Shell 补全脚本。 生成 Shell 补全脚本,一般无需手动执行,包管理器会在安装时自动处理。 --- --- url: /app/ren/overview.md description: 使用 `tuack-ng ren` 将题面渲染为指定格式的 PDF 或 Markdown。 --- ## 命令用法 ```txt 渲染题面 Usage: tuack-ng ren [OPTIONS] Arguments: 渲染目标模板 Options: -v, --verbose... 详细模式 -s 不自动打开生成的 PDF ``` 本命令可在工程内任意层级执行,会在获取必要比赛(日)配置的同时,仅渲染当前目录下的内容。 ## 输出路径 渲染产物输出在工程 `statements//` 目录下,例如: ```txt myoi/ └── statements/ └── noi/ └── day1.pdf ``` 在比赛根目录执行时,将渲染所有比赛日;在比赛日目录执行时,仅渲染该比赛日;以此类推。 ## 渲染流程 使用 `tuack-ng ren` 以渲染题面。 1. 读取每道题的 `statement.md` 2. 展开 MiniJinja 模板,MiniJinja 可用语法详见 [MiniJinja 模板](./format/template) 3. 对题面进行解析、修补与转换 4. 编译为最终格式(PDF 或 Markdown) 如果想要了解 Tuack-NG 支持的渲染目标,详见 [渲染目标](./targets); 如果需要知道如何编写题面,详见 [题面格式](./statement)。 --- --- url: /app/ren/targets.md description: Tuack-NG 目前支持五种渲染目标。 --- ## 目标列表 | 目标 | 说明 | 输出格式 | | ---------- | ----------------- | ---------------------- | | `noi` | NOI 风格 PDF | Typst → PDF | | `ccpc` | CCPC 风格 PDF | Typst → PDF | | `loj` | LOJ 风格 Markdown | Markdown(含额外修补) | | `uoj` | UOJ 风格 Markdown | Markdown(含额外修补) | | `markdown` | 普通 Markdown | Markdown | LOJ 和 UOJ 目标在标准 Markdown 基础上做了额外语法修补,以兼容对应平台的题面格式。 ## 依赖 * `noi`、`ccpc` 目标需要系统安装 `typst` 命令行工具,并添加到 `PATH` 环境变量 * `loj`、`uoj`、`markdown` 目标无需外部依赖 ## 额外信息 * `loj` 修补了表格,以确保自动合并可以在 LOJ 中正确工作。 * `uoj` 将所有标题等级顺延一级,并将表格转换为 HTML 表格。 --- --- url: /app/ren/format/syntax.md description: Tuack-NG 支持的 Markdown 语法参考。 --- ## 基本 Markdown ```md # h1 ## h2 ### h3 #### h4 ##### h5 ###### h6 paragraph 1 paragraph 2 paragraph break _emphasis_ _emphasis_ **strong** **strong** ~~delete~~ ``` ## 代码块 ````md ```python print("python code block") ``` 这是 `inline code` ```` ## 引用 ```md > quote > > > quote in quote ``` ## 列表 ```md - item A - item B - item C 1. item 1 2. item 2 3. item 3 ``` ## 链接与图片 ```md [NOI website](https://noi.cn/) 简单链接: Inline ![img](image.jpg) image ``` ## 图片扩展 ### 居中图片 ```md :::figure{caption=居中图片。在这里添加一些图片描述。} ![1.jpg](image.jpg) ::: ``` ### 无标题图片块 ```md :::figure caption 参数是可选的。 文本也可以放进去。 ::: ``` ### 尺寸控制 ```md 小![small](image.jpg){height=4em} ![small](image.jpg){width=4em}图片 ``` 支持的单位:`pt`、`mm`、`cm`、`in`、`em` 和按页面比例的 `%`。 ## 表格 ### 对齐方式 ```md | 左对齐 | 居中对齐 | 右对齐 | 默认居中 | | :----- | :------: | -----: | ------- | | 内容 | 内容 | 内容 | 内容 | ``` ### 单元格合并 使用 `^` 表示与上一行相同的内容,`<` 表示与左一列相同的内容: ```md | 如下 | 进行 | 单元格 | 合并 | | :--: | :----------------: | :----------------: | :--: | | 1 | $\le 10$ | $\le 10$ | 无 | | 2 | ^ | ^ | 无 | | 3 | ^ | ^ | 无 | | 4 | $\le 3\times 10^5$ | ^ | 无 | | 5 | ^ | ^ | 无 | | 6 | ^ | $\le 3\times 10^5$ | 无 | | 7 | ^ | ^ | 无 | | 8 | ^ | ^ | 无 | | 9 | ^ | 跨列合并 1 | < | | 10 | 大格子 | < | 无 | | 11 | ^ | < | 无 | ``` * `^`:继承上一行同列的值(行合并) * `<`:继承左侧单元格的值(列合并) ## LaTeX 公式 ```md inline latex $a^2 + b^2 = c^2$ $$ \sum_{i=1}^n i = \frac{n(n+1)}{2} $$ ``` --- --- url: /app/ren/format/template.md description: Tuack-NG 支持的模板语法一览。 --- 题面文件(`statement.md`)使用 MiniJinja 作为模板引擎,可以在题面中动态访问题目、比赛日和比赛的配置数据。 > \[!note] 提示 > 下文中的 `{{ }}` 是 MiniJinja 的表达式语法。更多语法详见 [MiniJinja 文档](https://docs.rs/minijinja/latest/minijinja/syntax/index.html)。 > > `problem` 的数据结构可在 查看,同时我们提供了 [JSON Schema](https://gist.github.com/Pulsar33550336/ece6e5f24a760be04b3fb5c7b9b6fe16)(由 DeepSeek 编写,可能不准确)。 ## 上下文 | 变量 | 说明 | | -------------- | -------------------- | | `problem` | 当前渲染的题目配置 | | `day` | 当前渲染的比赛日配置 | | `contest` | 当前渲染的比赛配置 | | `data_cases` | 数据点列表 | | `sample_cases` | 样例列表 | 调用 `problem` 有两种等效的格式: ```md {{ problem.a.b }} {{ problem["a"]["b"] }} ``` 当属性名带有空格时(如 `"time limit"`),只能使用方括号语法: ```md {{ problem["time limit"] }} ``` ## 函数 ### sample 用于在题面中嵌入样例。 | 函数 | 说明 | | ----------------------------- | -------------------------------------------------------------------- | | `sample.text(sample_id: u32)` | 将指定 ID 的样例以标题 + 代码块的形式嵌入题面。适合简短的样例。 | | `sample.file(sample_id: u32)` | 在题面中加入文本,提示测试用例查看下发文件中对应的样例。适合大样例。 | `简短` 的意思是短而小,不是让你放一个虽然很小但是三页纸长的样例进去。 `sample.text()` 会在题面中生成以下内容: ````md ## 样例 N 输入 ```txt (文件内容) ``` ## 样例 N 输出 ```txt (文件内容) ``` ```` `sample.file()` 会生成一段提示文本,格式为: ```md 见测试用例目录下的 _{problem.name}/{problem.name}{sample_id}.in_ 与 _{problem.name}/{problem.name}{sample_id}.ans_。 ``` ### tools 提供数字格式化工具。 | 函数 | 说明 | | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `tools.hn(num: f64, style?: str)` | 将数字转换到适合人类阅读的形式,输出 LaTeX 格式但不带 `$$`。`style` 可选:`"x"` 科学计数法、`","` 逗号分隔。未指定时自动选择最紧凑的格式 | | `tools.comma(num: i64)` | 将整数转换为逗号分隔形式 | | `tools.cases(cases_vec)` | 将数字范围转换为紧凑的表示形式,自带 `$$`。如 `cases([1,2,3,5,7,8,9])` 会转换为 `$1 \sim 3, 5, 7 \sim 9$`。接受单个数字或数字列表 | 示例: ```md 时间限制:$${{ tools.hn(problem["time limit"]) }}$$ 秒 对于数据点 {{ tools.cases(data_cases[1].case) }}:…… ``` ### statement 别名 `s`,提供输入输出格式辅助。 | 函数 | 说明 | | ------------------------- | --------------------------------------------- | | `statement.input_file()` | 输出一段文本,要求测试用例从指定位置读入 | | `statement.output_file()` | 输出一段文本,要求测试用例输出到指定位置 | | `statement.table(path)` | 调用 Lua 脚本渲染表格,详见 [Lua 表格](./lua) | `input_file()` 与 `output_file()` 的输出取决于题目是否配置了文件 IO: * 文件 IO 时:`从文件 _{name}.in_ 中读入数据。` / `输出到文件 _{name}.out_ 中。` * 标准 IO 时:`从标准输入读入数据。` / `输出到标准输出。` ## 过滤器 MiniJinja 支持过滤器语法,例如: ```md {{ problem.data | length }} ``` 在此示例中,我们使用了 `problem.data`,因为我们需要获取确凿(去捆绑)的数据点数量。如果你需要获取原始配置中的数据点(包括分组等),请使用 `problem.orig_data` 或者 `data_cases`,后者是前者的简写版本。 ## 示例 ### 输出时间限制 ```md {{ problem["time limit"] }} ``` ### 输出测试点数目 ```md {{ problem.data | length }} ``` ### 输出样例 1 ```md {{ sample.text(1) }} ``` --- --- url: /app/ren/format/lua.md description: Tuack-NG 支持使用 Lua 脚本在题面中生成 Markdown 表格,适用于动态数据范围表等场景。 --- ## 概述 Lua 脚本放置在题目目录的 `tables/` 文件夹下,通过题面模板中的 `{{ s.table("filename.lua") }}`(或 `{{ statement.table("filename.lua") }}`)调用,返回一个 Markdown 表格。 ## 调用示例 ```txt myoi/day1/aplusb/ ├── conf.json ├── tables/ │ └── data_range.lua └── statement.md ``` 在 `statement.md` 中调用: ```md {{ s.table("tables/data_range.lua") }} ``` ## 使用方法 关于 Lua 语言本身,请自行搜寻教程。 在 Lua 脚本中,你可以通过全局 `tng` 表访问题目配置信息和工具函数。 ## 函数与上下文 ### `tng.config` | 字段 | 类型 | 说明 | | ------------------------- | ------------------ | ---------- | | `tng.config.contest` | `ContestConfig` | 比赛配置 | | `tng.config.day` | `ContestDayConfig` | 比赛日配置 | | `tng.config.problem` | `ProblemConfig` | 题目配置 | | `tng.config.sample_cases` | `table` | 样例列表 | | `tng.config.data_cases` | `table` | 数据点列表 | `contest`、`day` 与 `problem` 的内容应当与配置文件 JSON 中的一致。 `sample_cases` 和 `data_cases` 提供了一个 `:map(func)` 方法,作为函数式编程接口。它接受一个函数作为参数,对列表中的每个元素依次调用该函数,将返回值依次收集为一个新表,与 Rust 中的 `.iter().map(|x| ...).collect()` 类似。 ### `tng.tools` | 函数 | 说明 | | ----------------------- | ------------------------------------------------------- | | `int_lg(num)` | 整数位数(如 `int_lg(1000)` → `4`) | | `comma(num)` | 逗号分隔数字 | | `hn(num, style?)` | 人类可读格式(style: `"x"` 科学计数法,`","` 逗号分隔) | | `cases(value)` | 转数字范围为紧凑表示(接受数字、数字表) | | `italic(text)` | `*text*` | | `bold(text)` | `**text**` | | `strikethrough(text)` | `~~text~~` | | `inline_code(text)` | `` `text` `` | | `link(text, url)` | `[text](url)` | | `autolink(url)` | `` | | `inline_latex(formula)` | `$formula$` | ### `tng.table` 这是 Lua 风格表格的**核心方法**,详见下文。 ## 表格语法 `tng.table{...}`(Lua 语法糖,等价于 `tng.table({...})`)根据传入的表定义创建一个 Markdown 表格。 它返回一个不透明类型,你的 Lua 脚本必须以它的返回值作为返回值。 ```lua tng.table{ headers = {"列 1", "列 2", "列 3"}, align = {"center", "default", "right"}, data = { {"a", "b", "c"}, {"d", "e", "f"}, }, merge_rules = { { col = 1, merge_row = true } } } ``` | 字段 | 类型 | 说明 | | ------------- | ------------ | ----------------------------------------------------------------------------- | | `headers` | `string[]` | 表头 | | `align` | `string[]` | 对齐方式:`"default"`、`"center"`、`"left"`、`"right"`,长度需与 headers 一致 | | `data` | `string[][]` | 表格数据,每行长度需与 headers 一致 | | `merge_rules` | `table[]?` | 合并规则,可选,详见下文 | ### 合并 表格中合并可以通过 `^`(向上)与 `<`(向左)。 对于同一列内的合并,Tuack-NG 提供了自动合并的方法。 #### merge\_rules `merge_rules` 用于减少表格中的重复内容,提升可读性。当在某一列启用该选项时,Tuack-NG 会将该列中的相邻相同块自动合并。 每条规则包含: | 字段 | 类型 | 说明 | | ----------- | ---------------------- | ----------------------------------------------- | | `col` | `number` 或 `number[]` | 要应用合并的列号(1-indexed),可指定单列或多列 | | `merge_row` | `boolean` | 是否启用行合并 | #### 手动合并 对于复杂的合并逻辑以及跨列合并,Tuack-NG 允许你自行使用 `^` 与 `<` 指定合并方式。 ::: details 为什么不增加自动跨列合并? 考虑以下表格: | a | b | | --- | --- | | a | a | | a | a | | a | b | 我们无法推测得出,您想要的效果是 | a | b | | --- | --- | | a | a | | ^ | ^ | | ^ | b | 还是 | a | b | | --- | --- | | a | < | | ^ | < | | a | b | 因此,我们将决定权交给您,您可以自行编写 Lua 代码实现自定义合并逻辑。 ::: ### 示例 从数据点配置动态生成表格: ```lua -- tables/data.lua local il = tng.tools.inline_latex local hn = tng.tools.hn local special_map = { a = "所有边权相等", b = "图为一条链", c = "图为菊花图", } return tng.table { headers = { "测试点编号", il("n \\le"), il("k\\le"), "特殊性质" }, align = { "center", "center", "center", "center" }, data = tng.config.data_cases:map(function(case) local args = case.args return { tng.tools.cases(case.id), il(hn(args.n)), il(hn(args.k)), special_map[args.special] or "无" } end), merge_rules = { { col = { 2, 3, 4 }, merge_row = true } } } ``` --- --- url: /app/test/overview.md description: 使用 `tuack-ng test` 测试测试用例程序,验证标程和各种解法是否达到预期分数。 --- > \[!caution] 警告 > **强烈建议不要**将 Tuack-NG 作为评测机使用。Tuack-NG 的测试功能仅用于出题期间验证程序行为,没有反作弊与安全限制机制。 ## 命令用法 ```txt 使用题解代码测试 Usage: tuack-ng test [TARGET] Arguments: [TARGET] 目标类型 Possible values: - data: 正式测试数据 - sample: 样例数据 [default: data] ``` 本命令可在工程内任意层级执行,仅对当前目录层级下存在的题目进行测试。 ## 测评结果 | 结果 | 含义 | | ---- | --------------------------------------- | | AC | 答案正确(Accepted) | | WA | 答案错误(Wrong Answer) | | TLE | 超时(Time Limit Exceeded) | | MLE | 超内存(Memory Limit Exceeded) | | RE | 运行时错误(Runtime Error) | | CE | 编译错误(Compile Error) | | PC | 部分正确(Partial Credit,由 SPJ 返回) | | UKE | 未知错误(Unknown Error) | Tuack-NG 支持跨平台的时间和空间检测,会在 TLE/MLE 时终止程序并记录结果。 ## 测评输出 每道题的测评结果会打印到标准输出,同时会以 CSV 格式写入题目文件夹下: * `result.csv`:正式数据测试结果 * `result-sample.csv`:样例数据测试结果 如需查看详细的每题得分和预期比对,请配置 `tests` 字段中的 `expected` 表达式,详见 [测试配置](./config)。 --- --- url: /app/test/config.md description: 通过题目的 `conf.json` 配置测试用例和测试行为。 --- ## 测试用例 在题目的 `conf.json` 中,通过 `tests` 字段配置待测试的测试用例: ```json { "tests": { "std": { "expected": "== 100", "path": "tests/std.cpp" }, "b-force": { "expected": [">= 60", "<= 80"], "path": "tests/b.cpp" } } } ``` ### 字段说明 | 字段 | 类型 | 说明 | | ---------- | ---------------------- | ---------------------------------------- | | `键名` | `string` | 测试用例名称,任意字符串,不影响实际测试 | | `expected` | `string` 或 `string[]` | 期望得分表达式,见下方说明 | | `path` | `string` | 程序文件路径,相对题目文件夹 | 如果测试用例较多,可使用 `tuack-ng gen code` 自动检测并写入配置。 ## `expected` 表达式 `expected` 是一个**布尔表达式**的右侧部分,左侧为实际得分。支持的语法包括但不限于: | 表达式 | 含义 | | -------- | ------------------- | | `== 100` | 实际得分等于 100 | | `>= 60` | 实际得分大于等于 60 | | `<= 30` | 实际得分小于等于 30 | 可以传入字符串数组表示多个条件需同时满足: ```json "expected": [">= 10", "<= 60"] ``` `expected == 100` 的测试用例会被 `tuack-ng dmk` 用作标程(std)来生成答案文件,详见 [造数据](../dmk/overview#工作流程)。 ## SPJ ### `checker` 字段 配置 Special Judge,替代默认的全文比较。 ```json { "checker": { "data": { "source": "chk/chk.cpp", "deps": ["chk/testlib.h"] }, "sample": { "source": "chk/chk.cpp", "deps": [] } } } ``` | 字段 | 类型 | 说明 | | ---------------- | --------- | ---------------------------------------------- | | `checker.data` | `object` | 正式数据的 SPJ 配置 | | `checker.sample` | `object?` | 样例数据的 SPJ 配置,为 `null` 时回退到 `data` | 每个配置项包含: | 字段 | 类型 | 说明 | | -------- | ---------- | ---------------------------------------------------------- | | `source` | `string` | SPJ 源文件路径(相对题目目录) | | `deps` | `string[]` | 依赖文件列表,显式声明需要参与编译的文件(如 `testlib.h`) | 未配置 `checker` 时使用默认的全文比较(过滤行末空格及文末回车)。SPJ 编写规范详见 [SPJ 编写参考](./spj)。 ## 交互题 交互题的配置、测试与数据生成详见 [交互题](../special/interactive/overview)。 ## 文件 IO 文件 IO 通过 `file-io` 配置。 `file-io` 可在比赛和比赛日配置,详见 [工程配置文件](../project/config),未指定时默认值为 `true`。 --- --- url: /app/test/spj.md description: Special Judge(SPJ)用于评测答案不唯一的题目,Tuack-NG 采用 Testlib 格式的 SPJ。 --- ## 要求 SPJ 必须使用 [Testlib Checker](https://oi-wiki.org/tools/testlib/checker/) 编写,因为 Tuack-NG 依赖 Testlib 导出的 XML 结果文件。 关于如何使用 Testlib,请参见上述链接。 ## 位置 SPJ 源文件应放在题目目录的 `chk/` 文件夹下: ```txt myoi/day1/aplusb/ ├── chk/ │ ├── chk.cpp # SPJ 源文件 │ └── testlib.h # Testlib 头文件 └── ... ``` ## 部分分 Tuack-NG 支持两种部分分表示方式,返回值应为 0 到 100 之间的数字,映射到当前测试点分值后四舍五入到整数: ```cpp quitf(_pc(score), "获得 %d 分", score); quitp(score, "获得 %.2f 分", score); ``` ### `_pc(score)` `_pc(score)` 直接指定一个 0–100 的整数得分: ```cpp if (score >= 50) quitf(_pc(100), "全部正确"); else quitf(_pc(score * 2), "部分正确"); ``` ### `quitp(score)` `quitp(score)` 接受浮点数作为得分比例(0–100): ```cpp double score = 100.0 * correct / total; quitp(score, "正确率 %.2f%%", score); ``` ## 编译 Tuack-NG 使用以下命令编译 SPJ,你无法自行修改: ```bash g++ -O2 -std=c++23 -o ``` ## 示例 ```cpp // chk/chk.cpp #include "testlib.h" int main(int argc, char* argv[]) { registerTestlibCmd(argc, argv); int n = inf.readInt(); int juryAns = ans.readInt(); int partAns = ouf.readInt(); if (juryAns != partAns) quitf(_wa, "期望 %d,实际 %d", juryAns, partAns); double score = 100.0; quitp(score, "答案正确"); } ``` --- --- url: /app/dmk/overview.md description: 调用生成器生成输入数据,并调用标程生成输出数据。 --- ## 命令用法 ```txt 生成数据 Usage: tuack-ng dmk [OPTIONS] [OBJECT] Arguments: 目标类型 Possible values: - data: 正式测试数据 - sample: 样例数据 命令 Possible values: - gen: 生成(未生成的)数据 - regen: 重新生成数据(使用相同种子) - reset: 重置种子并重新生成数据 [OBJECT] 测试点选择 [default: all] Options: --validate[=]... 生成后校验输入(覆盖配置) -v, --verbose... 详细模式 ``` 本命令**只能在题目目录下执行**。 ### OBJECT 选择器 支持逗号和范围语法,例如 `1-2,3,5` 表示测试点 1、2、3、5。 同时,`all` 表示全部测试点。 如果没有指定这个参数,则默认为 `all`。 *** ## 目标与动作 使用 `tuack-ng dmk` 以生成数据。 ### `data` / `sample` | TARGET | 说明 | | -------- | ------------------------------- | | `data` | 正式测试数据,对应 `data/` 目录 | | `sample` | 样例数据,对应 `sample/` 目录 | ### `gen` / `regen` / `reset` | ACTION | 说明 | | ------- | ---------------------------------------- | | `gen` | 仅生成尚未生成的数据点(已有数据的跳过) | | `regen` | 使用相同的种子重新生成所有数据点 | | `reset` | 重置种子并重新生成所有数据点 | 三种动作对种子和数据的影响详见 [随机种子](./seed)。 ## 工作流程 1. 将数据生成器(C++)编译成可执行文件 & 将标程编译成可执行文件 2. 运行生成器产生 `.in` 文件 3. 运行标程读取 `.in` 并产生 `.ans` 文件 标程的选取规则为:在 `tests` 配置中寻找第一个 `expected == 100` 的测试用例,详见 [expected 表达式](../test/config#expected-表达式)。 进度条指示各阶段的执行状态,状态标签包括 `GEN`(绿色)、`REGEN`(绿色加粗)、`RESET`(青色加粗)、`SKIP`、`EMPTY`(品红色加粗)、`FAIL`(红色加粗)。 `dmk` 与 `args` 等配置字段详见 [生成配置](./config)。 ## 输入校验 生成输入后可使用 Validator 校验其合法性,详见 [校验输入](../validate/overview)。 默认行为由 `generator` 配置中的 `validate` 字段控制,也可通过 `--validate[=true|false]` 临时覆盖。 --- --- url: /app/dmk/config.md description: 数据生成相关的配置字段说明。 --- ## `dmk` 控制数据点的自动生成行为。可在题目级设置全局默认值,也可在每个数据点单独覆盖。 | 值 | 说明 | | ---------- | -------------------------- | | `"on"` | 同时生成输入和输出文件 | | `"skip"` | 跳过此数据点,使用静态文件 | | `"input"` | 仅生成输入文件 | | `"output"` | 仅生成输出文件 | 题目级默认值: ```json { "dmk": "on" } ``` 数据点级覆盖: ```json { "data": [ { "id": 1, "score": 10, "dmk": "skip" }, { "id": [2, 5], "score": 10, "dmk": "on" } ] } ``` ## `args` 传递给数据生成器的参数。可在题目级设置全局值,也可在每个数据点单独继承/覆盖。 支持以下类型值: | 类型 | 示例 | | ----------------- | ----------------- | | 整型(integer) | `114514` | | 浮点数(number) | `1919.810` | | 字符串(string) | `"hutao39tianyi"` | | 布尔值(boolean) | `false` | 题目级全局参数: ```json { "args": { "n": 1000, "m": 500 } } ``` 数据点级覆盖: ```json { "data": [ { "id": 1, "score": 10, "args": { "n": 100 } } ] } ``` 生成器通过命令行 `--key=value` 方式接收这些参数。详见 [数据生成器规范 - 参数传递](./generator#参数传递)。 --- --- url: /app/dmk/seed.md description: Tuack-NG 使用 `.seed` 文件记录每个测试点的随机种子,确保数据可复现。 --- ## `.seed` 文件 `.seed` 文件位于 `data/` 或 `sample/` 目录下,JSON 格式: ```json {"1": 12345678, "2": 87654321} ``` ## 三种动作与种子的关系 | 动作 | 行为 | | ------- | ---------------------------------------------------- | | `gen` | 仅给尚无种子的测试点分配新种子,已有数据不会重新生成 | | `regen` | 使用相同的种子重新生成所有数据点 | | `reset` | 丢弃所有旧种子,重新分配新种子并重新生成 | > \[!important] > `.seed` 文件应纳入版本管理,以保证数据生成的可复现性。 ## 注意事项 不建议手动修改 `.seed` 文件。如需强制重新生成所有数据(例如重造数据),请使用 `reset` 动作。 --- --- url: /app/dmk/generator.md description: Tuack-NG 使用 C++ 程序作为数据生成器,驱动测试数据的自动生成。 --- ## 生成器位置 使用 `tuack-ng dmk` 以调用生成器生成数据。 推荐按照以下方式在题目目录中组织生成器: ```txt myoi/day1/aplusb/ ├── conf.json ├── gen/ │ ├── gen.cpp # 数据生成器 │ └── gen_sample.cpp # 样例数据生成器 └── ... ``` ## 配置文件 在题目的 `conf.json` 中,通过 `generator` 字段配置生成器: ```json { "generator": { "data": { "gen": "gen/gen.cpp", "deps": ["gen/testlib.h"] }, "sample": { "gen": "gen/gen_sample.cpp", "deps": [] } } } ``` * `generator.data`:正式测试数据的生成器配置 * `generator.sample`:样例数据的生成器配置(可选) 如果 `generator.sample` 未配置,`dmk sample` 会回退使用 `generator.data` | 字段 | 类型 | 说明 | | ---------- | ---------- | -------------------------------------------------- | | `gen` | `string` | 生成器源文件路径(相对题目目录) | | `deps` | `string[]` | 依赖文件列表,当这些文件发生变化时会重新编译生成器 | | `validate` | `boolean?` | 生成输入后是否用 Validator 校验,默认 `false`,详见 [校验配置](../validate/config) | ## 输入校验 如果配置了 Validator(见 [校验配置](../validate/config)),可以在生成输入后自动校验其合法性。 通过 `generator.data.validate`(或 `generator.sample.validate`)开启: ```json { "generator": { "data": { "gen": "gen/gen.cpp", "deps": [], "validate": true } } } ``` 校验失败时,该数据点的输入会标记为 `FAIL`。 也可以在命令行临时覆盖:`tuack-ng dmk data gen --validate` 强制开启,`--validate=false` 强制关闭。 ## 编写规范 ### 使用 Testlib 强烈建议使用 [Testlib](https://github.com/MikeMirzayanov/testlib) 编写生成器,以获得跨平台一致的随机数和健壮的命令行参数解析。 ```cpp #include "testlib.h" int main(int argc, char* argv[]) { registerGen(argc, argv, 1); // 注册生成器 rnd.setSeed(opt("seed")); // 设置随机数种子 int n = opt("n"); // 读取命名参数 int m = rnd.next(1, 1000); // 生成随机数 // ... return 0; } ``` ### 参数传递 生成器参数可以通过 `conf.json` 中数据点的 `args` 字段配置,见 [配置](./config) Tuack-NG 会将 `args` 中的键值对作为 `--key=value` 命令行参数传递给生成器。同时,**Tuack-NG 会传入 `--seed=<一个 64 位无符号整数>`,你必须使用它作为生成器的随机数种子**。 ### 编译参数 生成器使用 `-O2 -std=c++17` 编译参数进行编译,目前你无法自行修改。 ## 示例 ```cpp // gen/gen.cpp #include "testlib.h" #include int main(int argc, char* argv[]) { registerGen(argc, argv, 1); int n = opt("n"); int q = opt("q"); std::cout << n << " " << q << "\n"; for (int i = 0; i < n; i++) { std::cout << rnd.next(1, 1000000000) << " \n"[i == n - 1]; } // ... return 0; } ``` --- --- url: /app/validate/overview.md description: 使用 Validator 校验题目的输入数据是否合法。 --- ## 命令用法 ```txt 校验输入数据 Usage: tuack-ng validate [OPTIONS] [TARGET] [OBJECT] Arguments: [TARGET] 目标类型 Possible values: - data: 正式测试数据 - sample: 样例数据 [default: data] [OBJECT] 校验对象,使用 `,` 和 `-` 分割 (如 1,2-3,4-10) [default: all] Options: -v, --verbose... 详细模式 ``` 本命令可从**题目**、**场次**、**竞赛**三个层级调用,自动递归处理该层级下的所有题目。 ### OBJECT 选择器 支持逗号和范围语法,例如 `1-2,3,5` 表示测试点 1、2、3、5。 `all` 表示全部测试点,未指定时默认为 `all`。 ## 目标 | TARGET | 说明 | | -------- | ------------------------------- | | `data` | 正式测试数据,对应 `data/` 目录 | | `sample` | 样例数据,对应 `sample/` 目录 | ## 结果 每个测试点的输入会交给 Validator 校验: * `OK`:输入合法 * `FAIL`:输入不合法,并显示 Validator 输出的原因 Validator 的编写与配置详见 [配置](./config)。 ## 相关页面 * [配置](./config) — `validator` 字段配置 * [校验规范](./spec) — Validator 的编写规范 * [数据生成 - 输入校验](../dmk/overview#输入校验) — 生成输入后自动校验,使用 `generator.validate` 配置,并可通过 `--validate` 参数临时覆盖 --- --- url: /app/validate/config.md description: Validator 的编写与配置。 --- ## 位置 Validator 源文件应放在题目目录的 `val/` 文件夹下: ```txt myoi/day1/aplusb/ ├── val/ │ ├── val.cpp # Validator 源文件 │ └── testlib.h # Testlib 头文件 └── ... ``` ## 配置文件 在题目的 `conf.json` 中,通过 `validator` 字段配置 Validator: ```json { "validator": { "data": { "source": "val/val.cpp", "deps": ["val/testlib.h"] }, "sample": { "source": "val/val_sample.cpp", "deps": [] } } } ``` | 字段 | 类型 | 说明 | | -------- | ---------- | ------------------------------------ | | `source` | `string` | Validator 源文件路径(相对题目目录) | | `deps` | `string[]` | 依赖文件列表 | * `validator.data`:正式测试数据的 Validator * `validator.sample`:样例数据的 Validator(可选),未配置时 `validate sample` 回退使用 `validator.data` Validator 的编写规范详见 [校验规范](./spec)。 --- --- url: /app/validate/spec.md description: Validator 的编写规范。 --- ## 要求 Validator 从标准输入读取输入数据,校验通过时返回 0,否则返回非 0 并以标准错误输出原因。 最常见的是使用 [Testlib Validator](https://oi-wiki.org/tools/testlib/validator/) 编写: ```cpp #include "testlib.h" int main(int argc, char* argv[]) { registerValidation(argc, argv); int n = inf.readInt(1, 100000, "n"); inf.readEoln(); inf.readEof(); return 0; } ``` ## 示例 ```cpp // val/val.cpp #include "testlib.h" int main(int argc, char* argv[]) { registerValidation(argc, argv); int n = inf.readInt(1, 100000, "n"); inf.readEoln(); for (int i = 0; i < n; i++) { inf.readInt(1, 1000000000, "a_i"); if (i + 1 < n) inf.readSpace(); else inf.readEoln(); } inf.readEof(); return 0; } ``` --- --- url: /app/dump/overview.md description: 将题目导出到评测系统格式。 --- ## 命令用法 ```txt 导出题目到评测系统 Usage: tuack-ng dump [OPTIONS] Arguments: 导出目标 Possible values: - lemon: 导出为 Lemon 格式 - arbiter: 导出为 Arbiter 格式 Options: -v, --verbose... 详细模式 ``` 本命令**不能在题目目录下执行**,需在比赛日或比赛根目录执行: | 执行位置 | 行为 | | ---------- | -------------- | | 比赛根目录 | 导出所有比赛日 | | 比赛日目录 | 仅导出该比赛日 | ## 输出目录 导出产物输出在工程 `dump//` 目录下: ```txt myoi/ └── dump/ ├── lemon/ └── arbiter/ ``` 具体格式和目录结构见 [导出目标](./targets)。 --- --- url: /app/dump/targets.md description: Tuack-NG 支持的导出目标以及各自的局限性。 --- ## Lemon 使用 `tuack-ng dump lemon` 以导出为 Lemon 格式。 Lemon 是一个常用的 OI 桌面评测软件。导出为 Lemon 可识别的格式。 ### 输出结构 ```txt dump/lemon/ └── data/ ├── aplusb/ │ ├── aplusb1.in │ ├── aplusb1.ans │ ├── aplusb2.in │ ├── aplusb2.ans │ └── ... └── ... ``` ### 限制 * 数据点:`sum` 会将每个测试点独立列出,`min` 会将测试点捆绑,不支持 `max` 策略 * 交互题:不支持交互题 * SPJ:你可能需要使用 Lemon 专属 testlib。 * 编译选项:默认设为 `"default"`,需手动调整为实际值 * 编译器映射:`cpp → g++`、`c → gcc`、`pas → fpc`、`py → python`、`java → javac`,其他不支持 ## Arbiter 使用 `tuack-ng dump arbiter` 以导出为 Arbiter 格式。 Arbiter 是 NOI 系列赛事使用的评测系统。 ### 输出结构 ```txt dump/arbiter/ └── main/ ├── setup.cfg ├── team.info ├── day.info ├── task_.info ├── data/ # 评测数据 ├── evaldata/ # 评测数据副本 ├── final/ # 最终结果 ├── players/ # 测试用例程序 ├── result/ # 测评结果 ├── filter/ # SPJ 过滤器 ├── tmp/ # 临时文件 └── down/ # 样例下发文件 ``` ### 限制 * 数据点:不支持 `min`,`max` 策略 * SPJ:Arbiter 使用特殊的 SPJ 风格,不支持 Testlib ## 样例 (arbiter\_down) Arbiter 导出时会将样例文件单独复制到 `down/` 目录供测试用例下发: ```txt dump/arbiter/ └── down/ └── <比赛日名>/ ├── <题目名>/ │ ├── <题目名>1.in │ ├── <题目名>1.ans │ └── ... └── ... ``` 样例按 `samples` 配置中的顺序编号,同时会复制 `down/` 目录下未在 samples 中配置的额外文件。 --- --- url: /app/doc/overview.md description: 对题面进行质量检查和格式化。 --- ## 命令用法 ```txt 文档检查工具 Usage: tuack-ng doc Subcommands: format 格式化题面文档 check 检查题面文档问题 validate 查看配置文件加载信息 Options: -v, --verbose... 详细模式 ``` 本命令可在工程内任意层级执行,作用于当前目录下的所有题目。 ### `--explain` `check` 和 `format` 子命令支持 `--explain ` 参数,用于查看指定规则的详细说明: ```bash tuack-ng doc check --explain latex ``` ## 子命令 | 子命令 | 功能 | | ---------------------- | -------------------------------------------- | | [check-format](./check-format) | 检查题面文档问题,部分规则可自动修复 | | [validate](./validate) | 显示配置文件加载时的所有警告、错误和提示信息 | | [format](./check-format) | `check-format` 部分规则可自动修正文档 | --- --- url: /app/doc/check-format.md description: 检查文档中可能的不规范问题。 --- ## 用法 ```bash tuack-ng doc check tuack-ng doc check --explain latex ``` ## 规则列表 | 规则 | 说明 | 自动修复 | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -------- | | `invisible` | 检测不可见 Unicode 字符(零宽空格、BOM、软连字符、方向标记等) | **是** | | `autocorrect` | 检测中英文之间的空格和标点符号问题 | **是** | | `samples-not-found` | 检查 `sample.text(N)` / `sample.file(N)` 中的 ID 是否有效,对应的样例文件是否存在 | 否 | | `samples-too-large` | 检查 `sample.text` 引用的文件是否超过限制(80 行 / 40 列 / 10 KB),超过应改用 `sample.file` | **是** | | `samples-should-be-external` | 检查题面中是否有内联样例代码块,应提取为外置文件 | **是** | | `latex` | 检测 LaTeX 公式问题:未转义的函数名、运算符(`<=`、`>=`、`...`、`*`、`/`)、不带反斜杠的 `mod`、公式中的中文、大数字、首尾空格 | 否 | | `html` | 检测 Markdown 中的内联 HTML 和 HTML 块(通常不应使用) | 否 | ## 报告格式 每条检查结果包含: * 文件路径 * 行号和列号(如有) * 严重级别(warning / error) * 描述信息 > \[!note] 说明 > > 由于当前的架构限制,部分报告没有行号与列号。 > > 未来我们将解决这个问题。 --- --- url: /app/doc/validate.md description: 显示配置文件加载时的消息树。 --- ## 用法 ```bash tuack-ng doc validate ``` ## 说明 本命令读取当前工程的配置文件层次结构(比赛 → 比赛日 → 题目),逐级显示加载过程中产生的所有信息,包括: | 类型 | 说明 | | --------------- | -------------------------------------- | | 警告(warning) | 配置字段不符合预期 | | 错误(error) | 配置无法正常加载,可能导致命令执行失败 | | 提示(note) | 配置中其他需要注意的事项 | ## 用途 在 Tuack-NG 检测到警告或错误时,可能会引导您执行这个命令。 --- --- url: /app/special/interactive/overview.md description: Tuack-NG 交互题的配置、测试与数据生成。 --- ## 目录结构 交互题需要将交互库(grader)和头文件放在题目目录下,以下为建议的目录: ```txt myoi/day1/interactive_problem/ ├── interactive/ │ ├── grader.cpp # 交互库 │ ├── header.h # 测试用例需要包含的头文件 │ ├── sample_grader.cpp # 样例数据专用交互库(可选) │ └── dmk_grader.cpp # 数据生成专用交互库(可选) └── ... ``` ## 配置 在 `conf.json` 中将 `type` 设为 `"interactive"`,并配置 `interactive` 字段: ```json { "type": "interactive", "interactive": { "grader": "interactive/grader.cpp", "header": "interactive/header.h", "sample_grader": "interactive/sample_grader.cpp", "dmk_grader": "interactive/dmk_grader.cpp" } } ``` | 字段 | 类型 | 说明 | | --------------- | --------- | ------------------------------------------- | | `grader` | `string` | 交互库路径,用于正式数据测试 | | `header` | `string` | 测试用例需要包含的头文件路径 | | `sample_grader` | `string?` | 样例数据专用交互库,未设置时回退到 `grader` | | `dmk_grader` | `string?` | 数据生成专用交互库,未设置时回退到 `grader` | ## 测试 ### 支持的语言 仅 **C++** 编译器支持交互题。在比赛日配置中设置编译选项: ```json { "compile": { "cpp": "-O2 -std=c++14" } } ``` ### 运行 `tuack-ng test` 会自动检测题目类型,若为交互题则启用交互模式: * 使用 `tuack-ng test` 测试正式数据时,使用 `grader` 编译 * 使用 `tuack-ng test sample` 测试样例数据时,优先使用 `sample_grader`,未设置时回退到 `grader` ### SPJ 交互题的 SPJ 写法与传统题一致,详见 [SPJ 编写参考](../../test/spj)。 ## 数据生成 `tuack-ng dmk` 生成数据时,交互库方面优先使用 `dmk_grader`,未设置时回退到 `grader` 由于交互题的标程也需要链接交互库,`dmk_grader` 可以配置为与正式评测不同的版本(如去除反作弊逻辑,方便生成答案)。 ### DMK 行为 交互题可能不需要传统意义上的输入/输出文件(数据由交互库直接生成/对错由交互库直接判断),可通过全局 `dmk` 配置控制: ```json { "dmk": "input" } ``` 详见 [生成配置](../../dmk/config#dmk)。 ## 编写规范 ### `grader.cpp` 交互库负责与测试用例程序交互: * 提供测试用例需要调用的函数/接口 * 处理输入数据(从 stdin 或文件读取) * 调用测试用例实现的函数 * 检测测试用例输出(可能判定正确性) ### `header.h` 头文件声明测试用例需要实现的函数以及测试用例可以调用的交互库函数: ```cpp // header.h // 测试用例需要实现的函数 void solve(); // 交互库提供的函数 int query(int x, int y); ``` ### 样例交互库与正式交互库 `sample_grader` 和 `dmk_grader` 提供同一接口但行为不同的实现: * **正式 grader**:完整的评测逻辑 * **样例 grader**:简化版本,可能缺少反作弊逻辑等,用于下发文件 * **DMK grader**:用于数据生成(比如偏传统型题目的交互题) --- --- url: /app/special/migrate-tuack/overview.md description: 将你的工程从 Tuack 迁移到 Tuack-NG。 --- ## 摘要 我们提供了 [Tuack Migrater](https://github.com/tuack-ng/Tuack-Migrater) 来辅助您进行迁移。 但是**迁移过程并非完全自动化**,您需要手动执行一些操作。 ## 已知限制 * Tuack-NG 暂不支持多语言题面,迁移时会要求您指定一门语言进行迁移。 * Tuack-NG 暂不支持 Pretest(预测试)。 * Tuack-NG 的外置表格,外置样例,题面格式,数据生成器的语法均与 Tuack 不同,您需要自行重写,或者使用 LLM 等方式辅助重写。 ## 步骤 ### 安装迁移工具 从 [PyPI](https://pypi.org/project/tuack-migrater) 安装 Tuack Migrater: ```shell pip install tuack-migrater ``` ### 开始自动迁移 将工作目录切换到 Tuack 工程的根目录,执行: ```shell python3 -m tuack-migrater <你希望迁移后的新工程目录> ``` 在迁移过程中,您可能需要回答一些问题,并且本工具会做出提醒您在迁移后对部分内容进行手动适配,详见下文。 迁移后的新工程将会保存在 `<你希望迁移后的新工程目录>` 下。 ### 迁移后操作 #### 配置交互题 你可能会想将交互题纳入 Tuack-NG 的工程管理。 迁移脚本带有自动查找交互库的逻辑,如果没有成功检测,请参见 [交互题](../interactive/overview) 手动配置。 #### 迁移 SPJ Tuack-NG 的 SPJ 必须使用 Testlib 书写,参见 [SPJ 编写参考](../../test/spj.md)。 #### 迁移数据生成器 如果你使用了基于 Testlib 的数据生成器,你可能只需要少许操作便可以将其适配 Tuack-NG。 如果你使用了 Tuack 基于 Python 的数据生成器,你需要将其完整重写到 C++。 参见 [数据生成器规范](../../dmk/generator)。 #### 迁移题面内容 Tuack-NG 使用的 MiniJinja 本质上与 Tuack 的 Jinja 来自同一作者,因此语法类似,但是 Tuack-NG 对可调用的 API 做了较大更变。 迁移脚本已经对于力所能及的进行了迁移,并将不支持内容进行了注释。您仍需要自行复查。 另外,Tuack-NG 使用的 Markdown 格式相比 Tuack 有所拓展。 参见 [题面格式](../../ren/statement)。 #### 迁移外置表格 Tuack-NG 使用 Lua 作为外置表格,且不支持 Tuack 的格式,因此您必须重写。 详见 [Lua 表格](../../ren/format/lua)。 --- --- url: /app/faq/faq.md description: Tuack-NG 使用时的常见问题。 --- 本章记录了一些用户可能遇到的常见问题与解决方法。欢迎为这个页面做贡献。 ## 收到错误“配置文件版本过低,可能是 Tuack 的配置文件。请迁移到 Tuack-NG 配置文件格式再使用。”? * 您可能错误地修改了配置文件的 `version` 字段到 `2` 以下。Tuack-NG 为了防止误加载 Tuack 的配置文件会提前失败。请您纠正这个错误。 * 您可能尝试使用 Tuack-NG 加载 Tuack 的配置文件。您必须迁移后才能使用。关于自动迁移的进一步指引,参见 [从 Tuack 迁移](../special/migrate-tuack/overview)。 ## 生成数据时“标程未生成输出”/测试时标程没有输出? * 检查您的标程是否使用了文件 IO。除非您显式在比赛/比赛日层级关闭了文件 IO,否则在生成数据和测试时默认开启文件 IO。 详见:[测试配置 - 文件 IO](../test/config#文件-io) --- --- url: /app/faq/debug-info.md description: 获取 Tuack-NG 的调试信息。 --- 在使用 Tuack-NG 的过程中,您可以通过 [向开发者提交 Issue](https://github.com/tuack-ng/tuack-ng/issues/new/choose) 等方式反馈问题。在这时,您可能会被要求提交应用运行时的调试信息。本文教授您如何获取这些信息。 ## 获取日志 在运行任何命令时,在 `tuack-ng` 后面加上 `-v`(`--verbose`) 参数,即可启用详细日志记录,比如: ```bash tuack-ng -v ren noi ``` 在此模式下,您会看到许多平常运行时不可见的日志,请在复现您的 Bug 的同时,将这部分日志一并附上。 ## 获取诊断信息 > \[!caution] > > 诊断信息可能包含敏感数据,在分享时请注意检查。 在任意位置执行: ```bash tuack-ng develop diagnostic ``` 即可获取 Tuack-NG 的诊断信息。将其粘贴到 Issue 模板中的【诊断信息】中。 --- --- url: /app/faq/panic.md description: 关于 Tuack-NG 发生 Panic 时的进一步指引。 --- ## 发生了什么? 如果您是在 Tuack-NG 运行时被引导到这里的,那么意味着 Tuack-NG 出现了 Panic。 Panic 通常意味着程序出现了不可恢复的错误,您可以理解为算法竞赛中的 RE。这通常是因为没有正确处理错误造成的。 Tuack-NG 的命令行界面不应该出现 Panic。 ## 我该怎么办? 很抱歉 Tuack-NG 出现了问题,请向我们报告这个问题。 您应该在命令行看到了类似于以下信息: ```txt PANIC | 程序发生了无法挽回的异常,即将退出 PANIC | 如果你想要报告这个问题,请保留以下信息: PANIC | Panic 发生在:path/to/panic/path:xx:yy PANIC | Panic 信息:panic message ``` 请参照 [获取调试信息](./debug-info) 获取必须的调试信息,然后在本项目的 Github 仓库中开启一个 Issue,最好在标题中包含 `[PANIC]` 字样。 如果可以,请提交在本机可以复现 Panic 的项目文件,或者至少一个最小的复现环境与复现步骤。 请您**务必不要**只提交以上四行信息,否则我们可能很难复现你的问题。 --- --- url: /archive/guide/install.md --- # 安装 Tuack-NG 目前支持 Linux 和 Windows,其他操作系统可能可用,但不保证兼容性。 目前支持 x86\_64 架构,Linux 上 Arm64 架构,Windows 上 x86 架构,以及 Nix 包管理器,其他架构可能需要自行编译安装。 ## Debian 及衍生版(如 Ubuntu) 在 [Tuack-NG 的 Release 界面](https://github.com/tuack-ng/tuack-ng/releases) 下载 deb 安装包,并使用以下方法安装: ```bash apt install [下载下来的安装包名字].deb ``` ## Arch Linux 及衍生版(AUR) ::: tabs \== yay ```bash yay -S tuack-ng-bin ``` \== paru ```bash paru -S tuack-ng-bin ``` \== 手动安装 > \[!warning] 警告 > 仅限 Arch Linux 及衍生版可用,其他发行版不可以使用以下方法。 ```bash git clone https://aur.archlinux.org/tuack-ng-bin.git cd https://aur.archlinux.org/tuack-ng-bin.git makepkg -si ``` ::: ## Nix / NixOS > \[!note] 附注 > > 这种方法也许兼容 MacOS,但开发者没有设备测试,仅供参考。 ```bash nix profile add github:tuack-ng/tuack-ng ``` ## 其他发行版 暂时没有官方支持的安装方式。 如果你愿意为你的发行版贡献一个安装包/源,欢迎提交贡献。 --- --- url: /archive/guide/conf.md --- # 批量修改配置 批量修改配置文件的某一项。 ## 命令用法 ```txt 批量修改配置文件 Usage: tuack-ng conf Commands: title 设置标题 time 设置时间限制 length 设置比赛长度 conf 设置任意字段 选项: -v, --verbose... 详细模式 ``` `time` 只能在比赛日级目录调用,`length` 只能在比赛级目录调用。 `conf` 的值必须符合配置文件的类型规范,否则会拒绝修改。 --- --- url: /archive/guide/data-and-test/overview.md --- # 数据与评测 本章将教授你如何使用 Tuack-NG 的数据生成(Dmk),测试代码,自动检测数据与测试用例等功能。 --- --- url: /archive/intro/sample.md --- # 样例 ## 外置样例 正如上一节末尾所述,内置样例存在诸多不便。 因此,Tuack-NG 支持外置样例。 *** 现在,我们来修改先前的题面至外置样例。 我们修改前文的题面: ````md ... ## 输出格式 一个整数。 ## 样例 1 输入 // [!code --:12] ```txt 114 514 ``` ## 样例 1 输出 ```txt 628 ``` // [!code ++:1] {{ sample.text(1) }} ## 样例 1 解释 ... ```` 同时,建立 `myoi/day1/aplusb/sample` 文件夹。 在 `myoi/day1/aplusb/sample/1.in` 中写入: ```txt 114 514 ``` 在 `myoi/day1/aplusb/sample/1.ans` 中写入: ```txt 628 ``` 修改 `myoi/day1/aplusb/conf.json`: ```json { "version": 3, "folder": "problem", "type": "program", "name": "aplusb", "title": "题目名称", "time limit": 1.0, "memory limit": "512 MiB", "partial score": false, "args": {}, "samples": [ { "id": 1, "input": "a.in", // [!code --:2] "output": "a.ans" "input": "1.in", // [!code ++:2] "output": "1.ans" } ], "data": [ { "id": 1, "score": 5, "input": "1.in", "output": "1.ans", "subtask": 0, "args": {}, "dmk": "skip" }, { "id": [ 2, 3 ], "score": 5, "subtask": 0, "args": {} } ], "subtasks": { "0": "sum" }, "tests": {}, "use-chk": false } ``` 重新运行 `tuack-ng ren noi`,你会发现它和之前的效果一模一样。 ## 下发样例 在出题时,很可能需要造大样例。但是显然,你不能将大样例内嵌到题目中。 > \[!warning] 后果 > > \~~Typst 编译卡死~~ 因此,Tuack-NG 提供了 `{{ sample.file() }}` 函数,它的详细信息见 [题面格式 - 函数](../guide/statement#%E5%87%BD%E6%95%B0) ## 🎉 阶段性学习完成 恭喜你,已经掌握了使用 Tuack-NG 生成题面的全过程!🎉 如果你想进一步了解 Tuack-NG 的其他功能,如生成数据、测试题解、导出到评测机等,那么我们推荐您继续阅读教程。 --- --- url: /archive/guide/data-and-test/test-data-std.md --- # 测试用例,数据与标准程序 在继续接下来的教程前,我们首先要明确这三个概念。 ## 测试用例 测试用例是用于解决这道题的一系列程序,其中可能包括暴力/不应通过的错误解法和标准程序。 你可以配置每个测试用例的预期分数,Tuack-NG 会在没有达到预期时发出警告。 ## 数据 数据是用于测试测试用例(在开发时)与选手代码(在评测时)的输入 / 输出。 Tuack-NG 主要注重前者,后者使用 `dump` 命令交给评测机 / OJ 完成。 ## 标准程序 标准程序是测试用例中正确、标准且应当通过所有数据的代码。 ## 关系图 ```mermaid graph LR subgraph 出题工程 direction TD subgraph 测试用例 direction LR STD[标准程序] subgraph 非标准程序 direction LR BF[暴力程序] WA1[错误解法] end end D[Tuack-NG 评测 数据
输入/输出文件 .in / .out] STD -->|AC| D BF -->|预期部分分/WA| D WA1 -->|预期部分分/WA| D end subgraph 选手测评 direction TD TC[评测机测试 测试点
输入/输出文件 .in / .out] subgraph 选手程序 direction LR AC2[正解] WA2[错解] end AC2 -->|AC| TC WA2 -->|部分分/WA| TC end D -->|导出数据| TC STD <--> |对应| AC2 非标准程序 <--> |对应| WA2 ``` --- --- url: /archive/guide/test.md --- # 测试题目 使用配置文件里的选手文件进行测试,支持使用文件输入输出或者标准输入输出,使用 SPJ(必须是 Testlib 格式) > \[!caution] 警告 > **强烈建议不要**将 Tuack-NG 作为评测机,Tuack-NG 的测试功能仅用于在出题期间测试标程和各种解法是否达到预期分数,因此没有反作弊与安全限制机制,因此贸然使用可能轻则直接放过作弊者,~~重则被 `rm -rf /`~~。 ## 命令用法 该命令支持在工程内任何层级下执行,只会对当前目录下的题目进行测试。 ```txt 使用题解代码测试 Usage: tuack-ng test 选项: -v, --verbose... 详细模式 ``` ## 配置文件 要配置选手程序,需要在题目层级的配置文件下新增或修改 `tests` 字段,示例如下: ```json "tests": { "tests/b.cpp": { // 选手名称,可以是任何名称,不影响实际效果 "expected": [">= 10", "<= 60"], // 期望分数,可以是字符串数组或单个字符串,描述预期得分。应当是表达式的右侧 "path": "tests/b.cpp" // 相对于题目文件夹 }, "std": { "expected": "== 100", "path": "tests/std.cpp" } }, "use-chk": false // 是否使用 SPJ,如果使用,必须放到 data/chk/chk.cpp 下。 ``` 如果有大量文件,不方便手动录入,可以使用 `gen code`。 ## 测评 测评结果包括:AC, CE, RE, TLE, MLE, PC, WA, UKE。 Tuack-NG(应该)支持跨平台的时间、空间检测,会在 TLE/MLE 时终止程序并记录结果。 Tuack-NG 会将每道题的测评结果以 CSV 格式写入题目文件夹下的 `result.csv`。 ## SPJ 如果要使用 Special Judge(SPJ),请打开 `use-chk`,并将SPJ放到 `data/chk/chk.cpp` 下。 SPJ 必须使用 [Testlib Checker](https://oi-wiki.org/tools/testlib/checker/) 编写,因为 Tuack-NG 使用了 Testlib 的导出 XML 结果文件功能。 SPJ 可以照常编写,Tuack-NG 兼容 `quitf+_pc(score)` 和 `quitp(score)` 两种部分分表示方式,应当返回一个 0 到 100 之间的数字,将会映射到当前测试点分值后四舍五入到整数。 如果返回 `_fail`,Tuack-NG 会发出警告。 --- --- url: /archive/guide/ren.md --- # 渲染工程 渲染工程文件夹下内容到指定目标,目前有 NOI、CCPC 和 Markdown 目标。 ## 命令用法 ```txt 渲染题面 Usage: tuack-ng ren [OPTIONS] Arguments: 渲染目标模板 Options: --keep-tmp 保留临时目录用于调试 选项: -v, --verbose... 详细模式 ``` ## 示例 关于生成工程文件夹,请查看 [上一节](./gen.md) ```bash # 这是我们上一节生成的工程 $ tree myoi myoi ├── conf.json ├── day1 │ ├── a │ │ ├── conf.json │ │ └── statement.md │ ├── b │ │ ├── conf.json │ │ └── statement.md │ └── conf.json └── precaution.md 4 directories, 7 files $ cd myoi # 渲染 NOI 格式 $ tuack-ng ren noi $ cd .. $ tree myoi myoi ├── conf.json ├── day1 │ ├── a │ │ ├── conf.json │ │ └── statement.md │ ├── b │ │ ├── conf.json │ │ └── statement.md │ └── conf.json ├── precaution.md └── statements └── noi # 渲染结果 └── 场次名称.pdf 6 directories, 8 files ``` 该命令可以在任何工程下的目录被调用,只会渲染当前目录下内容,会自动获取比赛信息,而非 Tuack 的占位符。 --- --- url: /archive/docs/gen.md --- # 生成工程 > 使用 Tuack-NG 新建 **比赛 - 比赛日 - 题目** 的三层工程结构。 首先,让我们来了解 Tuack-NG 的工程结构。 ## 工程结构 ### 比赛 这是整个工程的**根目录**,代表着整个比赛(比如 NOIP 2025),其下包括三个组成部分: * `conf.json`:工程的配置文件。 * `precaution.md`:如果使用了渲染 PDF 功能,此文件的内容将可能被作为注意信息显示。 * 文件夹:**比赛日**的文件夹,包含各个比赛日。 ### 比赛日 这是工程的**第二层级**,代表着整个比赛日(比如省选的 Day1, Day2)。当然,如果只有一天,只建立一个比赛日也可以。其下包括两个组成部分: * `conf.json`:比赛日的配置文件。 * 文件夹:**题目**的文件夹,包含各个题目。 ### 题目 这是工程的**第三层级**,代表着比赛题(比如 NOIP 2025 的糖果店 (candy))。当然,如果只有一天,只建立一个文件夹也可以。其下包括多个组成部分: * `conf.json`:题目的配置文件。 * `statement.md`:这道题的题面。 * `data`:这道题的数据。 * `sample`:这道题的样例。 * `tests`:这个文件夹不是必须的,但我们建议将所有测试用例放到这里。 * `gen`:这道题的数据生成器。 注意:三层结构并不是唯一可行的组织结构。如果你想,可以将所有题目整合到同一个文件夹等等,但是 Tuack-NG 不保证在这些使用场景下会正常工作。 ## 命令 ### 生成比赛 我们假设比赛名字是 `myoi`,使用 Bash-like Shell。CMD / PowerShell 请自行转换命令。 ```bash $ tuack-ng gen contest myoi $ cd myoi myoi/ $ ls conf.json precaution.md ``` 现在,你的 `conf.json` 应该长这样: ```json { "version": 3, "folder": "contest", "name": "myoi", "subdir": [], "title": "试题标题", "short title": "试题副标题" } ``` 请你修改 `title` 和 `short title` 到你想要的名字,例如“CCF 全国青少年信息学奥林匹克联赛”与“CCF NOIP 2025”。 你的 `precaution.md` 已经有了 CCF 官方赛事的标准注意事项: ```md **注意事项(请仔细阅读)** 1. 文件名(程序名和输入输出文件名)必须使用英文小写。 2. `main` 函数的返回值类型必须是 `int`,程序正常结束时的返回值必须是 0。 3. 提交的程序代码文件的放置位置请参考各省的具体要求。 4. 因违反以上三点而出现的错误或问题,申诉时一律不予受理。 5. 若无特殊说明,结果的比较方式为全文比较(过滤行末空格及文末回车)。 6. 选手提交的程序源文件必须不大于 100KB。 7. 程序可使用的栈空间内存限制与题目的内存限制一致。 8. 全国统一评测时采用的机器配置为:Intel(R) Core(TM) i7-8700K CPU @3.70GHz,内存 32GB。上述时限以此配置为准。 9. 只提供 Linux 格式附加样例文件。 10. 评测在当前最新公布的 NOI Linux 下进行,各语言的编译器版本以此为准。 ``` 你可以自行修改。 ### 生成比赛日 在本章中,我们将只生成比赛日 `day1`,但是如果你想要生成多个比赛日,只需重复以下内容,当然请为不同比赛日指定不同名称。 ```bash myoi/ $ tuack-ng gen day day1 myoi/ $ cd day1 myoi/day1/ $ ls conf.json ``` 现在,你的 `day1/conf.json` 应该长这样: ```json { "version": 7, "folder": "day", "name": "day1", "subdir": [], "title": "场次标题", "compile": { "cpp": "-O2 -std=c++14 -static" }, "start time": [ 1970, 1, 1, 0, 0, 0 ], "end time": [ 1970, 1, 1, 0, 0, 0 ] } ``` 同时,你的 `conf.json` 也被自动修改了: ```json { "version": 7, "folder": "contest", "name": "myoi", "subdir": [ "day1" // 注意这里被自动添加了 ], "title": "CCF 全国青少年信息学奥林匹克联赛", "short title": "CCF NOIP 2025" } ``` 你可以修改 `title`,`compile`,`start time`,`end time` 到你想要的值。 下面是这些值的详细解释: * `title`:场次标题。如果你不需要它(比如比赛只有一天),请将它留空,即 `"title": ""`。 * `compile`:编译选项。目前可以给 `cpp`,`c`,`rs`(Rust),`py`(Python),`java` 设置编译选项,且理论上你可以自行补充。 * `start time` 和 `end time`:开始时间和结束时间。都是六元组 `[年, 月, 日, 时, 分, 秒]`。 ### 生成题目 现在,我们来生成题目。 一场比赛一般会有不止一道题,但我们在这里为了简便,只生成一道题 `aplusb`。和比赛日类似,如果想生成多道题目,只需重复以下步骤。 ```bash myoi/day1/ $ tuack-ng gen problem aplusb myoi/day1/ $ cd aplusb myoi/day1/aplusb/ $ ls conf.json statement.md ``` 现在,你的 `day1/aplusb/conf.json` 应该长这样: ```json { "version": 7, "folder": "problem", "type": "program", "name": "aplusb", "title": "题目名称", "time limit": 1.0, "memory limit": "512 MiB", "args": {}, "generator": null, "samples": [ { "id": 1, "input": "a.in", "output": "a.ans" } ], "data": [ { "id": 1, "score": 5, "input": "1.in", "output": "1.ans", "subtask": 0, "args": {}, "manual": false }, { "id": [ 2, 3 ], "score": 5, "subtask": 0, "args": {} } ], "subtasks": { "0": "sum" }, "tests": {}, "checker": null } ``` 请你按照你的需求修改 `title`,比如在这个示例中,我们将它设置为 `A + B Problem`。 对于题目配置文件中剩下的字段,我们将在后面的文档中逐渐涉及他们。 *** 现在,让我们看看项目的整体结构: ```ansi myoi ├── conf.json ├── day1 │   ├── aplusb │   │   ├── conf.json │   │   └── statement.md │   └── conf.json └── precaution.md 3 directories, 5 files ``` --- --- url: /archive/guide/gen.md --- # 生成工程 要想使用 Tuack-NG 管理工程,首先要生成一个工程。 ## 命令用法 ```txt 生成工程文件夹 Usage: tuack-ng gen [OPTIONS] Commands: contest 生成竞赛文件夹 day 生成竞赛日文件夹 problem 生成题目文件夹 data 自动检测数据 samples 自动检测样例 code 自动检测题解 all 自动检测所有 选项: -v, --verbose... 详细模式 ``` Tuack-NG 在建造工程时,会自动修改配置文件中的相应字段。 实际上,`gen` 能干的事情,已经远不止生成工程文件夹。 > \[!note] 注意 > 如果你在找与“配置文件批量修改”相关的子命令,请注意 Tuack-NG 已经将其拆分到 `conf`。 ## 示例 ```bash # 建造比赛 $ tuack-ng gen contest myoi $ cd myoi # 建造比赛日 $ tuack-ng gen day day1 $ cd day1 # 建造题目 $ tuack-ng gen problem a b $ cd ../.. $ tree myoi myoi ├── conf.json ├── day1 │ ├── a │ │ ├── conf.json │ │ └── statement.md │ ├── b │ │ ├── conf.json │ │ └── statement.md │ └── conf.json └── precaution.md 4 directories, 7 files ``` 暂时没有剩余命令的示例,`{data, sample}` 会在题目下同名文件夹里查找成对的输入输出文件,`code` 会递归查询并排除常见的不应查找的文件夹中的文件,并在使用符合人类直觉的比较方式排序后写入配置文件。 --- --- url: /archive/intro/first-pdf.md --- # 第一个 PDF 在上一节中,我们并没有讲述 `statement.md` 的内容。实际上,它长这样: ```md ## 题目描述 尽情创作吧! ## 输入格式 ## 输出格式 ## 样例 1 输入 ## 样例 1 输出 ## 样例 1 解释 ## 数据范围 ``` 这是一个非常简洁的模板,我们将要从这里开始题目的编写。 ## 题目编写 现在,我们来书写 A + B Problem 的题面: ````md ## 题目描述 输入两个整数 $a, b$,输出它们的和。 ## 输入格式 两个以空格分开的整数。 ## 输出格式 一个整数。 ## 样例 1 输入 ```txt 114 514 ``` ## 样例 1 输出 ```txt 628 ``` ## 样例 1 解释 这是显然的。 ## 数据范围 $|a|,|b| \le {10}^9$ ```` 现在,我们调用命令生成 PDF。 > \[!important] 注意 > > 你必须安装 Typst 并将其包括在环境变量中。 ```bash myoi/ $ tuack-ng ren noi * 结果已保存到: myoi/statements/noi/day1.pdf ``` 如果不出意料的话,这个 PDF 会自动使用默认打开方式开启。如果你不想这样,请添加 `-s` 参数以取消自动打开。 恭喜你,生成了第一个 PDF! *** 在出题过程中,我们经常可能会更改样例,比如修改输入输出格式、重造样例等。但是我们需要同时修改题面中的样例,这无疑会增加复杂性。 Tuack-NG 对此提供了解决方案,请阅读下一节了解详情。 --- --- url: /archive/guide/data-and-test/test.md --- # 运行测试 让配置的测试点发挥作用大致有几种方法:渲染时调用外置样例、测试、生成数据、导出到评测机。其中第一种已经在前面的章节讲述。 这一章,我们来讲述如何运行测试。 ## 配置 同样的,测试用例支持自动和手动配置。 --- --- url: /archive/guide/dmk.md --- # 造数据 使用 Generator 生成输入数据,并调用标程生成输出数据。 ## 命令用法 ```txt 生成数据 Usage: tuack-ng dmk [OPTIONS] [OBJECT] Arguments: 目标类型 Possible values: - data: 正式测试数据 - sample: 样例数据 命令 Possible values: - gen: 生成(未生成的)数据 - regen: 重新生成数据(使用相同种子) - reset: 重置种子并重新生成数据 [OBJECT] 测试点 [default: all] 选项: -v, --verbose... 详细模式 ``` 测试点可以使用 `-` 和 `,`,如 `1-2,3,5`。 ## 生成器 在 `gen/gen.cpp` 下写生成器。强烈建议使用 Testlib 以获得跨平台一致的随机数和健壮的命令行参数解析。 本文档没有完工,请参考 DeepWiki 来获取进一步用法。 --- --- url: /archive/guide/data-and-test/configure-data.md --- # 配置数据点 想要使用 Tuack-NG 的测试、生成数据等功能,你必须先配置数据点。 目前,Tuack-NG 支持以自动方式搜索数据点,但您也可以手动配置。 > \[!important] 自动配置和手动配置是什么关系? > > 两者是互补关系。 > > 自动配置解决了手动录入的复杂性,但是存在**局限性**,比如:无法自动将配置点简化为配置点组,无法配置复杂的子任务等。 > > 而手动配置可以解决自动配置的局限性,**更加细致**地设置数据点(比如子任务,数据生成配置等),但是需要更多的人工操作。 ## 自动搜索 您可以使用以下命令自动搜索样例和正式数据的数据点: ```shell # 自动搜索样例 tuack-ng gen samples # 自动搜索正式数据 tuack-ng gen data # 自动搜索样例、正式数据和测试代码(详见后续章节) tuack-ng gen all ``` 请注意,上述命令会将对应配置**覆盖**且不可恢复。建议用户在执行前进行备份。 ## 手动配置 Tuack-NG 的数据点通过配置文件中的以下字段配置: ```json { // 样例测试点 "samples": [ { // a sample } // ... ], // 正式数据测试点 "data": [ { // a data } // ... ], // 正式数据子任务 "subtasks": { // ... } } ``` ### 样例测试点 在前面,我们已经配置过样例的测试点。现在我们来看样例的完整配置方式。 一个样例测试点的配置结构如下: ```json { "id": 1, "input": "a.in", "output": "a.ans", "dmk": "on", "args": { // ... } } ``` * `id`:样例测试点编号。一般从 `1` 开始。 * `input`、`output`:样例输入输出文件。文件必须在 `sample` 路径下。\ 该字段可以不设置。如果未设置,将会寻找 `${id}.in` 和 `${id}.ans`。 * `dmk`、`args`:将在下一章讲述。 ### 正式数据 #### 正式数据测试点 一个正式数据测试点的配置结构如下: ```json { "id": 1, "score": 5, "input": "a.in", "output": "a.ans", "subtask": 0, "args": { // ... }, "dmk": "on" } ``` * `id`:正式数据测试点编号。\ 可以是数字或数组。\ 数组表示这个配置块代表了一个**测试点组**,代表着组内所有编号测试点的配置。 * `score`:本测试点的分数。\ 如果该配置块是测试点组,这个字段的值**代表其中每个值的分数,而非总和的分数。** * `input`、`output`:样例输入输出文件。文件必须在 `data` 路径下。\ 该字段可以不设置。如果未设置,将会寻找 `${id}.in` 和 `${id}.ans`。\ **测试点组不可设置这两个字段** * `subtask`:该测试点(组)的子任务编号。关于子任务的配置将在下一节描述。 * `args`、`dmk`:将在下一章讲述。 #### 子任务 子任务使用键值对配置,样式如下: ```json "0": "sum", "1": "max", // ... ``` * 键:子任务编号。必须是一个非负整数。 * 值:子任务的评分方式。必须是 `sum`、`min`、`max` 的其中一个: * `sum`:该子任务的总分和实际评分时分值所有子任务的**分值之和**。\ 这是最常见的评分方式。 * `min`:该子任务的总分和实际评分时分值所有子任务的**分值的最小值**。\ 适用于“打包评测/捆绑测试”的场景。 * `max`:该子任务的总分和实际评分时分值所有子任务的**分值的最大值**。\ 目前此选项较少使用。 --- --- url: /app/ren/statement.md description: 关于 Tuack-NG 的题面格式参考。 --- ## Markdown 语法 支持的语法范围详见 [题面语法](./format/syntax),包括标题、段落、代码块、引用、列表、链接与图片、表格(含单元格合并)、LaTeX 公式等。 ## MiniJinja 模板 题面文件(`statement.md`)使用 MiniJinja 作为模板引擎,支持在题面中动态访问题目配置数据和调用函数。详见 [MiniJinja 模板](./format/template)。 ## Lua 表格 Lua 表格用于解决 MiniJinja 在生成高复杂度表格时语法复杂,难以调试的问题。详见 [Lua 表格](./format/lua)。 --- --- url: /archive/guide/statement.md --- # 题面格式 本项目**完全兼容** CommonMark 规范,并且实现了 CNOI 的 Markdown 超集,详见 的 SupportGrammer。 本项目使用了 MiniJinja 作为模板的解析器,并且上下文中包括了指向当前渲染题目等信息的配置文件数据结构,这意味着,您可以在您的题面中任意访问当前题目的相关信息,甚至可以根据题目信息动态生成题面。 ## 上下文 目前,Tuack-NG 的 MiniJinja 包括了以下上下文: * `problem` : 当前渲染的题目 * `day` : 当前渲染的比赛日 * `contest` : 当前渲染的比赛 ## 函数 目前,Tuack-NG 的 MiniJinja 包括了四个函数上下文: * `sample` 上下文: * `text(sample_id: u32)` : 将 `sample_id` 指向的样例的文本加入题面。适合简短的样例。 附注:`简短` 的意思是短而小,不是让你放一个虽然很小但是三页纸长的样例进去。🤣 * `file(sample_id: u32)` : 在题面中加入文本,提示选手查看下发文件中的`sample_id` 指向的样例。适合大样例。 * `tools` 上下文 * `hn(num: f64, style?: str)` : 将 `num` 转换到适合人类阅读的形式(科学表示法或逗号分隔形式) 注意输出的是 Latex 格式并且不带 `$$` ,因此需要以 `$$` 包括。 `style` 可选,如果指定了,有以下两种选项: * `x` : 科学记数法 * `,` : 逗号分隔形式 如果没有指定,则自动选择最紧凑的格式。 * `comma(num: i64)` : 将 `num` 转换逗号分隔形式。 * `cases(cases_vec: Vec)` : 将数字范围转换为紧凑的表示形式,自带 `$$`。 比如 `cases([1,2,3,5,7,8,9])` 会转换为 `$1 \sim 3, 5, 7 \sim 9$`。 * `statement` 上下文(别名 `s`,效果一致) * `input_file()` : 输出一段文本,要求选手从指定位置读入。 * `output_file()` : 输出一段文本,要求选手输出到指定位置。 ## 示例 ### 输出时间限制 ```md {{ problem["time limit"] }} ``` ### 输出测试点数目 ```md {{ problem.data | length }} ``` ### 输出样例 1 ```md {{ sample.text(1) }} ``` > \[!note] 提示 > > `{{}}` 是 MiniJinja 的表达式语法,由于我们把 `problem` 直接扔进了上下文(实际上是把 json 扔进去了),我们可以直接用表达式调用 `problem`。(当然,这个表达式支持基本的算术运算) > > 调用 `problem` 有两种等效的格式: > > ```md > {{ problem.a.b }} > ``` > > ```md > {{ problem["a"]["b"] }} > ``` > > 需要注意的是,当属性名带有空格时(比如说示例 1),只能使用第二种语法。 > > `{{ problem.data | length }}` 是过滤器语法,在这里意为获取数组的长度。 > > 关于 MiniJinja 的更多语法,详见 > > `problem` 的数据结构可在 查看,同时我们提供了 [JSON Schema](https://gist.github.com/Pulsar33550336/ece6e5f24a760be04b3fb5c7b9b6fe16)(由 DeepSeek 编写,可能不准确)。