---
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  image
```
## 图片扩展
### 居中图片
```md
:::figure{caption=居中图片。在这里添加一些图片描述。}

:::
```
### 无标题图片块
```md
:::figure
caption 参数是可选的。
文本也可以放进去。
:::
```
### 尺寸控制
```md
小{height=4em} {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