# 进阶 ChoiceScript

> <https://www.choiceofgames.com/make-your-own-games/choicescript-advanced/>

一份关于ChoiceScript编程语言进阶功能的指南。如果您对本文件有任何疑问，请在 [ChoiceScript 论坛](http://www.choiceofgames.com/forum/categories/choicescript-help)上发帖。

## 初学者请不要直接从这里开始学习！

在阅读这份高级文档之前，请务必先阅读我们基础的 [ChoiceScript 介绍](/zh-hans_choicescript-guide/guan-fang-zhi-nan/choicescript-jian-jie.md)页面

## 更多命令

### \*line\_break

在文本中仅插入一个换行符，类似 HTML 中的 `<br>` 标签。通常不应使用此命令；只需按两次回车键，ChoiceScript 便会自动插入段落分隔。但除非存在段落分隔，否则 ChoiceScript 会自动将文本行连接起来；若需要比段落分隔更小的间隔，或仅需单行换行时，再使用 `*line_break`。

```
  So
  this
  is
  all
  one
  line.
 
  But this is a new paragraph.
 
  And this
  *line_break
  is two lines.

```

该代码将显示如下：

```
  So
  this
  is
  all
  one
  line.
 
  But this is a new paragraph.
 
  And this
  *line_break
  is two lines.
```

> So this is all one line
>
> But this is a new paragraph.
>
> And this\
> is two lines

### \*input\_number

与 `*input_text` 类似，但文本框中只允许输入数字。需指定变量名以及最小值和最大值。

```
  How many coins?
  *input_number coins 0 30
 
  You asked for ${coins} coins.
```

### \*rand

将一个变量设置为随机数。你设定最小值和最大值，剩下的交给我们。例如，这会将变量 `die_roll` 设置为 1 到 6（含）之间的一个值：

```
  *rand die_roll 1 6
```

因此，我们建议谨慎使用随机性，甚至可能将其作为最后的手段。

### \*bug

此命令会导致游戏停止并崩溃，并显示特定的错误信息。更多信息可在我们关于[自动测试 ChoiceScript 游戏](/zh-hans_choicescript-guide/guan-fang-zhi-nan/zi-dong-ce-shi-choicescript-you-xi.md)的指南中找到。

```
  *if someone_murdered and (victim = "none")
    *bug Someone was murdered, but there's no victim!
```

### \*redirect\_scene

该指令的行为类似于 `*goto_scene`，但仅能在状态界面中使用。在状态界面中，游戏处于“状态模式”，此时屏幕顶部的按钮会显示“返回游戏”而非“状态”。状态模式如同矩阵世界 (Matrix)，并非真实游戏空间。在状态模式下，使用 `*goto_scene` 指令仍会停留在状态模式中，不会影响主线游戏进程。若点击“返回游戏”，你将立即回到离开时的章节并切换至“游戏模式”，如同从梦境中苏醒。在状态界面中使用 `*redirect_scene` 指令，可实现在主线游戏中的 `*goto_scene` 跳转。

### \*looplimit

在 ChoiceScript 中，有可能写出“无限循环”的 bug，即游戏会一遍又一遍地永远运行相同的代码行。这类 bug 可能很难发现和修复。如果你的游戏存在无限循环 bug，你会看到类似这样的错误：“startup line 31: visited this line too many times (1000)”。当某一行在单次游戏流程中被运行（“访问”）了 1000 次时，就会触发此错误，这强烈表明你的游戏存在无限循环。如果没有这个机制，一旦遇到无限循环，ChoiceScript 就会永远挂起。如果你确实想在单次游戏流程中让某行代码运行 1000 次，有一个命令可以支持，即 `*looplimit`，你可以这样使用：

```
  *looplimit 1000000000
```

`*looplimit` 允许你将限制提高到或降低到你喜欢的任何数字。你也可以用 `*looplimit 0` 来关闭循环限制，但我们不推荐这样做。对任何人来说，一行代码运行十亿次应该都足够了。（循环限制的最大值大约是 1 万亿（12 个零），但是，你真的需要这么多么？！）

### \*create\_array

此命令是多次输入 `*create` 的快捷方式。您需要指定数组长度及所有变量的初始值，例如：`*create_array attributes 5 50`，其效果等同于编写以下代码：您也可以为每个变量分别赋值，例如：`*create_array attributes 5 10 20 30 40 50`。

```
  *create attributes_1 50
  *create attributes_2 50
  *create attributes_3 50
  *create attributes_4 50
  *create attributes_5 50
  *create attributes_count 5
```

### \*temp\_array

此命令与上述的 `*create_array` 类似，但使用 `*temp` 而非 `*create`。

## 检查点使用技巧

### 检查 `*save_checkpoint` 是否已执行

若在 `*save_checkpoint` 前使用 `*restore_checkpoint`，游戏将报错终止：

```
startup line 48: Invalid *restore_checkpoint; we haven’t saved a checkpoint
```

You can use a magic variable called `choice_saved_checkpoint`. It starts out false, but when you `*save_checkpoint`, it turns true.\
你可以使用一个名为 `choice_saved_checkpoint` 的“妙妙”变量。它初始值为假，但当你执行 `*save_checkpoint` 时，它会变为真。

（或者，考虑在游戏开始时立即添加一个 `*save_checkpoint`，这将确保 `*restore_checkpoint` 永远不会失败。）

### 从检查点恢复后运行一些代码

`*save_checkpoint` 会设置一个名为 `choice_just_restored_checkpoint` 的临时变量，并将其设为假。`*restore_checkpoint` 会在恢复过程中将 `choice_just_restored_checkpoint` 设为真。

```
*save_checkpoint
*if choice_just_restored_checkpoint
    You just restored!
```

### 排除部分状态（临时变量不被恢复）

有些游戏会追踪你从先前检查点恢复的次数，以便扣除使用检查点的“点数”，或限制你可以恢复的次数。

`*restore_checkpoint` 会寻找一个名为 `checkpoint_exclusions` 的变量，如下所示：

```
*create checkpoint_exclusions "experience_points courage wisdom"
```

被排除的变量在你执行 `*restore_checkpoint` 时将不会恢复为旧值。

```
*create checkpoint_uses 0
*create checkpoint_exclusions "checkpoint_uses"

*save_checkpoint
*if choice_just_restored_checkpoint
    You just restored!
    *set checkpoint_uses +1

You have restored from the checkpoint ${checkpoint_uses} times.

...

*choice
    #Continue to the next chapter.
        *finish
    #Restore to the previous checkpoint.
        *restore_checkpoint
```

### 多个检查点“槽位”

当您使用 `*save_checkpoint` 或 `*restore_checkpoint` 时，可以指定检查点“槽位”的名称。这允许您设置多个检查点，让玩家可以回溯到很久之前，或者仅仅回退一小段进度。

```
*save_checkpoint major
...
*save_checkpoint minor
...

*choice
    #Go back a little
        *restore_checkpoint minor
    #Go way, way back
        *restore_checkpoint major
```

（每个检查点槽位都有其对应的变量。在此示例中，当您执行`*save_checkpoint major` 时，`choice_saved_checkpoint_major`变量将被设为 `true`。）

### 随时从属性界面恢复到上一个检查点

你可以编写如下代码，从状态屏幕进行恢复：

```
[choicescript_stats.txt]
*stat_chart
    ...

*choice
    #Done.
        *finish
    *if (choice_saved_checkpoint) #Restore from the last checkpoint.
        *restore_checkpoint
```

请注意，你需要使用 `*if (choice_saved_checkpoint)` 来确保玩家不会在第一个检查点设置之前尝试恢复。（王洛木：或者在游戏最开始就设定一个存档点。）

### 玩家命名的存档槽位

可以编写代码，允许玩家为自己的存档槽位命名。

```
*create_array save_name 3 "[empty]"
*create checkpoint_exclusions save_name_1 save_name_2 save_name_3

...

*gosub save

...

*gosub restore

...

*label save
*temp slot
Which slot do you want to save?
*choice
    #Slot 1: ${save_name_1}
        *set slot 1
    #Slot 2: ${save_name_2}
        *set slot 2
    #Slot 3: ${save_name_3}
        *set slot 3
What do you want to call your saved game?
*input_text save_name[slot]
*if slot = 1
    *save_checkpoint slot1
    *return
*if slot = 2
    *save_checkpoint slot2
    *return
*if slot = 3
    *save_checkpoint slot3
    *return

*label restore

*choice
    *selectable_if (choice_saved_checkpoint_slot1) #Slot 1: ${save_name_1}
        *restore_checkpoint slot1
    *selectable_if (choice_saved_checkpoint_slot2) #Slot 2: ${save_name_2}
        *restore_checkpoint slot2
    *selectable_if (choice_saved_checkpoint_slot3) #Slot 3: ${save_name_3}
        *restore_checkpoint slot3
    #Cancel
        *return
```

## ChoiceScript 集成开发环境

[ChoiceScript 集成开发环境](#the-choicescript-idechoicescript-ji-cheng-kai-fa-huan-jing)是针对 ChoiceScript 语言的强大集成开发工具。它包含用于编写和脚本编程的代码编辑器，内置 ChoiceScript 运行环境（及其两项自动化测试功能），支持代码与游戏同步测试运行。该环境能够记录错误、高亮显示（并聚焦定位）问题代码行，提供拼写检查功能，甚至自动处理代码缩进格式。

请注意，ChoiceScript IDE 并非由 Choice of Games 开发或维护。如果您对 ChoiceScript IDE 有任何疑问，请通过 [ChoiceScript IDE 讨论帖](https://choicescriptide.github.io/discuss/)直接向IDE团队提问。

（王洛木：本注释写于 2026 年，这个软件有点老旧了，很多新的命令包括 `*ifid` 在内都不支持，建议还是直接 VS CODE 搭配插件。）

## 更多条件选项

除了在每行使用 `*if` 外，你还可以使用嵌套的条件块。这项技术相当高级，因为很难精确控制缩进。

```
  *choice
    #Rattle my saber.
      They rattle back.
      *finish
    *if republican
      *if president
        #Declare open war.
          Congress refuses to approve funding.
          *finish
      *else
        #Ask other Republicans to help out.
          Talk radio is on your side.
          *finish
    *else
      *if president
        #Work with the United Nations.
          Russia vetoes your plan.
          *finish
      *else
        #Ask other Democrats to help out.
          They do their best, but the party is divided.
          *finish
```

## 全局 `*hide_reuse` 与 *`*disable_reuse`*

你可以通过在 ChoiceScript 文件顶部添加 `*hide_reuse` 或 *`*disable_reuse`*，使所有选项都不可重用。然后，你可以使用 `*allow_reuse` 命令来允许某些选项被重用。

```
  *hide_reuse
  *label start
  *choice
    #One.
      The loneliest number that you'll ever do.
      *goto start
    #Two.
      Two can be as bad as one.
      *goto start
    *allow_reuse #I can't decide!
      Well, think it over.
      *goto start
    #Done.
      OK!
      *finish
```

## 数学

### 整数运算

你可以使用 `round()` 函数将变量四舍五入到最接近的整数。例如，这会将变量“foo”设置为 3：`*set foo round(2.5)`。你也可以使用 [`modulo` 运算符](http://en.wikipedia.org/wiki/Modulo_operation)计算除法后的余数。模运算相当奇特，但它有两个特别有趣的用途。首先，你可以通过检查 `X modulo Y = 0` 来判断数字 X 是否能被数字 Y 整除。其次，你可以用它来获取数字 X 的小数部分（小数点后的部分），通过计算 `X modulo 1`。例如，`3.14 modulo 1 = 0.14`。

### 指数与对数

你可以这样使用指数：`*set foo 3^7`。这会将 `foo` 设置为 2,187（3 的 7 次方）。你也可以这样计算对数（以 10 为底）：`*set foo log(1000)`。（你总是可以通过除法来使用不同的对数底数，例如：`*set foo log(2187)/log(3)`）

## 文本技巧

### 大写

你可以像这样仅将变量的首字母大写：`看！$!{He} 的首字母被大写了`。你也可以像这样将整个单词大写：`总统 $!!{name} 在耻辱中辞职`

### 粗体和斜体

你可以像这样将文字设置为粗体或斜体：`这是[b]粗体[/b]，而这是[i]斜体[/i]。`

### 在引号字符串中使用 ${}

你可以在引号字符串中使用变量，像这样：`*set name "Dr. ${last_name}"`

### 连接

你可以像这样将文本连接在一起：`*set murder "red"&"rum"`

### 引号

你可以通过使用反斜杠在文本中添加引号，就像这样：

```
  *set joke "she said it was \"ironic\"!"
```

如果你写下 `${joke}`，你会得到：

> she said it was "ironic"!

### 反斜杠

你可以通过使用更多反斜杠在文本中添加反斜杠，例如：

```
  *set slashy "Here's one backslash: \\ and here's two backslashes: \\\\"
```

如果你写下 `${slashy}`，就会得到：

> Here’s one backslash: \ and here’s two backslashes: \\\\

### 字符提取

你可以从变量中提取字符（字母或数字），像这样：

```
  *temp word "xyzzy"
  *temp first_letter word#1
  *temp second_letter word#2
  The first letter of the word "${word}" is ${first_letter} and the second letter of the word is ${second_letter}.
```

### 字符计数

你可以像这样统计单词中的字符数量：

```
  *temp word "plough"
  *temp word_length length(word)
  *temp last_letter word#word_length
  The word "${word}" is ${word_length} letters long, and so its last letter is ${last_letter}.
```

## `*gosub` 参数

在 `*gosub` 标签之后，您可以包含任意数量的参数。当您使用 `*params` 命令时，它会为每个参数设置名为 `param_1`、`param_2` 等的临时变量。（它还会设置一个 `param_count` 临时变量来记录参数数量；在此例中，`param_count` 将为 2。）

```
  *gosub visit "Dracula" "garlic"

  *label visit
  *params
  You go to visit ${param_1} and you bring ${param_2} with you.
  *return
```

由于 `param_1` 和 `param_2` 不是很好的名称，你可能会忍不住想写这样的代码：

```
  *params
  *temp person param_1
  *temp gift param_2
```

我们预料到了这一点；您只需在 `*params` 后写下参数名称，我们将为您设置临时变量，就像这样：

```
  *label visit
  *params person gift
  You go to visit ${person} and you bring ${gift} with you.
  *return
```

您也可以为 `*gosub_scene` 使用参数。由于 `*gosub_scene` 允许您选择性地指定标签，例如 `*gosub_scene travel visit`，因此如果您想向 `*gosub_scene` 传递参数，则必须指定标签名称。`*gosub_scene visit "Dracula"` 不会使用参数；它将尝试 `*gosub` 到 `visit.txt` 中的标签 `Dracula`。

具有程序员思维的人应当注意，参数只是普通的 `*temp` 变量，其作用域覆盖整个文件，而非子程序。因此，如果您在一个 `*gosub` 中嵌套 `*gosub`，`param_1` 可能会在第二个子程序中被修改，并且在 `*return` 时不会恢复原值。

然而，`*gosub_scene` 为 `*temp` 变量定义了新的作用域，所以如果您希望 `*param` 的作用域限定在子程序内（例如想要使用递归），可以使用 `*gosub_scene` 代替 `*gosub` 。

这里有一个计算[斐波那契数列](https://www.mathsisfun.com/numbers/fibonacci-sequence.html)的简单递归子程序示例。（请注意它只能与 `*gosub_scene` 配合使用；由于参数作用域问题，使用 `*gosub` 将无法正常工作。）

```
  *create return 0

  *gosub_scene startup fib 6
  ${return}

  *finish

  *label fib
  *params n
  *if n < 2
    *set return 1
    *return
  *gosub_scene startup fib (n-1)
  *temp prev return
  *gosub_scene startup fib (n-2)
  *set return +prev
  *return
```

## 真正怪异的引用

可能只有程序员才会欣赏这些。请注意！它们增加了复杂性却没有带来太多价值。

### 花括号

将一些文本放在花括号中，我们会将其转换为命名变量的值。

```
  *set honesty 30
  *set virtue "诚实"
  *set score {virtue}
  你的${virtue}分数是 ${score}
```

这将打印：

> Your honesty score is 30\
> 你的诚实分数是 30

你也可以直接写成：`你的${virtue}分数是${{virtue}}`。

通过引用设置：通过变量名设置变量，例如 `*set {"leadership"} 30` 会将 `leadership` 设置为 30。可在复杂代码中如此使用：

```
  *set virtue "courage"
  *set {virtue} 30
```

这段代码会将 `courage`（勇气）设置为 30。如果这看起来仍然没什么用处，请考虑到 `virtue`（美德）可能由先前的选择决定，因此它也可能将 `honesty`（诚实）设置为 30。您还可以将其与 `*rand`、`*input_text` 和 `*input_number` 结合使用。

仍然不相信？不用担心；您可能永远都用不到它。

### 按名称跳转到标签

```
  *temp superpower "invisibility"
  Your super power is:
  *goto {superpower}
  flight!
  *finish
  *label invisibility
  invisibility.
```

### 按名称跳转到标签：

你也可以将此与 `*goto_scene` 和 `*gosub_scene` 配合使用。请注意，Quicktest 实际上会跳过使用花括号引用的 `*goto_scene` 和 `*gosub_scene` 行。（Randomtest 则能正常工作。）如果你的文件中存在仅能通过花括号引用的 `*goto_scene` 命令访问的段落，请考虑在游戏的某个位置添加类似以下的部分，枚举所有可能的目的地。

```
  *if false
    *gosub_scene checkpoint chap1
    *gosub_scene checkpoint chap2
    *gosub_scene checkpoint chap3
```

Quicktest 将“运行”这些行，以验证 chap1、chap2 和 chap3 标签存在于检查点场景中，并确认这些标签确实会被 Quicktest 覆盖到。

### 数组括号

你可以在变量名后加上方括号，例如：`foo[1]`，以引用变量 `foo_1`。但你也可以在括号内放置任何内容，包括变量，例如：`strength[current_opponent]`。

```
  *create current_opponent 1
  *create_array strength 5 50
  *create_array damage 5 20
  You did ${damage[current_opponent]} points of damage.
  *set strength[current_opponent] -damage[current_opponent]
  *goto dialog[current_opponent]
  *gosub_scene dialog[current_opponent] took_damage
```

### 有问题吗？

若对本文件有疑问，请在 [ChoiceScript 论坛](http://www.choiceofgames.com/forum/categories/choicescript-help)发帖咨询。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://raster.gitbook.io/zh-hans_choicescript-guide/guan-fang-zhi-nan/jin-jie-choicescript.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
