From 9f93b088a6392b7a530ff11007eb68b332dbecbc Mon Sep 17 00:00:00 2001 From: Ray-Lum <51054841+Ray-Lum@users.noreply.github.com> Date: Wed, 2 Oct 2024 14:56:23 +0800 Subject: [PATCH 01/22] =?UTF-8?q?WriteWithInk=20=E7=AC=AC=E4=B8=80?= =?UTF-8?q?=E7=AB=A0=20=E7=AE=80=E4=BD=93=E4=B8=AD=E6=96=87=20=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 还会再更新的。 --- .../RunningYourInk_Simplified-Chinese.md | 454 ++ .../WritingWithInk_Simplified-Chinese.md | 3636 +++++++++++++++++ 2 files changed, 4090 insertions(+) create mode 100644 Documentation/RunningYourInk_Simplified-Chinese.md create mode 100644 Documentation/WritingWithInk_Simplified-Chinese.md diff --git a/Documentation/RunningYourInk_Simplified-Chinese.md b/Documentation/RunningYourInk_Simplified-Chinese.md new file mode 100644 index 00000000..a8661a01 --- /dev/null +++ b/Documentation/RunningYourInk_Simplified-Chinese.md @@ -0,0 +1,454 @@ +# 运行您的 Ink + +## Quick Start + +*Note that although these instructions are written with Unity in mind, it's possible (and straightforward) to run your ink in a non-Unity C# environment.* + +* Download the [latest version of the ink-unity-integration Unity package](/inkle/ink-unity-integration/releases), and add to your project. +* Select your `.ink` file in Unity, and you should see a *Play* button in the file's inspector. +* Click it, and you should get an Editor window that lets you play (preview) your story. +* To integrate into your game, see **Getting started with the runtime API**, below. + +## Further information + +Ink uses an intermediate `.json` format, which is compiled from the original `.ink` files. ink's Unity integration package automatically compiles ink files for you, but you can also compile them on the command line. See **Using inklecate on the command line** in the [README](http://www.github.com/inkle/ink) for more information. + +The main runtime code is included in the `ink-engine.dll`. + +We recommend that you create a wrapper MonoBehaviour component for the **ink** `Story`. Here, we'll call the component "Script" - in the "film script" sense, rather than the "Unity script" sense! + +```csharp +using Ink.Runtime; + +public class Script : MonoBehaviour { + + // Set this file to your compiled json asset + public TextAsset inkAsset; + + // The ink story that we're wrapping + Story _inkStory; +``` + +## Getting started with the runtime API + +As mentioned above, your `.ink` file(s) are compiled to a single `.json` file. This is treated by Unity as a TextAsset, that you can then load up in your game. + +The API for loading and running your story is very straightforward. Construct a new `Story` object, passing in the JSON string from the TextAsset. For example, in Unity: + +```csharp +using Ink.Runtime; + +... + +void Awake() +{ + _inkStory = new Story(inkAsset.text); +} +``` +From there, you make calls to the story in a loop. There are two repeating stages: + + 1. **Present content:** You repeatedly call `Continue()` on it, which returns individual lines of string content, until the `canContinue` property becomes false. For example: + +```csharp +while (_inkStory.canContinue) { + Debug.Log (_inkStory.Continue ()); +} +``` + + A simpler way to achieve the above is through one call to `_inkStory.ContinueMaximally()`. However, in many stories it's useful to pause the story at each line, for example when stepping through dialogue. Also, in such games, there may be state changes that should be reflected in the UI, such as resource counters. + + 2. **Make choice:** When there isn't any more content, you should check to see whether there any choices to present to the player. To do so, use something like: + +```csharp + if( _inkStory.currentChoices.Count > 0 ) + { + for (int i = 0; i < _inkStory.currentChoices.Count; ++i) { + Choice choice = _inkStory.currentChoices [i]; + Debug.Log("Choice " + (i + 1) + ". " + choice.text); + } + } + + //...and when the player provides input: + + _inkStory.ChooseChoiceIndex (index); + + //And now you're ready to return to step 1, and present content again. +``` +### Saving and loading + +To save the state of your story within your game, call: + +`string savedJson = _inkStory.state.ToJson();` + +...and then to load it again: + +`_inkStory.state.LoadJson(savedJson);` + +### Error handling + +If you made a mistake in your ink that the compiler can't catch, then the story will throw an exception. To avoid this and get standard Unity errors instead, you can use an error handler that you should assign when you create your story: + +```csharp +_inkStory = new Story(inkAsset.text); + +_inkStory.onError += (msg, type) => { + if( type == Ink.ErrorType.Warning ) + Debug.LogWarning(msg); + else + Debug.LogError(msg); +}; +``` +### Is that it? + +That's it! You can achieve a lot with just those simple steps, but for more advanced usage, including deep integration with your game, read on. + +For a sample Unity project in action with minimal UI, see [Aaron Broder's Blot repo](https://github.com/abroder/blot). + +## Engine usage and philosophy + +In Unity, we recommend using your own component class to wrap `Ink.Runtime.Story`. The runtime **ink** engine has been designed to be reasonably general purpose and have a simple API. We also recommend wrapping rather than inheriting from `Story`, so that you can expose to your game only the functionality that you need. + +Often when designing the flow for your game, the sequence of interactions between the player and the story may not precisely match the way the **ink** is evaluted. For example, with a classic choose-your-own-adventure type story, you may want to show multiple lines (paragraphs) of text and choices all at once. For a visual novel, you may want to display one line per screen. + +Additionally, since the **ink** engine outputs lines of plain text, it can be effectively used for your own simple sub-formats. For example, for a dialog based game, you could write: + + * Lisa: Where did he go? + Joe: I think he jumped over the garden fence. + * * Lisa: Let's take a look. + * * Lisa: So he's gone for good? + +As far as the **ink** engine is concerned, the `:` characters are just text. But as the lines of text and choices are produced by the game, you can do some simple text parsing of your own to turn the string `Joe: What's up?` into a game-specific dialog object that references the speaker and the text (or even audio). + +This approach can be taken even further to text that flexibly indicates non-content directives. Again, these directives come out of the engine as text, but can parsed by your game for a specific purpose: + + PROPLIST table, chair, apple, orange + +The above approach is used in our current game for the writer to declare the props that they expect to be in the scene. These might be picked up in the game editor in order to automatically fill a scene with placeholder objects, or just to validate that the level designer has populated the scene correctly. + +To mark up content more explicitly, you may want to use *tags* or *external functions* - see below. At inkle, we find that we use a mixture, but we actually find the above approach useful for a very large portion of our interaction with the game - it's very flexible. + + +## Marking up your ink content with tags + +Tags can be used to add metadata to your game content that isn't intended to appear to the player. Within ink, add a `#` character followed by any string content you want to pass over to the game. There are three main places where you can add these hash tags: + +### Line by line tags + +One use case is for a graphic adventure that has different art for characters depending on their facial expression. So, you could do: + + Passepartout: Really, Monsieur. # surly + +On the game side, every time you get content with `_inkStory.Continue()`, you can get a list of tags with `_inkStory.currentTags`, which will return a `List`, in the above case with just one element: `"surly"`. + +To add more than one tag, simply delimit them with more `#` characters: + + Passepartout: Really, Monsieur. # surly # really_monsieur.ogg + +The above demonstrate another possible use case: providing full voice-over for your game, by marking up your ink with the audio filenames. + +Tags for a line can be written above it, or on the end of the line: + + # the first tag + # the second tag + This is the line of content. # the third tag + +All of the above tags will be included in the `currentTags` list. + +### Knot tags + +Any tags that you include at the very top of a knot: + + === Munich == + # location: Germany + # overview: munich.ogg + # require: Train ticket + First line of content in the knot. + +...are accessible by calling `_inkStory.TagsForContentAtPath("your_knot")`, which is useful for getting metadata from a knot before you actually want the game to go there. + +Note that these tags will also appear in the `currentTags` list for the first line of content in the knot. + +### Global tags + +Any tags provided at the very top of the main ink file are accessible via the `Story`'s `globalTags` property, which also returns a `List`. Any top level story metadata can be included there. + +We suggest the following by convention, if you wish to share your ink stories publicly: + + # author: Joseph Humfrey + # title: My Wonderful Ink Story + +Note that [Inky](https://github.com/inkle/inky) will use the title tag in this format as the `

` tag in a story exported for web. + +#### Choice tags + +Tags can also be applied to choices. Depending where the tag is placed inside the ink line, the tag will appear on both the choice and the content generated by that choice; on just the choice; or just on the output: + + * A choice! #a tag on both choice and content + * [A choice #a choice tag not on content ] + * A choice[!] which continues. #a tag on output content only + +You can use all three in the same ink line: + + * A choice #shared_tag [ with detail #choice_tag ] and content # content_tag + +The choice tags are stored inside the `List tags` on the choice object. + +#### Advanced: Tags are dynamic + +Note that the content of a tag can contain any inline **ink**, such as shuffles, cycles, function calls and variable replacements. + + {character}: Hello there! #{character}_greeting.jpg + + I open the door. #suspense_music{RANDOM(1, 4)}.mp3 + +## Jumping to a particular "scene" + +Top level named sections in **ink** are called knots (see [the writing tutorial](WritingWithInk.md)). You can tell the runtime engine to jump to a particular named knot: + +```csharp +_inkStory.ChoosePathString("myKnotName"); +``` + +And then call `Continue()` as usual. + +To jump directly to a stitch within a knot, use a `.` as a separator: + +```csharp +_inkStory.ChoosePathString("myKnotName.theStitchWithin"); +``` + +(Note that this path string is a *runtime* path rather than the path as used within the **ink** format. It's just been designed so that for the basics of knots and stitches, the format works out the same. Unfortunately however, you can't reference gather or choice labels this way.) + +## Setting/getting ink variables + +The state of the variables in the **ink** engine is, appropriately enough, stored within the `variablesState` object within the `story`. You can both get and set variables directly on this object: + +```csharp +_inkStory.variablesState["player_health"] = 100 + +int health = (int) _inkStory.variablesState["player_health"] +``` + +## Read/Visit counts + +To find out the number of times that a knot or stitch has been visited by the ink engine, you can use this API: + +```csharp +_inkStory.state.VisitCountAtPathString("..."); +``` + +The path string is in the form `"yourKnot"` for knots, and `"yourKnot.yourStitch"` for stitches. + +## Variable observers + +You can register a delegate function to be called whenever a particular variable changes. This can be useful to reflect the state of certain **ink** variables directly in the UI. For example: + +```csharp +_inkStory.ObserveVariable ("health", (string varName, object newValue) => { + SetHealthInUI((int)newValue); +}); +``` + +The reason that the variable name is passed in is so that you can have a single observer function that observes multiple different variables. + + +## Running functions + +You can run ink functions directly from C# using `EvaluationFunction`. + +You can pass the expected arguments for the ink function, if any. + +If the ink function has a return value, it will be returned by EvaluationFunction. +You do not need to Continue() over any text lines that may exist in the function; it runs to the end. Any content is written to the textOutput parameter, with a line break between each line. + +```csharp +var returnValue = _inkStory.EvaluationFunction("myFunctionName", out textOutput, params); +``` + +## External functions + +You can define game-side functions in C# that can be called directly from **ink**. To do so: + +1. Declare an external function using something like this at the top of one of your **ink** files, in global scope: + + EXTERNAL playSound(soundName) + +2. Bind your C# function. For example: + +```csharp + _inkStory.BindExternalFunction ("playSound", (string name) => { + _audioController.Play(name); + }); +``` + + There are convenience overloads for BindExternalFunction, for up to four parameters, for both generic `System.Func` and `System.Action`. There is also a general purpose `BindExternalFunctionGeneral` that takes an object array for more than 4 parameters. + +3. You can then call that function within the **ink**: + + ~ playSound("whack") + +The types you can use as parameters and return values are int, float, bool (automatically converted from **ink**’s internal ints) and string. + +#### Alternatives to external functions + +Remember that in addition to external functions, there are other good ways to communicate between your ink and your game: + +* You can set up a variable observer if you just want the game to know when some state has changed. This is perfect for say, changing the score in the UI. + +* You can use [tags](RunningYourInk.md#marking-up-your-ink-content-with-tags) to add invisible metadata to a line in ink. + +* In inkle's games such as [Heaven's Vault](https://www.inklestudios.com/heavensvault), we use the text itself to write instructions to the game, and then have a game-specific text parser decide what to do with it. This is a very flexible approach, and allows us to have a different style of writing on each project. For example, we use the following syntax to ask the game to set up a particular camera shot: + + `>>> SHOT: view_over_bridge` + +#### Actions v.s. Pure functions + +**Warning:** The following section is subtly complex! However, don't worry - you can probably ignore it and use default behaviour. If you find a situation where glue isn't working the way you expect and there's an external function in there somewhere, or if you're just plain curious, read on... + +There are two kinds of external functions: + +* **Actions** - for example, to play sounds, show images, etc. Generally, these may change game state in some way. +* **Pure functions** - those that don't cause side effects. Specifically 1) **it should be harmless to call them more than once**, and 2) **they shouldn't affect game state**. For example, a mathematical calculation, or pure inspection of game state. + +By default, external functions are treated as Actions, since we think this is the primary use-case for most people. However, the distinction can be important for subtle reasons to do with the way that glue works. + +When the engine looks at content, it may look ahead further than you would expect *just in case* there is glue in future content that would turn two separate lines into one. + +However, external functions that are intended to be run as actions, you don't want them to be run prospectively, since the player is likely to notice, so for this kind we cancel any attempt to glue content together. If it was in the middle of prospectively looking ahead and it sees an action, it'll stop before running it. + +Conversely, if all you're doing is a mathematical calculation for example, you don't want your glue to break. For example: + +``` +The square root of 9 +~ temp x = sqrt(9) +<> is {x}. +``` + +You can define how you want your function to behave when you bind it, using the `lookaheadSafe` parameter: + +```csharp +public void BindExternalFunction(string funcName, Func func, bool lookaheadSafe=false) +``` + +* **Actions** should have `lookaheadSafe = false` +* **Pure functions** should have `lookaheadSafe = true` + +### Fallbacks for external functions + +When testing your story, either in [Inky](/inkle/inky) or in the [ink-unity integration](/inkle/ink-unity-integration/) player window, you don't get an opportunity to bind a game function before running the story. To get around this, you can define a *fallback function* within ink, which is run if the `EXTERNAL` function can't be found. To do so, simply create an ink function with the same name and parameters. For example, for the above `multiply` example, create the ink function: + +``` +=== function multiply(x,y) === +// Usually external functions can only return placeholder +// results, otherwise they'd be defined in ink! +~ return 1 +``` + +## Multiple parallel flows (BETA) + +It is possible to have multiple parallel "flows" - allowing the following examples: + +- A background conversation between two characters while the protagonist talks to someone else. The protagonist could then leave and interject in the background conversation. +- Non-blocking interactions: you could interact with an object with generates a bunch of choices, but "pause" them, and go and interact with something else. The original object's choices won't block you from interacting with something new, and you can resume them later. + +The API is relatively simple: + +- `story.SwitchFlow("Your flow name")` - create a new Flow context, or switch to an existing one. The name can be anything you like, though you may choose to use the same name as an entry knot that you would go on to choose with `story.ChoosePathString("knotName")`. +- `story.SwitchToDefaultFlow()` - before you start switching Flows there's an implicit default Flow. To return to it, call this method. +- `story.RemoveFlow("Your flow name")` - destroy a previously created Flow. If the Flow is already active, it returns to the default flow. +- `story.aliveFlowNames` - the names of currently alive flows. A flow is alive if it's previously been switched to and hasn't been destroyed. Does not include default flow. +- `story.currentFlowIsDefaultFlow` - true if the default flow is currently active. By definition, will also return true if not using flow functionality. +- `story.currentFlowName` — a string containing the name of the currently active flow. May contain internal identifier for default flow, so use `currentFlowIsDefault` to check first. +) + +## Working with LISTs + +Ink lists are a more complex type used in the ink engine, so interacting with them is a bit more involved than with ints, floats and strings. + +Lists always need to know the origin of their items. For example, in ink you can do: + + ~ myList = (Orange, House) + +...even though `Orange` may have come from a list called `fruit` and `House` may have come from a list called `places`. In ink these *origin* lists are automatically resolved for you when writing. However when work in game code, you have to be more explicit, and tell the engine which origin lists your items belong to. + +To create a list with items from a single origin, and assign it to a variable in the game: + +```csharp +var newList = new Ink.Runtime.InkList("fruit", story); +newList.AddItem("Orange"); +newList.AddItem("Apple"); +story.variablesState["myList"] = newList; +``` + +If you're modifying a list, and you know that it has/had elements from a particular origin already: + +```csharp +var fruit = story.variablesState["fruit"] as Ink.Runtime.InkList; +fruit.AddItem("Apple"); +``` + +Note that single list items in ink, such as: + + VAR lunch = Apple + +...are actually just lists with single items in them rather than a different type. So to create them on the game side, just use the techniques above to create a list with just one item. + +You can also create lists from items if you explicitly know all the metadata for the items - i.e. the origin name as as well as the int value assigned to it. This is useful if you're building a list out of existing lists. Note that InkLists actually derive from `Dictionary`, where the key is an `InkListItem` (which in turn has `originName` and `itemName` strings), and the value is the int value: + +```csharp +var newList = new Ink.Runtime.InkList(); +var fruit = story.variablesState["fruit"] as Ink.Runtime.InkList; +var places = story.variablesState["places"] as Ink.Runtime.InkList; +foreach(var item in fruit) { + newList.Add(item.Key, item.Value); +} +foreach (var item in places) { + newList.Add(item.Key, item.Value); +} +story.variablesState["myList"] = newList; +``` + +To test if your list contains a particular item: + +```csharp +fruit = story.variablesState["fruit"] as Ink.Runtime.InkList; +if( fruit.ContainsItemNamed("Apple") ) { + // We're eating apple's tonight! +} +``` + +Lists also expose many of the operations you can do in ink: + +```csharp +list.minItem // equivalent to calling LIST_MIN(list) in ink +list.maxItem // equivalent to calling LIST_MAX(list) in ink +list.inverse // equivalent to calling LIST_INVERT(list) in ink +list.all // equivalent to calling LIST_ALL(list) in ink +list.Union(otherList) // equivalent to (list + otherList) in ink +list.Intersect(otherList) // equivalent to (list ^ otherList) in ink +list.Without(otherList) // equivalent to (list - otherList) in ink +list.Contains(otherList) // equivalent to (list ? otherList) in ink +``` + +## Using the compiler + +Precompiling your stories is more efficient than loading .ink at runtime. That said, it's a useful approach for some situations, and can be done with the following code: + +```csharp +// inkFileContents: linked TextAsset, or Resources.Load, or even StreamingAssets +var compiler = new Ink.Compiler(inkFileContents); +Ink.Runtime.Story story = compiler.Compile(); +Debug.Log(story.Continue()); +``` + +Note that if your story is broken up into several ink files using `INCLUDE`, that you will need to use: + +```csharp +var compiler = new Ink.Compiler(inkFileContents, new Compiler.Options +{ + countAllVisits = true, + fileHandler = new UnityInkFileHandler(Path.GetDirectoryName(inkAbsoluteFilePath)) +}); +Ink.Runtime.Story story = compiler.Compile(); +Debug.Log(story.Continue()); +``` diff --git a/Documentation/WritingWithInk_Simplified-Chinese.md b/Documentation/WritingWithInk_Simplified-Chinese.md new file mode 100644 index 00000000..4892f6ab --- /dev/null +++ b/Documentation/WritingWithInk_Simplified-Chinese.md @@ -0,0 +1,3636 @@ +# 使用 Ink 进行写作 +
+ 内容目录 + +- [使用 Ink 进行写作](#使用-ink-进行写作) + - [声明](#声明) + - [介绍](#介绍) +- [第 1 部分:基础|Part One: The Basics](#第-1-部分基础part-one-the-basics) + - [1) 内容|Content](#1-内容content) + - [最简单的 ink 脚本|Hello, World!](#最简单的-ink-脚本hello-world) + - [注释|Comments](#注释comments) + - [标签|Tags](#标签tags) + - [2) 选择|Choices](#2-选择choices) + - [不输出选择文本|Suppressing choice text](#不输出选择文本suppressing-choice-text) + - [进阶:混合选项与输出文本|Advanced: mixing choice and output text](#进阶混合选项与输出文本advanced-mixing-choice-and-output-text) + - [多样化的选择|Multiple Choices](#多样化的选择multiple-choices) + - [3) 结点|Knots](#3-结点knots) + - [内容的片段被称为结点|Pieces of content are called knots](#内容的片段被称为结点pieces-of-content-are-called-knots) + - [撰写一个结点|Writing a knot](#撰写一个结点writing-a-knot) + - [进阶:一个结点更复杂的“你好世界”|Advanced: a knottier "hello world"](#进阶一个结点更复杂的你好世界advanced-a-knottier-hello-world) + - [4) 分道|Diverts](#4-分道diverts) + - [从结点分道到结点|Knots divert to knots](#从结点分道到结点knots-divert-to-knots) + - [分道是不可见的|Diverts are invisible](#分道是不可见的diverts-are-invisible) + - [胶合|Glue](#胶合glue) + - [5) 为故事流程进行分支|Branching The Flow](#5-为故事流程进行分支branching-the-flow) + - [基本分支|Basic branching](#基本分支basic-branching) + - [分支与合并|Branching and joining](#分支与合并branching-and-joining) + - [故事流|The story flow](#故事流the-story-flow) + - [进阶:循环|Advanced: Loops](#进阶循环advanced-loops) + - [6) 包含和接缝|Includes and Stitches](#6-包含和接缝includes-and-stitches) + - [结点可以是次级分道|Knots can be subdivided](#结点可以是次级分道knots-can-be-subdivided) + - [接缝需要有独一无二的名称|Stitches have unique names](#接缝需要有独一无二的名称stitches-have-unique-names) + - [默认为第一个接缝|The first stitch is the default](#默认为第一个接缝the-first-stitch-is-the-default) + - [内部分道|Local diverts](#内部分道local-diverts) + - [脚本文件可组合|Script files can be combined](#脚本文件可组合script-files-can-be-combined) + - [7) 可变选项|Varying Choices](#7-可变选项varying-choices) + - [选项只能被使用一次|Choices can only be used once](#选项只能被使用一次choices-can-only-be-used-once) + - [后备选项|Fallback choices](#后备选项fallback-choices) + - [后备选项示例|Example of a fallback choice](#后备选项示例example-of-a-fallback-choice) + - [粘滞选项|Sticky choices](#粘滞选项sticky-choices) + - [条件选项|Conditional Choices](#条件选项conditional-choices) + - [进阶:多重条件|Advanced: multiple conditions](#进阶多重条件advanced-multiple-conditions) + - [逻辑运算符:AND 和 OR|Logical operators: AND and OR](#逻辑运算符and-和-orlogical-operators-and-and-or) + - [进阶:结点与接缝的实际阅读次数|Advanced: knot/stitch labels are actually read counts](#进阶结点与接缝的实际阅读次数advanced-knotstitch-labels-are-actually-read-counts) + - [进阶:更多逻辑|Advanced: more logic](#进阶更多逻辑advanced-more-logic) + - [8) 可变文本|Variable Text](#8-可变文本variable-text) + - [文本是可以变更的|Text can vary](#文本是可以变更的text-can-vary) + - [序列、循环以及其他类型的替文|Sequences, cycles and other alternatives](#序列循环以及其他类型的替文sequences-cycles-and-other-alternatives) + - [替文的类型|Types of alternatives](#替文的类型types-of-alternatives) + - [替文的特点|Features of Alternatives](#替文的特点features-of-alternatives) + - [示例|Examples](#示例examples) + - [另行参见:多行替文|Sneak Preview: Multiline alternatives](#另行参见多行替文sneak-preview-multiline-alternatives) + - [条件文本|Conditional Text](#条件文本conditional-text) + - [9) 游戏查询和函数|Game Queries and Functions](#9-游戏查询和函数game-queries-and-functions) + - [选项计数函数|CHOICE\_COUNT()](#选项计数函数choice_count) + - [总回合计数函数|TURNS()](#总回合计数函数turns) + - [分道计数函数|TURNS\_SINCE(-\> knot)](#分道计数函数turns_since--knot) + - [功能预览:在功能中使用分道计数函数|Sneak preview: using TURNS\_SINCE in a function](#功能预览在功能中使用分道计数函数sneak-preview-using-turns_since-in-a-function) + - [种子随机函数|SEED\_RANDOM()](#种子随机函数seed_random) + - [进阶:更多查询|Advanced: more queries](#进阶更多查询advanced-more-queries) +- [第 2 部分:编织|Part 2: Weave](#第-2-部分编织part-2-weave) + - [1) Gathers](#1-gathers) + - [Gather points gather the flow back together](#gather-points-gather-the-flow-back-together) + - [Options and gathers form chains of content](#options-and-gathers-form-chains-of-content) + - [The weave philosophy](#the-weave-philosophy) + - [2) Nested Flow](#2-nested-flow) + - [Options can be nested](#options-can-be-nested) + - [Gather points can be nested too](#gather-points-can-be-nested-too) + - [Advanced: What gathers do](#advanced-what-gathers-do) + - [You can nest as many levels are you like](#you-can-nest-as-many-levels-are-you-like) + - [Example: a conversation with nested nodes](#example-a-conversation-with-nested-nodes) + - [3) Tracking a Weave](#3-tracking-a-weave) + - [Weaves are largely unaddressed](#weaves-are-largely-unaddressed) + - [Gathers and options can be labelled](#gathers-and-options-can-be-labelled) + - [Scope](#scope) + - [Advanced: all options can be labelled](#advanced-all-options-can-be-labelled) + - [Advanced: Loops in a weave](#advanced-loops-in-a-weave) + - [Advanced: diverting to options](#advanced-diverting-to-options) + - [Advanced: Gathers directly after an option](#advanced-gathers-directly-after-an-option) +- [第 3 部分:变量和逻辑|Part 3: Variables and Logic](#第-3-部分变量和逻辑part-3-variables-and-logic) + - [1) Global Variables](#1-global-variables) + - [Defining Global Variables](#defining-global-variables) + - [Using Global Variables](#using-global-variables) + - [Advanced: storing diverts as variables](#advanced-storing-diverts-as-variables) + - [Advanced: Global variables are externally visible](#advanced-global-variables-are-externally-visible) + - [Printing variables](#printing-variables) + - [Evaluating strings](#evaluating-strings) + - [2) Logic](#2-logic) + - [Mathematics](#mathematics) + - [RANDOM(min, max)](#randommin-max) + - [Advanced: numerical types are implicit](#advanced-numerical-types-are-implicit) + - [Advanced: INT(), FLOOR() and FLOAT()](#advanced-int-floor-and-float) + - [String queries](#string-queries) + - [3) Conditional blocks (if/else)](#3-conditional-blocks-ifelse) + - [A simple 'if'](#a-simple-if) + - [Extended if/else if/else blocks](#extended-ifelse-ifelse-blocks) + - [Switch blocks](#switch-blocks) + - [Example: context-relevant content](#example-context-relevant-content) + - [Conditional blocks are not limited to logic](#conditional-blocks-are-not-limited-to-logic) + - [多行替文|Multiline blocks](#多行替文multiline-blocks) + - [Advanced: modified shuffles](#advanced-modified-shuffles) + - [4) Temporary Variables](#4-temporary-variables) + - [Temporary variables are for scratch calculations](#temporary-variables-are-for-scratch-calculations) + - [Knots and stitches can take parameters](#knots-and-stitches-can-take-parameters) + - [Example: a recursive knot definition](#example-a-recursive-knot-definition) + - [Advanced: sending divert targets as parameters](#advanced-sending-divert-targets-as-parameters) + - [5) 函数|Functions](#5-函数functions) + - [Defining and calling functions](#defining-and-calling-functions) + - [Functions don't have to return anything](#functions-dont-have-to-return-anything) + - [Functions can be called inline](#functions-can-be-called-inline) + - [Examples](#examples) + - [Example: turning numbers into words](#example-turning-numbers-into-words) + - [Parameters can be passed by reference](#parameters-can-be-passed-by-reference) + - [6) 常量|Constants](#6-常量constants) + - [Global Constants](#global-constants) + - [7) Advanced: Game-side logic](#7-advanced-game-side-logic) +- [Part 4: Advanced Flow Control](#part-4-advanced-flow-control) + - [1) Tunnels](#1-tunnels) + - [Tunnels run sub-stories](#tunnels-run-sub-stories) + - [Advanced: Tunnels can return elsewhere](#advanced-tunnels-can-return-elsewhere) + - [Advanced: Tunnels use a call-stack](#advanced-tunnels-use-a-call-stack) + - [2) Threads](#2-threads) + - [Threads join multiple sections together](#threads-join-multiple-sections-together) + - [Uses of threads](#uses-of-threads) + - [When does a side-thread end?](#when-does-a-side-thread-end) + - [Using `-> DONE`](#using---done) + - [Example: adding the same choice to several places](#example-adding-the-same-choice-to-several-places) + - [Example: organisation of wide choice points](#example-organisation-of-wide-choice-points) +- [Part 5: Advanced State Tracking](#part-5-advanced-state-tracking) + - [Note: New feature alert!](#note-new-feature-alert) + - [1) Basic Lists](#1-basic-lists) + - [2) Reusing Lists](#2-reusing-lists) + - [States can be used repeatedly](#states-can-be-used-repeatedly) + - [List values can share names](#list-values-can-share-names) + - [Advanced: a LIST is actually a variable](#advanced-a-list-is-actually-a-variable) + - [3) List Values](#3-list-values) + - [Converting values to numbers](#converting-values-to-numbers) + - [Converting numbers to values](#converting-numbers-to-values) + - [Advanced: defining your own numerical values](#advanced-defining-your-own-numerical-values) + - [4) Multivalued Lists](#4-multivalued-lists) + - [Lists are boolean sets](#lists-are-boolean-sets) + - [Assiging multiple values](#assiging-multiple-values) + - [Adding and removing entries](#adding-and-removing-entries) + - [Basic Queries](#basic-queries) + - [Testing for emptiness](#testing-for-emptiness) + - [Testing for exact equality](#testing-for-exact-equality) + - [Testing for containment](#testing-for-containment) + - [Warning: no lists contain the empty list](#warning-no-lists-contain-the-empty-list) + - [Example: basic knowledge tracking](#example-basic-knowledge-tracking) + - [Example: a doctor's surgery](#example-a-doctors-surgery) + - [Advanced: nicer list printing](#advanced-nicer-list-printing) + - [Lists don't need to have multiple entries](#lists-dont-need-to-have-multiple-entries) + - [The "full" list](#the-full-list) + - [Advanced: "refreshing" a list's type](#advanced-refreshing-a-lists-type) + - [Advanced: a portion of the "full" list](#advanced-a-portion-of-the-full-list) + - [Example: Tower of Hanoi](#example-tower-of-hanoi) + - [5) Advanced List Operations](#5-advanced-list-operations) + - [Comparing lists](#comparing-lists) + - ["Distinctly bigger than"](#distinctly-bigger-than) + - ["Definitely never smaller than"](#definitely-never-smaller-than) + - [Health warning!](#health-warning) + - [Inverting lists](#inverting-lists) + - [Footnote](#footnote) + - [Intersecting lists](#intersecting-lists) + - [6) Multi-list Lists](#6-multi-list-lists) + - [Lists to track objects](#lists-to-track-objects) + - [Lists to track multiple states](#lists-to-track-multiple-states) + - [How does this affect queries?](#how-does-this-affect-queries) + - [7) Long example: crime scene](#7-long-example-crime-scene) + - [8) Summary](#8-summary) + - [Flags](#flags) + - [State machines](#state-machines) + - [Properties](#properties) +- [Part 6: International character support in identifiers](#part-6-international-character-support-in-identifiers) + - [Supported Identifier Characters](#supported-identifier-characters) + +
+ +## 声明 + +简体中文教程是从英文原文翻译而来。可能会存在版本滞后性,故入与英文版有出入,请与英文版为准。 + +部分不重要的段落由 ChatGPT 或 DeepL 组合翻译。 + +重要的部分完全由人工翻译。 + +本文译者:王洛木 (Nomo Wang) + +翻译时的软件版本:【发布分支时记得修改这里……】 + +## 介绍 + +**Ink** 是一种脚本语言,围绕着用流程标记纯文本以生成交互式脚本的理念而构建。 + +它的基本功能是编写“选择你自己的”故事或分支对话树。但它真正的优势在于能够编写包含大量选项和复杂流程重组的对话。 + +**Ink** 提供了一些功能,使非专业作家能够频繁进行分支,并以轻重缓急的方式演绎这些分支的后果,毫不费力。 + +脚本力求简洁、逻辑清晰,因此可以通过“用眼睛”测试分支对话。在可能的情况下,流程以声明的方式进行描述。 + +它的设计也考虑到了重写的问题,因此编辑流程应该快速便捷。 + +# 第 1 部分:基础|Part One: The Basics + +## 1) 内容|Content + +### 最简单的 ink 脚本|Hello, World! + +最基础的 ink 脚本就是在 .ink 文件中输入文本就行了。 + + 你好,世界! + +在运行时,这会直接输出文本,然后就停止了。 + +另起一行就可以新建段落。就像这个脚本: + + 你好,世界! + 你好? + 哈喽,你在那里么? + +输出结果与上面的脚本是一样的,所见即所得。 + +### 注释|Comments + +默认情况下,文件中的所有文本都将出现在输出内容中,除非特别标注。 + +最简单的标记就是注释。**Ink** 支持两种注释。一种是供阅读代码的人使用的注释,编译器会忽略它: + + “你怎么看?”她问到。 + + // 这行以双斜杠开始的内容不会被打印输出……(译者注:也就是单行注释,可以直接跟在某行文本之后。) + + “我不可能发表评论。”我回复道。 + + /* + ……夹在本段落上下的两个标记符号之间部分可以写无限长的注释,包括换行。 + */ + + +另外还有一种是用来提醒作者需要做什么用的,编译器会在编译时打印出来: + +(译者注:TODO 后面的冒号要使用半角冒号,也就是英文冒号,然后后面打个空格) + + TODO: 这个段落应该写…… + +### 标签|Tags + +引擎运行时,游戏中的文本内容会以“原样”显示。但有时在一行内容上标注额外信息,告诉游戏如何处理该内容也是很有用的。 + +**Ink** 提供了一个简单的系统,可以用标签标记内容行。 + + 一行普通的游戏文本。# 颜色-蓝色 + A line of normal game-text. # colour it blue + +这些标签不会显示在主文本流中,但可以被游戏读取,并根据需要使用。请参阅 [《运行您的 Ink》](RunningYourInk_Simplified-Chinese.md#marking-up-your-ink-content-with-tags) 获取更多信息。 + +## 2) 选择|Choices + +玩家可通过文本选项来进行输入。文本选项用 "*"字符表示。 + +如果没有给出其他流程指示,一旦做出选择,就会根据选项进入下一行文本。 + + 你好世界! + * 你也好! + 见到你真是太好了! + +上面的这个脚本会在游戏中这样输出: + + 你好世界! + 1: 你也好! + + > 1 + 你也好! + 见到你真是太好了! + +在默认情况下,选项的文本会在输出中再显示一次。 + +### 不输出选择文本|Suppressing choice text + +有些游戏将选择文本与结果分开。在 **Ink** 中,如果文本选项的文本写在方括号中,则该文本不会被打印到响应中。 + + + 你好世界! + * [你也好!] + 见到你真是太好了! + +输出结果: + + 你好世界! + 1: 你也好! + + > 1 + 见到你真是太好了! + +#### 进阶:混合选项与输出文本|Advanced: mixing choice and output text + +可以使用方括号来分割选项文本在输出中的范围: +* 方括号 前面的 内容会在选项和输出中都发引出来 +* 方括号 内部的 部分只会显示在选项内 +* 方括号 后面的 则只会打印在输出的内容里。 + +这可以为故事线提供不同的结尾方式。 + +比如下面这个脚本: + + 你好世界! + * 你好 + * 你[也好!]也一样! + 见到你真是太好了! + +输出的结果是: + + 你好世界! + 1: 你也好! + > 1 + 你也一样! + 见到你真是太好了! + +这在编写对话选择的时候很实用,下面是一个脚本示例: + + “你说什么?”我的老大问我。 + * “我有点累了[。”],老大……”我重复着。 + “这样啊。”他回应道:“那休息一下吧。” + +这将输出: + + “你说什么?”我的老大问我。 + 1: “我有点累了。” + > 1 + “我有点累了,老大……”我重复着。 + “这样啊。”他回应道:“那休息一下吧。” + +### 多样化的选择|Multiple Choices + +为了让选择更真实,我们需要提供一些替代选项。只需列出备选方案即可: + + “你说什么?”我的老大问我。 + * “我有点累了[。”],老大……”我重复着。 + “这样啊。”他回应道:“那休息一下吧。” + * “没事的老大!”[]我说。 + “很好,那继续吧。” + * “我说,这次的冒险真的很可怕[……”],我真的不想再继续了…… + “啊……别这样。”他安慰着我:“看起来你现在有些累了。明天,事情一定会有所好转的。” + +上面这段脚本的游戏结果如下: + + “你说什么?”我的老大问我。 + + 1: “我有点累了。” + 2: “没事的老大!” + 3: “我说,这次的冒险真的很可怕……” + + > 3 + “我说,这次的冒险真的很可怕,我真的不想再继续了…… + “啊……别这样。”他安慰着我:“看起来你现在有些累了。明天,事情一定会有所好转的。” + +上述语法足以编写一组选项。在真正的游戏中,我们需要根据玩家的选择将流程从一个点移动到另一个点。为此,我们需要引入更多的结构。 + +## 3) 结点|Knots + +### 内容的片段被称为结点|Pieces of content are called knots + +为了让游戏能够分支,我们需要用名称来标记内容的不同部分(就像老式游戏本中的 “第 18 段”之类的)。 + +这些 部分 (Section) 就被称为“结点”(Knots),是 Ink 内容的基本结构单元。 + +### 撰写一个结点|Writing a knot + +结点的起点用两个或以上的等号表示,如下几行均为符合规范的结点起点(每行一个例子): + + === top_knot === + === top_knot + ==top_knot + +需要注意的是: +* 区分大小写。 +* 末尾的等号是可选项。 +* 无法使用连字符 "-"。还有其他一些特殊的标点符号也无法使用。在出现不可使用的标点符号时,编辑器会报错提醒。 +* 可以使用数字开头,但是不可以使用纯数字。 +* 中间不可以有空格,不然一来结点名会被截取到空格前,二来跳转箭头也无法指向对应结点。 +* 可以使用英文字符以外的字符,但是不推荐。一是因为截止至翻译为止,使用了英文字符以外的字符不会被 Inky 用蓝色标记;而是因为很有可能部分字符会导致结点失效。所以不建议使用英文与数字以外的字符。 + +等号这一行就是该结点的标题(当然等号和空格不会算在内)。在这下面的内容都在这个结点内。 + + === back_in_london === + + 我们于晚上 9 点 45 分准时到达伦敦。 + +#### 进阶:一个结点更复杂的“你好世界”|Advanced: a knottier "hello world" + +在启动 Ink 文件时,结点以外的内容会自动运行。但节点不会。因此,如果你开始使用节点来管理内容,就需要告诉游戏该去哪里。我们可以使用分道箭头 `->`来做到这一点,下一部分将对此进行详细介绍。 + +这是一个简单的结点跳转脚本: + + -> top_knot + + === top_knot === + 你好世界! + +不过,**Ink** 不喜欢“开放式”结局(这里说的意思是:需要有一个标记来告诉 Ink 结点结束了),当它认为出现这种情况时,会在编译和或运行时发出警告。上面的脚本就会在编译时发出这样的警告: + + WARNING: Apparent loose end exists where the flow runs out. Do you need a '-> END' statement, choice or divert? on line 3 of tests/test.ink + + 警告:显然在流程结束的地方少写了点什么。是否需要 "->END" 语句、选项或跳转?此问题发生在 tests/test.ink 的第 3 行。 + +(译者注:程序内的文本已经在翻译了……所以这里这里姑且先保留原本的报错,以便对照查询。下同。) + +在运行时的报错则是这样的: + + Runtime error in tests/test.ink line 3: ran out of content. Do you need a '-> DONE' or '-> END'? + + 在运行到 tests/test.ink 的第 3 行时出现错误:没有更多内容了。或许您需要 "-> DONE" 或者 "-> END"? + +下面的这个脚本则不会在游玩或者编译时出现问题: + + === top_knot === + 你好世界! + -> END + +`-> END` 是一个同时给写作者和编译器用的标记,表示 "故事流程现在应该停止"。 + +## 4) 分道|Diverts + +### 从结点分道到结点|Knots divert to knots + +您可以使用“分道箭头”`->`来让故事从一个结点分道到另一个结。无需任何用户输入,分道会立即发生。 + + === back_in_london === + + 我们于晚上 9 点 45 分准时到达伦敦。 + -> hurry_home + + === hurry_home === + 我们以最快的速度赶回萨维尔街。 + +#### 分道是不可见的|Diverts are invisible + +分道甚至可以在句子中可以无缝衔接: + + === hurry_home === + 我们赶回萨维尔街, -> as_fast_as_we_could + + === as_fast_as_we_could === + 用我们最快的速度。 + +这将会输出: + + 我们赶回萨维尔街,用我们最快的速度。 + +#### 胶合|Glue + +脚本在另起一行的时候默认会有一个不可见的换行符。但是在某些情况下,您可能不希望您的文本换行,但是在脚本里又需要换行来写。那么这时就可以使用 `<>` 或 "glue"来实现。 + + === hurry_home === + 我们赶回<> + -> to_savile_row + + === to_savile_row === + 萨维尔街, + -> as_fast_as_we_could + + === as_fast_as_we_could === + <>用我们最快的速度。 + +输出是这样的: + + 我们赶回萨维尔街,用我们最快的速度。 + +您最好不要使用多个胶合:多个相邻的胶合语法不会产生额外的效果。(并且也没有办法“屏蔽”胶合;一旦一行被胶合起来,就无法再拆分开。) + +## 5) 为故事流程进行分支|Branching The Flow + +### 基本分支|Basic branching + +将结点、选项和分道结合起来,就形成了的自助游戏 (choose-your-own game) 的基本结构。 + + === paragraph_1 === + 你站在安纳兰德城墙边,手持长剑。 + * [打开大门] -> paragraph_2 + * [砸了那个大门] -> paragraph_3 + * [打道回府] -> paragraph_4 + + === paragraph_2 === + 你打开了大门,踏上了门里那条小路。 + + ... + +### 分支与合并|Branching and joining + +利用分道,作者可以将故事流分支,然后再次合并起来,且不会让玩家看到流程已经重新连接。 + + === back_in_london === + + 我们于晚上 9 点 45 分准时到达伦敦。 + + * “要没时间了!”我大喊。 + -> hurry_outside + + * "老大,时间还够呢!"[] 我说。 + 老大用力拍了拍我的头,把我拽出了门。 + -> dragged_outside + + * [我们立刻向家里赶去], -> hurry_outside + + + + === hurry_outside === + + 我们赶回萨维尔街,-> as_fast_as_we_could + + + === dragged_outside === + 他坚持要我们赶回萨维尔街的家, + -> as_fast_as_we_could + + + === as_fast_as_we_could === + <>用我们最快的速度。 + + +### 故事流|The story flow + +结点和分道相结合就形成了游戏的基本故事流程。但这种流程是“扁平”的——既没有调用堆栈,分道也不会从某处“折返”。 + +在大多数水墨脚本中,故事流程从顶部开始,像意大利面条一样乱蹦乱跳,最终,希望能到达"->结束"。 +在大部分 Ink 脚本中,故事流从顶部开始,然后就像是一盘意面一样,最终到达一个 `-> END`。 + +这种松散的结构方式可以让作者轻松的续写、分支或合并,也不必担心他们在写作过程中就要想好要创建的结构。而在创建新的分支或分流时,既然不需要任何模板,也不需要跟踪任何状态。 + +#### 进阶:循环|Advanced: Loops + +您可以使用分道来创建循环内容,**Ink** 有多种可以利用这一点的功能,包括使内容自行变化的方法,以及控制选项选择频率的方法。 + +更多信息请参阅这些章节: +* [可变文本|Variable Text](#8-可变文本variable-text) +* [条件选项|Conditional Choices](#条件选项conditional-choices) + +另外,下列内容符合规范但是并不好: + + === round === + 然后 + -> round + +(译者注:上面这是一个无限循环。) + +## 6) 包含和接缝|Includes and Stitches + +### 结点可以是次级分道|Knots can be subdivided + +随着故事越来越长,如果没有一些额外的结构,就会变得越来越难以组织。 + +结点可以包括一种被称为“接缝” (Stitches) 的子部分。这些接缝使用一个等号标记。 + + === the_orient_express === + = in_first_class + ... + = in_third_class + ... + = in_the_guards_van + ... + = missed_the_train + ... + +例如,可以结点来指定一个场景,然后用接缝来表示场景中的事件。 + +### 接缝需要有独一无二的名称|Stitches have unique names + +接缝可以使用它的“地址”(Address) 来进行分道。 + + * [乘坐三等座] + -> the_orient_express.in_third_class + + * [乘坐警卫间] + -> the_orient_express.in_the_guards_van + +### 默认为第一个接缝|The first stitch is the default + +转到包含接缝的结点时,将转到结点中的第一个接缝。所以: + + * [乘坐一等座] + "先生,一等座还有空位么?" + -> The_orient_express + +与下面这个脚本是一样的: + + * [乘坐一等座] + "先生,一等座还有空位么?" + -> the_orient_express.in_first_class + +(……除非我们在结点内移动了接缝的顺序!) + + +您也可以在结点内的那些接缝上方加入任何内容。然而你需要记得为接缝进行分道。因为引擎在有接缝前有内容的时候*不会*自动进入第一个接缝,举个例子: + + === the_orient_express === + + 已经上了火车了,但是坐到哪里呢? + * [一等座] -> in_first_class + * [二等座] -> in_second_class + + = in_first_class + ... + = in_second_class + ... + + +### 内部分道|Local diverts + +如果你要在结点内进行分道,那么您不需要使用完整的地址就可以进行内部接缝。 + + -> the_orient_express + + === the_orient_express === + = in_first_class + 我安顿好了我的老大。 + * [去三等座] + -> in_third_class + + = in_third_class + 我把我自己安排在三等座。 + +这意味着接缝和结点不能共用名称,但是如果相同名称的接缝分别属于不同的结点则可以使用。(因此,"东方快车”和“蒙古号”这两个结点里面都可以包含叫“一等座”的接缝。) + +如果使用了模棱两可的名称,编译器会发出警告。 + +### 脚本文件可组合|Script files can be combined + +您还可以可以把您的脚本内容拆分到多个文件中,只需要使用“包含声明”`INCLUDE` 就可以了。 + + INCLUDE newspaper.ink + INCLUDE cities/vienna.ink + INCLUDE journeys/orient_express.ink + +包含语句应始终放在文件头,而不是在结点内。 + +把文件分割开不会影响到分道跳转。(换句话说,只要你在文件头声明过了要用到的文件,那么就可以进行跨文件分道。) + +## 7) 可变选项|Varying Choices + +### 选项只能被使用一次|Choices can only be used once + +默认情况下,游戏中的每个选择都只能被选择一次。如果你的故事中没有循环,你就不会注意到这种行为。但如果你使用了循环,你很快就会发现你的选项消失了…… + + === find_help === + 你在人群中拼命地寻找着友善的面孔。 + * 那个戴帽子的女人[?]粗暴地把你推到了一边。-> find_help + * 那个拿公文包的男人[?]一脸嫌弃地看着你然后走开了。-> find_help + +输出结果: + + 你在人群中拼命地寻找着友善的面孔。 + 1: 那个戴帽子的女人? + 2: 那个拿公文包的男人? + + > 1 + 那个戴帽子的女人粗暴地把你推到了一边。 + 你在人群中拼命地寻找着友善的面孔。 + + 1: 那个拿公文包的男人? + + > + +……然后你就发现什么选项都没剩下了。 + +#### 后备选项|Fallback choices + +上面的示例到此为止,因为下一个选择会导致在运行时出现“内容不足”的错误。 + + > 1 + 那个拿公文包的男人一脸嫌弃地看着你然后走开了。 + 你在人群中拼命地寻找着友善的面孔。 + + Runtime error in tests/test.ink line 6: ran out of content. Do you need a '-> DONE' or '-> END'? + + 在运行到 tests/test.ink 的第 6 行时出现错误:内容不足。您需要 "-> DONE" 还是 "-> END"? + +我们可以用“后备选项”来解决这个问题。后备选项并不会显示给玩家,而是当玩家没有别的选项的时候就会自动选择它。 + +后备选项写起来很简单,就是“没有选择文本的选项”: + + * -> out_of_options + +此外,我们还可以稍微滥用一下这个语法,使用“空接箭头”来做一个带有内容的默认的选择: + + * -> + 穆德始终无法解释他是如何从着火的车厢里逃出来的。-> season_2 + +#### 后备选项示例|Example of a fallback choice + +将其与前面的例子相加,就得出了结果: + + === find_help === + + 你在人群中拼命地寻找着友善的面孔。 + * 那个戴帽子的女人[?]粗暴地把你推到了一边。-> find_help + * 那个拿公文包的男人[?]一脸嫌弃地看着你然后走开了。-> find_help + * -> + 但为时已晚:你倒在了列车站台上。这就是结局。 + -> END + +这将输出: + + 你在人群中拼命地寻找着友善的面孔。 + 1: 那个戴帽子的女人? + 2: 那个拿公文包的男人? + + > 1 + 那个戴帽子的女人粗暴地把你推到了一边。 + 你在人群中拼命地寻找着友善的面孔。 + + 1: 那个拿公文包的男人? + + > 1 + 那个拿公文包的男人一脸嫌弃地看着你然后走开了。 + 你在人群中拼命地寻找着友善的面孔。 + 但为时已晚:你倒在了列车站台上。这就是结局。 + +### 粘滞选项|Sticky choices + +当然,“一次性”的行为并不总是我们想要的,所以我们还有第二种选择:“粘滞”选择。粘滞就是不会被用完的选择,选一次之后还能再选,它用 "+"标记。 + + === homers_couch === + + [吃另一个甜甜圈] + 你吃了另一个甜甜圈。 -> homers_couch + * [从沙发上起来] + 你挣扎着从沙发上站起来,去创作史诗。 + -> END + +后备选项也可以是粘滞选项: + + === conversation_loop + * [谈论最近的天气] -> chat_weather + * [谈论孩子们的事情] -> chat_children + + -> sit_in_silence_again + +### 条件选项|Conditional Choices + +您还可以手动打开或关闭选择。**Ink** 有很多可用的逻辑,但最简单的检测是“玩家是否看过某个特定内容”。 + +游戏中的每个结点与接缝都有一个唯一的地址(这样它就可以被分道到),我们使用相同的地址来检测该内容是否被查看过。 + + * { not visit_paris } [去巴黎] -> visit_paris + + { visit_paris } [回到巴黎] -> visit_paris + * { visit_paris.met_estelle } [致电艾斯特尔女士] -> phone_estelle + +需要注意的是:如果要检测的 `knot_name`(结点名)内含有接缝的话,则需要看完*所有的*接缝后,返回的结果才是“ture”(是、真)。 + +还要注意的是,条件选项也是一次性选项,因此你仍然需要将其标识为粘滞选项才可进行重复选择。 + +#### 进阶:多重条件|Advanced: multiple conditions + +您可以在一个选项上使用多个逻辑检测;如果这样做的话,那么*所有的*检测都必须通过之后,对应的选项才会出现。 + + * { not visit_paris } [去巴黎] -> visit_paris + + { visit_paris } { not bored_of_paris } [回到巴黎] -> visit_paris + +#### 逻辑运算符:AND 和 OR|Logical operators: AND and OR + +上述“多重条件”实际上只是带有普通 AND 运算符条件编程。Ink 支持常用的 `and`(和、也、并且,也可以写成 `&&`)还有 `or` (或、或者,也可以写成 `||`),也支持半角括号。 + + * { not (visit_paris or visit_rome) && (visit_london || visit_new_york) } [等等,到底要去哪儿?我有点糊涂了。] -> visit_someplace + +译者注:上方的示例条件部分翻译过来: +* “伪代码”:{非 (visit_paris 或 visit_rome) 且 (visit_london 或着 visit_new_york)} +* 人话:没有 访问过巴黎 或者 访问过罗马,且 访问过伦敦 或者 访问过纽约 + +对于非程序员来说,假定 `X` 和 `Y` 是两个结点,那么 `X and Y` 就表示 `X` 和 `Y` 都必须为真。`X or Y` 表示二者之一或二者皆是。我们没有 `xor`(“异或”,即当两两数值相同时为否,而数值不同时为真。)。 + +您也可以使用标准的 `!` 来表示 `not`,不过有时会让编译器感到困惑,因为它认为 `{!text}` 是本文接下来会提到的一种“一次性替文”。我们建议使用 `not` 因为布尔检测很令人头大。(译者注:此外,非程序员会相对难以理解布尔运算。所以此处建议不引入运算符 `!`) + +#### 进阶:结点与接缝的实际阅读次数|Advanced: knot/stitch labels are actually read counts + +这是检测: + + * {seen_clue} [指责杰斐逊先生] + +这实际上是在检测一个*整数*,而不是在检测一个是与否的标志。以这种方式使用的结点或接缝实际上是在设置一个整数变量,其中包含玩家看到该地址内容的次数。 + +如果它不为零,就会在类似上面的检测中返回 `true`,但也可以更具体一些: + + * {seen_clue > 3} [直接逮捕杰斐逊先生] + + +#### 进阶:更多逻辑|Advanced: more logic + +**Ink** 支持的逻辑和条件性远不止这些,请参阅[变量和逻辑](#第-3-部分变量和逻辑part-3-variables-and-logic)部分。 + + +## 8) 可变文本|Variable Text + +### 文本是可以变更的|Text can vary + +到目前为止,我们看到的所有内容都是静态、固定的文本。但是,内容也可以在打印输出时发生变化。 + +### 序列、循环以及其他类型的替文|Sequences, cycles and other alternatives + +最简单的可变文本就是替文,它依据某些规则进行选择。**Ink** 支持多种类型的替文。替文写在 `{`...`}` 这样的花括号内,各个替文元素之间使用半角分隔符 `|` 隔开。 + +只有当一个内容片段被多次访问时,这些替文才会有效! + +#### 替文的类型|Types of alternatives + +**序列**(默认替文类型): + +序列(或称 "倒数区块")是一组会跟踪它自己被查看了多少次,并在每次观看时显示下一个元素的替文元素组。当其中的替文元素用完时,它会保持显示最后一个元素: + + 无线电嘶嘶作响。{"三!"|"二!"|"一!"|*传来一声巨大的白噪音,如同炸雷。*|但那只是静电噪声。} + + {我用五英镑纸币买了一杯咖啡,又给朋友买了第二杯。} + + {我用我的五英镑钞票买了一杯咖啡|我为我的朋友买了第二杯咖啡。|我没有钱没更多咖啡了。} + +**循环**(使用 `&` 标记): + +循环就像序列一样,但是它会循环它的内容: + + 今天是{&星期一|星期二|星期三|星期四|星期五|星期六|星期天}。 + +**一次性**(使用 `!` 标记): + +一次性替文和序列提问类似,但是当它们没有新内容可以显示的时候就什么也不现实。(你可以把这个想象成最后一条内容为空的序列替文)。 + + 他跟我开了个玩笑。{!我礼貌性地笑了一下。|我微笑了一下。|我苦笑了一下。|我向我自己保证我不会再有反应了。} + +**乱序**(使用 `~` 标记): + +乱序会产生随机输出。 + + 我跑了一枚硬币。{~正面|反面}。 + +#### 替文的特点|Features of Alternatives + +替文可以包含空白元素: + + 我向前走了一步。{!||||然后灯灭了。-> eek} + +替文可以套娃: + + 鼠熊{&{&一下子就|}挠|抓}{&伤了你|到了你的{&腿|胳膊|脸颊}}。 + +替文可以嵌套分道声明: + + 我{就这么等着。|继续等着。|都等睡着了。|都睡醒了还没有等到。|放弃并离开了。-> leave_post_office} + +也可以在选项中使用替文: + + + “你好,{&老大|福格先生|天气不错|棕色眼睛的朋友}!”[]我问候道。 + +(……但有一点要注意;你不能使用 `{` 这个符号来作为一个选项的文本,因为它看起来像一个表达式。) + +(……但是注意事项也有关于注意事项的注意事项,如果您在 `{` 之前下一个转译空格 `\ `,那么 Ink 就会将那个花括号识别为文本了。) + + + \ {&他们向沙地进发|他们向沙漠出发|一行人沿着老路向南。} + +#### 示例|Examples + +替文可以在循环中使用,从而不费吹灰之力就能创造出智能的、紧跟游戏状态的演出。 + +这是一个单结点版本的打地鼠游戏。请注意,在这个脚本中我们只使用了一次性选择的选项,还有后备选项,以确保地鼠永远不会移动,游戏永远会结束。 + + === whack_a_mole === + {我一锤子砸下去。|{~没打着!|啥也没!|啊,它去哪了?|啊哈!打中了!-> END}} + 这{~讨厌的|该死的|可恶的}{~东西|啮齿动物}仍然{在什么地方|藏在某处|逍遥在外|在什么地方嘲笑我|没有被敲死|还没有完犊子}。<> + {!头套给丫薅掉!|必须打它脸!} + * [{&打|击打|试试}左上角]-> whack_a_mole + * [{&敲|锤|砸}右上角]-> whack_a_mole + * [{&猛击|锤击}中间]-> whack_a_mole + * [{&埋伏|奇袭}左下角]-> whack_a_mole + * [{&钉打|重击}右下角]-> whack_a_mole + * -> + 然后你就被“累鼠”了。地鼠打败了你! + -> END + +这个“游戏”的实况是这样的: + + 我一锤子砸下去。 + 这讨厌的东西仍然在什么地方。头套给丫薅掉! + + 1: 打左上角 + 2: 敲右上角 + 3: 猛击中间 + 4: 埋伏左下角 + 5: 钉打右下角 + + > 1 + 没打着! + 这该死的啮齿动物仍然在什么地方。必须打它脸! + + 1: 捶右上角 + 2: 锤击中间 + 3: 奇袭左下角 + 4: 重击右下角 + + > 4 + 啥也没! + 这可恶的东西仍然逍遥法外。 + + 1: 砸右上角 + 2: 猛击中间 + 3: 埋伏左下角 + + > 2 + + 啊,它去哪了? + 这讨厌的东西仍然在什么地方嘲笑我。 + + 1: 敲右上角 + 2: 奇袭左下角 + + > 1 + 啊哈!打中了! + +这有一个关于游戏生命周期的建议:注意活用粘滞选项——无尽的的电视诱惑: + +And here's a bit of lifestyle advice. Note the sticky choice - the lure of the television will never fade: + + === turn_on_television === + 我{第一次|第二次|又|再一次}打开电视,但是{没有什么有意思的,所以我又把它关掉了|仍然没有什么值得一看的|这次的东西甚至更让我没兴趣了|啥也没,都是乐色|这次是一个关于鲨鱼的节目,我不喜欢鲨鱼}。 + + + [要不,再看看别的?]-> turn_on_television + * [还是出去逛逛吧]-> go_outside_instead + + === go_outside_instead === + -> END + + + +#### 另行参见:多行替文|Sneak Preview: Multiline alternatives +**Ink** 还有另一种格式来制作替换内容块用的替文。详见 [多行替文](#多行替文multiline-blocks)。 + + + +### 条件文本|Conditional Text + +文本也可以像选项一样根据逻辑检测的结果不同而变化。 + + {met_blofeld: “我看见他了。只有那么一瞬间。”} + +还有 + + “他的名字是{met_blofeld.learned_his_name: 弗朗茨|个秘密}。” + +它们可以作为单独一行的出现,也可以出现在内容的某个部分中。它们甚至可以嵌套,例如: + + {met_blofeld: “我看见他了。只有那么一瞬间。他的真名{met_blofeld.learned_his_name: 是弗朗茨|还需要保密}。”|“我想他了。他很邪恶么?” + +这可能会输出一下结果: + + “我看见他了。只有那么一瞬间。他的真名是弗朗茨。” + +或: + + “我看见他了。只有那么一瞬间。他的真名还需要保密。” + +或者: + + “我想他了。他很邪恶么?” + +## 9) 游戏查询和函数|Game Queries and Functions + +**Ink** 提供了关于游戏状态的一些非常有用的“游戏等级”查询,这可以用于逻辑条件。它们并不完全是本编程语言的一部分,但它们总是可用的,而且作者无法对它们进行编辑。从某种意义上说,它们是本编程语言语言的“标准函数库”。 + +命名惯例是使用大写字母。 + +### 选项计数函数|CHOICE_COUNT() + +`CHOICE_COUNT` 会返回当前块目前已创建了的选项的个数。例如: + + * {false} 选项 A + * {ture} 选项 B + * {CHOICE_COUNT() == 1} 选项 C + +这回生成两个选项,B 和 C。这对于控制玩家在一个回合内有多少个选项是很有用的。 + +### 总回合计数函数|TURNS() + +`TURNS()` 这个函数会返回游戏开始后的游戏回合数。 + +### 分道计数函数|TURNS_SINCE(-> knot) + +`TURNS_SINCE` 返回自上次访问某个结点或接缝之后,玩家操作了多少次。(玩家操作在形式上来说就是玩家的交互输入)。 + +值为 0 就表示“你目前正在你所检测的结点或接缝中使用这个函数”。值为 -1 就表示那个要检测的结点或接缝还从来没有被看过。其它任何的正值都表示你要检测的内容在多少个回合之前出现过了。 + + * {TURNS_SINCE(-> sleeping.intro) > 10} 你感到疲乏……-> sleeping + * {TURNS_SINCE(-> laugh) == 0} 你尝试不再笑。 + +请注意:传递参数给 `TURNS_SINCE` 的是具体的“分道目标”,而不是简单的结点地址本身(因为结点地址在程序那边是一串数字,是一个读数,而不是一个故事中的某个位置) + +TODO: (向编译器传递 `-c` 的要求) +(译者注:上面这个 TODO 是 Ink 的开发者给他们自己写的版本计划。) + +#### 功能预览:在功能中使用分道计数函数|Sneak preview: using TURNS_SINCE in a function + +`TURNS_SINCE(->x) == 0` 检测是一个非常有用的函数,通常值得将其单独包装成一个 Ink 的功能。 + + === function came_from(-> x) + ~ return TURNS_SINCE(x) == 0 + +[函数](#5-函数functions)这个章节对此处的语法概述会讲的更清楚一些。简单来说,上面的这句语法可以让写出一些以下的内容: + + * {came_from(-> nice_welcome)} ‘来到这让我很开心!’ + * {came_from(-> nasty_welcome)} ‘咱还是快一些吧。’ + +……这可以让游戏对玩家*刚才*看到的内容作出反应。 + +### 种子随机函数|SEED_RANDOM() + +处于测试的目的,通常需要固定的随机数生成器,以便每次游戏都能产生相同的结果。您可以通过给随机数系统“设定种子号 (Seeding)”来做到这一点。 + + ~ SEED_RANDOM(235) + +您传给种子函数的种子号是有您任意指定的,但是如果提供了相同的数字就会产生结果相同的序列。所以为了产生不同的随机序列,您需要提供不同的种子号。 + +#### 进阶:更多查询|Advanced: more queries + +您也可以创建您自己的外部函数,但是语法会略有不同,详情请见后文中的[函数](#5-函数functions)章节。 + +# 第 2 部分:编织|Part 2: Weave + +So far, we've been building branched stories in the simplest way, with "options" that link to "pages". + +But this requires us to uniquely name every destination in the story, which can slow down writing and discourage minor branching. + +**Ink** has a much more powerful syntax available, designed for simplifying story flows which have an always-forwards direction (as most stories do, and most computer programs don't). + +This format is called "weave", and its built out of the basic content/option syntax with two new features: the gather mark, `-`, and the nesting of choices and gathers. + +## 1) Gathers + +### Gather points gather the flow back together + +Let's go back to the first multi-choice example at the top of this document. + + "What's that?" my master asked. + * "I am somewhat tired[."]," I repeated. + "Really," he responded. "How deleterious." + * "Nothing, Monsieur!"[] I replied. + * "I said, this journey is appalling[."] and I want no more of it." + "Ah," he replied, not unkindly. "I see you are feeling frustrated. Tomorrow, things will improve." + +In a real game, all three of these options might well lead to the same conclusion - Monsieur Fogg leaves the room. We can do this using a gather, without the need to create any new knots, or add any diverts. + + "What's that?" my master asked. + * "I am somewhat tired[."]," I repeated. + "Really," he responded. "How deleterious." + * "Nothing, Monsieur!"[] I replied. + "Very good, then." + * "I said, this journey is appalling[."] and I want no more of it." + "Ah," he replied, not unkindly. "I see you are feeling frustrated. Tomorrow, things will improve." + + - With that Monsieur Fogg left the room. + +This produces the following playthrough: + + "What's that?" my master asked. + + 1: "I am somewhat tired." + 2: "Nothing, Monsieur!" + 3: "I said, this journey is appalling." + + > 1 + "I am somewhat tired," I repeated. + "Really," he responded. "How deleterious." + With that Monsieur Fogg left the room. + +### Options and gathers form chains of content + +We can string these gather-and-branch sections together to make branchy sequences that always run forwards. + + === escape === + I ran through the forest, the dogs snapping at my heels. + + * I checked the jewels[] were still in my pocket, and the feel of them brought a spring to my step. <> + + * I did not pause for breath[] but kept on running. <> + + * I cheered with joy. <> + + - The road could not be much further! Mackie would have the engine running, and then I'd be safe. + + * I reached the road and looked about[]. And would you believe it? + * I should interrupt to say Mackie is normally very reliable[]. He's never once let me down. Or rather, never once, previously to that night. + + - The road was empty. Mackie was nowhere to be seen. + +This is the most basic kind of weave. The rest of this section details additional features that allow weaves to nest, contain side-tracks and diversions, divert within themselves, and above all, reference earlier choices to influence later ones. + +#### The weave philosophy + +Weaves are more than just a convenient encapsulation of branching flow; they're also a way to author more robust content. The `escape` example above has already four possible routes through, and a more complex sequence might have lots and lots more. Using normal diverts, one has to check the links by chasing the diverts from point to point and it's easy for errors to creep in. + +With a weave, the flow is guaranteed to start at the top and "fall" to the bottom. Flow errors are impossible in a basic weave structure, and the output text can be easily skim read. That means there's no need to actually test all the branches in game to be sure they work as intended. + +Weaves also allow for easy redrafting of choice-points; in particular, it's easy to break a sentence up and insert additional choices for variety or pacing reasons, without having to re-engineer any flow. + + +## 2) Nested Flow + +The weaves shown above are quite simple, "flat" structures. Whatever the player does, they take the same number of turns to get from top to bottom. However, sometimes certain choices warrant a bit more depth or complexity. + +For that, we allow weaves to nest. + +This section comes with a warning. Nested weaves are very powerful and very compact, but they can take a bit of getting used to! + +### Options can be nested + +Consider the following scene: + + - "Well, Poirot? Murder or suicide?" + * "Murder!" + * "Suicide!" + - Ms. Christie lowered her manuscript a moment. The rest of the writing group sat, open-mouthed. + +The first choice presented is "Murder!" or "Suicide!". If Poirot declares a suicide, there's no more to do, but in the case of murder, there's a follow-up question needed - who does he suspect? + +We can add new options via a set of nested sub-choices. We tell the script that these new choices are "part of" another choice by using two asterisks, instead of just one. + + + - "Well, Poirot? Murder or suicide?" + * "Murder!" + "And who did it?" + * * "Detective-Inspector Japp!" + * * "Captain Hastings!" + * * "Myself!" + * "Suicide!" + - Mrs. Christie lowered her manuscript a moment. The rest of the writing group sat, open-mouthed. + +(Note that it's good style to also indent the lines to show the nesting, but the compiler doesn't mind.) + +And should we want to add new sub-options to the other route, we do that in similar fashion. + + - "Well, Poirot? Murder or suicide?" + * "Murder!" + "And who did it?" + * * "Detective-Inspector Japp!" + * * "Captain Hastings!" + * * "Myself!" + * "Suicide!" + "Really, Poirot? Are you quite sure?" + * * "Quite sure." + * * "It is perfectly obvious." + - Mrs. Christie lowered her manuscript a moment. The rest of the writing group sat, open-mouthed. + +Now, that initial choice of accusation will lead to specific follow-up questions - but either way, the flow will come back together at the gather point, for Mrs. Christie's cameo appearance. + +But what if we want a more extended sub-scene? + +### Gather points can be nested too + +Sometimes, it's not a question of expanding the number of options, but having more than one additional beat of story. We can do this by nesting gather points as well as options. + + - "Well, Poirot? Murder or suicide?" + * "Murder!" + "And who did it?" + * * "Detective-Inspector Japp!" + * * "Captain Hastings!" + * * "Myself!" + - - "You must be joking!" + * * "Mon ami, I am deadly serious." + * * "If only..." + * "Suicide!" + "Really, Poirot? Are you quite sure?" + * * "Quite sure." + * * "It is perfectly obvious." + - Mrs. Christie lowered her manuscript a moment. The rest of the writing group sat, open-mouthed. + +If the player chooses the "murder" option, they'll have two choices in a row on their sub-branch - a whole flat weave, just for them. + +#### Advanced: What gathers do + +Gathers are hopefully intuitive, but their behaviour is a little harder to put into words: in general, after an option has been taken, the story finds the next gather down that isn't on a lower level, and diverts to it. + +The basic idea is this: options separate the paths of the story, and gathers bring them back together. (Hence the name, "weave"!) + + +### You can nest as many levels are you like + +Above, we used two levels of nesting; the main flow, and the sub-flow. But there's no limit to how many levels deep you can go. + + - "Tell us a tale, Captain!" + * "Very well, you sea-dogs. Here's a tale..." + * * "It was a dark and stormy night..." + * * * "...and the crew were restless..." + * * * * "... and they said to their Captain..." + * * * * * "...Tell us a tale Captain!" + * "No, it's past your bed-time." + - To a man, the crew began to yawn. + +After a while, this sub-nesting gets hard to read and manipulate, so it's good style to divert away to a new stitch if a side-choice goes unwieldy. + +But, in theory at least, you could write your entire story as a single weave. + +### Example: a conversation with nested nodes + +Here's a longer example: + + - I looked at Monsieur Fogg + * ... and I could contain myself no longer. + 'What is the purpose of our journey, Monsieur?' + 'A wager,' he replied. + * * 'A wager!'[] I returned. + He nodded. + * * * 'But surely that is foolishness!' + * * * 'A most serious matter then!' + - - - He nodded again. + * * * 'But can we win?' + 'That is what we will endeavour to find out,' he answered. + * * * 'A modest wager, I trust?' + 'Twenty thousand pounds,' he replied, quite flatly. + * * * I asked nothing further of him then[.], and after a final, polite cough, he offered nothing more to me. <> + * * 'Ah[.'],' I replied, uncertain what I thought. + - - After that, <> + * ... but I said nothing[] and <> + - we passed the day in silence. + - -> END + +with a couple of possible playthroughs. A short one: + + I looked at Monsieur Fogg + + 1: ... and I could contain myself no longer. + 2: ... but I said nothing + + > 2 + ... but I said nothing and we passed the day in silence. + +and a longer one: + + I looked at Monsieur Fogg + + 1: ... and I could contain myself no longer. + 2: ... but I said nothing + + > 1 + ... and I could contain myself no longer. + 'What is the purpose of our journey, Monsieur?' + 'A wager,' he replied. + + 1: 'A wager!' + 2: 'Ah.' + + > 1 + 'A wager!' I returned. + He nodded. + + 1: 'But surely that is foolishness!' + 2: 'A most serious matter then!' + + > 2 + 'A most serious matter then!' + He nodded again. + + 1: 'But can we win?' + 2: 'A modest wager, I trust?' + 3: I asked nothing further of him then. + + > 2 + 'A modest wager, I trust?' + 'Twenty thousand pounds,' he replied, quite flatly. + After that, we passed the day in silence. + +Hopefully, this demonstrates the philosophy laid out above: that weaves offer a compact way to offer a lot of branching, a lot of choices, but with the guarantee of getting from beginning to end! + + +## 3) Tracking a Weave + +Sometimes, the weave structure is sufficient. But when it's not, we need a bit more control. + +### Weaves are largely unaddressed + +By default, lines of content in a weave don't have an address or label, which means they can't be diverted to, and they can't be tested for. In the most basic weave structure, choices vary the path the player takes through the weave and what they see, but once the weave is finished those choices and that path are forgotten. + +But should we want to remember what the player has seen, we can - we add in labels where they're needed using the `(label_name)` syntax. + +### Gathers and options can be labelled + +Gather points at any nested level can be labelled using brackets. + + - (top) + +Once labelled, gather points can be diverted to, or tested for in conditionals, just like knots and stitches. This means you can use previous decisions to alter later outcomes inside the weave, while still keeping all the advantages of a clear, reliable forward-flow. + +Options can also be labelled, just like gather points, using brackets. Label brackets come before conditions in the line. + +These addresses can be used in conditional tests, which can be useful for creating options unlocked by other options. + + === meet_guard === + The guard frowns at you. + + * (greet) [Greet him] + 'Greetings.' + * (get_out) 'Get out of my way[.'],' you tell the guard. + + - 'Hmm,' replies the guard. + + * {greet} 'Having a nice day?' // only if you greeted him + + * 'Hmm?'[] you reply. + + * {get_out} [Shove him aside] // only if you threatened him + You shove him sharply. He stares in reply, and draws his sword! + -> fight_guard // this route diverts out of the weave + + - 'Mff,' the guard replies, and then offers you a paper bag. 'Toffee?' + + +### Scope + +Inside the same block of weave, you can simply use the label name; from outside the block you need a path, either to a different stitch within the same knot: + + === knot === + = stitch_one + - (gatherpoint) Some content. + = stitch_two + * {stitch_one.gatherpoint} Option + +or pointing into another knot: + + === knot_one === + - (gather_one) + * {knot_two.stitch_two.gather_two} Option + + === knot_two === + = stitch_two + - (gather_two) + * {knot_one.gather_one} Option + + +#### Advanced: all options can be labelled + +In truth, all content in ink is a weave, even if there are no gathers in sight. That means you can label *any* option in the game with a bracket label, and then reference it using the addressing syntax. In particular, this means you can test *which* option a player took to reach a particular outcome. + + === fight_guard === + ... + = throw_something + * (rock) [Throw rock at guard] -> throw + * (sand) [Throw sand at guard] -> throw + + = throw + You hurl {throw_something.rock:a rock|a handful of sand} at the guard. + + +#### Advanced: Loops in a weave + +Labelling allows us to create loops inside weaves. Here's a standard pattern for asking questions of an NPC. + + - (opts) + * 'Can I get a uniform from somewhere?'[] you ask the cheerful guard. + 'Sure. In the locker.' He grins. 'Don't think it'll fit you, though.' + * 'Tell me about the security system.' + 'It's ancient,' the guard assures you. 'Old as coal.' + * 'Are there dogs?' + 'Hundreds,' the guard answers, with a toothy grin. 'Hungry devils, too.' + // We require the player to ask at least one question + * {loop} [Enough talking] + -> done + - (loop) + // loop a few times before the guard gets bored + { -> opts | -> opts | } + He scratches his head. + 'Well, can't stand around talking all day,' he declares. + - (done) + You thank the guard, and move away. + + + + + +#### Advanced: diverting to options + +Options can also be diverted to: but the divert goes to the output of having chosen that choice, *as though the choice had been chosen*. So the content printed will ignore square bracketed text, and if the option is once-only, it will be marked as used up. + + - (opts) + * [Pull a face] + You pull a face, and the soldier comes at you! -> shove + + * (shove) [Shove the guard aside] You shove the guard to one side, but he comes back swinging. + + * {shove} [Grapple and fight] -> fight_the_guard + + - -> opts + +produces: + + 1: Pull a face + 2: Shove the guard aside + + > 1 + You pull a face, and the soldier comes at you! You shove the guard to one side, but he comes back swinging. + + 1: Grapple and fight + + > + +#### Advanced: Gathers directly after an option + +The following is valid, and frequently useful. + + * "Are you quite well, Monsieur?"[] I asked. + - - (quitewell) "Quite well," he replied. + * "How did you do at the crossword, Monsieur?"[] I asked. + -> quitewell + * I said nothing[] and neither did my Master. + - We fell into companionable silence once more. + +Note the level 2 gather point directly below the first option: there's nothing to gather here, really, but it gives us a handy place to divert the second option to. + + + + + + +# 第 3 部分:变量和逻辑|Part 3: Variables and Logic + +So far we've made conditional text, and conditional choices, using tests based on what content the player has seen so far. + +**Ink** also supports variables, both temporary and global, storing numerical and content data, or even story flow commands. It is fully-featured in terms of logic, and contains a few additional structures to help keep the often complex logic of a branching story better organised. + + +## 1) Global Variables + +The most powerful kind of variable, and arguably the most useful for a story, is a variable to store some unique property about the state of the game - anything from the amount of money in the protagonist's pocket, to a value representing the protagonist's state of mind. + +This kind of variable is called "global" because it can be accessed from anywhere in the story - both set, and read from. (Traditionally, programming tries to avoid this kind of thing, as it allows one part of a program to mess with another, unrelated part. But a story is a story, and stories are all about consequences: what happens in Vegas rarely stays there.) + +### Defining Global Variables + +Global variables can be defined anywhere, via a `VAR` statement. They should be given an initial value, which defines what type of variable they are - integer, floating point (decimal), content, or a story address. + + VAR knowledge_of_the_cure = false + VAR players_name = "Emilia" + VAR number_of_infected_people = 521 + VAR current_epilogue = -> they_all_die_of_the_plague + +### Using Global Variables + +We can test global variables to control options, and provide conditional text, in a similar way to what we have previously seen. + + === the_train === + The train jolted and rattled. { mood > 0:I was feeling positive enough, however, and did not mind the odd bump|It was more than I could bear}. + * { not knows_about_wager } 'But, Monsieur, why are we travelling?'[] I asked. + * { knows_about_wager} I contemplated our strange adventure[]. Would it be possible? + +#### Advanced: storing diverts as variables + +A "divert" statement is actually a type of value in itself, and can be stored, altered, and diverted to. + + VAR current_epilogue = -> everybody_dies + + === continue_or_quit === + Give up now, or keep trying to save your Kingdom? + * [Keep trying!] -> more_hopeless_introspection + * [Give up] -> current_epilogue + + +#### Advanced: Global variables are externally visible + +Global variables can be accessed, and altered, from the runtime as well from the story, so provide a good way to communicate between the wider game and the story. + +The **Ink** layer is often be a good place to store gameplay-variables; there's no save/load issues to consider, and the story itself can react to the current values. + + + +### Printing variables + +The value of a variable can be printed as content using an inline syntax similar to sequences, and conditional text: + + VAR friendly_name_of_player = "Jackie" + VAR age = 23 + + My name is Jean Passepartout, but my friends call me {friendly_name_of_player}. I'm {age} years old. + +This can be useful in debugging. For more complex printing based on logic and variables, see the section on [functions](#5-functions). + +### Evaluating strings + +It might be noticed that above we refered to variables as being able to contain "content", rather than "strings". That was deliberate, because a string defined in ink can contain ink - although it will always evaluate to a string. (Yikes!) + + VAR a_colour = "" + + ~ a_colour = "{~red|blue|green|yellow}" + + {a_colour} + +... produces one of red, blue, green or yellow. + +Note that once a piece of content like this is evaluated, its value is "sticky". (The quantum state collapses.) So the following: + + The goon hits you, and sparks fly before you eyes, {a_colour} and {a_colour}. + +... won't produce a very interesting effect. (If you really want this to work, use a text function to print the colour!) + +This is also why + + VAR a_colour = "{~red|blue|green|yellow}" + +is explicitly disallowed; it would be evaluated on the construction of the story, which probably isn't what you want. + + +## 2) Logic + +Obviously, our global variables are not intended to be constants, so we need a syntax for altering them. + +Since by default, any text in an **Ink** script is printed out directly to the screen, we use a markup symbol to indicate that a line of content is intended meant to be doing some numerical work, we use the `~` mark. + +The following statements all assign values to variables: + + + === set_some_variables === + ~ knows_about_wager = true + ~ x = (x * x) - (y * y) + c + ~ y = 2 * x * y + +and the following will test conditions: + + { x == 1.2 } + { x / 2 > 4 } + { y - 1 <= x * x } + +### Mathematics + +**Ink** supports the four basic mathematical operations (`+`, `-`, `*` and `/`), as well as `%` (or `mod`), which returns the remainder after integer division. There's also POW for to-the-power-of: + + {POW(3, 2)} is 9. + {POW(16, 0.5)} is 4. + + +If more complex operations are required, one can write functions (using recursion if necessary), or call out to external, game-code functions (for anything more advanced). + + +#### RANDOM(min, max) + +Ink can generate random integers if required using the RANDOM function. RANDOM is authored to be like a dice (yes, pendants, we said *a dice*), so the min and max values are both inclusive. + + ~ temp dice_roll = RANDOM(1, 6) + + ~ temp lazy_grading_for_test_paper = RANDOM(30, 75) + + ~ temp number_of_heads_the_serpent_has = RANDOM(3, 8) + +The random number generator can be seeded for testing purposes, see the section of Game Queries and Functions section above. + +#### Advanced: numerical types are implicit + +Results of operations - in particular, for division - are typed based on the type of the input. So integer division returns integer, but floating point division returns floating point results. + + ~ x = 2 / 3 + ~ y = 7 / 3 + ~ z = 1.2 / 0.5 + +assigns `x` to be 0, `y` to be 2 and `z` to be 2.4. + +#### Advanced: INT(), FLOOR() and FLOAT() + +In cases where you don't want implicit types, or you want to round off a variable, you can cast it directly. + + {INT(3.2)} is 3. + {FLOOR(4.8)} is 4. + {INT(-4.8)} is -4. + {FLOOR(-4.8)} is -5. + + {FLOAT(4)} is, um, still 4. + + + +### String queries + +Oddly for a text-engine, **Ink** doesn't have much in the way of string-handling: it's assumed that any string conversion you need to do will be handled by the game code (and perhaps by external functions.) But we support three basic queries - equality, inequality, and substring (which we call ? for reasons that will become clear in a later chapter). + +The following all return true: + + { "Yes, please." == "Yes, please." } + { "No, thank you." != "Yes, please." } + { "Yes, please" ? "ease" } + + +## 3) Conditional blocks (if/else) + +We've seen conditionals used to control options and story content; **Ink** also provides an equivalent of the normal if/else-if/else structure. + +### A simple 'if' + +The if syntax takes its cue from the other conditionals used so far, with the `{`...`}` syntax indicating that something is being tested. + + { x > 0: + ~ y = x - 1 + } + +Else conditions can be provided: + + { x > 0: + ~ y = x - 1 + - else: + ~ y = x + 1 + } + +### Extended if/else if/else blocks + +The above syntax is actually a specific case of a more general structure, something like a "switch" statement of another language: + + { + - x > 0: + ~ y = x - 1 + - else: + ~ y = x + 1 + } + +And using this form we can include 'else-if' conditions: + + { + - x == 0: + ~ y = 0 + - x > 0: + ~ y = x - 1 + - else: + ~ y = x + 1 + } + +(Note, as with everything else, the white-space is purely for readability and has no syntactic meaning.) + +### Switch blocks + +And there's also an actual switch statement: + + { x: + - 0: zero + - 1: one + - 2: two + - else: lots + } + +#### Example: context-relevant content + +Note these tests don't have to be variable-based and can use read-counts, just as other conditionals can, and the following construction is quite frequent, as a way of saying "do some content which is relevant to the current game state": + + === dream === + { + - visited_snakes && not dream_about_snakes: + ~ fear++ + -> dream_about_snakes + + - visited_poland && not dream_about_polish_beer: + ~ fear-- + -> dream_about_polish_beer + + - else: + // breakfast-based dreams have no effect + -> dream_about_marmalade + } + +The syntax has the advantage of being easy to extend, and prioritise. + + + +### Conditional blocks are not limited to logic + +Conditional blocks can be used to control story content as well as logic: + + I stared at Monsieur Fogg. + { know_about_wager: + <> "But surely you are not serious?" I demanded. + - else: + <> "But there must be a reason for this trip," I observed. + } + He said nothing in reply, merely considering his newspaper with as much thoroughness as entomologist considering his latest pinned addition. + +You can even put options inside conditional blocks: + + { door_open: + * I strode out of the compartment[] and I fancied I heard my master quietly tutting to himself. -> go_outside + - else: + * I asked permission to leave[] and Monsieur Fogg looked surprised. -> open_door + * I stood and went to open the door[]. Monsieur Fogg seemed untroubled by this small rebellion. -> open_door + } + +...but note that the lack of weave-syntax and nesting in the above example isn't accidental: to avoid confusing the various kinds of nesting at work, you aren't allowed to include gather points inside conditional blocks. + +### 多行替文|Multiline blocks + +There's one other class of multiline block, which expands on the alternatives system from above. The following are all valid and do what you might expect: + + // Sequence: go through the alternatives, and stick on last + { stopping: + - I entered the casino. + - I entered the casino again. + - Once more, I went inside. + } + + // Shuffle: show one at random + At the table, I drew a card. <> + { shuffle: + - Ace of Hearts. + - King of Spades. + - 2 of Diamonds. + 'You lose this time!' crowed the croupier. + } + + // Cycle: show each in turn, and then cycle + { cycle: + - I held my breath. + - I waited impatiently. + - I paused. + } + + // Once: show each, once, in turn, until all have been shown + { once: + - Would my luck hold? + - Could I win the hand? + } + +#### Advanced: modified shuffles + +The shuffle block above is really a "shuffled cycle"; in that it'll shuffle the content, play through it, then reshuffle and go again. + +There are two other versions of shuffle: + +`shuffle once` which will shuffle the content, play through it, and then do nothing. + + { shuffle once: + - The sun was hot. + - It was a hot day. + } + +`shuffle stopping` will shuffle all the content (except the last entry), and once its been played, it'll stick on the last entry. + + { shuffle stopping: + - A silver BMW roars past. + - A bright yellow Mustang takes the turn. + - There are like, cars, here. + } + + +## 4) Temporary Variables + +### Temporary variables are for scratch calculations + +Sometimes, a global variable is unwieldy. **Ink** provides temporary variables for quick calculations of things. + + === near_north_pole === + ~ temp number_of_warm_things = 0 + { blanket: + ~ number_of_warm_things++ + } + { ear_muffs: + ~ number_of_warm_things++ + } + { gloves: + ~ number_of_warm_things++ + } + { number_of_warm_things > 2: + Despite the snow, I felt incorrigibly snug. + - else: + That night I was colder than I have ever been. + } + +The value in a temporary variable is thrown away after the story leaves the stitch in which it was defined. + +### Knots and stitches can take parameters + +A particularly useful form of temporary variable is a parameter. Any knot or stitch can be given a value as a parameter. + + * [Accuse Hasting] + -> accuse("Hastings") + * [Accuse Mrs Black] + -> accuse("Claudia") + * [Accuse myself] + -> accuse("myself") + + === accuse(who) === + "I accuse {who}!" Poirot declared. + "Really?" Japp replied. "{who == "myself":You did it?|{who}?}" + "And why not?" Poirot shot back. + + +... and you'll need to use parameters if you want to pass a temporary value from one stitch to another! + +#### Example: a recursive knot definition + +Temporary variables are safe to use in recursion (unlike globals), so the following will work. + + -> add_one_to_one_hundred(0, 1) + + === add_one_to_one_hundred(total, x) === + ~ total = total + x + { x == 100: + -> finished(total) + - else: + -> add_one_to_one_hundred(total, x + 1) + } + + === finished(total) === + "The result is {total}!" you announce. + Gauss stares at you in horror. + -> END + + +(In fact, this kind of definition is useful enough that **Ink** provides a special kind of knot, called, imaginatively enough, a `function`, which comes with certain restrictions and can return a value. See the section below.) + + +#### Advanced: sending divert targets as parameters + +Knot/stitch addresses are a type of value, indicated by a `->` character, and can be stored and passed around. The following is therefore legal, and often useful: + + === sleeping_in_hut === + You lie down and close your eyes. + -> generic_sleep (-> waking_in_the_hut) + + === generic_sleep (-> waking) + You sleep perchance to dream etc. etc. + -> waking + + === waking_in_the_hut + You get back to your feet, ready to continue your journey. + +...but note the `->` in the `generic_sleep` definition: that's the one case in **Ink** where a parameter needs to be typed: because it's too easy to otherwise accidentally do the following: + + === sleeping_in_hut === + You lie down and close your eyes. + -> generic_sleep (waking_in_the_hut) + +... which sends the read count of `waking_in_the_hut` into the sleeping knot, and then attempts to divert to it. + + + + + +## 5) 函数|Functions + +The use of parameters on knots means they are almost functions in the usual sense, but they lack one key concept - that of the call stack, and the use of return values. + +**Ink** includes functions: they are knots, with the following limitations and features: + +A function: +- cannot contain stitches +- cannot use diverts or offer choices +- can call other functions +- can include printed content +- can return a value of any type +- can recurse safely + +(Some of these may seem quite limiting, but for more story-oriented call-stack-style features, see the section on [Tunnels](#1-tunnels).) + +Return values are provided via the `~ return` statement. + +### Defining and calling functions + +To define a function, simply declare a knot to be one: + + === function say_yes_to_everything === + ~ return true + + === function lerp(a, b, k) === + ~ return ((b - a) * k) + a + +Functions are called by name, and with brackets, even if they have no parameters: + + ~ x = lerp(2, 8, 0.3) + + * {say_yes_to_everything()} 'Yes.' + +As in any other language, a function, once done, returns the flow to wherever it was called from - and despite not being allowed to divert the flow, functions can still call other functions. + + === function say_no_to_nothing === + ~ return say_yes_to_everything() + +### Functions don't have to return anything + +A function does not need to have a return value, and can simply do something that is worth packaging up: + + === function harm(x) === + { stamina < x: + ~ stamina = 0 + - else: + ~ stamina = stamina - x + } + +...though remember a function cannot divert, so while the above prevents a negative Stamina value, it won't kill a player who hits zero. + +### Functions can be called inline + +Functions can be called on `~` content lines, but can also be called during a piece of content. In this context, the return value, if there is one, is printed (as well as anything else the function wants to print.) If there is no return value, nothing is printed. + +Content is, by default, 'glued in', so the following: + + Monsieur Fogg was looking {describe_health(health)}. + + === function describe_health(x) === + { + - x == 100: + ~ return "spritely" + - x > 75: + ~ return "chipper" + - x > 45: + ~ return "somewhat flagging" + - else: + ~ return "despondent" + } + +produces: + + Monsieur Fogg was looking despondent. + +#### Examples + +For instance, you might include: + + === function max(a,b) === + { a < b: + ~ return b + - else: + ~ return a + } + + === function exp(x, e) === + // returns x to the power e where e is an integer + { e <= 0: + ~ return 1 + - else: + ~ return x * exp(x, e - 1) + } + +Then: + + The maximum of 2^5 and 3^3 is {max(exp(2,5), exp(3,3))}. + +produces: + + The maximum of 2^5 and 3^3 is 32. + + +#### Example: turning numbers into words + +The following example is long, but appears in pretty much every inkle game to date. (Recall that a hyphenated line inside multiline curly braces indicates either "a condition to test" or, if the curly brace began with a variable, "a value to compare against".) + + === function print_num(x) === + { + - x >= 1000: + {print_num(x / 1000)} thousand { x mod 1000 > 0:{print_num(x mod 1000)}} + - x >= 100: + {print_num(x / 100)} hundred { x mod 100 > 0:and {print_num(x mod 100)}} + - x == 0: + zero + - else: + { x >= 20: + { x / 10: + - 2: twenty + - 3: thirty + - 4: forty + - 5: fifty + - 6: sixty + - 7: seventy + - 8: eighty + - 9: ninety + } + { x mod 10 > 0:<>-<>} + } + { x < 10 || x > 20: + { x mod 10: + - 1: one + - 2: two + - 3: three + - 4: four + - 5: five + - 6: six + - 7: seven + - 8: eight + - 9: nine + } + - else: + { x: + - 10: ten + - 11: eleven + - 12: twelve + - 13: thirteen + - 14: fourteen + - 15: fifteen + - 16: sixteen + - 17: seventeen + - 18: eighteen + - 19: nineteen + } + } + } + +which enables us to write things like: + + ~ price = 15 + + I pulled out {print_num(price)} coins from my pocket and slowly counted them. + "Oh, never mind," the trader replied. "I'll take half." And she took {print_num(price / 2)}, and pushed the rest back over to me. + + + +### Parameters can be passed by reference + +Function parameters can also be passed 'by reference', meaning that the function can actually alter the the variable being passed in, instead of creating a temporary variable with that value. + +For instance, most **inkle** stories include the following: + + === function alter(ref x, k) === + ~ x = x + k + +Lines such as: + + ~ gold = gold + 7 + ~ health = health - 4 + +then become: + + ~ alter(gold, 7) + ~ alter(health, -4) + +which are slightly easier to read, and (more usefully) can be done inline for maximum compactness. + + * I ate a biscuit[] and felt refreshed. {alter(health, 2)} + * I gave a biscuit to Monsieur Fogg[] and he wolfed it down most undecorously. {alter(foggs_health, 1)} + - <> Then we continued on our way. + +Wrapping up simple operations in function can also provide a simple place to put debugging information, if required. + + + + +## 6) 常量|Constants + + +### Global Constants + +Interactive stories often rely on state machines, tracking what stage some higher level process has reached. There are lots of ways to do this, but the most conveninent is to use constants. + +Sometimes, it's convenient to define constants to be strings, so you can print them out, for gameplay or debugging purposes. + + CONST HASTINGS = "Hastings" + CONST POIROT = "Poirot" + CONST JAPP = "Japp" + + VAR current_chief_suspect = HASTINGS + + === review_evidence === + { found_japps_bloodied_glove: + ~ current_chief_suspect = POIROT + } + Current Suspect: {current_chief_suspect} + +Sometimes giving them values is useful: + + CONST PI = 3.14 + CONST VALUE_OF_TEN_POUND_NOTE = 10 + +And sometimes the numbers are useful in other ways: + + CONST LOBBY = 1 + CONST STAIRCASE = 2 + CONST HALLWAY = 3 + + CONST HELD_BY_AGENT = -1 + + VAR secret_agent_location = LOBBY + VAR suitcase_location = HALLWAY + + === report_progress === + { + - secret_agent_location == suitcase_location: + The secret agent grabs the suitcase! + ~ suitcase_location = HELD_BY_AGENT + + - secret_agent_location < suitcase_location: + The secret agent moves forward. + ~ secret_agent_location++ + } + +Constants are simply a way to allow you to give story states easy-to-understand names. + +## 7) Advanced: Game-side logic + +There are two core ways to provide game hooks in the **Ink** engine. External function declarations in ink allow you to directly call C# functions in the game, and variable observers are callbacks that are fired in the game when ink variables are modified. Both of these are described in [Running your ink](RunningYourInk.md). + +# Part 4: Advanced Flow Control + + +## 1) Tunnels + +The default structure for **Ink** stories is a "flat" tree of choices, branching and joining back together, perhaps looping, but with the story always being "at a certain place". + +But this flat structure makes certain things difficult: for example, imagine a game in which the following interaction can happen: + + === crossing_the_date_line === + * "Monsieur!"[] I declared with sudden horror. "I have just realised. We have crossed the international date line!" + - Monsieur Fogg barely lifted an eyebrow. "I have adjusted for it." + * I mopped the sweat from my brow[]. A relief! + * I nodded, becalmed[]. Of course he had! + * I cursed, under my breath[]. Once again, I had been belittled! + +...but it can happen at several different places in the story. We don't want to have to write copies of the content for each different place, but when the content is finished it needs to know where to return to. We can do this using parameters: + + === crossing_the_date_line(-> return_to) === + ... + - -> return_to + + ... + + === outside_honolulu === + We arrived at the large island of Honolulu. + - (postscript) + -> crossing_the_date_line(-> done) + - (done) + -> END + + ... + + === outside_pitcairn_island === + The boat sailed along the water towards the tiny island. + - (postscript) + -> crossing_the_date_line(-> done) + - (done) + -> END + +Both of these locations now call and execute the same segment of storyflow, but once finished they return to where they need to go next. + +But what if the section of story being called is more complex - what if it spreads across several knots? Using the above, we'd have to keep passing the 'return-to' parameter from knot to knot, to ensure we always knew where to return. + +So instead, **Ink** integrates this into the language with a new kind of divert, that functions rather like a subroutine, and is called a 'tunnel'. + +### Tunnels run sub-stories + +The tunnel syntax looks like a divert, with another divert on the end: + + -> crossing_the_date_line -> + +This means "do the crossing_the_date_line story, then continue from here". + +Inside the tunnel itself, the syntax is simplified from the parameterised example: all we do is end the tunnel using the `->->` statement which means, essentially, "go on". + + === crossing_the_date_line === + // this is a tunnel! + ... + - ->-> + +Note that tunnel knots aren't declared as such, so the compiler won't check that tunnels really do end in `->->` statements, except at run-time. So you will need to write carefully to ensure that all the flows into a tunnel really do come out again. + +Tunnels can also be chained together, or finish on a normal divert: + + ... + // this runs the tunnel, then diverts to 'done' + -> crossing_the_date_line -> done + ... + + ... + //this runs one tunnel, then another, then diverts to 'done' + -> crossing_the_date_line -> check_foggs_health -> done + ... + +Tunnels can be nested, so the following is valid: + + === plains === + = night_time + The dark grass is soft under your feet. + + [Sleep] + -> sleep_here -> wake_here -> day_time + = day_time + It is time to move on. + + === wake_here === + You wake as the sun rises. + + [Eat something] + -> eat_something -> + + [Make a move] + - ->-> + + === sleep_here === + You lie down and try to close your eyes. + -> monster_attacks -> + Then it is time to sleep. + -> dream -> + ->-> + +... and so on. + + +#### Advanced: Tunnels can return elsewhere + +Sometimes, in a story, things happen. So sometimes a tunnel can't guarantee that it will always want to go back to where it came from. **Ink** supplies a syntax to allow you to "returning from a tunnel but actually go somewhere else" but it should be used with caution as the possibility of getting very confused is very high indeed. + +Still, there are cases where it's indispensable: + + === fall_down_cliff + -> hurt(5) -> + You're still alive! You pick yourself up and walk on. + + === hurt(x) + ~ stamina -= x + { stamina <= 0: + ->-> youre_dead + } + + === youre_dead + Suddenly, there is a white light all around you. Fingers lift an eyepiece from your forehead. 'You lost, buddy. Out of the chair.' + +And even in less drastic situations, we might want to break up the structure: + + -> talk_to_jim -> + + === talk_to_jim + - (opts) + * [ Ask about the warp lacelles ] + -> warp_lacells -> + * [ Ask about the shield generators ] + -> shield_generators -> + * [ Stop talking ] + ->-> + - -> opts + + = warp_lacells + { shield_generators : ->-> argue } + "Don't worry about the warp lacelles. They're fine." + ->-> + + = shield_generators + { warp_lacells : ->-> argue } + "Forget about the shield generators. They're good." + ->-> + + = argue + "What's with all these questions?" Jim demands, suddenly. + ... + ->-> + +#### Advanced: Tunnels use a call-stack + +Tunnels are on a call-stack, so can safely recurse. + + +## 2) Threads + +So far, everything in ink has been entirely linear, despite all the branching and diverting. But it's actually possible for a writer to 'fork' a story into different sub-sections, to cover more possible player actions. + +We call this 'threading', though it's not really threading in the sense that computer scientists mean it: it's more like stitching in new content from various places. + +Note that this is definitely an advanced feature: the engineering stories becomes somewhat more complex once threads are involved! + +### Threads join multiple sections together + +Threads allow you to compose sections of content from multiple sources in one go. For example: + + == thread_example == + I had a headache; threading is hard to get your head around. + <- conversation + <- walking + + + == conversation == + It was a tense moment for Monty and me. + * "What did you have for lunch today?"[] I asked. + "Spam and eggs," he replied. + * "Nice weather, we're having,"[] I said. + "I've seen better," he replied. + - -> house + + == walking == + We continued to walk down the dusty road. + * [Continue walking] + -> house + + == house == + Before long, we arrived at his house. + -> END + +It allows multiple sections of story to combined together into a single section: + + I had a headache; threading is hard to get your head around. + It was a tense moment for Monty and me. + We continued to walk down the dusty road. + 1: "What did you have for lunch today?" + 2: "Nice weather, we're having," + 3: Continue walking + +On encountering a thread statement such as `<- conversation`, the compiler will fork the story flow. The first fork considered will run the content at `conversation`, collecting up any options it finds. Once it has run out of flow here it'll then run the other fork. + +All the content is collected and shown to the player. But when a choice is chosen, the engine will move to that fork of the story and collapse and discard the others. + +Note that global variables are *not* forked, including the read counts of knots and stitches. + +### Uses of threads + +In a normal story, threads might never be needed. + +But for games with lots of independent moving parts, threads quickly become essential. Imagine a game in which characters move independently around a map: the main story hub for a room might look like the following: + + CONST HALLWAY = 1 + CONST OFFICE = 2 + + VAR player_location = HALLWAY + VAR generals_location = HALLWAY + VAR doctors_location = OFFICE + + == run_player_location + { + - player_location == HALLWAY: -> hallway + } + + == hallway == + <- characters_present(HALLWAY) + * [Drawers] -> examine_drawers + * [Wardrobe] -> examine_wardrobe + * [Go to Office] -> go_office + - -> run_player_location + = examine_drawers + // etc... + + // Here's the thread, which mixes in dialogue for characters you share the room with at the moment. + + == characters_present(room) + { generals_location == room: + <- general_conversation + } + { doctors_location == room: + <- doctor_conversation + } + -> DONE + + == general_conversation + * [Ask the General about the bloodied knife] + "It's a bad business, I can tell you." + - -> run_player_location + + == doctor_conversation + * [Ask the Doctor about the bloodied knife] + "There's nothing strange about blood, is there?" + - -> run_player_location + + + +Note in particular, that we need an explicit way to return the player who has gone down a side-thread to return to the main flow. In most cases, threads will either need a parameter telling them where to return to, or they'll need to end the current story section. + + +### When does a side-thread end? + +Side-threads end when they run out of flow to process: and note, they collect up options to display later (unlike tunnels, which collect options, display them and follow them until they hit an explicit return, possibly several moves later). + +Sometimes a thread has no content to offer - perhaps there is no conversation to have with a character after all, or perhaps we have simply not written it yet. In that case, we must mark the end of the thread explicitly. + +If we didn't, the end of content might be a story-bug or a hanging story thread, and we want the compiler to tell us about those. + +### Using `-> DONE` + +In cases where we want to mark the end of a thread, we use `-> DONE`: meaning "the flow intentionally ends here". If we don't, we might end up with a warning message - we can still play the game, but it's a reminder that we have unfinished business. + +The example at the start of this section will generate a warning; it can be fixed as follows: + + == thread_example == + I had a headache; threading is hard to get your head around. + <- conversation + <- walking + -> DONE + +The extra DONE tells ink that the flow here has ended and it should rely on the threads for the next part of the story. + +Note that we don't need a `-> DONE` if the flow ends with options that fail their conditions. The engine treats this as a valid, intentional, end of flow state. + +**You do not need a `-> DONE` after an option has been chosen**. Once an option is chosen, a thread is no longer a thread - it is simply the normal story flow once more. + +Using `-> END` in this case will not end the thread, but the whole story flow. (And this is the real reason for having two different ways to end flow.) + + +#### Example: adding the same choice to several places + +Threads can be used to add the same choice into lots of different places. When using them this way, it's normal to pass a divert as a parameter, to tell the story where to go after the choice is done. + + === outside_the_house + The front step. The house smells. Of murder. And lavender. + - (top) + <- review_case_notes(-> top) + * [Go through the front door] + I stepped inside the house. + -> the_hallway + * [Sniff the air] + I hate lavender. It makes me think of soap, and soap makes me think about my marriage. + -> top + + === the_hallway + The hallway. Front door open to the street. Little bureau. + - (top) + <- review_case_notes(-> top) + * [Go through the front door] + I stepped out into the cool sunshine. + -> outside_the_house + * [Open the bureau] + Keys. More keys. Even more keys. How many locks do these people need? + -> top + + === review_case_notes(-> go_back_to) + + {not done || TURNS_SINCE(-> done) > 10} + [Review my case notes] + // the conditional ensures you don't get the option to check repeatedly + {I|Once again, I} flicked through the notes I'd made so far. Still not obvious suspects. + - (done) -> go_back_to + +Note this is different than a tunnel, which runs the same block of content but doesn't give a player a choice. So a layout like: + + <- childhood_memories(-> next) + * [Look out of the window] + I daydreamed as we rolled along... + - (next) Then the whistle blew... + +might do exactly the same thing as: + + * [Remember my childhood] + -> think_back -> + * [Look out of the window] + I daydreamed as we rolled along... + - (next) Then the whistle blew... + +but as soon as the option being threaded in includes multiple choices, or conditional logic on choices (or any text content, of course!), the thread version becomes more practical. + + +#### Example: organisation of wide choice points + +A game which uses ink as a script rather than a literal output might often generate very large numbers of parallel choices, intended to be filtered by the player via some other in-game interaction - such as walking around an environment. Threads can be useful in these cases simply to divide up choices. + +``` +=== the_kitchen +- (top) + <- drawers(-> top) + <- cupboards(-> top) + <- room_exits += drawers (-> goback) + // choices about the drawers... + ... += cupboards(-> goback) + // choices about cupboards + ... += room_exits + // exits; doesn't need a "return point" as if you leave, you go elsewhere + ... +``` + +# Part 5: Advanced State Tracking + +Games with lots of interaction can get very complex, very quickly and the writer's job is often as much about maintaining continuity as it is about content. + +This becomes particularly important if the game text is intended to model anything - whether it's a game of cards, the player's knowledge of the gameworld so far, or the state of the various light-switches in a house. + +**Ink** does not provide a full world-modelling system in the manner of a classic parser IF authoring language - there are no "objects", no concepts of "containment" or being "open" or "locked". However, it does provide a simple yet powerful system for tracking state-changes in a very flexible way, to enable writers to approximate world models where necessary. + +#### Note: New feature alert! + +This feature is very new to the language. That means we haven't begun to discover all the ways it might be used - but we're pretty sure it's going to be useful! So if you think of a clever usage we'd love to know! + + +## 1) Basic Lists + +The basic unit of state-tracking is a list of states, defined using the `LIST` keyword. Note that a list is really nothing like a C# list (which is an array). + +For instance, we might have: + + LIST kettleState = cold, boiling, recently_boiled + +This line defines two things: firstly three new values - `cold`, `boiling` and `recently_boiled` - and secondly, a variable, called `kettleState`, to hold these states. + +We can tell the list what value to take: + + ~ kettleState = cold + +We can change the value: + + * [Turn on kettle] + The kettle begins to bubble and boil. + ~ kettleState = boiling + +We can query the value: + + * [Touch the kettle] + { kettleState == cold: + The kettle is cool to the touch. + - else: + The outside of the kettle is very warm! + } + +For convenience, we can give a list a value when it's defined using a bracket: + + LIST kettleState = cold, (boiling), recently_boiled + // at the start of the game, this kettle is switched on. Edgy, huh? + +...and if the notation for that looks a bit redundant, there's a reason for that coming up in a few subsections time. + + + +## 2) Reusing Lists + +The above example is fine for the kettle, but what if we have a pot on the stove as well? We can then define a list of states, but put them into variables - and as many variables as we want. + + LIST daysOfTheWeek = Monday, Tuesday, Wednesday, Thursday, Friday + VAR today = Monday + VAR tomorrow = Tuesday + +### States can be used repeatedly + +This allows us to use the same state machine in multiple places. + + LIST heatedWaterStates = cold, boiling, recently_boiled + VAR kettleState = cold + VAR potState = cold + + * {kettleState == cold} [Turn on kettle] + The kettle begins to boil and bubble. + ~ kettleState = boiling + * {potState == cold} [Light stove] + The water in the pot begins to boil and bubble. + ~ potState = boiling + +But what if we add a microwave as well? We might want start generalising our functionality a bit: + + LIST heatedWaterStates = cold, boiling, recently_boiled + VAR kettleState = cold + VAR potState = cold + VAR microwaveState = cold + + === function boilSomething(ref thingToBoil, nameOfThing) + The {nameOfThing} begins to heat up. + ~ thingToBoil = boiling + + === do_cooking + * {kettleState == cold} [Turn on kettle] + {boilSomething(kettleState, "kettle")} + * {potState == cold} [Light stove] + {boilSomething(potState, "pot")} + * {microwaveState == cold} [Turn on microwave] + {boilSomething(microwaveState, "microwave")} + +or even... + + LIST heatedWaterStates = cold, boiling, recently_boiled + VAR kettleState = cold + VAR potState = cold + VAR microwaveState = cold + + === cook_with(nameOfThing, ref thingToBoil) + + {thingToBoil == cold} [Turn on {nameOfThing}] + The {nameOfThing} begins to heat up. + ~ thingToBoil = boiling + -> do_cooking.done + + === do_cooking + <- cook_with("kettle", kettleState) + <- cook_with("pot", potState) + <- cook_with("microwave", microwaveState) + - (done) + +Note that the "heatedWaterStates" list is still available as well, and can still be tested, and take a value. + +#### List values can share names + +Reusing lists brings with it ambiguity. If we have: + + LIST colours = red, green, blue, purple + LIST moods = mad, happy, blue + + VAR status = blue + +... how can the compiler know which blue you meant? + +We resolve these using a `.` syntax similar to that used for knots and stitches. + + VAR status = colours.blue + +...and the compiler will issue an error until you specify. + +Note the "family name" of the state, and the variable containing a state, are totally separate. So + + { statesOfGrace == statesOfGrace.fallen: + // is the current state "fallen" + } + +... is correct. + + +#### Advanced: a LIST is actually a variable + +One surprising feature is the statement + + LIST statesOfGrace = ambiguous, saintly, fallen + +actually does two things simultaneously: it creates three values, `ambiguous`, `saintly` and `fallen`, and gives them the name-parent `statesOfGrace` if needed; and it creates a variable called `statesOfGrace`. + +And that variable can be used like a normal variable. So the following is valid, if horribly confusing and a bad idea: + + LIST statesOfGrace = ambiguous, saintly, fallen + + ~ statesOfGrace = 3.1415 // set the variable to a number not a list value + +...and it wouldn't preclude the following from being fine: + + ~ temp anotherStateOfGrace = statesOfGrace.saintly + + + + +## 3) List Values + +When a list is defined, the values are listed in an order, and that order is considered to be significant. In fact, we can treat these values as if they *were* numbers. (That is to say, they are enums.) + + LIST volumeLevel = off, quiet, medium, loud, deafening + VAR lecturersVolume = quiet + VAR murmurersVolume = quiet + + { lecturersVolume < deafening: + ~ lecturersVolume++ + + { lecturersVolume > murmurersVolume: + ~ murmurersVolume++ + The murmuring gets louder. + } + } + +The values themselves can be printed using the usual `{...}` syntax, but this will print their name. + + The lecturer's voice becomes {lecturersVolume}. + +### Converting values to numbers + +The numerical value, if needed, can be got explicitly using the LIST_VALUE function. Note the first value in a list has the value 1, and not the value 0. + + The lecturer has {LIST_VALUE(deafening) - LIST_VALUE(lecturersVolume)} notches still available to him. + +### Converting numbers to values + +You can go the other way by using the list's name as a function: + + LIST Numbers = one, two, three + VAR score = one + ~ score = Numbers(2) // score will be "two" + +### Advanced: defining your own numerical values + +By default, the values in a list start at 1 and go up by one each time, but you can specify your own values if you need to. + + LIST primeNumbers = two = 2, three = 3, five = 5 + +If you specify a value, but not the next value, ink will assume an increment of 1. So the following is the same: + + LIST primeNumbers = two = 2, three, five = 5 + + +## 4) Multivalued Lists + +The following examples have all included one deliberate untruth, which we'll now remove. Lists - and variables containing list values - do not have to contain only one value. + +### Lists are boolean sets + +A list variable is not a variable containing a number. Rather, a list is like the in/out nameboard in an accommodation block. It contains a list of names, each of which has a room-number associated with it, and a slider to say "in" or "out". + +Maybe no one is in: + + LIST DoctorsInSurgery = Adams, Bernard, Cartwright, Denver, Eamonn + +Maybe everyone is: + + LIST DoctorsInSurgery = (Adams), (Bernard), (Cartwright), (Denver), (Eamonn) + +Or maybe some are and some aren't: + + LIST DoctorsInSurgery = (Adams), Bernard, (Cartwright), Denver, Eamonn + +Names in brackets are included in the initial state of the list. + +Note that if you're defining your own values, you can place the brackets around the whole term or just the name: + + LIST primeNumbers = (two = 2), (three) = 3, (five = 5) + +#### Assiging multiple values + +We can assign all the values of the list at once as follows: + + ~ DoctorsInSurgery = (Adams, Bernard) + ~ DoctorsInSurgery = (Adams, Bernard, Eamonn) + +We can assign the empty list to clear a list out: + + ~ DoctorsInSurgery = () + + +#### Adding and removing entries + +List entries can be added and removed, singly or collectively. + + ~ DoctorsInSurgery = DoctorsInSurgery + Adams + ~ DoctorsInSurgery += Adams // this is the same as the above + ~ DoctorsInSurgery -= Eamonn + ~ DoctorsInSurgery += (Eamonn, Denver) + ~ DoctorsInSurgery -= (Adams, Eamonn, Denver) + +Trying to add an entry that's already in the list does nothing. Trying to remove an entry that's not there also does nothing. Neither produces an error, and a list can never contain duplicate entries. + + +### Basic Queries + +We have a few basic ways of getting information about what's in a list: + + LIST DoctorsInSurgery = (Adams), Bernard, (Cartwright), Denver, Eamonn + + {LIST_COUNT(DoctorsInSurgery)} // "2" + {LIST_MIN(DoctorsInSurgery)} // "Adams" + {LIST_MAX(DoctorsInSurgery)} // "Cartwright" + {LIST_RANDOM(DoctorsInSurgery)} // "Adams" or "Cartwright" + +#### Testing for emptiness + +Like most values in ink, a list can be tested "as it is", and will return true, unless it's empty. + + { DoctorsInSurgery: The surgery is open today. | Everyone has gone home. } + +#### Testing for exact equality + +Testing multi-valued lists is slightly more complex than single-valued ones. Equality (`==`) now means 'set equality' - that is, all entries are identical. + +So one might say: + + { DoctorsInSurgery == (Adams, Bernard): + Dr Adams and Dr Bernard are having a loud argument in one corner. + } + +If Dr Eamonn is in as well, the two won't argue, as the lists being compared won't be equal - DoctorsInSurgery will have an Eamonn that the list (Adams, Bernard) doesn't have. + +Not equals works as expected: + + { DoctorsInSurgery != (Adams, Bernard): + At least Adams and Bernard aren't arguing. + } + +#### Testing for containment + +What if we just want to simply ask if Adams and Bernard are present? For that we use a new operator, `has`, otherwise known as `?`. + + { DoctorsInSurgery ? (Adams, Bernard): + Dr Adams and Dr Bernard are having a hushed argument in one corner. + } + +And `?` can apply to single values too: + + { DoctorsInSurgery has Eamonn: + Dr Eamonn is polishing his glasses. + } + +We can also negate it, with `hasnt` or `!?` (not `?`). Note this starts to get a little complicated as + + DoctorsInSurgery !? (Adams, Bernard) + +does not mean neither Adams nor Bernard is present, only that they are not *both* present (and arguing). + +#### Warning: no lists contain the empty list + +Note that the test + + SomeList ? () + +will always return false, regardless of whether `SomeList` itself is empty. In practice this is the most useful default, as you'll often want to do tests like: + + SilverWeapons ? best_weapon_to_use + +to fail if the player is empty-handed. + +#### Example: basic knowledge tracking + +The simplest use of a multi-valued list is for tracking "game flags" tidily. + + LIST Facts = (Fogg_is_fairly_odd), first_name_phileas, (Fogg_is_English) + + {Facts ? Fogg_is_fairly_odd:I smiled politely.|I frowned. Was he a lunatic?} + '{Facts ? first_name_phileas:Phileas|Monsieur}, really!' I cried. + +In particular, it allows us to test for multiple game flags in a single line. + + { Facts ? (Fogg_is_English, Fogg_is_fairly_odd): + <> 'I know Englishmen are strange, but this is *incredible*!' + } + + +#### Example: a doctor's surgery + +We're overdue a fuller example, so here's one. + + LIST DoctorsInSurgery = (Adams), Bernard, Cartwright, (Denver), Eamonn + + -> waiting_room + + === function whos_in_today() + In the surgery today are {DoctorsInSurgery}. + + === function doctorEnters(who) + { DoctorsInSurgery !? who: + ~ DoctorsInSurgery += who + Dr {who} arrives in a fluster. + } + + === function doctorLeaves(who) + { DoctorsInSurgery ? who: + ~ DoctorsInSurgery -= who + Dr {who} leaves for lunch. + } + + === waiting_room + {whos_in_today()} + * [Time passes...] + {doctorLeaves(Adams)} {doctorEnters(Cartwright)} {doctorEnters(Eamonn)} + {whos_in_today()} + +This produces: + + In the surgery today are Adams, Denver. + + > Time passes... + + Dr Adams leaves for lunch. Dr Cartwright arrives in a fluster. Dr Eamonn arrives in a fluster. + + In the surgery today are Cartwright, Denver, Eamonn. + +#### Advanced: nicer list printing + +The basic list print is not especially attractive for use in-game. The following is better: + + === function listWithCommas(list, if_empty) + {LIST_COUNT(list): + - 2: + {LIST_MIN(list)} and {listWithCommas(list - LIST_MIN(list), if_empty)} + - 1: + {list} + - 0: + {if_empty} + - else: + {LIST_MIN(list)}, {listWithCommas(list - LIST_MIN(list), if_empty)} + } + + LIST favouriteDinosaurs = (stegosaurs), brachiosaur, (anklyosaurus), (pleiosaur) + + My favourite dinosaurs are {listWithCommas(favouriteDinosaurs, "all extinct")}. + +It's probably also useful to have an is/are function to hand: + + === function isAre(list) + {LIST_COUNT(list) == 1:is|are} + + My favourite dinosaurs {isAre(favouriteDinosaurs)} {listWithCommas(favouriteDinosaurs, "all extinct")}. + +And to be pendantic: + + My favourite dinosaur{LIST_COUNT(favouriteDinosaurs) != 1:s} {isAre(favouriteDinosaurs)} {listWithCommas(favouriteDinosaurs, "all extinct")}. + + +#### Lists don't need to have multiple entries + +Lists don't *have* to contain multiple values. If you want to use a list as a state-machine, the examples above will all work - set values using `=`, `++` and `--`; test them using `==`, `<`, `<=`, `>` and `>=`. These will all work as expected. + +### The "full" list + +Note that `LIST_COUNT`, `LIST_MIN` and `LIST_MAX` are refering to who's in/out of the list, not the full set of *possible* doctors. We can access that using + + LIST_ALL(element of list) + +or + + LIST_ALL(list containing elements of a list) + + {LIST_ALL(DoctorsInSurgery)} // Adams, Bernard, Cartwright, Denver, Eamonn + {LIST_COUNT(LIST_ALL(DoctorsInSurgery))} // "5" + {LIST_MIN(LIST_ALL(Eamonn))} // "Adams" + +Note that printing a list using `{...}` produces a bare-bones representation of the list; the values as words, delimited by commas. + +#### Advanced: "refreshing" a list's type + +If you really need to, you can make an empty list that knows what type of list it is. + + LIST ValueList = first_value, second_value, third_value + VAR myList = () + + ~ myList = ValueList() + +You'll then be able to do: + + { LIST_ALL(myList) } + +#### Advanced: a portion of the "full" list + +You can also retrieve just a "slice" of the full list, using the `LIST_RANGE` function. There are two formulations, both valid: + + LIST_RANGE(list_name, min_integer_value, max_integer_value) + +and + + LIST_RANGE(list_name, min_value, max_value) + +Min and max values here are inclusive. If the game can’t find the values, it’ll get as close as it can, but never go outside the range. So for example: + + {LIST_RANGE(LIST_ALL(primeNumbers), 10, 20)} + +will produce + + 11, 13, 17, 19 + + + +### Example: Tower of Hanoi + +To demonstrate a few of these ideas, here's a functional Tower of Hanoi example, written so no one else has to write it. + + + LIST Discs = one, two, three, four, five, six, seven + VAR post1 = () + VAR post2 = () + VAR post3 = () + + ~ post1 = LIST_ALL(Discs) + + -> gameloop + + === function can_move(from_list, to_list) === + { + - LIST_COUNT(from_list) == 0: + // no discs to move + ~ return false + - LIST_COUNT(to_list) > 0 && LIST_MIN(from_list) > LIST_MIN(to_list): + // the moving disc is bigger than the smallest of the discs on the new tower + ~ return false + - else: + // nothing stands in your way! + ~ return true + + } + + === function move_ring( ref from, ref to ) === + ~ temp whichRingToMove = LIST_MIN(from) + ~ from -= whichRingToMove + ~ to += whichRingToMove + + == function getListForTower(towerNum) + { towerNum: + - 1: ~ return post1 + - 2: ~ return post2 + - 3: ~ return post3 + } + + === function name(postNum) + the {postToPlace(postNum)} temple + + === function Name(postNum) + The {postToPlace(postNum)} temple + + === function postToPlace(postNum) + { postNum: + - 1: first + - 2: second + - 3: third + } + + === function describe_pillar(listNum) == + ~ temp list = getListForTower(listNum) + { + - LIST_COUNT(list) == 0: + {Name(listNum)} is empty. + - LIST_COUNT(list) == 1: + The {list} ring lies on {name(listNum)}. + - else: + On {name(listNum)}, are the discs numbered {list}. + } + + + === gameloop + Staring down from the heavens you see your followers finishing construction of the last of the great temples, ready to begin the work. + - (top) + + [ Regard the temples] + You regard each of the temples in turn. On each is stacked the rings of stone. {describe_pillar(1)} {describe_pillar(2)} {describe_pillar(3)} + <- move_post(1, 2, post1, post2) + <- move_post(2, 1, post2, post1) + <- move_post(1, 3, post1, post3) + <- move_post(3, 1, post3, post1) + <- move_post(3, 2, post3, post2) + <- move_post(2, 3, post2, post3) + -> DONE + + = move_post(from_post_num, to_post_num, ref from_post_list, ref to_post_list) + + { can_move(from_post_list, to_post_list) } + [ Move a ring from {name(from_post_num)} to {name(to_post_num)} ] + { move_ring(from_post_list, to_post_list) } + { stopping: + - The priests far below construct a great harness, and after many years of work, the great stone ring is lifted up into the air, and swung over to the next of the temples. + The ropes are slashed, and in the blink of an eye it falls once more. + - Your next decree is met with a great feast and many sacrifices. After the funeary smoke has cleared, work to shift the great stone ring begins in earnest. A generation grows and falls, and the ring falls into its ordained place. + - {cycle: + - Years pass as the ring is slowly moved. + - The priests below fight a war over what colour robes to wear, but while they fall and die, the work is still completed. + } + } + -> top + + + +## 5) Advanced List Operations + +The above section covers basic comparisons. There are a few more powerful features as well, but - as anyone familiar with mathematical sets will know - things begin to get a bit fiddly. So this section comes with an 'advanced' warning. + +A lot of the features in this section won't be necessary for most games. + +### Comparing lists + +We can compare lists less than exactly using `>`, `<`, `>=` and `<=`. Be warned! The definitions we use are not exactly standard fare. They are based on comparing the numerical value of the elements in the lists being tested. + +#### "Distinctly bigger than" + +`LIST_A > LIST_B` means "the smallest value in A is bigger than the largest values in B": in other words, if put on a number line, the entirety of A is to the right of the entirety of B. `<` does the same in reverse. + +#### "Definitely never smaller than" + +`LIST_A >= LIST_B` means - take a deep breath now - "the smallest value in A is at least the smallest value in B, and the largest value in A is at least the largest value in B". That is, if drawn on a number line, the entirety of A is either above B or overlaps with it, but B does not extend higher than A. + +Note that `LIST_A > LIST_B` implies `LIST_A != LIST_B`, and `LIST_A >= LIST_B` allows `LIST_A == LIST_B` but precludes `LIST_A < LIST_B`, as you might hope. + +#### Health warning! + +`LIST_A >= LIST_B` is *not* the same as `LIST_A > LIST_B or LIST_A == LIST_B`. + +The moral is, don't use these unless you have a clear picture in your mind. + +### Inverting lists + +A list can be "inverted", which is the equivalent of going through the accommodation in/out name-board and flipping every switch to the opposite of what it was before. + + LIST GuardsOnDuty = (Smith), (Jones), Carter, Braithwaite + + === function changingOfTheGuard + ~ GuardsOnDuty = LIST_INVERT(GuardsOnDuty) + + +Note that `LIST_INVERT` on an empty list will return a null value, if the game doesn't have enough context to know what invert. If you need to handle that case, it's safest to do it by hand: + + === function changingOfTheGuard + {!GuardsOnDuty: // "is GuardsOnDuty empty right now?" + ~ GuardsOnDuty = LIST_ALL(Smith) + - else: + ~ GuardsOnDuty = LIST_INVERT(GuardsOnDuty) + } + +#### Footnote + +The syntax for inversion was originally `~ list` but we changed it because otherwise the line + + ~ list = ~ list + +was not only functional, but actually caused list to invert itself, which seemed excessively perverse. + +### Intersecting lists + +The `has` or `?` operator is, somewhat more formally, the "are you a subset of me" operator, ⊇, which includes the sets being equal, but which doesn't include if the larger set doesn't entirely contain the smaller set. + +To test for "some overlap" between lists, we use the overlap operator, `^`, to get the *intersection*. + + LIST CoreValues = strength, courage, compassion, greed, nepotism, self_belief, delusions_of_godhood + VAR desiredValues = (strength, courage, compassion, self_belief ) + VAR actualValues = ( greed, nepotism, self_belief, delusions_of_godhood ) + + {desiredValues ^ actualValues} // prints "self_belief" + +The result is a new list, so you can test it: + + {desiredValues ^ actualValues: The new president has at least one desirable quality.} + + {LIST_COUNT(desiredValues ^ actualValues) == 1: Correction, the new president has only one desirable quality. {desiredValues ^ actualValues == self_belief: It's the scary one.}} + + + + +## 6) Multi-list Lists + + +So far, all of our examples have included one large simplification, again - that the values in a list variable have to all be from the same list family. But they don't. + +This allows us to use lists - which have so far played the role of state-machines and flag-trackers - to also act as general properties, which is useful for world modelling. + +This is our inception moment. The results are powerful, but also more like "real code" than anything that's come before. + +### Lists to track objects + +For instance, we might define: + + LIST Characters = Alfred, Batman, Robin + LIST Props = champagne_glass, newspaper + + VAR BallroomContents = (Alfred, Batman, newspaper) + VAR HallwayContents = (Robin, champagne_glass) + +We could then describe the contents of any room by testing its state: + + === function describe_room(roomState) + { roomState ? Alfred: Alfred is here, standing quietly in a corner. } { roomState ? Batman: Batman's presence dominates all. } { roomState ? Robin: Robin is all but forgotten. } + <> { roomState ? champagne_glass: A champagne glass lies discarded on the floor. } { roomState ? newspaper: On one table, a headline blares out WHO IS THE BATMAN? AND *WHO* IS HIS BARELY-REMEMBERED ASSISTANT? } + +So then: + + { describe_room(BallroomContents) } + +produces: + + Alfred is here, standing quietly in a corner. Batman's presence dominates all. + + On one table, a headline blares out WHO IS THE BATMAN? AND *WHO* IS HIS BARELY-REMEMBERED ASSISTANT? + +While: + + { describe_room(HallwayContents) } + +gives: + + Robin is all but forgotten. + + A champagne glass lies discarded on the floor. + +And we could have options based on combinations of things: + + * { currentRoomState ? (Batman, Alfred) } [Talk to Alfred and Batman] + 'Say, do you two know each other?' + +### Lists to track multiple states + +We can model devices with multiple states. Back to the kettle again... + + LIST OnOff = on, off + LIST HotCold = cold, warm, hot + + VAR kettleState = (off, cold) // we need brackets because it's a proper, multi-valued list now + + === function turnOnKettle() === + { kettleState ? hot: + You turn on the kettle, but it immediately flips off again. + - else: + The water in the kettle begins to heat up. + ~ kettleState -= off + ~ kettleState += on + // note we avoid "=" as it'll remove all existing states + } + + === function can_make_tea() === + ~ return kettleState ? (hot, off) + +These mixed states can make changing state a bit trickier, as the off/on above demonstrates, so the following helper function can be useful. + + === function changeStateTo(ref stateVariable, stateToReach) + // remove all states of this type + ~ stateVariable -= LIST_ALL(stateToReach) + // put back the state we want + ~ stateVariable += stateToReach + + which enables code like: + + ~ changeState(kettleState, on) + ~ changeState(kettleState, warm) + + +#### How does this affect queries? + +The queries given above mostly generalise nicely to multi-valued lists + + LIST Letters = a,b,c + LIST Numbers = one, two, three + + VAR mixedList = (a, three, c) + + {LIST_ALL(mixedList)} // a, one, b, two, c, three + {LIST_COUNT(mixedList)} // 3 + {LIST_MIN(mixedList)} // a + {LIST_MAX(mixedList)} // three or c, albeit unpredictably + + {mixedList ? (a,b) } // false + {mixedList ^ LIST_ALL(a)} // a, c + + { mixedList >= (one, a) } // true + { mixedList < (three) } // false + + { LIST_INVERT(mixedList) } // one, b, two + + +## 7) Long example: crime scene + +Finally, here's a long example, demonstrating a lot of ideas from this section in action. You might want to try playing it before reading through to better understand the various moving parts. + + -> murder_scene + + // Helper function: popping elements from lists + === function pop(ref list) + ~ temp x = LIST_MIN(list) + ~ list -= x + ~ return x + + // + // System: items can have various states + // Some are general, some specific to particular items + // + + + LIST OffOn = off, on + LIST SeenUnseen = unseen, seen + + LIST GlassState = (none), steamed, steam_gone + LIST BedState = (made_up), covers_shifted, covers_off, bloodstain_visible + + // + // System: inventory + // + + LIST Inventory = (none), cane, knife + + === function get(x) + ~ Inventory += x + + // + // System: positioning things + // Items can be put in and on places + // + + LIST Supporters = on_desk, on_floor, on_bed, under_bed, held, with_joe + + === function move_to_supporter(ref item_state, new_supporter) === + ~ item_state -= LIST_ALL(Supporters) + ~ item_state += new_supporter + + + // System: Incremental knowledge. + // Each list is a chain of facts. Each fact supersedes the fact before + // + + VAR knowledgeState = () + + === function reached (x) + ~ return knowledgeState ? x + + === function between(x, y) + ~ return knowledgeState? x && not (knowledgeState ^ y) + + === function reach(statesToSet) + ~ temp x = pop(statesToSet) + { + - not x: + ~ return false + + - not reached(x): + ~ temp chain = LIST_ALL(x) + ~ temp statesGained = LIST_RANGE(chain, LIST_MIN(chain), x) + ~ knowledgeState += statesGained + ~ reach (statesToSet) // set any other states left to set + ~ return true // and we set this state, so true + + - else: + ~ return false || reach(statesToSet) + } + + // + // Set up the game + // + + VAR bedroomLightState = (off, on_desk) + + VAR knifeState = (under_bed) + + + // + // Knowledge chains + // + + + LIST BedKnowledge = neatly_made, crumpled_duvet, hastily_remade, body_on_bed, murdered_in_bed, murdered_while_asleep + + LIST KnifeKnowledge = prints_on_knife, joe_seen_prints_on_knife,joe_wants_better_prints, joe_got_better_prints + + LIST WindowKnowledge = steam_on_glass, fingerprints_on_glass, fingerprints_on_glass_match_knife + + + // + // Content + // + + === murder_scene === + The bedroom. This is where it happened. Now to look for clues. + - (top) + { bedroomLightState ? seen: <- seen_light } + <- compare_prints(-> top) + + * (dobed) [The bed...] + The bed was low to the ground, but not so low something might not roll underneath. It was still neatly made. + ~ reach (neatly_made) + - - (bedhub) + * * [Lift the bedcover] + I lifted back the bedcover. The duvet underneath was crumpled. + ~ reach (crumpled_duvet) + ~ BedState = covers_shifted + * * (uncover) {reached(crumpled_duvet)} + [Remove the cover] + Careful not to disturb anything beneath, I removed the cover entirely. The duvet below was rumpled. + Not the work of the maid, who was conscientious to a point. Clearly this had been thrown on in a hurry. + ~ reach (hastily_remade) + ~ BedState = covers_off + * * (duvet) {BedState == covers_off} [ Pull back the duvet ] + I pulled back the duvet. Beneath it was a sheet, sticky with blood. + ~ BedState = bloodstain_visible + ~ reach (body_on_bed) + Either the body had been moved here before being dragged to the floor - or this is was where the murder had taken place. + * * {BedState !? made_up} [ Remake the bed ] + Carefully, I pulled the bedsheets back into place, trying to make it seem undisturbed. + ~ BedState = made_up + * * [Test the bed] + I pushed the bed with spread fingers. It creaked a little, but not so much as to be obnoxious. + * * (darkunder) [Look under the bed] + Lying down, I peered under the bed, but could make nothing out. + + * * {TURNS_SINCE(-> dobed) > 1} [Something else?] + I took a step back from the bed and looked around. + -> top + - - -> bedhub + + * {darkunder && bedroomLightState ? on_floor && bedroomLightState ? on} + [ Look under the bed ] + I peered under the bed. Something glinted back at me. + - - (reaching) + * * [ Reach for it ] + I fished with one arm under the bed, but whatever it was, it had been kicked far enough back that I couldn't get my fingers on it. + -> reaching + * * {Inventory ? cane} [Knock it with the cane] + -> knock_with_cane + + * * {reaching > 1 } [ Stand up ] + I stood up once more, and brushed my coat down. + -> top + + * (knock_with_cane) {reaching && TURNS_SINCE(-> reaching) >= 4 && Inventory ? cane } [Use the cane to reach under the bed ] + Positioning the cane above the carpet, I gave the glinting thing a sharp tap. It slid out from the under the foot of the bed. + ~ move_to_supporter( knifeState, on_floor ) + * * (standup) [Stand up] + Satisfied, I stood up, and saw I had knocked free a bloodied knife. + -> top + + * * [Look under the bed once more] + Moving the cane aside, I looked under the bed once more, but there was nothing more there. + -> standup + + * {knifeState ? on_floor} [Pick up the knife] + Careful not to touch the handle, I lifted the blade from the carpet. + ~ get(knife) + + * {Inventory ? knife} [Look at the knife] + The blood was dry enough. Dry enough to show up partial prints on the hilt! + ~ reach (prints_on_knife) + + * [ The desk... ] + I turned my attention to the desk. A lamp sat in one corner, a neat, empty in-tray in the other. There was nothing else out. + Leaning against the desk was a wooden cane. + ~ bedroomLightState += seen + + - - (deskstate) + * * (pickup_cane) {Inventory !? cane} [Pick up the cane ] + ~ get(cane) + I picked up the wooden cane. It was heavy, and unmarked. + + * * { bedroomLightState !? on } [Turn on the lamp] + -> operate_lamp -> + + * * [Look at the in-tray ] + I regarded the in-tray, but there was nothing to be seen. Either the victim's papers were taken, or his line of work had seriously dried up. Or the in-tray was all for show. + + + + (open) {open < 3} [Open a drawer] + I tried {a drawer at random|another drawer|a third drawer}. {Locked|Also locked|Unsurprisingly, locked as well}. + + * * {deskstate >= 2} [Something else?] + I took a step away from the desk once more. + -> top + + - - -> deskstate + + * {(Inventory ? cane) && TURNS_SINCE(-> deskstate) <= 2} [Swoosh the cane] + I was still holding the cane: I gave it an experimental swoosh. It was heavy indeed, though not heavy enough to be used as a bludgeon. + But it might have been useful in self-defence. Why hadn't the victim reached for it? Knocked it over? + + * [The window...] + I went over to the window and peered out. A dismal view of the little brook that ran down beside the house. + + - - (window_opts) + <- compare_prints(-> window_opts) + * * (downy) [Look down at the brook] + { GlassState ? steamed: + Through the steamed glass I couldn't see the brook. -> see_prints_on_glass -> window_opts + } + I watched the little stream rush past for a while. The house probably had damp but otherwise, it told me nothing. + * * (greasy) [Look at the glass] + { GlassState ? steamed: -> downy } + The glass in the window was greasy. No one had cleaned it in a while, inside or out. + * * { GlassState ? steamed && not see_prints_on_glass && downy && greasy } + [ Look at the steam ] + A cold day outside. Natural my breath should steam. -> see_prints_on_glass -> + + + {GlassState ? steam_gone} [ Breathe on the glass ] + I breathed gently on the glass once more. { reached (fingerprints_on_glass): The fingerprints reappeared. } + ~ GlassState = steamed + + + + [Something else?] + { window_opts < 2 || reached (fingerprints_on_glass) || GlassState ? steamed: + I looked away from the dreary glass. + {GlassState ? steamed: + ~ GlassState = steam_gone + <> The steam from my breath faded. + } + -> top + } + I leant back from the glass. My breath had steamed up the pane a little. + ~ GlassState = steamed + + - - -> window_opts + + * {top >= 5} [Leave the room] + I'd seen enough. I {bedroomLightState ? on:switched off the lamp, then} turned and left the room. + -> joe_in_hall + + - -> top + + + = operate_lamp + I flicked the light switch. + { bedroomLightState ? on: + <> The bulb fell dark. + ~ bedroomLightState += off + ~ bedroomLightState -= on + - else: + { bedroomLightState ? on_floor: <> A little light spilled under the bed.} { bedroomLightState ? on_desk : <> The light gleamed on the polished tabletop. } + ~ bedroomLightState -= off + ~ bedroomLightState += on + } + ->-> + + + = compare_prints (-> backto) + * { between ((fingerprints_on_glass, prints_on_knife), fingerprints_on_glass_match_knife) } + [Compare the prints on the knife and the window ] + Holding the bloodied knife near the window, I breathed to bring out the prints once more, and compared them as best I could. + Hardly scientific, but they seemed very similar - very similiar indeed. + ~ reach (fingerprints_on_glass_match_knife) + -> backto + + = see_prints_on_glass + ~ reach (fingerprints_on_glass) + {But I could see a few fingerprints, as though someone hadpressed their palm against it.|The fingerprints were quite clear and well-formed.} They faded as I watched. + ~ GlassState = steam_gone + ->-> + + = seen_light + * {bedroomLightState !? on} [ Turn on lamp ] + -> operate_lamp -> + + * { bedroomLightState !? on_bed && BedState ? bloodstain_visible } + [ Move the light to the bed ] + ~ move_to_supporter(bedroomLightState, on_bed) + + I moved the light over to the bloodstain and peered closely at it. It had soaked deeply into the fibres of the cotton sheet. + There was no doubt about it. This was where the blow had been struck. + ~ reach (murdered_in_bed) + + * { bedroomLightState !? on_desk } {TURNS_SINCE(-> floorit) >= 2 } + [ Move the light back to the desk ] + ~ move_to_supporter(bedroomLightState, on_desk) + I moved the light back to the desk, setting it down where it had originally been. + * (floorit) { bedroomLightState !? on_floor && darkunder } + [Move the light to the floor ] + ~ move_to_supporter(bedroomLightState, on_floor) + I picked the light up and set it down on the floor. + - -> top + + === joe_in_hall + My police contact, Joe, was waiting in the hall. 'So?' he demanded. 'Did you find anything interesting?' + - (found) + * {found == 1} 'Nothing.' + He shrugged. 'Shame.' + -> done + * { Inventory ? knife } 'I found the murder weapon.' + 'Good going!' Joe replied with a grin. 'We thought the murderer had gotten rid of it. I'll bag that for you now.' + ~ move_to_supporter(knifeState, with_joe) + + * {reached(prints_on_knife)} { knifeState ? with_joe } + 'There are prints on the blade[.'],' I told him. + He regarded them carefully. + 'Hrm. Not very complete. It'll be hard to get a match from these.' + ~ reach (joe_seen_prints_on_knife) + * { reached((fingerprints_on_glass_match_knife, joe_seen_prints_on_knife)) } + 'They match a set of prints on the window, too.' + 'Anyone could have touched the window,' Joe replied thoughtfully. 'But if they're more complete, they should help us get a decent match!' + ~ reach (joe_wants_better_prints) + * { between(body_on_bed, murdered_in_bed)} + 'The body was moved to the bed at some point[.'],' I told him. 'And then moved back to the floor.' + 'Why?' + * * 'I don't know.' + Joe nods. 'All right.' + * * 'Perhaps to get something from the floor?' + 'You wouldn't move a whole body for that.' + * * 'Perhaps he was killed in bed.' + 'It's just speculation at this point,' Joe remarks. + * { reached(murdered_in_bed) } + 'The victim was murdered in bed, and then the body was moved to the floor.' + 'Why?' + * * 'I don't know.' + Joe nods. 'All right, then.' + * * 'Perhaps the murderer wanted to mislead us.' + 'How so?' + * * * 'They wanted us to think the victim was awake[.'], I replied thoughtfully. 'That they were meeting their attacker, rather than being stabbed in their sleep.' + * * * 'They wanted us to think there was some kind of struggle[.'],' I replied. 'That the victim wasn't simply stabbed in their sleep.' + - - - 'But if they were killed in bed, that's most likely what happened. Stabbed, while sleeping.' + ~ reach (murdered_while_asleep) + * * 'Perhaps the murderer hoped to clean up the scene.' + 'But they were disturbed? It's possible.' + + * { found > 1} 'That's it.' + 'All right. It's a start,' Joe replied. + -> done + - -> found + - (done) + { + - between(joe_wants_better_prints, joe_got_better_prints): + ~ reach (joe_got_better_prints) + <> 'I'll get those prints from the window now.' + - reached(joe_seen_prints_on_knife): + <> 'I'll run those prints as best I can.' + - else: + <> 'Not much to go on.' + } + -> END + + + +## 8) Summary + +To summarise a difficult section, **Ink**'s list construction provides: + +### Flags +* Each list entry is an event +* Use `+=` to mark an event as having occurred +* Test using `?` and `!?` + +Example: + + LIST GameEvents = foundSword, openedCasket, metGorgon + { GameEvents ? openedCasket } + { GameEvents ? (foundSword, metGorgon) } + ~ GameEvents += metGorgon + +### State machines +* Each list entry is a state +* Use `=` to set the state; `++` and `--` to step forward or backward +* Test using `==`, `>` etc + +Example: + + LIST PancakeState = ingredients_gathered, batter_mix, pan_hot, pancakes_tossed, ready_to_eat + { PancakeState == batter_mix } + { PancakeState < ready_to_eat } + ~ PancakeState++ + +### Properties +* Each list is a different property, with values for the states that property can take (on or off, lit or unlit, etc) +* Change state by removing the old state, then adding in the new +* Test using `?` and `!?` + +Example: + + LIST OnOffState = on, off + LIST ChargeState = uncharged, charging, charged + + VAR PhoneState = (off, uncharged) + + * {PhoneState !? uncharged } [Plug in phone] + ~ PhoneState -= LIST_ALL(ChargeState) + ~ PhoneState += charging + You plug the phone into charge. + * { PhoneState ? (on, charged) } [ Call my mother ] + + + + +# Part 6: International character support in identifiers + +By default, ink has no limitations on the use of non-ASCII characters inside the story content. However, a limitation currently exsits +on the characters that can be used for names of constants, variables, stictches, diverts and other named flow elements (a.k.a. *identifiers*). + +Sometimes it is inconvenient for a writer using a non-ASCII language to write a story because they have to constantly switch to naming identifiers in ASCII and then switching back to whatever language they are using for the story. In addition, naming identifiers in the author's own language could improve the overal readibility of the raw story format. + +In an effort to assist in the above scenario, ink *automatically* supports a list of pre-defined non-ASCII character ranges that can be used as identifiers. In general, those ranges have been selected to include the alpha-numeric subset of the official unicode character range, which would suffice for naming identifiers. The below section gives more detailed information on the non-ASCII characters that ink automatically supports. + +### Supported Identifier Characters + +The support for the additional character ranges in ink is currently limited to a predefined set of character ranges. + +Below is a listing of the currently supported identifier ranges. + + - **Arabic** + + Enables characters for languages of the Arabic family and is a subset of the official *Arabic* unicode range `\u0600`-`\u06FF`. + + + - **Armenian** + + Enables characters for the Armenian language and is a subset of the official *Armenian* unicode range `\u0530`-`\u058F`. + + + - **Cyrillic** + + Enables characters for languages using the Cyrillic alphabet and is a subset of the official *Cyrillic* unicode range `\u0400`-`\u04FF`. + + + - **Greek** + + Enables characters for languages using the Greek alphabet and is a subset of the official *Greek and Coptic* unicode range `\u0370`-`\u03FF`. + + + - **Hebrew** + + Enables characters in Hebrew using the Hebrew alphabet and is a subset of the official *Hebrew* unicode range `\u0590`-`\u05FF`. + + + - **Latin Extended A** + + Enables an extended character range subset of the Latin alphabet - completely represented by the official *Latin Extended-A* unicode range `\u0100`-`\u017F`. + + + - **Latin Extended B** + + Enables an extended character range subset of the Latin alphabet - completely represented by the official *Latin Extended-B* unicode range `\u0180`-`\u024F`. + +- **Latin 1 Supplement** + + Enables an extended character range subset of the Latin alphabet - completely represented by the official *Latin 1 Supplement* unicode range `\u0080` - `\u00FF`. + + +**NOTE!** ink files should be saved in UTF-8 format, which ensures that the above character ranges are supported. + +If a particular character range that you would like to use within identifiers isn't supported, feel free to open an [issue](/inkle/ink/issues/new) or [pull request](/inkle/ink/pulls) on the main ink repo. From b5114e5f36cee25de541d6f4cb3bc8bfcc7d8ed5 Mon Sep 17 00:00:00 2001 From: Ray-Lum <51054841+Ray-Lum@users.noreply.github.com> Date: Wed, 2 Oct 2024 15:02:51 +0800 Subject: [PATCH 02/22] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E6=88=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Documentation/WritingWithInk_Simplified-Chinese.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Documentation/WritingWithInk_Simplified-Chinese.md b/Documentation/WritingWithInk_Simplified-Chinese.md index 4892f6ab..4e8f5f0f 100644 --- a/Documentation/WritingWithInk_Simplified-Chinese.md +++ b/Documentation/WritingWithInk_Simplified-Chinese.md @@ -184,9 +184,13 @@ 重要的部分完全由人工翻译。 -本文译者:王洛木 (Nomo Wang) +译者:王洛木 (Nomo_Wang@outlook.com),如果您有关于翻译的问题要提,且不想要占用 Discussion 资源,可以发邮件给译者。 -翻译时的软件版本:【发布分支时记得修改这里……】 +翻译时的 Ink 版本:1.2.0 + +翻译最后更新时间:2024年10月2日 + +翻译仍在更新。 ## 介绍 From 0ae89534b7f60c7b4564afdb736cce25a965a91c Mon Sep 17 00:00:00 2001 From: Ray-Lum <51054841+Ray-Lum@users.noreply.github.com> Date: Fri, 11 Oct 2024 22:17:55 +0800 Subject: [PATCH 03/22] =?UTF-8?q?=E4=BF=9D=E5=AD=98=E8=BF=9B=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WritingWithInk_Simplified-Chinese.md | 383 +++++++++--------- 1 file changed, 189 insertions(+), 194 deletions(-) diff --git a/Documentation/WritingWithInk_Simplified-Chinese.md b/Documentation/WritingWithInk_Simplified-Chinese.md index 4e8f5f0f..d889d90e 100644 --- a/Documentation/WritingWithInk_Simplified-Chinese.md +++ b/Documentation/WritingWithInk_Simplified-Chinese.md @@ -58,18 +58,18 @@ - [功能预览:在功能中使用分道计数函数|Sneak preview: using TURNS\_SINCE in a function](#功能预览在功能中使用分道计数函数sneak-preview-using-turns_since-in-a-function) - [种子随机函数|SEED\_RANDOM()](#种子随机函数seed_random) - [进阶:更多查询|Advanced: more queries](#进阶更多查询advanced-more-queries) -- [第 2 部分:编织|Part 2: Weave](#第-2-部分编织part-2-weave) - - [1) Gathers](#1-gathers) - - [Gather points gather the flow back together](#gather-points-gather-the-flow-back-together) - - [Options and gathers form chains of content](#options-and-gathers-form-chains-of-content) - - [The weave philosophy](#the-weave-philosophy) - - [2) Nested Flow](#2-nested-flow) - - [Options can be nested](#options-can-be-nested) - - [Gather points can be nested too](#gather-points-can-be-nested-too) - - [Advanced: What gathers do](#advanced-what-gathers-do) - - [You can nest as many levels are you like](#you-can-nest-as-many-levels-are-you-like) - - [Example: a conversation with nested nodes](#example-a-conversation-with-nested-nodes) - - [3) Tracking a Weave](#3-tracking-a-weave) +- [第 2 部分:织体|Part 2: Weave](#第-2-部分织体part-2-weave) + - [1) 收束|Gathers](#1-收束gathers) + - [收束点将故事流收束到一起|Gather points gather the flow back together](#收束点将故事流收束到一起gather-points-gather-the-flow-back-together) + - [内容链的选项和收束|Options and gathers form chains of content](#内容链的选项和收束options-and-gathers-form-chains-of-content) + - [织体的理念(方法论)|The weave philosophy](#织体的理念方法论the-weave-philosophy) + - [2) 嵌套故事流|Nested Flow](#2-嵌套故事流nested-flow) + - [选项可以嵌套|Options can be nested](#选项可以嵌套options-can-be-nested) + - [收束点也可以嵌套|Gather points can be nested too](#收束点也可以嵌套gather-points-can-be-nested-too) + - [进阶:收束这个操作做了什么|Advanced: What gathers do](#进阶收束这个操作做了什么advanced-what-gathers-do) + - [你可以根据你自己的需要设置多层嵌套|You can nest as many levels are you like](#你可以根据你自己的需要设置多层嵌套you-can-nest-as-many-levels-are-you-like) + - [示例:用嵌套节点写的对话|Example: a conversation with nested nodes](#示例用嵌套节点写的对话example-a-conversation-with-nested-nodes) + - [3) 追踪织体|Tracking a Weave](#3-追踪织体tracking-a-weave) - [Weaves are largely unaddressed](#weaves-are-largely-unaddressed) - [Gathers and options can be labelled](#gathers-and-options-can-be-labelled) - [Scope](#scope) @@ -114,7 +114,7 @@ - [6) 常量|Constants](#6-常量constants) - [Global Constants](#global-constants) - [7) Advanced: Game-side logic](#7-advanced-game-side-logic) -- [Part 4: Advanced Flow Control](#part-4-advanced-flow-control) +- [第 4 部分:进阶流程控制|Part 4: Advanced Flow Control](#第-4-部分进阶流程控制part-4-advanced-flow-control) - [1) Tunnels](#1-tunnels) - [Tunnels run sub-stories](#tunnels-run-sub-stories) - [Advanced: Tunnels can return elsewhere](#advanced-tunnels-can-return-elsewhere) @@ -126,7 +126,7 @@ - [Using `-> DONE`](#using---done) - [Example: adding the same choice to several places](#example-adding-the-same-choice-to-several-places) - [Example: organisation of wide choice points](#example-organisation-of-wide-choice-points) -- [Part 5: Advanced State Tracking](#part-5-advanced-state-tracking) +- [第 5 部分:进阶状态追踪|Part 5: Advanced State Tracking](#第-5-部分进阶状态追踪part-5-advanced-state-tracking) - [Note: New feature alert!](#note-new-feature-alert) - [1) Basic Lists](#1-basic-lists) - [2) Reusing Lists](#2-reusing-lists) @@ -171,7 +171,7 @@ - [Flags](#flags) - [State machines](#state-machines) - [Properties](#properties) -- [Part 6: International character support in identifiers](#part-6-international-character-support-in-identifiers) +- [第 6 部分:标识符中的国际字符支持|Part 6: International character support in identifiers](#第-6-部分标识符中的国际字符支持part-6-international-character-support-in-identifiers) - [Supported Identifier Characters](#supported-identifier-characters) @@ -1020,254 +1020,254 @@ TODO: (向编译器传递 `-c` 的要求) 您也可以创建您自己的外部函数,但是语法会略有不同,详情请见后文中的[函数](#5-函数functions)章节。 -# 第 2 部分:编织|Part 2: Weave +# 第 2 部分:织体|Part 2: Weave -So far, we've been building branched stories in the simplest way, with "options" that link to "pages". +到目前为止,我们一直在用最简单的方式构建分支故事,即通过“选项 (Options)”链接到“页面 (Pages)”。 -But this requires us to uniquely name every destination in the story, which can slow down writing and discourage minor branching. +但这要求我们对故事中的每个目的地都进行唯一命名,这可能会减慢写作速度,并阻碍小分支的出现。 -**Ink** has a much more powerful syntax available, designed for simplifying story flows which have an always-forwards direction (as most stories do, and most computer programs don't). +**Ink** 有一种功能更强大的语法,专门用于简化始终向前的故事流(大多数故事都是这样,而大多数计算机程序则不是)。 -This format is called "weave", and its built out of the basic content/option syntax with two new features: the gather mark, `-`, and the nesting of choices and gathers. +这种格式就被称为“织体 (Weave)”,它在基本内容和选项语法的基础上增加了两个新功能:收束 (Gather),`-`,还有选择与收束的嵌套。 -## 1) Gathers +## 1) 收束|Gathers -### Gather points gather the flow back together +### 收束点将故事流收束到一起|Gather points gather the flow back together -Let's go back to the first multi-choice example at the top of this document. +让我们回到本文开头的第一个多选示例。 - "What's that?" my master asked. - * "I am somewhat tired[."]," I repeated. - "Really," he responded. "How deleterious." - * "Nothing, Monsieur!"[] I replied. - * "I said, this journey is appalling[."] and I want no more of it." - "Ah," he replied, not unkindly. "I see you are feeling frustrated. Tomorrow, things will improve." + “你说什么?”我的老大问我。 + * “我有点累了[。”],老大……”我重复着。 + “这样啊。”他回应道:“那休息一下吧。” + * “没事的老大!”[]我说。 + “很好,那继续吧。” + * “我说,这次的冒险真的很可怕[……”],我真的不想再继续了…… + “啊……别这样。”他安慰着我:“看起来你现在有些累了。明天,事情一定会有所好转的。” -In a real game, all three of these options might well lead to the same conclusion - Monsieur Fogg leaves the room. We can do this using a gather, without the need to create any new knots, or add any diverts. +在实际游戏中,这三个选项都可能导致相同的结果——福格先生离开了房间。我们可以用“收束”而无需创建任何新的结点或分道而完成这一点。 - "What's that?" my master asked. - * "I am somewhat tired[."]," I repeated. - "Really," he responded. "How deleterious." - * "Nothing, Monsieur!"[] I replied. - "Very good, then." - * "I said, this journey is appalling[."] and I want no more of it." - "Ah," he replied, not unkindly. "I see you are feeling frustrated. Tomorrow, things will improve." + “你说什么?”我的老大问我。 + * “我有点累了[。”],老大……”我重复着。 + “这样啊。”他回应道:“那休息一下吧。” + * “没事的老大!”[]我说。 + “很好,那继续吧。” + * “我说,这次的冒险真的很可怕[……”],我真的不想再继续了…… + “啊……别这样。”他安慰着我:“看起来你现在有些累了。明天,事情一定会有所好转的。” - - With that Monsieur Fogg left the room. + - 说完,福格先生离开了房间。 -This produces the following playthrough: +这可能会输出以下的游玩路径: - "What's that?" my master asked. + “你说什么?”我的老大问我。 - 1: "I am somewhat tired." - 2: "Nothing, Monsieur!" - 3: "I said, this journey is appalling." + 1: “我有点累了。” + 2: “没事的老大!” + 3: “我说,这次的冒险真的很可怕……” > 1 - "I am somewhat tired," I repeated. - "Really," he responded. "How deleterious." - With that Monsieur Fogg left the room. + “我有点累了,老大……”我重复着。 + “这样啊。”他回应道:“那休息一下吧。” + 说完,福格先生离开了房间。 -### Options and gathers form chains of content +### 内容链的选项和收束|Options and gathers form chains of content -We can string these gather-and-branch sections together to make branchy sequences that always run forwards. +我们可以将这些收束和分支部分串联起来,以使得故事分支继续推进。 === escape === - I ran through the forest, the dogs snapping at my heels. - - * I checked the jewels[] were still in my pocket, and the feel of them brought a spring to my step. <> - - * I did not pause for breath[] but kept on running. <> - - * I cheered with joy. <> + 我在森林里奔跑,狗在我身后追赶。 - - The road could not be much further! Mackie would have the engine running, and then I'd be safe. + * 我检查了一下珠宝[是否还在。],它们还在我的口袋里,它们的触感给了我慰籍,让我的脚步跟踩了弹簧一样跑的更快了。<> - * I reached the road and looked about[]. And would you believe it? - * I should interrupt to say Mackie is normally very reliable[]. He's never once let me down. Or rather, never once, previously to that night. + * 不要停下来啊![]继续向前奔跑。<> - - The road was empty. Mackie was nowhere to be seen. + * 我高兴地欢呼起来。<> -This is the most basic kind of weave. The rest of this section details additional features that allow weaves to nest, contain side-tracks and diversions, divert within themselves, and above all, reference earlier choices to influence later ones. + - 路已经不远了!麦基会发动引擎,然后我就安全了。 + + * 我走到路上,四处张望[]。你敢信吗? + * 我要插一句,麦基通常都非常可靠[]。他从没让我失望过。或者说,在那天晚上之前,他从没让我失望过。 -#### The weave philosophy + - 路上空无一人。麦基不见踪影。 -Weaves are more than just a convenient encapsulation of branching flow; they're also a way to author more robust content. The `escape` example above has already four possible routes through, and a more complex sequence might have lots and lots more. Using normal diverts, one has to check the links by chasing the diverts from point to point and it's easy for errors to creep in. +这是组织织体最基本的方式。本节的其余部分将详细介绍一些附加功能,这些功能可以用来制作织体嵌套、内容的旁道 (Side-Track) 和分道 (Diversions)、在自身内部进行分道,最重要的是,还根据前面的选择来影响后面的内容。 -With a weave, the flow is guaranteed to start at the top and "fall" to the bottom. Flow errors are impossible in a basic weave structure, and the output text can be easily skim read. That means there's no need to actually test all the branches in game to be sure they work as intended. +#### 织体的理念(方法论)|The weave philosophy -Weaves also allow for easy redrafting of choice-points; in particular, it's easy to break a sentence up and insert additional choices for variety or pacing reasons, without having to re-engineer any flow. +织体不仅是对分支的方便封装,也是编写更经得起推敲的内容的一种方法。上面的 `escape` 示例就已经有四种可能的路径了,而更复杂的序列可能会有更多更多的路径。如果使用普通的分道,就必须挨个检查结点链接,这样很容易出现错误。 +在织体中,流程保证从顶部开始,然后一路走到底。在基本的织体结构中,流程错误是不可能发生的,而且输出文本可以很容易地略读。 +这就意味着无需在游戏中实际测试所有分支也能确保它们按预期运行。 -## 2) Nested Flow +织体还可以方便地重新起草选择点,特别是那些出于多样性或节奏的选择。它很容易将句子拆开并插入额外的选项,而无需重新设计任何流程。 -The weaves shown above are quite simple, "flat" structures. Whatever the player does, they take the same number of turns to get from top to bottom. However, sometimes certain choices warrant a bit more depth or complexity. +## 2) 嵌套故事流|Nested Flow -For that, we allow weaves to nest. +上图中的织体是非常简单的“扁平”结构。无论玩家做什么,从开头到结尾都需要相同的回合数。然后有时某些选择应当需要更多的深度或复杂性。 -This section comes with a warning. Nested weaves are very powerful and very compact, but they can take a bit of getting used to! +为此,我们允许织体嵌套。 -### Options can be nested +本节有一个警告。嵌套编织功能强大,结构紧凑,但需要一点时间来适应! -Consider the following scene: +### 选项可以嵌套|Options can be nested - - "Well, Poirot? Murder or suicide?" - * "Murder!" - * "Suicide!" - - Ms. Christie lowered her manuscript a moment. The rest of the writing group sat, open-mouthed. +请看下面的场景: -The first choice presented is "Murder!" or "Suicide!". If Poirot declares a suicide, there's no more to do, but in the case of murder, there's a follow-up question needed - who does he suspect? + - “波洛?你认为这是谋杀还是自杀?” + * “谋杀!” + * “自杀!” + - 克里斯蒂女士稍后放下了手稿。写作小组的其他成员坐在一旁,张大了嘴巴。 -We can add new options via a set of nested sub-choices. We tell the script that these new choices are "part of" another choice by using two asterisks, instead of just one. +第一个选择是“谋杀!”或“自杀!”。如果波洛宣布是自杀,那就没什么可做的了,但如果是谋杀,就需要追问——他怀疑谁? +我们可以通过一组嵌套的子选项来添加新的选项。我们可以用两个星号而不是一个星号来表示这些新选项是另一个选项的“一部分”。 - - "Well, Poirot? Murder or suicide?" - * "Murder!" - "And who did it?" - * * "Detective-Inspector Japp!" - * * "Captain Hastings!" - * * "Myself!" - * "Suicide!" - - Mrs. Christie lowered her manuscript a moment. The rest of the writing group sat, open-mouthed. + - “波洛?你认为这是谋杀还是自杀?” + * “谋杀!” + “那你认为是谁干的呢?” + ** “贾普探长!” + ** “黑斯廷斯上尉!” + ** “就是我!” + * “自杀!” + - 克里斯蒂女士稍后放下了手稿。写作小组的其他成员坐在一旁,张大了嘴巴。 -(Note that it's good style to also indent the lines to show the nesting, but the compiler doesn't mind.) +(注意,使用缩进来显示嵌套也是一种好的风格,这会便于作者审阅,但编译器并不会介意)。 -And should we want to add new sub-options to the other route, we do that in similar fashion. +如果我们想在另一条路径上添加新的子选项,也可以用类似的方法来实现。 - - "Well, Poirot? Murder or suicide?" - * "Murder!" - "And who did it?" - * * "Detective-Inspector Japp!" - * * "Captain Hastings!" - * * "Myself!" - * "Suicide!" - "Really, Poirot? Are you quite sure?" - * * "Quite sure." - * * "It is perfectly obvious." - - Mrs. Christie lowered her manuscript a moment. The rest of the writing group sat, open-mouthed. + - “波洛?你认为这是谋杀还是自杀?” + * “谋杀!” + “那你认为是谁干的呢?” + ** “贾普探长!” + ** “黑斯廷斯上尉!” + ** “就是我!” + * “自杀!” + “真的么,波洛?你确定么?” + ** “非常确定。” + ** “这是显而易见的。” + - 克里斯蒂女士稍后放下了手稿。写作小组的其他成员坐在一旁,张大了嘴巴。 -Now, that initial choice of accusation will lead to specific follow-up questions - but either way, the flow will come back together at the gather point, for Mrs. Christie's cameo appearance. +现在,最初的指控选择将引出具体的后续问题——但无论如何,流程都将在克里斯蒂夫人的出场时收束到一起。 -But what if we want a more extended sub-scene? +但是,如果我们想要一个更长的分镜头呢? -### Gather points can be nested too +### 收束点也可以嵌套|Gather points can be nested too -Sometimes, it's not a question of expanding the number of options, but having more than one additional beat of story. We can do this by nesting gather points as well as options. +有时,问题不在于选项数量的增加,而在于故事节点的增加。我们可以通过嵌套收束点和选项来实现这一点。 - - "Well, Poirot? Murder or suicide?" - * "Murder!" - "And who did it?" - * * "Detective-Inspector Japp!" - * * "Captain Hastings!" - * * "Myself!" - - - "You must be joking!" - * * "Mon ami, I am deadly serious." - * * "If only..." - * "Suicide!" - "Really, Poirot? Are you quite sure?" - * * "Quite sure." - * * "It is perfectly obvious." - - Mrs. Christie lowered her manuscript a moment. The rest of the writing group sat, open-mouthed. + - “波洛?你认为这是谋杀还是自杀?” + * “谋杀!” + “那你认为是谁干的呢?” + ** “贾普探长!” + ** “黑斯廷斯上尉!” + ** “就是我!” + -- “你一定是在开玩笑!” + ** “我的朋友,我是认真的。” + ** “只是……” + * “自杀!” + “真的么,波洛?你确定么?” + ** “非常确定。” + ** “这是显而易见的。” + - 克里斯蒂女士稍后放下了手稿。写作小组的其他成员坐在一旁,张大了嘴巴。 -If the player chooses the "murder" option, they'll have two choices in a row on their sub-branch - a whole flat weave, just for them. +如果玩家选择了“谋杀”选项,他们的这条子分支上就会连续出现两个选择——只属于这条子分支的扁平织体。 -#### Advanced: What gathers do +#### 进阶:收束这个操作做了什么|Advanced: What gathers do -Gathers are hopefully intuitive, but their behaviour is a little harder to put into words: in general, after an option has been taken, the story finds the next gather down that isn't on a lower level, and diverts to it. +收束是直观的,但它们的行为却很难用语言表达:一般来说,在一个选项被选中后,故事会找到下一个收束点并向其分道而去。 -The basic idea is this: options separate the paths of the story, and gathers bring them back together. (Hence the name, "weave"!) +基本原理是:选项将故事情节分开,而收束则将它们重新聚拢。(这一套下来得名“织体”) +### 你可以根据你自己的需要设置多层嵌套|You can nest as many levels are you like -### You can nest as many levels are you like +上面,我们使用了两层嵌套:主流程和子流程。但是,我们并没有限制嵌套的深度。 -Above, we used two levels of nesting; the main flow, and the sub-flow. But there's no limit to how many levels deep you can go. + - “跟我们讲个故事吧,队长!” + * “好吧,你们这些‘海狗’。我还真有个故事……” + ** “那是一个风雨交加的漆黑夜晚……” + *** “……船员们都很不安……” + **** “……他们对船长说……” + ***** “船长给我们讲个故事吧!” + * “不行,你们该上床睡觉了。” + - 船员们打起了哈欠。 - - "Tell us a tale, Captain!" - * "Very well, you sea-dogs. Here's a tale..." - * * "It was a dark and stormy night..." - * * * "...and the crew were restless..." - * * * * "... and they said to their Captain..." - * * * * * "...Tell us a tale Captain!" - * "No, it's past your bed-time." - - To a man, the crew began to yawn. +过一段时间后,这种嵌套就会变得难以阅读和操作,因此,如果嵌套会变得臃肿的话,将其分道到一个新的接缝会是一个好的操作。 -After a while, this sub-nesting gets hard to read and manipulate, so it's good style to divert away to a new stitch if a side-choice goes unwieldy. +但至少在理论上,你可以把整个故事只写成一个织体。 -But, in theory at least, you could write your entire story as a single weave. +### 示例:用嵌套节点写的对话|Example: a conversation with nested nodes -### Example: a conversation with nested nodes +这个示例有点长: -Here's a longer example: - - - I looked at Monsieur Fogg - * ... and I could contain myself no longer. - 'What is the purpose of our journey, Monsieur?' - 'A wager,' he replied. - * * 'A wager!'[] I returned. - He nodded. - * * * 'But surely that is foolishness!' - * * * 'A most serious matter then!' - - - - He nodded again. - * * * 'But can we win?' - 'That is what we will endeavour to find out,' he answered. - * * * 'A modest wager, I trust?' - 'Twenty thousand pounds,' he replied, quite flatly. - * * * I asked nothing further of him then[.], and after a final, polite cough, he offered nothing more to me. <> - * * 'Ah[.'],' I replied, uncertain what I thought. - - - After that, <> - * ... but I said nothing[] and <> - - we passed the day in silence. - - -> END - -with a couple of possible playthroughs. A short one: - - I looked at Monsieur Fogg - - 1: ... and I could contain myself no longer. - 2: ... but I said nothing + - 我看着福格先生 + * ……我再也控制不住我自己了。 + “我们此行的目的是什么,先生?” + “为了打个赌。”他说。 + ** “打个赌!”[]我重复着。 + 他点点头。 + *** “但这真的很蠢!” + *** “这也太糟了吧!” + --- 他又点了点头。 + *** “那我们能赢么?” + “这正是我们要努力查明的。”他回答道。 + *** “赌注应该不大吧?” + “两万英镑。”他斩钉截铁地回答道。 + *** 我没什么想问的了。 + 他最后礼貌地咳嗽了一声后,也没有再说什么。<> + ** “啊?”[]我不敢相信。 + -- 在那之后,<> + * ……但我什么也没有说。<> + - 我们在沉默中度过了一天。 + - -> END + +有几种可能的玩法。一个短的: + + 我看着福格先生 + + 1: ……我再也控制不住我自己了。 + 2: ……但我什么也没有说。 > 2 - ... but I said nothing and we passed the day in silence. + ……但我什么也没有说。我们在沉默中度过了一天。 -and a longer one: +一个长点的: - I looked at Monsieur Fogg - - 1: ... and I could contain myself no longer. - 2: ... but I said nothing + 我看着福格先生 + + 1: ……我再也控制不住我自己了。 + 2: ……但我什么也没有说。 > 1 - ... and I could contain myself no longer. - 'What is the purpose of our journey, Monsieur?' - 'A wager,' he replied. + ……我再也控制不住我自己了。 + “我们此行的目的是什么,先生?” + “为了打个赌。”他说。 - 1: 'A wager!' - 2: 'Ah.' + 1: “打个赌!” + 2: “啊?” > 1 - 'A wager!' I returned. - He nodded. - - 1: 'But surely that is foolishness!' - 2: 'A most serious matter then!' + “打个赌!”我重复着。 + 他点点头。 + + 1: “但这真的很蠢!” + 2: “这也太糟了吧!” > 2 - 'A most serious matter then!' - He nodded again. + “这也太糟了吧!” + 他又点了点头。 - 1: 'But can we win?' - 2: 'A modest wager, I trust?' - 3: I asked nothing further of him then. + 1: “那我们能赢么?” + 2: “赌注应该不大吧?” + 3: 我没什么想问的了。 > 2 - 'A modest wager, I trust?' - 'Twenty thousand pounds,' he replied, quite flatly. - After that, we passed the day in silence. + “赌注应该不大吧?” + “两万英镑。”他斩钉截铁地回答道。 + 在那之后,我们在沉默中度过了一天。 -Hopefully, this demonstrates the philosophy laid out above: that weaves offer a compact way to offer a lot of branching, a lot of choices, but with the guarantee of getting from beginning to end! +希望这能证明上文所阐述的理念:编织提供了一种紧凑的方式,可以提供很多分支、很多选择,但又能保证一定可以从开头走到结尾! -## 3) Tracking a Weave +## 3) 追踪织体|Tracking a Weave Sometimes, the weave structure is sufficient. But when it's not, we need a bit more control. @@ -1410,11 +1410,6 @@ The following is valid, and frequently useful. Note the level 2 gather point directly below the first option: there's nothing to gather here, really, but it gives us a handy place to divert the second option to. - - - - - # 第 3 部分:变量和逻辑|Part 3: Variables and Logic So far we've made conditional text, and conditional choices, using tests based on what content the player has seen so far. @@ -2080,7 +2075,7 @@ Constants are simply a way to allow you to give story states easy-to-understand There are two core ways to provide game hooks in the **Ink** engine. External function declarations in ink allow you to directly call C# functions in the game, and variable observers are callbacks that are fired in the game when ink variables are modified. Both of these are described in [Running your ink](RunningYourInk.md). -# Part 4: Advanced Flow Control +# 第 4 部分:进阶流程控制|Part 4: Advanced Flow Control ## 1) Tunnels @@ -2439,7 +2434,7 @@ A game which uses ink as a script rather than a literal output might often gener ... ``` -# Part 5: Advanced State Tracking +# 第 5 部分:进阶状态追踪|Part 5: Advanced State Tracking Games with lots of interaction can get very complex, very quickly and the writer's job is often as much about maintaining continuity as it is about content. @@ -3581,7 +3576,7 @@ Example: -# Part 6: International character support in identifiers +# 第 6 部分:标识符中的国际字符支持|Part 6: International character support in identifiers By default, ink has no limitations on the use of non-ASCII characters inside the story content. However, a limitation currently exsits on the characters that can be used for names of constants, variables, stictches, diverts and other named flow elements (a.k.a. *identifiers*). From 14f3986be63b2ce3966b59a168b4bede62b46ecd Mon Sep 17 00:00:00 2001 From: Ray-Lum <51054841+Ray-Lum@users.noreply.github.com> Date: Sat, 12 Oct 2024 15:56:29 +0800 Subject: [PATCH 04/22] Update WritingWithInk_Simplified-Chinese.md --- .../WritingWithInk_Simplified-Chinese.md | 164 +++++++++--------- 1 file changed, 80 insertions(+), 84 deletions(-) diff --git a/Documentation/WritingWithInk_Simplified-Chinese.md b/Documentation/WritingWithInk_Simplified-Chinese.md index d889d90e..e008e2dd 100644 --- a/Documentation/WritingWithInk_Simplified-Chinese.md +++ b/Documentation/WritingWithInk_Simplified-Chinese.md @@ -70,13 +70,13 @@ - [你可以根据你自己的需要设置多层嵌套|You can nest as many levels are you like](#你可以根据你自己的需要设置多层嵌套you-can-nest-as-many-levels-are-you-like) - [示例:用嵌套节点写的对话|Example: a conversation with nested nodes](#示例用嵌套节点写的对话example-a-conversation-with-nested-nodes) - [3) 追踪织体|Tracking a Weave](#3-追踪织体tracking-a-weave) - - [Weaves are largely unaddressed](#weaves-are-largely-unaddressed) - - [Gathers and options can be labelled](#gathers-and-options-can-be-labelled) - - [Scope](#scope) - - [Advanced: all options can be labelled](#advanced-all-options-can-be-labelled) - - [Advanced: Loops in a weave](#advanced-loops-in-a-weave) - - [Advanced: diverting to options](#advanced-diverting-to-options) - - [Advanced: Gathers directly after an option](#advanced-gathers-directly-after-an-option) + - [织体是庞大且没有地址索引的|Weaves are largely unaddressed](#织体是庞大且没有地址索引的weaves-are-largely-unaddressed) + - [收束和选项也可以打标签|Gathers and options can be labelled](#收束和选项也可以打标签gathers-and-options-can-be-labelled) + - [界限|Scope](#界限scope) + - [进阶:所有的选项都可以打标签|Advanced: all options can be labelled](#进阶所有的选项都可以打标签advanced-all-options-can-be-labelled) + - [进阶:在织体里循环|Advanced: Loops in a weave](#进阶在织体里循环advanced-loops-in-a-weave) + - [进阶:分道指向到选项|Advanced: diverting to options](#进阶分道指向到选项advanced-diverting-to-options) + - [进阶:在一个选项后直接收束|Advanced: Gathers directly after an option](#进阶在一个选项后直接收束advanced-gathers-directly-after-an-option) - [第 3 部分:变量和逻辑|Part 3: Variables and Logic](#第-3-部分变量和逻辑part-3-variables-and-logic) - [1) Global Variables](#1-global-variables) - [Defining Global Variables](#defining-global-variables) @@ -1269,146 +1269,142 @@ TODO: (向编译器传递 `-c` 的要求) ## 3) 追踪织体|Tracking a Weave -Sometimes, the weave structure is sufficient. But when it's not, we need a bit more control. +有时只使用织体这种结构就足够了。但如果不够,我们就需要更多的控制。 -### Weaves are largely unaddressed +### 织体是庞大且没有地址索引的|Weaves are largely unaddressed -By default, lines of content in a weave don't have an address or label, which means they can't be diverted to, and they can't be tested for. In the most basic weave structure, choices vary the path the player takes through the weave and what they see, but once the weave is finished those choices and that path are forgotten. +默认情况下,织体结构中的内容行都没有地址或标签,这意味着它们无法被分道到其他地方,也就无法进行测试。 +在最基本的织体结构中,玩家的选择会改变织体的路径和他们所看到的内容,但一旦织体看完了,这些选择和路径就会被遗忘。 -But should we want to remember what the player has seen, we can - we add in labels where they're needed using the `(label_name)` syntax. +不过如果我们想记住玩家看过的内容,也是可以的——我们可以使用 `(label_name)` 语法在需要的地方添加标签。 -### Gathers and options can be labelled +### 收束和选项也可以打标签|Gathers and options can be labelled -Gather points at any nested level can be labelled using brackets. +任何嵌套层的收束点都可以用括号标注。就像: - - (top) + - (top) -Once labelled, gather points can be diverted to, or tested for in conditionals, just like knots and stitches. This means you can use previous decisions to alter later outcomes inside the weave, while still keeping all the advantages of a clear, reliable forward-flow. +一旦贴上标签,收束点就可以像结点和接缝一样被分道或是测试。这意味着您可以利用之前的决定来改变织体中的后续结果,同时继续保持清晰、可靠且继续发展等织体的所有优点。 -Options can also be labelled, just like gather points, using brackets. Label brackets come before conditions in the line. +选项也可以用括号来打标签,就像收束点一样。但是标签括号需要写在每个选项的文本之前。 -These addresses can be used in conditional tests, which can be useful for creating options unlocked by other options. +这些地址可以在条件测试中使用,对于创建被其他选项解锁的选项时非常有用。 === meet_guard === - The guard frowns at you. + 警卫皱着眉头看着你。 - * (greet) [Greet him] - 'Greetings.' - * (get_out) 'Get out of my way[.'],' you tell the guard. + * (greet) [向他打招呼] + “你好啊。” + * (get_out) “让一下。”[]你和警卫说道。 - - 'Hmm,' replies the guard. + - “嗯……”警卫应了一声。 - * {greet} 'Having a nice day?' // only if you greeted him + * {greet} “今天过得怎么样?” // 当你选择了向他打招呼的时候 + “还不赖。” - * 'Hmm?'[] you reply. + * “怎么了?”[]你有些好奇。 - * {get_out} [Shove him aside] // only if you threatened him - You shove him sharply. He stares in reply, and draws his sword! - -> fight_guard // this route diverts out of the weave + * {get_out} [把他推到一边] // 当你选择了威胁他时 + 你把他粗暴地推到一边,他瞪着眼睛看着你,然后拔出了剑! + -> fight_guard // 这个路径将分道出这个织体 - - 'Mff,' the guard replies, and then offers you a paper bag. 'Toffee?' + - “哦……”警卫回应着,然后递给你一个小纸袋。“太妃糖?” +### 界限|Scope -### Scope - -Inside the same block of weave, you can simply use the label name; from outside the block you need a path, either to a different stitch within the same knot: +在同一织体块内,您可以简单地使用标签名称;而在织体块外,您需要一个路径,或者是通往同一结点的不同接缝的路径: === knot === = stitch_one - - (gatherpoint) Some content. + - (gatherpoint) 一些内容。 = stitch_two - * {stitch_one.gatherpoint} Option + * {stitch_one.gatherpoint} 选项 -or pointing into another knot: +或者指向另一个结点里: === knot_one === - (gather_one) - * {knot_two.stitch_two.gather_two} Option + * {knot_two.stitch_two.gather_two} 选项 === knot_two === = stitch_two - (gather_two) - * {knot_one.gather_one} Option - + * {knot_one.gather_one} 选项 -#### Advanced: all options can be labelled +#### 进阶:所有的选项都可以打标签|Advanced: all options can be labelled -In truth, all content in ink is a weave, even if there are no gathers in sight. That means you can label *any* option in the game with a bracket label, and then reference it using the addressing syntax. In particular, this means you can test *which* option a player took to reach a particular outcome. +事实上,Ink 里所有的内容都是织体,即使看不到任何收束。这意味着你可以用括号标注游戏中的*任何*选项,然后用寻址语法引用它。这意味着你可以测试玩家是通过*哪一个*选项得出特定结果的。 === fight_guard === - ... + …… = throw_something - * (rock) [Throw rock at guard] -> throw - * (sand) [Throw sand at guard] -> throw + * (rock) [朝警卫扔石头] -> throw + * (sand) [朝警卫扔沙子] -> throw = throw - You hurl {throw_something.rock:a rock|a handful of sand} at the guard. + 你朝警卫扔了{throw_something.rock:一块石头|一把沙子}。 +#### 进阶:在织体里循环|Advanced: Loops in a weave -#### Advanced: Loops in a weave - -Labelling allows us to create loops inside weaves. Here's a standard pattern for asking questions of an NPC. +标签可以让我们在编制织体的过程中创建循环。下面是向 NPC 提问的标准模式。 - (opts) - * 'Can I get a uniform from somewhere?'[] you ask the cheerful guard. - 'Sure. In the locker.' He grins. 'Don't think it'll fit you, though.' - * 'Tell me about the security system.' - 'It's ancient,' the guard assures you. 'Old as coal.' - * 'Are there dogs?' - 'Hundreds,' the guard answers, with a toothy grin. 'Hungry devils, too.' - // We require the player to ask at least one question - * {loop} [Enough talking] - -> done + * “我能从哪里拿一套制服吗?”[]你问那个开朗的警卫。 + “当然可以,就在那个柜子里。”他咧嘴一笑。 + * “告诉我安保系统的情况。” + “‘它’相当古老,”警卫向你保证:“就像一块煤炭。” + * “有狗么?” + “很多。”警卫咧嘴一笑回答道:“饿的跟魔鬼一样。” + // 我们需要玩家询问至少一个问题 + * {loop} [没什么想说的了] + -> done + - (loop) - // loop a few times before the guard gets bored + // 在警卫厌烦之前询问几次 { -> opts | -> opts | } - He scratches his head. - 'Well, can't stand around talking all day,' he declares. + 他挠挠头。 + “好了,咱不能一天到晚就站着说话了吧?”他说道。 - (done) - You thank the guard, and move away. - + 你谢过了警卫,然后离开了。 +#### 进阶:分道指向到选项|Advanced: diverting to options - - -#### Advanced: diverting to options - -Options can also be diverted to: but the divert goes to the output of having chosen that choice, *as though the choice had been chosen*. So the content printed will ignore square bracketed text, and if the option is once-only, it will be marked as used up. +选项也可以被分道指向:但会直接分道到该选项的输出,*就像选择了该选项一样*。因此,打印的内容将忽略方括号内的文字,如果该选项只能使用一次,它将被标记为次数用尽。 - (opts) - * [Pull a face] - You pull a face, and the soldier comes at you! -> shove + * [向警卫做鬼脸] + 你做了个鬼脸,于是警卫向你冲过来了! -> shove - * (shove) [Shove the guard aside] You shove the guard to one side, but he comes back swinging. - - * {shove} [Grapple and fight] -> fight_the_guard + * (shove) [推搡警卫]你推了一把警卫,但是他很快就摆正了重心。 + + * {shove} [跟他打架] -> fight_the_guard - - -> opts + - -> opts -produces: +输出: - 1: Pull a face - 2: Shove the guard aside + 1: 向警卫做鬼脸 + 2: 推搡警卫 > 1 - You pull a face, and the soldier comes at you! You shove the guard to one side, but he comes back swinging. + 你做了个鬼脸,于是警卫向你冲过来了!你推了一把警卫,但是他很快就摆正了重心。 - 1: Grapple and fight + 1: 跟他打架 > -#### Advanced: Gathers directly after an option +#### 进阶:在一个选项后直接收束|Advanced: Gathers directly after an option -The following is valid, and frequently useful. +以下内容不仅有效,而且经常使用。 - * "Are you quite well, Monsieur?"[] I asked. - - - (quitewell) "Quite well," he replied. - * "How did you do at the crossword, Monsieur?"[] I asked. + * “您还好么,先生?”[]我问到。 + -- (quitewell) “挺好的。”他回应道。 + * “填字游戏做得怎么样了,先生?”[]我问到。 -> quitewell - * I said nothing[] and neither did my Master. - - We fell into companionable silence once more. + * 我什么也没说[],我的老大也什么都没说。 + - 我们彼此再次陷入了沉默。 -Note the level 2 gather point directly below the first option: there's nothing to gather here, really, but it gives us a handy place to divert the second option to. +注意上方示例中的二级收束点:这里其实真没什么好收束的,但是它为我们提供了一个方便的地方来分道第二个选项。 # 第 3 部分:变量和逻辑|Part 3: Variables and Logic From 9bb221d90183c8b6e4737d7b4552aac74c539eb0 Mon Sep 17 00:00:00 2001 From: Ray-Lum <51054841+Ray-Lum@users.noreply.github.com> Date: Wed, 16 Oct 2024 18:57:00 +0800 Subject: [PATCH 05/22] Update WritingWithInk_Simplified-Chinese.md --- .../WritingWithInk_Simplified-Chinese.md | 99 +++++++++---------- 1 file changed, 48 insertions(+), 51 deletions(-) diff --git a/Documentation/WritingWithInk_Simplified-Chinese.md b/Documentation/WritingWithInk_Simplified-Chinese.md index e008e2dd..aba70f9f 100644 --- a/Documentation/WritingWithInk_Simplified-Chinese.md +++ b/Documentation/WritingWithInk_Simplified-Chinese.md @@ -78,14 +78,14 @@ - [进阶:分道指向到选项|Advanced: diverting to options](#进阶分道指向到选项advanced-diverting-to-options) - [进阶:在一个选项后直接收束|Advanced: Gathers directly after an option](#进阶在一个选项后直接收束advanced-gathers-directly-after-an-option) - [第 3 部分:变量和逻辑|Part 3: Variables and Logic](#第-3-部分变量和逻辑part-3-variables-and-logic) - - [1) Global Variables](#1-global-variables) - - [Defining Global Variables](#defining-global-variables) - - [Using Global Variables](#using-global-variables) - - [Advanced: storing diverts as variables](#advanced-storing-diverts-as-variables) - - [Advanced: Global variables are externally visible](#advanced-global-variables-are-externally-visible) - - [Printing variables](#printing-variables) - - [Evaluating strings](#evaluating-strings) - - [2) Logic](#2-logic) + - [1) 全局变量|Global Variables](#1-全局变量global-variables) + - [定义全局变量|Defining Global Variables](#定义全局变量defining-global-variables) + - [使用全局变量|Using Global Variables](#使用全局变量using-global-variables) + - [进阶:将分道存储为变量|Advanced: storing diverts as variables](#进阶将分道存储为变量advanced-storing-diverts-as-variables) + - [进阶:全局变量是对外可见的|Advanced: Global variables are externally visible](#进阶全局变量是对外可见的advanced-global-variables-are-externally-visible) + - [打印输出变量|Printing variables](#打印输出变量printing-variables) + - [叠加态字符串|Evaluating strings](#叠加态字符串evaluating-strings) + - [2) 逻辑|Logic](#2-逻辑logic) - [Mathematics](#mathematics) - [RANDOM(min, max)](#randommin-max) - [Advanced: numerical types are implicit](#advanced-numerical-types-are-implicit) @@ -97,7 +97,7 @@ - [Switch blocks](#switch-blocks) - [Example: context-relevant content](#example-context-relevant-content) - [Conditional blocks are not limited to logic](#conditional-blocks-are-not-limited-to-logic) - - [多行替文|Multiline blocks](#多行替文multiline-blocks) + - [多行代码块|Multiline blocks](#多行代码块multiline-blocks) - [Advanced: modified shuffles](#advanced-modified-shuffles) - [4) Temporary Variables](#4-temporary-variables) - [Temporary variables are for scratch calculations](#temporary-variables-are-for-scratch-calculations) @@ -930,7 +930,7 @@ And here's a bit of lifestyle advice. Note the sticky choice - the lure of the t #### 另行参见:多行替文|Sneak Preview: Multiline alternatives -**Ink** 还有另一种格式来制作替换内容块用的替文。详见 [多行替文](#多行替文multiline-blocks)。 +**Ink** 还有另一种格式来制作替换内容块用的替文。详见 [多行代码块](#多行代码块multiline-blocks)。 @@ -1408,92 +1408,89 @@ TODO: (向编译器传递 `-c` 的要求) # 第 3 部分:变量和逻辑|Part 3: Variables and Logic -So far we've made conditional text, and conditional choices, using tests based on what content the player has seen so far. +到目前为止,我们已经可以制作了条件文本和条件选择,并根据玩家目前所看到的内容进行了检测。 -**Ink** also supports variables, both temporary and global, storing numerical and content data, or even story flow commands. It is fully-featured in terms of logic, and contains a few additional structures to help keep the often complex logic of a branching story better organised. +此外,**Ink** 还支持临时和全局变量,可存储数字和内容数据,甚至故事流程命令。在逻辑方面,**Ink** 功能齐全,还包含一些额外的结构,有助于更好地组织分支故事中复杂的逻辑。 +## 1) 全局变量|Global Variables -## 1) Global Variables +这是最强大的一种变量,也可以说是对故事最有用的一种变量,是用来存储有关游戏状态的一些独特属性的变量——从主人公口袋里的钱的数量到代表主人公精神状态的值等,不一而足。 -The most powerful kind of variable, and arguably the most useful for a story, is a variable to store some unique property about the state of the game - anything from the amount of money in the protagonist's pocket, to a value representing the protagonist's state of mind. +这种变量被称为“全局变量”,因为它可以从故事中的任何地方访问——既可以设置,也可以读取。(从传统上来说,程序设计会尽量避免这种情况的发生,因为这会让程序的一部分与另一部分无关。但故事就是故事,而故事都是关于后果的:比如《赌城之旅》的故事也不会一直就在那个赌场里耗着对吧)。 -This kind of variable is called "global" because it can be accessed from anywhere in the story - both set, and read from. (Traditionally, programming tries to avoid this kind of thing, as it allows one part of a program to mess with another, unrelated part. But a story is a story, and stories are all about consequences: what happens in Vegas rarely stays there.) +### 定义全局变量|Defining Global Variables -### Defining Global Variables - -Global variables can be defined anywhere, via a `VAR` statement. They should be given an initial value, which defines what type of variable they are - integer, floating point (decimal), content, or a story address. +全局变量可以通过 `VAR` 语句在任何地方定义。全局变量应有一个初始值,该值定义了变量的类型--整数、浮点数(十进制)、内容或故事地址。 VAR knowledge_of_the_cure = false VAR players_name = "Emilia" VAR number_of_infected_people = 521 VAR current_epilogue = -> they_all_die_of_the_plague -### Using Global Variables +### 使用全局变量|Using Global Variables -We can test global variables to control options, and provide conditional text, in a similar way to what we have previously seen. +我们可以通过测试全局变量来控制选项,并提供条件文本,这与我们之前看到的方法类似。 === the_train === - The train jolted and rattled. { mood > 0:I was feeling positive enough, however, and did not mind the odd bump|It was more than I could bear}. - * { not knows_about_wager } 'But, Monsieur, why are we travelling?'[] I asked. - * { knows_about_wager} I contemplated our strange adventure[]. Would it be possible? + 火车颠簸得嘎嘎作响。{ mood > 0: 不过,我的心情还是很积极的,并不在意这零星的颠簸|我忍无可忍了}。 + * { not knows_about_wager } “先生,我们为什么要旅行?”[]我问到。 + * { knows_about_wager } 我认真思考着我们奇怪的冒险[],这件事真的可行吗? -#### Advanced: storing diverts as variables +#### 进阶:将分道存储为变量|Advanced: storing diverts as variables -A "divert" statement is actually a type of value in itself, and can be stored, altered, and diverted to. +“分道”语句本身实际上也是一种值,可以被存储、更改和转道。 VAR current_epilogue = -> everybody_dies === continue_or_quit === - Give up now, or keep trying to save your Kingdom? - * [Keep trying!] -> more_hopeless_introspection - * [Give up] -> current_epilogue - - -#### Advanced: Global variables are externally visible + 是现在就放弃,还是继续努力拯救你的王国? + * [继续努力!] -> more_hopeless_introspection + * [放弃了] -> current_epilogue -Global variables can be accessed, and altered, from the runtime as well from the story, so provide a good way to communicate between the wider game and the story. +#### 进阶:全局变量是对外可见的|Advanced: Global variables are externally visible -The **Ink** layer is often be a good place to store gameplay-variables; there's no save/load issues to consider, and the story itself can react to the current values. +全局变量可以在运行时和剧情中访问或修改,这在更广泛的程度上为游戏和剧情之间的联结提供了一种很好的方式。 +“**Ink** 层”通常是存储游戏变量的好地方;无需考虑保存和加载问题,而且故事本身也能对当前值做出反应。 +### 打印输出变量|Printing variables -### Printing variables +变量的值可以使用跟序列和条件文本类似的行内语法打印为内容的一部分: -The value of a variable can be printed as content using an inline syntax similar to sequences, and conditional text: - - VAR friendly_name_of_player = "Jackie" + VAR friendly_name_of_player = "杰基" VAR age = 23 - My name is Jean Passepartout, but my friends call me {friendly_name_of_player}. I'm {age} years old. + 我的名字是金·帕斯帕特奥特,但是我的朋友都叫我{friendly_name_of_player}。我{age}岁了。 -This can be useful in debugging. For more complex printing based on logic and variables, see the section on [functions](#5-functions). +这对调试很有用。有关基于逻辑和变量的更复杂打印输出,请参阅[函数](#5-函数functions)章节。 -### Evaluating strings +### 叠加态字符串|Evaluating strings -It might be noticed that above we refered to variables as being able to contain "content", rather than "strings". That was deliberate, because a string defined in ink can contain ink - although it will always evaluate to a string. (Yikes!) +也许你会注意到,上面我们提到的变量可以包含“内容”,而不是“字符串”。这是故意的,因为使用 Ink 定义的字符串可以包含 Ink 本身,尽管它的值总是字符串。 VAR a_colour = "" - ~ a_colour = "{~red|blue|green|yellow}" + ~ a_colour = "{~红|蓝|绿|黄}" {a_colour} -... produces one of red, blue, green or yellow. +这样就会在调用 `a_color` 的时候产生红色、蓝色、绿色或黄色中的某一种。 -Note that once a piece of content like this is evaluated, its value is "sticky". (The quantum state collapses.) So the following: +但要注意,像这样的内容一旦被观测,其值就会被“粘住”(就像薛定谔的猫被观测后就会坍缩为某个状态)下面是例子: + 歹徒打中了你,你眼冒{a_colour}和{a_colour}的星星。 The goon hits you, and sparks fly before you eyes, {a_colour} and {a_colour}. -... won't produce a very interesting effect. (If you really want this to work, use a text function to print the colour!) +……这样写就不会产生非常有趣的效果。上面的结果只会“让你眼冒同一种颜色的星星”(如果您真的希望这样做,我们也十分建议使用文本相关的功能,也就是替文来打印输出颜色!)。 -This is also why +这也就是为什么我们不推荐: VAR a_colour = "{~red|blue|green|yellow}" -is explicitly disallowed; it would be evaluated on the construction of the story, which probably isn't what you want. +因为它是全局变量,会直接影响到整个游戏。 -## 2) Logic +## 2) 逻辑|Logic Obviously, our global variables are not intended to be constants, so we need a syntax for altering them. @@ -1671,9 +1668,10 @@ You can even put options inside conditional blocks: ...but note that the lack of weave-syntax and nesting in the above example isn't accidental: to avoid confusing the various kinds of nesting at work, you aren't allowed to include gather points inside conditional blocks. -### 多行替文|Multiline blocks +### 多行代码块|Multiline blocks + +还有一类多行代码块是对上述替代系统的扩展。下面这些都是有效的,并能实现您所期望的功能: -There's one other class of multiline block, which expands on the alternatives system from above. The following are all valid and do what you might expect: // Sequence: go through the alternatives, and stick on last { stopping: @@ -2073,7 +2071,6 @@ There are two core ways to provide game hooks in the **Ink** engine. External fu # 第 4 部分:进阶流程控制|Part 4: Advanced Flow Control - ## 1) Tunnels The default structure for **Ink** stories is a "flat" tree of choices, branching and joining back together, perhaps looping, but with the story always being "at a certain place". From 9ae614cd746b6e9f8428142049c1abeeea5c4ea8 Mon Sep 17 00:00:00 2001 From: Ray-Lum <51054841+Ray-Lum@users.noreply.github.com> Date: Thu, 24 Oct 2024 16:59:22 +0800 Subject: [PATCH 06/22] Update WritingWithInk_Simplified-Chinese.md --- .../WritingWithInk_Simplified-Chinese.md | 115 ++++++++++-------- 1 file changed, 67 insertions(+), 48 deletions(-) diff --git a/Documentation/WritingWithInk_Simplified-Chinese.md b/Documentation/WritingWithInk_Simplified-Chinese.md index aba70f9f..9e873aa0 100644 --- a/Documentation/WritingWithInk_Simplified-Chinese.md +++ b/Documentation/WritingWithInk_Simplified-Chinese.md @@ -86,15 +86,15 @@ - [打印输出变量|Printing variables](#打印输出变量printing-variables) - [叠加态字符串|Evaluating strings](#叠加态字符串evaluating-strings) - [2) 逻辑|Logic](#2-逻辑logic) - - [Mathematics](#mathematics) - - [RANDOM(min, max)](#randommin-max) - - [Advanced: numerical types are implicit](#advanced-numerical-types-are-implicit) - - [Advanced: INT(), FLOOR() and FLOAT()](#advanced-int-floor-and-float) - - [String queries](#string-queries) - - [3) Conditional blocks (if/else)](#3-conditional-blocks-ifelse) - - [A simple 'if'](#a-simple-if) - - [Extended if/else if/else blocks](#extended-ifelse-ifelse-blocks) - - [Switch blocks](#switch-blocks) + - [数学|Mathematics](#数学mathematics) + - [指定范围的随机整数函数|RANDOM(min, max)](#指定范围的随机整数函数randommin-max) + - [进阶:数值类型是隐藏但存在的|Advanced: numerical types are implicit](#进阶数值类型是隐藏但存在的advanced-numerical-types-are-implicit) + - [进阶:自定义变量类型|Advanced: INT(), FLOOR() and FLOAT()](#进阶自定义变量类型advanced-int-floor-and-float) + - [字符串查询|String queries](#字符串查询string-queries) + - [3) 条件代码块(如果,否则)|Conditional blocks (if/else)](#3-条件代码块如果否则conditional-blocks-ifelse) + - [一个简单的“如果”|A simple 'if'](#一个简单的如果a-simple-if) + - [扩展判断条件代码块(如果、或者、否则)|Extended if/else if/else blocks](#扩展判断条件代码块如果或者否则extended-ifelse-ifelse-blocks) + - [开关代码块|Switch blocks](#开关代码块switch-blocks) - [Example: context-relevant content](#example-context-relevant-content) - [Conditional blocks are not limited to logic](#conditional-blocks-are-not-limited-to-logic) - [多行代码块|Multiline blocks](#多行代码块multiline-blocks) @@ -1492,38 +1492,35 @@ TODO: (向编译器传递 `-c` 的要求) ## 2) 逻辑|Logic -Obviously, our global variables are not intended to be constants, so we need a syntax for altering them. +显然,我们的全局变量并不打算成为常量,因此我们需要一种语法来更改它们。 -Since by default, any text in an **Ink** script is printed out directly to the screen, we use a markup symbol to indicate that a line of content is intended meant to be doing some numerical work, we use the `~` mark. - -The following statements all assign values to variables: +由于默认情况下,**Ink** 脚本中的任何文本都会直接打印输出到屏幕上,因此我们使用一个标记符号来表示某一行内容的目的是进行一些数字运算,我们使用 `~` 标记。 +以下语句都可以为变量赋值: === set_some_variables === ~ knows_about_wager = true ~ x = (x * x) - (y * y) + c ~ y = 2 * x * y -and the following will test conditions: +检测条件可以这样写: { x == 1.2 } { x / 2 > 4 } { y - 1 <= x * x } -### Mathematics - -**Ink** supports the four basic mathematical operations (`+`, `-`, `*` and `/`), as well as `%` (or `mod`), which returns the remainder after integer division. There's also POW for to-the-power-of: - - {POW(3, 2)} is 9. - {POW(16, 0.5)} is 4. +### 数学|Mathematics +**Ink**支持四种基本数学运算(`+`、`-`、`*` 和 `/`),以及返回整除后余数的 `%`(或 `mod`)。此外还有 POW 可以来表示幂的运算: -If more complex operations are required, one can write functions (using recursion if necessary), or call out to external, game-code functions (for anything more advanced). + {POW(3, 2)} 的结果是 9. + {POW(16, 0.5)} 的结果是 4. +如果需要进行更复杂的操作,可以编写函数(必要时可以使用递归),或调用外部游戏代码函数(以进行更高级的操作)。 -#### RANDOM(min, max) +#### 指定范围的随机整数函数|RANDOM(min, max) -Ink can generate random integers if required using the RANDOM function. RANDOM is authored to be like a dice (yes, pendants, we said *a dice*), so the min and max values are both inclusive. +如果需要,墨水可以使用 RANDOM 函数生成随机整数。RANDOM 就像一个骰子(Shai子、Tou子,无所谓你知道是什么就行。🎲),因此最小值和最大值都是包含在内的。 ~ temp dice_roll = RANDOM(1, 6) @@ -1531,55 +1528,69 @@ Ink can generate random integers if required using the RANDOM function. RANDOM i ~ temp number_of_heads_the_serpent_has = RANDOM(3, 8) -The random number generator can be seeded for testing purposes, see the section of Game Queries and Functions section above. +可为随机数生成器添加种子以进行测试,请参阅上文的“游戏查询和功能”部分。 + +译者注:这个随机整数函数语法必定是 + + ~ temp <变量名称> = RANDOM(min,max) + +那个 `temp` 改不成别的。 -#### Advanced: numerical types are implicit +#### 进阶:数值类型是隐藏但存在的|Advanced: numerical types are implicit -Results of operations - in particular, for division - are typed based on the type of the input. So integer division returns integer, but floating point division returns floating point results. +运算结果,尤其是除法运算的结果,是根据输入的类型进行类型化的。因此,整数除法返回整数结果,而浮点除法返回浮点结果。 ~ x = 2 / 3 ~ y = 7 / 3 ~ z = 1.2 / 0.5 -assigns `x` to be 0, `y` to be 2 and `z` to be 2.4. +这会使得 `x` 为 0,`y` 为 2,`z` 为 2.4。 -#### Advanced: INT(), FLOOR() and FLOAT() +#### 进阶:自定义变量类型|Advanced: INT(), FLOOR() and FLOAT() -In cases where you don't want implicit types, or you want to round off a variable, you can cast it directly. +如果不想使用上面那种自动但是隐藏的类型,或想对变量进行取舍,则可以直接将其转换为指定类型。 - {INT(3.2)} is 3. - {FLOOR(4.8)} is 4. - {INT(-4.8)} is -4. - {FLOOR(-4.8)} is -5. +| 代码 | 类型 | 备注 | +| - | - | - | +| INT() | 整数 | 向零取整,正数取整后会小于等于原来的数,负数反之 | +| FLOOR() | 整数 | 向下取整,取整后的数小于或等于原来的数 | +| FLOAT() | 浮点数 | 双精度二进制浮点数,说人话就是带有小数的数据 | - {FLOAT(4)} is, um, still 4. + {INT(3.2)} 是 3. + {FLOOR(4.8)} 是 4. + {INT(-4.8)} 是 -4. + {FLOOR(-4.8)} 是 -5. + {FLOAT(4)} 嗯……还是 4. +译者注:截止至翻译更新时还没有向上取整。 -### String queries +### 字符串查询|String queries -Oddly for a text-engine, **Ink** doesn't have much in the way of string-handling: it's assumed that any string conversion you need to do will be handled by the game code (and perhaps by external functions.) But we support three basic queries - equality, inequality, and substring (which we call ? for reasons that will become clear in a later chapter). +奇怪的是,作为一款文本引擎,**Ink** 却并没有太多字符串处理功能:因为我们假定任何需要进行的字符串转换的都将由游戏代码(或许还有外部函数)来处理。 但我们支持三种基本查询:相等、不相等和子字符串(我们用 `?` 来查询,原因会在稍后的章节中阐明)。 -The following all return true: +以下的每行内容都会返回“真”: { "Yes, please." == "Yes, please." } { "No, thank you." != "Yes, please." } { "Yes, please" ? "ease" } -## 3) Conditional blocks (if/else) +## 3) 条件代码块(如果,否则)|Conditional blocks (if/else) -We've seen conditionals used to control options and story content; **Ink** also provides an equivalent of the normal if/else-if/else structure. +前面我们已经看到条件代码块可以用于控制选项和故事内容;现在介绍 **Ink** 提供的与普通 if/else-if/else 结构相当的结构。 -### A simple 'if' +### 一个简单的“如果”|A simple 'if' -The if syntax takes its cue from the other conditionals used so far, with the `{`...`}` syntax indicating that something is being tested. +if 语法查询从开始到当前所产生的所有文本、选项还有结果。用两个花括号 `{`……`}` 括起来的内容为要判断的内容。 { x > 0: ~ y = x - 1 } -Else conditions can be provided: +译者注:上面这个翻译成自然语言是:如果 x > 0,就运算 y = x - 1 + +然后,可以添加“否则”(else),并提供其他条件: { x > 0: ~ y = x - 1 @@ -1587,9 +1598,11 @@ Else conditions can be provided: ~ y = x + 1 } -### Extended if/else if/else blocks +译者注:这个翻译成自然语言是:如果 x > 0,就运算 y = x - 1。否则运算 y = x + 1。`else` 前面的 `-` 是必要的。 + +### 扩展判断条件代码块(如果、或者、否则)|Extended if/else if/else blocks -The above syntax is actually a specific case of a more general structure, something like a "switch" statement of another language: +上述语法实际上是一种更通用结构的特殊情况,类似于其他语言的 "switch "语句。下面例子中单独的 `-` 开头意味着新的 `if` 判断,作为一个简单的判断来说,只是把判断条件写到了下一行: { - x > 0: @@ -1598,7 +1611,9 @@ The above syntax is actually a specific case of a more general structure, someth ~ y = x + 1 } -And using this form we can include 'else-if' conditions: +译者注:翻译为自然语言:如果 x 大于 0,那么运算 y = x - 1。否则运算 y = x + 1 + +使用这种结构,我们还可以实现“或者 (else-if)”: { - x == 0: @@ -1609,11 +1624,15 @@ And using this form we can include 'else-if' conditions: ~ y = x + 1 } -(Note, as with everything else, the white-space is purely for readability and has no syntactic meaning.) +(请注意:和其他地方一样,空格纯粹是为了便于阅读,没有任何语法意义。) + +译者注:翻译为自然语言:如果 x 等于 0,那么 y 等于 0;或者如果 x 大于 0,运算 y = x - 1。否则,运算 y = x + 1 + +译者再注:作为条件语句,if(如果)肯定是要有的;然后 if-else(或者)是可以没有或者有多个的;else(否则)可以没有,但是有的话只能有一个。“或者”这个用法是有先后顺序的,以写在前面的为先。 -### Switch blocks +### 开关代码块|Switch blocks -And there's also an actual switch statement: +还有一个开关代码块示例: { x: - 0: zero From f898a7b25bef9d797e0a83842d262b55318bdd04 Mon Sep 17 00:00:00 2001 From: Ray-Lum <51054841+Ray-Lum@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:45:01 +0800 Subject: [PATCH 07/22] Update WritingWithInk_Simplified-Chinese.md --- .../WritingWithInk_Simplified-Chinese.md | 110 +++++++++--------- 1 file changed, 56 insertions(+), 54 deletions(-) diff --git a/Documentation/WritingWithInk_Simplified-Chinese.md b/Documentation/WritingWithInk_Simplified-Chinese.md index 9e873aa0..29561a3a 100644 --- a/Documentation/WritingWithInk_Simplified-Chinese.md +++ b/Documentation/WritingWithInk_Simplified-Chinese.md @@ -95,11 +95,11 @@ - [一个简单的“如果”|A simple 'if'](#一个简单的如果a-simple-if) - [扩展判断条件代码块(如果、或者、否则)|Extended if/else if/else blocks](#扩展判断条件代码块如果或者否则extended-ifelse-ifelse-blocks) - [开关代码块|Switch blocks](#开关代码块switch-blocks) - - [Example: context-relevant content](#example-context-relevant-content) - - [Conditional blocks are not limited to logic](#conditional-blocks-are-not-limited-to-logic) + - [示例:与背景相关的内容|Example: context-relevant content](#示例与背景相关的内容example-context-relevant-content) + - [条件块代码块不仅限于逻辑|Conditional blocks are not limited to logic](#条件块代码块不仅限于逻辑conditional-blocks-are-not-limited-to-logic) - [多行代码块|Multiline blocks](#多行代码块multiline-blocks) - - [Advanced: modified shuffles](#advanced-modified-shuffles) - - [4) Temporary Variables](#4-temporary-variables) + - [进阶:修改洗牌随机|Advanced: modified shuffles](#进阶修改洗牌随机advanced-modified-shuffles) + - [4) 临时变量|Temporary Variables](#4-临时变量temporary-variables) - [Temporary variables are for scratch calculations](#temporary-variables-are-for-scratch-calculations) - [Knots and stitches can take parameters](#knots-and-stitches-can-take-parameters) - [Example: a recursive knot definition](#example-a-recursive-knot-definition) @@ -1635,15 +1635,15 @@ if 语法查询从开始到当前所产生的所有文本、选项还有结果 还有一个开关代码块示例: { x: - - 0: zero - - 1: one - - 2: two - - else: lots + - 0: 零 + - 1: 一 + - 2: 二 + - else: 许多 } -#### Example: context-relevant content +#### 示例:与背景相关的内容|Example: context-relevant content -Note these tests don't have to be variable-based and can use read-counts, just as other conditionals can, and the following construction is quite frequent, as a way of saying "do some content which is relevant to the current game state": +请注意,这些测试并不一定要基于变量,也可以使用阅读次数,就像其他条件一样,下面的结构也很常见,是“做一些与当前游戏状态相关的内容”的一种表达方式: === dream === { @@ -1660,90 +1660,92 @@ Note these tests don't have to be variable-based and can use read-counts, just a -> dream_about_marmalade } -The syntax has the advantage of being easy to extend, and prioritise. +这种语法的优点是易于扩展和方便确定优先级。 +译者注:`++` 的意思是 +1,`--` 的意思是 -1。 +### 条件块代码块不仅限于逻辑|Conditional blocks are not limited to logic -### Conditional blocks are not limited to logic +条件块代码块同样可用于控制故事内容和逻辑: -Conditional blocks can be used to control story content as well as logic: - - I stared at Monsieur Fogg. + 我盯着福格先生。 { know_about_wager: - <> "But surely you are not serious?" I demanded. + <> "但你不是认真的吧?" 我问到。 - else: - <> "But there must be a reason for this trip," I observed. + <> "但这次旅行一定是有原因的,"我确信。 } - He said nothing in reply, merely considering his newspaper with as much thoroughness as entomologist considering his latest pinned addition. +他什么也没有回答,只是像个在研究新品种的昆虫学家一样,死死地盯着他的报纸。 -You can even put options inside conditional blocks: +你甚至可以把选项放在条件代码块中: { door_open: - * I strode out of the compartment[] and I fancied I heard my master quietly tutting to himself. -> go_outside + * 我大步走出车厢[],我仿佛听到老大在悄悄地自言自语。 -> go_outside - else: - * I asked permission to leave[] and Monsieur Fogg looked surprised. -> open_door - * I stood and went to open the door[]. Monsieur Fogg seemed untroubled by this small rebellion. -> open_door + * 我请求离开[],福格先生一脸惊讶。 -> open_door + * 我站起来去开门[]。福格先生似乎并没有被这小小的叛逆举动所困扰。 -> open_door } -...but note that the lack of weave-syntax and nesting in the above example isn't accidental: to avoid confusing the various kinds of nesting at work, you aren't allowed to include gather points inside conditional blocks. +……但请注意,上述示例中缺少织体语法和嵌套并不是偶然的:这是为了避免混淆各种嵌套。所以无法在条件块中包含收束点。 ### 多行代码块|Multiline blocks -还有一类多行代码块是对上述替代系统的扩展。下面这些都是有效的,并能实现您所期望的功能: - +还有一类多行代码块是对上述替文系统的扩展。下面这些都是有效的,并能实现您所期望的功能: - // Sequence: go through the alternatives, and stick on last + // 序列:按顺序替换后备选项,最后确定 { stopping: - - I entered the casino. - - I entered the casino again. - - Once more, I went inside. + - 我进入了赌场 + - 我又进入了赌场。 + - 再一次,我进来了。 } - // Shuffle: show one at random - At the table, I drew a card. <> + // 洗牌随机:随机抽取一个来显示,抽完所有结果后重抽 + 在桌子上,我抽了一张牌。<> { shuffle: - - Ace of Hearts. - - King of Spades. - - 2 of Diamonds. - 'You lose this time!' crowed the croupier. + - 红桃 A + - 黑桃 K + - 方片 2 + “你这把不走运啊!”荷官嚷嚷着。 } - // Cycle: show each in turn, and then cycle + // 循环:挨个显示,然后再重头 { cycle: - - I held my breath. - - I waited impatiently. - - I paused. + - 我屏住呼吸。 + - 我不耐烦地等待着。 + - 我停顿了一下。 } - // Once: show each, once, in turn, until all have been shown + // 一次性:每个结果在一回游戏里只会抽到一次,抽完了就没有了。 { once: - - Would my luck hold? - - Could I win the hand? + - 我的运气能保持住吗? + - 我能赢吗? } -#### Advanced: modified shuffles +译者注:上面说到的这些方案写法上来说像是某种判定条件,但实际上您可认为是一种“叫对名字就可以放出来的咒语”。只要按照上面的格式正确拼写,就可以使用了。 + +#### 进阶:修改洗牌随机|Advanced: modified shuffles -The shuffle block above is really a "shuffled cycle"; in that it'll shuffle the content, play through it, then reshuffle and go again. +上面提到的洗牌随机实际上是一个“洗牌随机并循环”;即它会将内容洗牌随机后输出一遍。然后再把所有选项洗牌随机后,再输出一遍。 -There are two other versions of shuffle: +所以还有两个经过修改的洗牌随机: -`shuffle once` which will shuffle the content, play through it, and then do nothing. +`shuffle once` 这个可以将内容洗牌后输出。但是输出完了之后就不会再收回并重新洗牌,所以用完就没有内容了。 { shuffle once: - - The sun was hot. - - It was a hot day. + - 太阳真大。 + - 好热的一天。 } -`shuffle stopping` will shuffle all the content (except the last entry), and once its been played, it'll stick on the last entry. +`shuffle stopping` 将对所有内容进行洗牌(最后一条除外),一旦输出完毕,就会停留在最后一条上。 + +译者注:最后一条不参与洗牌。所以并不是最后一条输出什么就停留在什么上,而一定是写在最后的那一条被固定。 { shuffle stopping: - - A silver BMW roars past. - - A bright yellow Mustang takes the turn. - - There are like, cars, here. + - 一辆银色宝马轰鸣而过。 + - 一辆亮黄色的野马在转弯 + - 这里有很多车 } - -## 4) Temporary Variables +## 4) 临时变量|Temporary Variables ### Temporary variables are for scratch calculations From 400d98d9c6a6d12fd1409881839240600e63c9d4 Mon Sep 17 00:00:00 2001 From: Ray-Lum <51054841+Ray-Lum@users.noreply.github.com> Date: Thu, 31 Oct 2024 11:59:04 +0800 Subject: [PATCH 08/22] Update WritingWithInk_Simplified-Chinese.md --- .../WritingWithInk_Simplified-Chinese.md | 95 +++++++++++-------- 1 file changed, 53 insertions(+), 42 deletions(-) diff --git a/Documentation/WritingWithInk_Simplified-Chinese.md b/Documentation/WritingWithInk_Simplified-Chinese.md index 29561a3a..8ad40f6a 100644 --- a/Documentation/WritingWithInk_Simplified-Chinese.md +++ b/Documentation/WritingWithInk_Simplified-Chinese.md @@ -100,10 +100,10 @@ - [多行代码块|Multiline blocks](#多行代码块multiline-blocks) - [进阶:修改洗牌随机|Advanced: modified shuffles](#进阶修改洗牌随机advanced-modified-shuffles) - [4) 临时变量|Temporary Variables](#4-临时变量temporary-variables) - - [Temporary variables are for scratch calculations](#temporary-variables-are-for-scratch-calculations) - - [Knots and stitches can take parameters](#knots-and-stitches-can-take-parameters) - - [Example: a recursive knot definition](#example-a-recursive-knot-definition) - - [Advanced: sending divert targets as parameters](#advanced-sending-divert-targets-as-parameters) + - [临时变量用于临时计算|Temporary variables are for scratch calculations](#临时变量用于临时计算temporary-variables-are-for-scratch-calculations) + - [结点和接缝可接收参数|Knots and stitches can take parameters](#结点和接缝可接收参数knots-and-stitches-can-take-parameters) + - [示例:定义一个递归结点|Example: a recursive knot definition](#示例定义一个递归结点example-a-recursive-knot-definition) + - [进阶:将分道目标作为参数来传递|Advanced: sending divert targets as parameters](#进阶将分道目标作为参数来传递advanced-sending-divert-targets-as-parameters) - [5) 函数|Functions](#5-函数functions) - [Defining and calling functions](#defining-and-calling-functions) - [Functions don't have to return anything](#functions-dont-have-to-return-anything) @@ -188,7 +188,7 @@ 翻译时的 Ink 版本:1.2.0 -翻译最后更新时间:2024年10月2日 +翻译最后更新时间:2024年10月31日 翻译仍在更新。 @@ -784,7 +784,6 @@ * {seen_clue > 3} [直接逮捕杰斐逊先生] - #### 进阶:更多逻辑|Advanced: more logic **Ink** 支持的逻辑和条件性远不止这些,请参阅[变量和逻辑](#第-3-部分变量和逻辑part-3-variables-and-logic)部分。 @@ -1747,9 +1746,9 @@ if 语法查询从开始到当前所产生的所有文本、选项还有结果 ## 4) 临时变量|Temporary Variables -### Temporary variables are for scratch calculations +### 临时变量用于临时计算|Temporary variables are for scratch calculations -Sometimes, a global variable is unwieldy. **Ink** provides temporary variables for quick calculations of things. +有时,全局变量会显得笨重。**Ink** 提供了临时变量,方便进行一些快速计算。 === near_north_pole === ~ temp number_of_warm_things = 0 @@ -1763,35 +1762,34 @@ Sometimes, a global variable is unwieldy. **Ink** provides temporary variables f ~ number_of_warm_things++ } { number_of_warm_things > 2: - Despite the snow, I felt incorrigibly snug. + 尽管下着雪,但我却感到无比温暖。 - else: - That night I was colder than I have ever been. + 那一晚是我人生中最冷的一晚。 } -The value in a temporary variable is thrown away after the story leaves the stitch in which it was defined. +临时变量的值在故事离开定义它的接缝 (Stitch) 后会被丢弃。 -### Knots and stitches can take parameters +### 结点和接缝可接收参数|Knots and stitches can take parameters -A particularly useful form of temporary variable is a parameter. Any knot or stitch can be given a value as a parameter. +临时变量的一种特别有用形式是参数。任何结点或接缝都可以接收一个参数值。 - * [Accuse Hasting] - -> accuse("Hastings") - * [Accuse Mrs Black] - -> accuse("Claudia") - * [Accuse myself] - -> accuse("myself") + * [指控海斯廷斯] + -> accuse("海斯廷斯") + * [指控布莱克夫人] + -> accuse("莱克夫人") + * [指控我自己] + -> accuse("自己") === accuse(who) === - "I accuse {who}!" Poirot declared. - "Really?" Japp replied. "{who == "myself":You did it?|{who}?}" - "And why not?" Poirot shot back. - + “我指控{who}!” 波洛宣布道。 + “真的吗?” 贾普问道。 “{who == "myself":是你做的?|你{who}?}” + “怎么会不是呢?” 波洛反问道。 -... and you'll need to use parameters if you want to pass a temporary value from one stitch to another! +……如果想从一个接缝传递临时值到另一个接缝时,就需要使用参数! -#### Example: a recursive knot definition +#### 示例:定义一个递归结点|Example: a recursive knot definition -Temporary variables are safe to use in recursion (unlike globals), so the following will work. +在递归中使用临时变量是安全的(与全局变量不同),因此以下代码将正常运行。 -> add_one_to_one_hundred(0, 1) @@ -1804,40 +1802,51 @@ Temporary variables are safe to use in recursion (unlike globals), so the follow } === finished(total) === - "The result is {total}!" you announce. - Gauss stares at you in horror. + “结果是 {total}!” 你宣布。 + 高斯惊恐地盯着你。 -> END +(事实上,因为这种定义足够有用,所以 **Ink** 提供了一种特殊的结点类型,称为“函数 (Function)”,对它进行一些限制,就可以返回一个值。详见函数章节。) -(In fact, this kind of definition is useful enough that **Ink** provides a special kind of knot, called, imaginatively enough, a `function`, which comes with certain restrictions and can return a value. See the section below.) - +#### 进阶:将分道目标作为参数来传递|Advanced: sending divert targets as parameters -#### Advanced: sending divert targets as parameters - -Knot/stitch addresses are a type of value, indicated by a `->` character, and can be stored and passed around. The following is therefore legal, and often useful: +结点和接缝的地址是一种值,用 `->` 字符表示,可以被存储和传递。因此以下代码是合规的,常用且非常有用: === sleeping_in_hut === - You lie down and close your eyes. + 你躺下并闭上了眼睛。 -> generic_sleep (-> waking_in_the_hut) - === generic_sleep (-> waking) - You sleep perchance to dream etc. etc. + === generic_sleep (-> waking) + 你睡着了,也许会做梦等等等等。 -> waking === waking_in_the_hut - You get back to your feet, ready to continue your journey. + 你站起身来,准备继续你的旅程。 -...but note the `->` in the `generic_sleep` definition: that's the one case in **Ink** where a parameter needs to be typed: because it's too easy to otherwise accidentally do the following: +ChatGPT 解析: - === sleeping_in_hut === - You lie down and close your eyes. - -> generic_sleep (waking_in_the_hut) +这段 Ink 代码的运行方式如下: -... which sends the read count of `waking_in_the_hut` into the sleeping knot, and then attempts to divert to it. +1. 进入 sleeping_in_hut: + * 读者来到 sleeping_in_hut 结点,描述告诉他们:“你躺下并闭上了眼睛。” + * 然后,代码使用 -> generic_sleep (-> waking_in_the_hut) 将控制权转到 generic_sleep 结点,同时将 waking_in_the_hut 这个跳转目标作为参数传递给 generic_sleep。 +2. 进入 generic_sleep 并使用参数: + * 进入 generic_sleep 后,读者看到“你睡着了,也许会做梦等等等等。”这部分描述。 + * 此外,generic_sleep 中 -> waking 的跳转实际上会指向传入的参数 waking_in_the_hut,即在 generic_sleep 结点完成后,将控制权转移到 waking_in_the_hut。 +3. 进入 waking_in_the_hut: + * 最后,代码跳转到 waking_in_the_hut,这里的描述告诉读者:“你站起身来,准备继续你的旅程。”这完成了这段代码的流程。 +总结:这种结构的目的是让 generic_sleep 结点可以根据传入的参数跳转到不同的“醒来”位置,使其能够在不同场景中复用,增强代码的灵活性。 +译者注:这段说人话的意思就是,临时使用上方结点给出的分道参数替换下方的分道来做到临时接入不同的结点。 +……请注意 `generic_sleep` 定义中的 `->`:这是 **Ink** 中唯一一个需要将参数类型化的情况,因为否则很容易犯如下错误: + === sleeping_in_hut === + 你躺下并闭上了眼睛。 + -> generic_sleep (waking_in_the_hut) + +……这将会让 waking_in_the_hut 的读取计数传递到 sleeping 结点,然后试图分道跳转到它。 ## 5) 函数|Functions @@ -2090,6 +2099,7 @@ Constants are simply a way to allow you to give story states easy-to-understand There are two core ways to provide game hooks in the **Ink** engine. External function declarations in ink allow you to directly call C# functions in the game, and variable observers are callbacks that are fired in the game when ink variables are modified. Both of these are described in [Running your ink](RunningYourInk.md). + # 第 4 部分:进阶流程控制|Part 4: Advanced Flow Control ## 1) Tunnels @@ -2448,6 +2458,7 @@ A game which uses ink as a script rather than a literal output might often gener ... ``` + # 第 5 部分:进阶状态追踪|Part 5: Advanced State Tracking Games with lots of interaction can get very complex, very quickly and the writer's job is often as much about maintaining continuity as it is about content. From 471ef0df548b842bf2038ea761a1995b58560498 Mon Sep 17 00:00:00 2001 From: Ray-Lum <51054841+Ander-Index@users.noreply.github.com> Date: Wed, 20 Nov 2024 15:09:38 +0800 Subject: [PATCH 09/22] Update WritingWithInk_Simplified-Chinese.md --- .../WritingWithInk_Simplified-Chinese.md | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/Documentation/WritingWithInk_Simplified-Chinese.md b/Documentation/WritingWithInk_Simplified-Chinese.md index 8ad40f6a..d9700df7 100644 --- a/Documentation/WritingWithInk_Simplified-Chinese.md +++ b/Documentation/WritingWithInk_Simplified-Chinese.md @@ -1,9 +1,28 @@ # 使用 Ink 进行写作 + +## 声明 + +简体中文教程是从英文原文翻译而来。可能会存在版本滞后性,故入与英文版有出入,请与英文版为准。 + +部分不重要的段落由 ChatGPT 或 DeepL 组合翻译。 + +重要的部分完全由人工翻译。 + +译者:王洛木 (Nomo_Wang@outlook.com),如果您有关于翻译的问题要提,且不想要占用 Discussion 资源,可以发邮件给译者。 + +翻译时的 Ink 版本:1.2.0 + +翻译最后更新时间:2024年10月31日 + +翻译仍在更新。 + +## 内容目录
内容目录 - [使用 Ink 进行写作](#使用-ink-进行写作) - [声明](#声明) + - [内容目录](#内容目录) - [介绍](#介绍) - [第 1 部分:基础|Part One: The Basics](#第-1-部分基础part-one-the-basics) - [1) 内容|Content](#1-内容content) @@ -176,21 +195,6 @@
-## 声明 - -简体中文教程是从英文原文翻译而来。可能会存在版本滞后性,故入与英文版有出入,请与英文版为准。 - -部分不重要的段落由 ChatGPT 或 DeepL 组合翻译。 - -重要的部分完全由人工翻译。 - -译者:王洛木 (Nomo_Wang@outlook.com),如果您有关于翻译的问题要提,且不想要占用 Discussion 资源,可以发邮件给译者。 - -翻译时的 Ink 版本:1.2.0 - -翻译最后更新时间:2024年10月31日 - -翻译仍在更新。 ## 介绍 From ba46891fab684e363c84840ef3e47e4cbae39f15 Mon Sep 17 00:00:00 2001 From: Ray-Lum Date: Wed, 20 Nov 2024 15:55:10 +0800 Subject: [PATCH 10/22] Update WritingWithInk_Simplified-Chinese.md --- .../WritingWithInk_Simplified-Chinese.md | 68 ++++++++++--------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/Documentation/WritingWithInk_Simplified-Chinese.md b/Documentation/WritingWithInk_Simplified-Chinese.md index d9700df7..9307279e 100644 --- a/Documentation/WritingWithInk_Simplified-Chinese.md +++ b/Documentation/WritingWithInk_Simplified-Chinese.md @@ -124,9 +124,9 @@ - [示例:定义一个递归结点|Example: a recursive knot definition](#示例定义一个递归结点example-a-recursive-knot-definition) - [进阶:将分道目标作为参数来传递|Advanced: sending divert targets as parameters](#进阶将分道目标作为参数来传递advanced-sending-divert-targets-as-parameters) - [5) 函数|Functions](#5-函数functions) - - [Defining and calling functions](#defining-and-calling-functions) - - [Functions don't have to return anything](#functions-dont-have-to-return-anything) - - [Functions can be called inline](#functions-can-be-called-inline) + - [定义和调用函数|Defining and calling functions](#定义和调用函数defining-and-calling-functions) + - [函数不一定非要有个返回值|Functions don't have to return anything](#函数不一定非要有个返回值functions-dont-have-to-return-anything) + - [函数可以直接在同一行内被调用|Functions can be called inline](#函数可以直接在同一行内被调用functions-can-be-called-inline) - [Examples](#examples) - [Example: turning numbers into words](#example-turning-numbers-into-words) - [Parameters can be passed by reference](#parameters-can-be-passed-by-reference) @@ -134,7 +134,7 @@ - [Global Constants](#global-constants) - [7) Advanced: Game-side logic](#7-advanced-game-side-logic) - [第 4 部分:进阶流程控制|Part 4: Advanced Flow Control](#第-4-部分进阶流程控制part-4-advanced-flow-control) - - [1) Tunnels](#1-tunnels) + - [1) 隧道|Tunnels](#1-隧道tunnels) - [Tunnels run sub-stories](#tunnels-run-sub-stories) - [Advanced: Tunnels can return elsewhere](#advanced-tunnels-can-return-elsewhere) - [Advanced: Tunnels use a call-stack](#advanced-tunnels-use-a-call-stack) @@ -1854,25 +1854,25 @@ ChatGPT 解析: ## 5) 函数|Functions -The use of parameters on knots means they are almost functions in the usual sense, but they lack one key concept - that of the call stack, and the use of return values. +在结点上使用参数会使他们几乎等同于通常意义下的函数,但是它们缺少一个关键概念——调用栈和返回值。 -**Ink** includes functions: they are knots, with the following limitations and features: +**Ink** 包含了这样的功能:它们是结点,但是具有以下的限制和特性: -A function: -- cannot contain stitches -- cannot use diverts or offer choices -- can call other functions -- can include printed content -- can return a value of any type -- can recurse safely +一个函数: +- 不能包含接缝 (Stitchs) +- 不能使用分道或提供选择 +- 可以调用其他函数 +- 可以包含已打印输出的内容 +- 可以返回任何类型的值 +- 可以安全地递归 -(Some of these may seem quite limiting, but for more story-oriented call-stack-style features, see the section on [Tunnels](#1-tunnels).) +(这些限制看起来或许有些严格,所以如果需要更多面向故事的调用栈风格的功能,请查看[隧道](#1-隧道tunnels)部分。) -Return values are provided via the `~ return` statement. +返回值通过 `~ return` 语句提供。 -### Defining and calling functions +### 定义和调用函数|Defining and calling functions -To define a function, simply declare a knot to be one: +要定义一个函数,只需要将一个结点声明为函数即可: === function say_yes_to_everything === ~ return true @@ -1880,20 +1880,22 @@ To define a function, simply declare a knot to be one: === function lerp(a, b, k) === ~ return ((b - a) * k) + a -Functions are called by name, and with brackets, even if they have no parameters: +译者注:就像上面这样,以 "function" 开头并空一格写上函数名就可以了。 + +函数通过名称和括号调用,哪怕它们并没有参数: ~ x = lerp(2, 8, 0.3) * {say_yes_to_everything()} 'Yes.' -As in any other language, a function, once done, returns the flow to wherever it was called from - and despite not being allowed to divert the flow, functions can still call other functions. +与其他编程语言蕾丝,一个函数再一次执行完毕后,要将流程返回到调用它的位置——尽管函数不能进行分道,但是仍然函数仍然可以调用其它函数。 === function say_no_to_nothing === ~ return say_yes_to_everything() -### Functions don't have to return anything +### 函数不一定非要有个返回值|Functions don't have to return anything -A function does not need to have a return value, and can simply do something that is worth packaging up: +一个函数不一定需要一个返回值,可以让函数仅仅只是执行一些操作: === function harm(x) === { stamina < x: @@ -1902,31 +1904,31 @@ A function does not need to have a return value, and can simply do something tha ~ stamina = stamina - x } -...though remember a function cannot divert, so while the above prevents a negative Stamina value, it won't kill a player who hits zero. +……要记得函数是不能进行分道的,所以上面这些代码虽然可以防止耐力值 (Stamina) 变为负数,但是不会让耐力归零的玩家死亡。 -### Functions can be called inline +### 函数可以直接在同一行内被调用|Functions can be called inline -Functions can be called on `~` content lines, but can also be called during a piece of content. In this context, the return value, if there is one, is printed (as well as anything else the function wants to print.) If there is no return value, nothing is printed. +函数不仅可以在 `~` 行内调用,还可以在内容中直接调用。在这种情况下,如果函数有返回值,那么这个返回值就回被打印输出(当然也有可能输出其他内容。)如果没有任何返回值,那么就不会打印输出任何内容。 -Content is, by default, 'glued in', so the following: +默认情况下,内容是“胶合”在一起的,所以以下代码: - Monsieur Fogg was looking {describe_health(health)}. + 福格先生看起来{describe_health(health)}。 === function describe_health(x) === { - x == 100: - ~ return "spritely" + ~ return "轻松愉快" - x > 75: - ~ return "chipper" + ~ return "略显疲惫" - x > 45: - ~ return "somewhat flagging" + ~ return "有些颓丧" - else: - ~ return "despondent" + ~ return "神情恍惚" } -produces: +会输出: - Monsieur Fogg was looking despondent. + 福格先生看起来精神恍惚。 #### Examples @@ -2106,7 +2108,7 @@ There are two core ways to provide game hooks in the **Ink** engine. External fu # 第 4 部分:进阶流程控制|Part 4: Advanced Flow Control -## 1) Tunnels +## 1) 隧道|Tunnels The default structure for **Ink** stories is a "flat" tree of choices, branching and joining back together, perhaps looping, but with the story always being "at a certain place". From cfb9f7b0559b245730924b49a472fa9817304a96 Mon Sep 17 00:00:00 2001 From: Ander-Index Date: Wed, 11 Dec 2024 15:40:09 +0800 Subject: [PATCH 11/22] Update WritingWithInk_Simplified-Chinese.md --- .../WritingWithInk_Simplified-Chinese.md | 112 +++++++++--------- 1 file changed, 53 insertions(+), 59 deletions(-) diff --git a/Documentation/WritingWithInk_Simplified-Chinese.md b/Documentation/WritingWithInk_Simplified-Chinese.md index 9307279e..98f104d7 100644 --- a/Documentation/WritingWithInk_Simplified-Chinese.md +++ b/Documentation/WritingWithInk_Simplified-Chinese.md @@ -128,8 +128,8 @@ - [函数不一定非要有个返回值|Functions don't have to return anything](#函数不一定非要有个返回值functions-dont-have-to-return-anything) - [函数可以直接在同一行内被调用|Functions can be called inline](#函数可以直接在同一行内被调用functions-can-be-called-inline) - [Examples](#examples) - - [Example: turning numbers into words](#example-turning-numbers-into-words) - - [Parameters can be passed by reference](#parameters-can-be-passed-by-reference) + - [示例:将数字转化为文字|Example: turning numbers into words](#示例将数字转化为文字example-turning-numbers-into-words) + - [参数可以通过引用来传递|Parameters can be passed by reference](#参数可以通过引用来传递parameters-can-be-passed-by-reference) - [6) 常量|Constants](#6-常量constants) - [Global Constants](#global-constants) - [7) Advanced: Game-side logic](#7-advanced-game-side-logic) @@ -1932,7 +1932,7 @@ ChatGPT 解析: #### Examples -For instance, you might include: +举个实例,您可以写这样的东西: === function max(a,b) === { a < b: @@ -1942,114 +1942,108 @@ For instance, you might include: } === function exp(x, e) === - // returns x to the power e where e is an integer + // 返回 x 的 e 次幂,其中 e 是整数 { e <= 0: ~ return 1 - else: ~ return x * exp(x, e - 1) } -Then: +然后: - The maximum of 2^5 and 3^3 is {max(exp(2,5), exp(3,3))}. - -produces: + 2^5 和 3^3 中的最大值是 {max(exp(2,5), exp(3,3))}. +输出: - The maximum of 2^5 and 3^3 is 32. + 2^5 和 3^3 中的最大值是 32。 -#### Example: turning numbers into words +#### 示例:将数字转化为文字|Example: turning numbers into words -The following example is long, but appears in pretty much every inkle game to date. (Recall that a hyphenated line inside multiline curly braces indicates either "a condition to test" or, if the curly brace began with a variable, "a value to compare against".) +一下示例虽然较长,但几乎可以出现在每个 Inkle 游戏中。(请记得,带有连字符的行出现在多行大括号中时,表示为“要测试的条件”;如果大括号以变量开头,则表示“要比较的值”。) === function print_num(x) === { - x >= 1000: - {print_num(x / 1000)} thousand { x mod 1000 > 0:{print_num(x mod 1000)}} + {print_num(x / 1000)} 一千 { x mod 1000 > 0:{print_num(x mod 1000)}} - x >= 100: - {print_num(x / 100)} hundred { x mod 100 > 0:and {print_num(x mod 100)}} + {print_num(x / 100)} 一百 { x mod 100 > 0:and {print_num(x mod 100)}} - x == 0: - zero + 零 - else: { x >= 20: { x / 10: - - 2: twenty - - 3: thirty - - 4: forty - - 5: fifty - - 6: sixty - - 7: seventy - - 8: eighty - - 9: ninety + - 2: 二十 + - 3: 三十 + - 4: 四十 + - 5: 五十 + - 6: 六十 + - 7: 七十 + - 8: 八十 + - 9: 九十 } { x mod 10 > 0:<>-<>} } { x < 10 || x > 20: { x mod 10: - - 1: one - - 2: two - - 3: three - - 4: four - - 5: five - - 6: six - - 7: seven - - 8: eight - - 9: nine + - 1: 一 + - 2: 二 + - 3: 三 + - 4: 四 + - 5: 五 + - 6: 六 + - 7: 七 + - 8: 八 + - 9: 九 } - else: { x: - - 10: ten - - 11: eleven - - 12: twelve - - 13: thirteen - - 14: fourteen - - 15: fifteen - - 16: sixteen - - 17: seventeen - - 18: eighteen - - 19: nineteen + - 10: 十 + - 11: 十一 + - 12: 十二 + - 13: 十三 + - 14: 十四 + - 15: 十五 + - 16: 十六 + - 17: 十七 + - 18: 十八 + - 19: 十九 } } } -which enables us to write things like: +有了上面的函数,咱们就可以使用这样的功能: ~ price = 15 - I pulled out {print_num(price)} coins from my pocket and slowly counted them. - "Oh, never mind," the trader replied. "I'll take half." And she took {print_num(price / 2)}, and pushed the rest back over to me. - + 我从口袋里掏出{print_num(price)}枚硬币,慢慢地数着。 + “哦,算了,”商人回答道,“我只要一半。”然后她拿走了{print_num(price / 2)}枚,把剩下的硬币推回给我。 +### 参数可以通过引用来传递|Parameters can be passed by reference -### Parameters can be passed by reference +函数的参数也可以通过“引用”来传递,这意味着函数可以直接修改被传入的变量,而不是创建一个临时变量来保存该值。 -Function parameters can also be passed 'by reference', meaning that the function can actually alter the the variable being passed in, instead of creating a temporary variable with that value. - -For instance, most **inkle** stories include the following: +举个例子,大部分的 **Inkle** 故事都可以包含: === function alter(ref x, k) === ~ x = x + k -Lines such as: +那么像这样的行: ~ gold = gold + 7 ~ health = health - 4 -then become: +就可以写成: ~ alter(gold, 7) ~ alter(health, -4) -which are slightly easier to read, and (more usefully) can be done inline for maximum compactness. - - * I ate a biscuit[] and felt refreshed. {alter(health, 2)} - * I gave a biscuit to Monsieur Fogg[] and he wolfed it down most undecorously. {alter(foggs_health, 1)} - - <> Then we continued on our way. - -Wrapping up simple operations in function can also provide a simple place to put debugging information, if required. - +这种写法可以增加易读性,并且(更实用的是)它们可以在一行内就完成,从而实现更紧凑的代码。 + * 我吃了一块饼干[]之后,觉得精神焕发。{alter(health, 2)} + * 我给了福格先生一块饼干[],他一口吞了下去,一点也不优雅。{alter(foggs_health, 1)} + - <> 然后,我们继续赶路了。 +将简单的操作封装进函数还有一个方便的好处,就是可以在需要的时候加入调试信息。 ## 6) 常量|Constants From 727e8969a3c56868f03b982979392404117bc171 Mon Sep 17 00:00:00 2001 From: Ander-Index Date: Wed, 11 Dec 2024 15:53:33 +0800 Subject: [PATCH 12/22] Update WritingWithInk_Simplified-Chinese.md --- .../WritingWithInk_Simplified-Chinese.md | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/Documentation/WritingWithInk_Simplified-Chinese.md b/Documentation/WritingWithInk_Simplified-Chinese.md index 98f104d7..070b3052 100644 --- a/Documentation/WritingWithInk_Simplified-Chinese.md +++ b/Documentation/WritingWithInk_Simplified-Chinese.md @@ -131,8 +131,8 @@ - [示例:将数字转化为文字|Example: turning numbers into words](#示例将数字转化为文字example-turning-numbers-into-words) - [参数可以通过引用来传递|Parameters can be passed by reference](#参数可以通过引用来传递parameters-can-be-passed-by-reference) - [6) 常量|Constants](#6-常量constants) - - [Global Constants](#global-constants) - - [7) Advanced: Game-side logic](#7-advanced-game-side-logic) + - [全局常量|Global Constants](#全局常量global-constants) + - [7) 进阶:游戏端逻辑|Advanced: Game-side logic](#7-进阶游戏端逻辑advanced-game-side-logic) - [第 4 部分:进阶流程控制|Part 4: Advanced Flow Control](#第-4-部分进阶流程控制part-4-advanced-flow-control) - [1) 隧道|Tunnels](#1-隧道tunnels) - [Tunnels run sub-stories](#tunnels-run-sub-stories) @@ -2047,16 +2047,15 @@ ChatGPT 解析: ## 6) 常量|Constants +### 全局常量|Global Constants -### Global Constants +交互式故事通常依赖于状态指示器来跟踪某些高级流程所处的阶段。有很多方法可以实现这一点,但最方便的方法是使用常量。 -Interactive stories often rely on state machines, tracking what stage some higher level process has reached. There are lots of ways to do this, but the most conveninent is to use constants. +有时,将常量定义为字符串是很方便的,因为这样可以将它们打印出来,用于游戏展示或调试的目的。 -Sometimes, it's convenient to define constants to be strings, so you can print them out, for gameplay or debugging purposes. - - CONST HASTINGS = "Hastings" - CONST POIROT = "Poirot" - CONST JAPP = "Japp" + CONST HASTINGS = "黑斯廷斯" + CONST POIROT = "波洛" + CONST JAPP = "贾普" VAR current_chief_suspect = HASTINGS @@ -2064,14 +2063,14 @@ Sometimes, it's convenient to define constants to be strings, so you can print t { found_japps_bloodied_glove: ~ current_chief_suspect = POIROT } - Current Suspect: {current_chief_suspect} + 当前的怀疑对象:{current_chief_suspect} -Sometimes giving them values is useful: +有时候,为一些常量赋值也很实用: CONST PI = 3.14 CONST VALUE_OF_TEN_POUND_NOTE = 10 -And sometimes the numbers are useful in other ways: +有时,数字常量还可以用在其他地方,下面的例子就是用数字来代替位置: CONST LOBBY = 1 CONST STAIRCASE = 2 @@ -2085,20 +2084,23 @@ And sometimes the numbers are useful in other ways: === report_progress === { - secret_agent_location == suitcase_location: - The secret agent grabs the suitcase! + 特工抓住了手提箱! ~ suitcase_location = HELD_BY_AGENT - secret_agent_location < suitcase_location: - The secret agent moves forward. + 特工向前走去。 ~ secret_agent_location++ } -Constants are simply a way to allow you to give story states easy-to-understand names. +上面这个例子中,常量只是为了给故事的状态赋予易于理解的名称。 -## 7) Advanced: Game-side logic +## 7) 进阶:游戏端逻辑|Advanced: Game-side logic -There are two core ways to provide game hooks in the **Ink** engine. External function declarations in ink allow you to directly call C# functions in the game, and variable observers are callbacks that are fired in the game when ink variables are modified. Both of these are described in [Running your ink](RunningYourInk.md). +在 Ink 引擎中提供游戏钩子有两种核心方法: +* 外部函数声明:在 Ink 中可以声明外部函数,允许你直接调用游戏中的 C# 函数。 +* 变量观察器:当 Ink 中的变量被修改时,触发游戏中的回调函数。 +这两种方法的详细描述见 [Running your ink](RunningYourInk.md). # 第 4 部分:进阶流程控制|Part 4: Advanced Flow Control From 8d1f73ccbee8fff6fc6a675e8d3ecc4be67fce6b Mon Sep 17 00:00:00 2001 From: Ander-Index Date: Fri, 13 Dec 2024 17:18:41 +0800 Subject: [PATCH 13/22] Update WritingWithInk_Simplified-Chinese.md --- .../WritingWithInk_Simplified-Chinese.md | 109 +++++++++--------- 1 file changed, 56 insertions(+), 53 deletions(-) diff --git a/Documentation/WritingWithInk_Simplified-Chinese.md b/Documentation/WritingWithInk_Simplified-Chinese.md index 070b3052..69a3eec7 100644 --- a/Documentation/WritingWithInk_Simplified-Chinese.md +++ b/Documentation/WritingWithInk_Simplified-Chinese.md @@ -12,7 +12,7 @@ 翻译时的 Ink 版本:1.2.0 -翻译最后更新时间:2024年10月31日 +翻译最后更新时间:2024 年 12 月 13 日 翻译仍在更新。 @@ -135,9 +135,9 @@ - [7) 进阶:游戏端逻辑|Advanced: Game-side logic](#7-进阶游戏端逻辑advanced-game-side-logic) - [第 4 部分:进阶流程控制|Part 4: Advanced Flow Control](#第-4-部分进阶流程控制part-4-advanced-flow-control) - [1) 隧道|Tunnels](#1-隧道tunnels) - - [Tunnels run sub-stories](#tunnels-run-sub-stories) - - [Advanced: Tunnels can return elsewhere](#advanced-tunnels-can-return-elsewhere) - - [Advanced: Tunnels use a call-stack](#advanced-tunnels-use-a-call-stack) + - [隧道运行子故事|Tunnels run sub-stories](#隧道运行子故事tunnels-run-sub-stories) + - [进阶:隧道可以返回到其它位置|Advanced: Tunnels can return elsewhere](#进阶隧道可以返回到其它位置advanced-tunnels-can-return-elsewhere) + - [进阶:隧道是使用调用栈的|Advanced: Tunnels use a call-stack](#进阶隧道是使用调用栈的advanced-tunnels-use-a-call-stack) - [2) Threads](#2-threads) - [Threads join multiple sections together](#threads-join-multiple-sections-together) - [Uses of threads](#uses-of-threads) @@ -2106,27 +2106,28 @@ ChatGPT 解析: ## 1) 隧道|Tunnels -The default structure for **Ink** stories is a "flat" tree of choices, branching and joining back together, perhaps looping, but with the story always being "at a certain place". +**Ink** 的默认结构是一颗“扁平”的选择树,分叉、合并、或者循环……但是故事始终处于“某个位置”。 -But this flat structure makes certain things difficult: for example, imagine a game in which the following interaction can happen: +这种扁平的结构有时会让某些情景变得复杂: +举个例子,设想一个游戏中可能会出现一下互动: === crossing_the_date_line === - * "Monsieur!"[] I declared with sudden horror. "I have just realised. We have crossed the international date line!" - - Monsieur Fogg barely lifted an eyebrow. "I have adjusted for it." - * I mopped the sweat from my brow[]. A relief! - * I nodded, becalmed[]. Of course he had! - * I cursed, under my breath[]. Once again, I had been belittled! + * “先生!”[] 我惊呼,“我刚刚意识到,咱们已经穿越了国际日期变更线!” + - 福格先生只是微微抬了一下眉毛。“我已经考虑到了。” + * 我擦了擦额头上的冷汗[],顿时松了一口气! + * 我点了点头,心情平静下来[]。他当然已经准备好了! + * 我低声咒骂了一句[]。我又一次被轻视了! -...but it can happen at several different places in the story. We don't want to have to write copies of the content for each different place, but when the content is finished it needs to know where to return to. We can do this using parameters: +但这个交互可能发生在故事的不同位置。我们不希望为每个位置都重复写一份相同的内容。但在内容结束时,程序需要知道返回到哪里。我们可以通过参数来实现这一点: === crossing_the_date_line(-> return_to) === ... - - -> return_to + - -> return_to ... === outside_honolulu === - We arrived at the large island of Honolulu. + 我们到达了檀香山这座大岛。 - (postscript) -> crossing_the_date_line(-> done) - (done) @@ -2135,83 +2136,84 @@ But this flat structure makes certain things difficult: for example, imagine a g ... === outside_pitcairn_island === - The boat sailed along the water towards the tiny island. + 船沿着水面驶向那个小小的皮特凯恩岛。 - (postscript) -> crossing_the_date_line(-> done) - (done) -> END -Both of these locations now call and execute the same segment of storyflow, but once finished they return to where they need to go next. +现在,这两个位置都调用并执行了相同的一段故事流程,但在完成后,它们会返回到各自需要前往的下一步。 -But what if the section of story being called is more complex - what if it spreads across several knots? Using the above, we'd have to keep passing the 'return-to' parameter from knot to knot, to ensure we always knew where to return. +然而,如果被调用的故事段更加复杂——比如它跨越了多个结点 (knots) 怎么办?按照上述方法,我们不得不在结点之间不断传递“返回位置”的参数,以确保每次都知道返回到哪里。 -So instead, **Ink** integrates this into the language with a new kind of divert, that functions rather like a subroutine, and is called a 'tunnel'. +译者注:以上的示例是表示,如果不使用“隧道”的写法,要怎么在分道到另一个地方并执行完毕后分道回原来的位置。接下来才要说 **Ink** 为这个操作提供的“隧道”语法。 -### Tunnels run sub-stories +为了解决这一问题,**Ink** 将这一功能集成到了语言本身,提供了一种新类型的分道 (Divert),其功能类似于子流程,被称为“隧道(Tunnel)”。 -The tunnel syntax looks like a divert, with another divert on the end: +### 隧道运行子故事|Tunnels run sub-stories + +隧道的语法看起来就像是一个分道,只是在分到的最后再另一个分道: -> crossing_the_date_line -> -This means "do the crossing_the_date_line story, then continue from here". +上面这个就表示“执行 crossing_the_date_line 的内容,然后从这里继续”。 -Inside the tunnel itself, the syntax is simplified from the parameterised example: all we do is end the tunnel using the `->->` statement which means, essentially, "go on". +在隧道内部,其语法相比参数化的示例更加简化:我们只需使用 `->->` 声明来结束隧道。这句话的意思基本上是“继续”。 === crossing_the_date_line === - // this is a tunnel! + // 这是一个隧道! ... - ->-> -Note that tunnel knots aren't declared as such, so the compiler won't check that tunnels really do end in `->->` statements, except at run-time. So you will need to write carefully to ensure that all the flows into a tunnel really do come out again. +请注意,隧道结点并不以特殊的方式声明,因此编译器并不会在编译时检查隧道是否确实以 `->->` 语句结束,这种检查只会在运行时进行。因此,你需要仔细检查,以确保所有进入了隧道的流程都能再正确的返回出来。 -Tunnels can also be chained together, or finish on a normal divert: +隧道可以串联在一起,也可以使用普通分道结束: ... - // this runs the tunnel, then diverts to 'done' + // 运行隧道后跳转到 'done' -> crossing_the_date_line -> done ... ... - //this runs one tunnel, then another, then diverts to 'done' + // 运行一个隧道,然后运行另一个隧道,最后跳转到 'done' -> crossing_the_date_line -> check_foggs_health -> done ... -Tunnels can be nested, so the following is valid: +隧道可以嵌套使用,所以下面的例子也是支持的。 === plains === = night_time - The dark grass is soft under your feet. + 你脚下黑色的草地非常柔软。 + [Sleep] -> sleep_here -> wake_here -> day_time = day_time - It is time to move on. + 是时候动身了。 === wake_here === - You wake as the sun rises. - + [Eat something] + 太阳升起,你醒来了。 + + [吃点什么] -> eat_something -> - + [Make a move] + + [出发] - ->-> === sleep_here === - You lie down and try to close your eyes. + 你躺下来,试图闭上眼睛。 -> monster_attacks -> - Then it is time to sleep. + 是时候睡觉了。 -> dream -> ->-> -... and so on. - +……大概就是这样。 -#### Advanced: Tunnels can return elsewhere +#### 进阶:隧道可以返回到其它位置|Advanced: Tunnels can return elsewhere -Sometimes, in a story, things happen. So sometimes a tunnel can't guarantee that it will always want to go back to where it came from. **Ink** supplies a syntax to allow you to "returning from a tunnel but actually go somewhere else" but it should be used with caution as the possibility of getting very confused is very high indeed. +有时,在故事中,事情可能不会像是预期一样发生。所以有时候隧道也无法保证它总是能返回到它之前的位置。所以为了解决这种情况,**Ink** 提供了一种语法,允许你“从隧道返回,但实际上去往其它的地方。”不过这种功能应当谨慎使用,毕竟这很容易导致逻辑混乱。 -Still, there are cases where it's indispensable: +当然,在某些情况下,这种灵活性是必不可少的。 === fall_down_cliff -> hurt(5) -> - You're still alive! You pick yourself up and walk on. + 你还活着!你站了起来继续前进。 === hurt(x) ~ stamina -= x @@ -2220,41 +2222,42 @@ Still, there are cases where it's indispensable: } === youre_dead - Suddenly, there is a white light all around you. Fingers lift an eyepiece from your forehead. 'You lost, buddy. Out of the chair.' - -And even in less drastic situations, we might want to break up the structure: + 突然,周围一片白光。有人伸手摘下你额头上的目镜。‘你输了,伙计。离开椅子吧。 + +即使故事情节没有生死攸关的紧张感,我们也可以通过灵活的跳转机制来调整叙事的流程结构: -> talk_to_jim -> === talk_to_jim - (opts) - * [ Ask about the warp lacelles ] + * [询问关于超空间装置的事] -> warp_lacells -> - * [ Ask about the shield generators ] + * [询问关于护盾发生器的事] -> shield_generators -> - * [ Stop talking ] + * [停止交谈] ->-> - -> opts = warp_lacells { shield_generators : ->-> argue } - "Don't worry about the warp lacelles. They're fine." + “别担心超空间装置,它们没问题。” ->-> = shield_generators { warp_lacells : ->-> argue } - "Forget about the shield generators. They're good." + “忘了护盾发生器吧,它们一切正常。” ->-> = argue - "What's with all these questions?" Jim demands, suddenly. - ... + “问这么多问题干什么?”吉姆突然质问道。 + ... ->-> -#### Advanced: Tunnels use a call-stack +译者注:上面的这个例子会在问完一个问题要问另一个的时候进入 'argue' -Tunnels are on a call-stack, so can safely recurse. +#### 进阶:隧道是使用调用栈的|Advanced: Tunnels use a call-stack +隧道是基于调用栈的,因此可以安全地递归调用。 ## 2) Threads From f956be58bfa69f53182b38a1978e5db5c4de5b0c Mon Sep 17 00:00:00 2001 From: Ander-Index Date: Fri, 13 Dec 2024 18:14:09 +0800 Subject: [PATCH 14/22] Update WritingWithInk_Simplified-Chinese.md --- .../WritingWithInk_Simplified-Chinese.md | 123 +++++++++--------- 1 file changed, 61 insertions(+), 62 deletions(-) diff --git a/Documentation/WritingWithInk_Simplified-Chinese.md b/Documentation/WritingWithInk_Simplified-Chinese.md index 69a3eec7..f6b8c959 100644 --- a/Documentation/WritingWithInk_Simplified-Chinese.md +++ b/Documentation/WritingWithInk_Simplified-Chinese.md @@ -46,10 +46,10 @@ - [分支与合并|Branching and joining](#分支与合并branching-and-joining) - [故事流|The story flow](#故事流the-story-flow) - [进阶:循环|Advanced: Loops](#进阶循环advanced-loops) - - [6) 包含和接缝|Includes and Stitches](#6-包含和接缝includes-and-stitches) + - [6) 包含和针脚|Includes and Stitches](#6-包含和针脚includes-and-stitches) - [结点可以是次级分道|Knots can be subdivided](#结点可以是次级分道knots-can-be-subdivided) - - [接缝需要有独一无二的名称|Stitches have unique names](#接缝需要有独一无二的名称stitches-have-unique-names) - - [默认为第一个接缝|The first stitch is the default](#默认为第一个接缝the-first-stitch-is-the-default) + - [针脚需要有独一无二的名称|Stitches have unique names](#针脚需要有独一无二的名称stitches-have-unique-names) + - [默认为第一个针脚|The first stitch is the default](#默认为第一个针脚the-first-stitch-is-the-default) - [内部分道|Local diverts](#内部分道local-diverts) - [脚本文件可组合|Script files can be combined](#脚本文件可组合script-files-can-be-combined) - [7) 可变选项|Varying Choices](#7-可变选项varying-choices) @@ -60,7 +60,7 @@ - [条件选项|Conditional Choices](#条件选项conditional-choices) - [进阶:多重条件|Advanced: multiple conditions](#进阶多重条件advanced-multiple-conditions) - [逻辑运算符:AND 和 OR|Logical operators: AND and OR](#逻辑运算符and-和-orlogical-operators-and-and-or) - - [进阶:结点与接缝的实际阅读次数|Advanced: knot/stitch labels are actually read counts](#进阶结点与接缝的实际阅读次数advanced-knotstitch-labels-are-actually-read-counts) + - [进阶:结点与针脚的实际阅读次数|Advanced: knot/stitch labels are actually read counts](#进阶结点与针脚的实际阅读次数advanced-knotstitch-labels-are-actually-read-counts) - [进阶:更多逻辑|Advanced: more logic](#进阶更多逻辑advanced-more-logic) - [8) 可变文本|Variable Text](#8-可变文本variable-text) - [文本是可以变更的|Text can vary](#文本是可以变更的text-can-vary) @@ -120,7 +120,7 @@ - [进阶:修改洗牌随机|Advanced: modified shuffles](#进阶修改洗牌随机advanced-modified-shuffles) - [4) 临时变量|Temporary Variables](#4-临时变量temporary-variables) - [临时变量用于临时计算|Temporary variables are for scratch calculations](#临时变量用于临时计算temporary-variables-are-for-scratch-calculations) - - [结点和接缝可接收参数|Knots and stitches can take parameters](#结点和接缝可接收参数knots-and-stitches-can-take-parameters) + - [结点和针脚可接收参数|Knots and stitches can take parameters](#结点和针脚可接收参数knots-and-stitches-can-take-parameters) - [示例:定义一个递归结点|Example: a recursive knot definition](#示例定义一个递归结点example-a-recursive-knot-definition) - [进阶:将分道目标作为参数来传递|Advanced: sending divert targets as parameters](#进阶将分道目标作为参数来传递advanced-sending-divert-targets-as-parameters) - [5) 函数|Functions](#5-函数functions) @@ -138,8 +138,8 @@ - [隧道运行子故事|Tunnels run sub-stories](#隧道运行子故事tunnels-run-sub-stories) - [进阶:隧道可以返回到其它位置|Advanced: Tunnels can return elsewhere](#进阶隧道可以返回到其它位置advanced-tunnels-can-return-elsewhere) - [进阶:隧道是使用调用栈的|Advanced: Tunnels use a call-stack](#进阶隧道是使用调用栈的advanced-tunnels-use-a-call-stack) - - [2) Threads](#2-threads) - - [Threads join multiple sections together](#threads-join-multiple-sections-together) + - [2) 缝合|Threads](#2-缝合threads) + - [缝合把多个部分合并到一起|Threads join multiple sections together](#缝合把多个部分合并到一起threads-join-multiple-sections-together) - [Uses of threads](#uses-of-threads) - [When does a side-thread end?](#when-does-a-side-thread-end) - [Using `-> DONE`](#using---done) @@ -551,13 +551,13 @@ (译者注:上面这是一个无限循环。) -## 6) 包含和接缝|Includes and Stitches +## 6) 包含和针脚|Includes and Stitches ### 结点可以是次级分道|Knots can be subdivided 随着故事越来越长,如果没有一些额外的结构,就会变得越来越难以组织。 -结点可以包括一种被称为“接缝” (Stitches) 的子部分。这些接缝使用一个等号标记。 +结点可以包括一种被称为“针脚” (Stitches) 的子部分。这些针脚使用一个等号标记。 === the_orient_express === = in_first_class @@ -569,11 +569,11 @@ = missed_the_train ... -例如,可以结点来指定一个场景,然后用接缝来表示场景中的事件。 +例如,可以结点来指定一个场景,然后用针脚来表示场景中的事件。 -### 接缝需要有独一无二的名称|Stitches have unique names +### 针脚需要有独一无二的名称|Stitches have unique names -接缝可以使用它的“地址”(Address) 来进行分道。 +针脚可以使用它的“地址”(Address) 来进行分道。 * [乘坐三等座] -> the_orient_express.in_third_class @@ -581,9 +581,9 @@ * [乘坐警卫间] -> the_orient_express.in_the_guards_van -### 默认为第一个接缝|The first stitch is the default +### 默认为第一个针脚|The first stitch is the default -转到包含接缝的结点时,将转到结点中的第一个接缝。所以: +转到包含针脚的结点时,将转到结点中的第一个针脚。所以: * [乘坐一等座] "先生,一等座还有空位么?" @@ -595,10 +595,10 @@ "先生,一等座还有空位么?" -> the_orient_express.in_first_class -(……除非我们在结点内移动了接缝的顺序!) +(……除非我们在结点内移动了针脚的顺序!) -您也可以在结点内的那些接缝上方加入任何内容。然而你需要记得为接缝进行分道。因为引擎在有接缝前有内容的时候*不会*自动进入第一个接缝,举个例子: +您也可以在结点内的那些针脚上方加入任何内容。然而你需要记得为针脚进行分道。因为引擎在有针脚前有内容的时候*不会*自动进入第一个针脚,举个例子: === the_orient_express === @@ -614,7 +614,7 @@ ### 内部分道|Local diverts -如果你要在结点内进行分道,那么您不需要使用完整的地址就可以进行内部接缝。 +如果你要在结点内进行分道,那么您不需要使用完整的地址就可以进行内部针脚。 -> the_orient_express @@ -627,7 +627,7 @@ = in_third_class 我把我自己安排在三等座。 -这意味着接缝和结点不能共用名称,但是如果相同名称的接缝分别属于不同的结点则可以使用。(因此,"东方快车”和“蒙古号”这两个结点里面都可以包含叫“一等座”的接缝。) +这意味着针脚和结点不能共用名称,但是如果相同名称的针脚分别属于不同的结点则可以使用。(因此,"东方快车”和“蒙古号”这两个结点里面都可以包含叫“一等座”的针脚。) 如果使用了模棱两可的名称,编译器会发出警告。 @@ -745,13 +745,13 @@ 您还可以手动打开或关闭选择。**Ink** 有很多可用的逻辑,但最简单的检测是“玩家是否看过某个特定内容”。 -游戏中的每个结点与接缝都有一个唯一的地址(这样它就可以被分道到),我们使用相同的地址来检测该内容是否被查看过。 +游戏中的每个结点与针脚都有一个唯一的地址(这样它就可以被分道到),我们使用相同的地址来检测该内容是否被查看过。 * { not visit_paris } [去巴黎] -> visit_paris + { visit_paris } [回到巴黎] -> visit_paris * { visit_paris.met_estelle } [致电艾斯特尔女士] -> phone_estelle -需要注意的是:如果要检测的 `knot_name`(结点名)内含有接缝的话,则需要看完*所有的*接缝后,返回的结果才是“ture”(是、真)。 +需要注意的是:如果要检测的 `knot_name`(结点名)内含有针脚的话,则需要看完*所有的*针脚后,返回的结果才是“ture”(是、真)。 还要注意的是,条件选项也是一次性选项,因此你仍然需要将其标识为粘滞选项才可进行重复选择。 @@ -776,13 +776,13 @@ 您也可以使用标准的 `!` 来表示 `not`,不过有时会让编译器感到困惑,因为它认为 `{!text}` 是本文接下来会提到的一种“一次性替文”。我们建议使用 `not` 因为布尔检测很令人头大。(译者注:此外,非程序员会相对难以理解布尔运算。所以此处建议不引入运算符 `!`) -#### 进阶:结点与接缝的实际阅读次数|Advanced: knot/stitch labels are actually read counts +#### 进阶:结点与针脚的实际阅读次数|Advanced: knot/stitch labels are actually read counts 这是检测: * {seen_clue} [指责杰斐逊先生] -这实际上是在检测一个*整数*,而不是在检测一个是与否的标志。以这种方式使用的结点或接缝实际上是在设置一个整数变量,其中包含玩家看到该地址内容的次数。 +这实际上是在检测一个*整数*,而不是在检测一个是与否的标志。以这种方式使用的结点或针脚实际上是在设置一个整数变量,其中包含玩家看到该地址内容的次数。 如果它不为零,就会在类似上面的检测中返回 `true`,但也可以更具体一些: @@ -985,9 +985,9 @@ And here's a bit of lifestyle advice. Note the sticky choice - the lure of the t ### 分道计数函数|TURNS_SINCE(-> knot) -`TURNS_SINCE` 返回自上次访问某个结点或接缝之后,玩家操作了多少次。(玩家操作在形式上来说就是玩家的交互输入)。 +`TURNS_SINCE` 返回自上次访问某个结点或针脚之后,玩家操作了多少次。(玩家操作在形式上来说就是玩家的交互输入)。 -值为 0 就表示“你目前正在你所检测的结点或接缝中使用这个函数”。值为 -1 就表示那个要检测的结点或接缝还从来没有被看过。其它任何的正值都表示你要检测的内容在多少个回合之前出现过了。 +值为 0 就表示“你目前正在你所检测的结点或针脚中使用这个函数”。值为 -1 就表示那个要检测的结点或针脚还从来没有被看过。其它任何的正值都表示你要检测的内容在多少个回合之前出现过了。 * {TURNS_SINCE(-> sleeping.intro) > 10} 你感到疲乏……-> sleeping * {TURNS_SINCE(-> laugh) == 0} 你尝试不再笑。 @@ -1193,7 +1193,7 @@ TODO: (向编译器传递 `-c` 的要求) * “不行,你们该上床睡觉了。” - 船员们打起了哈欠。 -过一段时间后,这种嵌套就会变得难以阅读和操作,因此,如果嵌套会变得臃肿的话,将其分道到一个新的接缝会是一个好的操作。 +过一段时间后,这种嵌套就会变得难以阅读和操作,因此,如果嵌套会变得臃肿的话,将其分道到一个新的针脚会是一个好的操作。 但至少在理论上,你可以把整个故事只写成一个织体。 @@ -1287,7 +1287,7 @@ TODO: (向编译器传递 `-c` 的要求) - (top) -一旦贴上标签,收束点就可以像结点和接缝一样被分道或是测试。这意味着您可以利用之前的决定来改变织体中的后续结果,同时继续保持清晰、可靠且继续发展等织体的所有优点。 +一旦贴上标签,收束点就可以像结点和针脚一样被分道或是测试。这意味着您可以利用之前的决定来改变织体中的后续结果,同时继续保持清晰、可靠且继续发展等织体的所有优点。 选项也可以用括号来打标签,就像收束点一样。但是标签括号需要写在每个选项的文本之前。 @@ -1315,7 +1315,7 @@ TODO: (向编译器传递 `-c` 的要求) ### 界限|Scope -在同一织体块内,您可以简单地使用标签名称;而在织体块外,您需要一个路径,或者是通往同一结点的不同接缝的路径: +在同一织体块内,您可以简单地使用标签名称;而在织体块外,您需要一个路径,或者是通往同一结点的不同针脚的路径: === knot === = stitch_one @@ -1771,11 +1771,11 @@ if 语法查询从开始到当前所产生的所有文本、选项还有结果 那一晚是我人生中最冷的一晚。 } -临时变量的值在故事离开定义它的接缝 (Stitch) 后会被丢弃。 +临时变量的值在故事离开定义它的针脚 (Stitch) 后会被丢弃。 -### 结点和接缝可接收参数|Knots and stitches can take parameters +### 结点和针脚可接收参数|Knots and stitches can take parameters -临时变量的一种特别有用形式是参数。任何结点或接缝都可以接收一个参数值。 +临时变量的一种特别有用形式是参数。任何结点或针脚都可以接收一个参数值。 * [指控海斯廷斯] -> accuse("海斯廷斯") @@ -1789,7 +1789,7 @@ if 语法查询从开始到当前所产生的所有文本、选项还有结果 “真的吗?” 贾普问道。 “{who == "myself":是你做的?|你{who}?}” “怎么会不是呢?” 波洛反问道。 -……如果想从一个接缝传递临时值到另一个接缝时,就需要使用参数! +……如果想从一个针脚传递临时值到另一个针脚时,就需要使用参数! #### 示例:定义一个递归结点|Example: a recursive knot definition @@ -1814,7 +1814,7 @@ if 语法查询从开始到当前所产生的所有文本、选项还有结果 #### 进阶:将分道目标作为参数来传递|Advanced: sending divert targets as parameters -结点和接缝的地址是一种值,用 `->` 字符表示,可以被存储和传递。因此以下代码是合规的,常用且非常有用: +结点和针脚的地址是一种值,用 `->` 字符表示,可以被存储和传递。因此以下代码是合规的,常用且非常有用: === sleeping_in_hut === 你躺下并闭上了眼睛。 @@ -1859,7 +1859,7 @@ ChatGPT 解析: **Ink** 包含了这样的功能:它们是结点,但是具有以下的限制和特性: 一个函数: -- 不能包含接缝 (Stitchs) +- 不能包含针脚 (Stitchs) - 不能使用分道或提供选择 - 可以调用其他函数 - 可以包含已打印输出的内容 @@ -2259,55 +2259,54 @@ ChatGPT 解析: 隧道是基于调用栈的,因此可以安全地递归调用。 -## 2) Threads +## 2) 缝合|Threads -So far, everything in ink has been entirely linear, despite all the branching and diverting. But it's actually possible for a writer to 'fork' a story into different sub-sections, to cover more possible player actions. +到目前为止,尽管 **Ink** 中有大量分支和跳转,但一切都是线性的。然而,作者实际上可以将故事“分叉”为不同的子部分,以涵盖更多可能的玩家行为。 -We call this 'threading', though it's not really threading in the sense that computer scientists mean it: it's more like stitching in new content from various places. +我们称这种机制为“缝合 (Thread)”,尽管它并不完全符合计算机科学中“线程(也是 Thread)”的定义:因为这更像是从不同地方“缝合”新内容到当前故事中。 -Note that this is definitely an advanced feature: the engineering stories becomes somewhat more complex once threads are involved! +需要注意的是,这是一个高级功能:一旦涉及缝合,故事的设计会变得更加复杂! -### Threads join multiple sections together +### 缝合把多个部分合并到一起|Threads join multiple sections together -Threads allow you to compose sections of content from multiple sources in one go. For example: +缝合操作允许你将多个来源的内容一次性组合成一个部分,例如: == thread_example == - I had a headache; threading is hard to get your head around. + 我有点头疼;缝合操作实在是有点难以理解。 <- conversation <- walking - == conversation == - It was a tense moment for Monty and me. - * "What did you have for lunch today?"[] I asked. - "Spam and eggs," he replied. - * "Nice weather, we're having,"[] I said. - "I've seen better," he replied. - - -> house + 对于蒙蒂和我来说,这真是一个紧张的时刻。 + * “你今天午餐吃了什么?”[]我问道。 + “午餐肉和鸡蛋,”他回答。 + * “天气不错啊,”[] 我说道。 + “见过更好的,”他回答。 + - -> house == walking == - We continued to walk down the dusty road. - * [Continue walking] - -> house + 我们继续沿着尘土飞扬的道路走。 + * [继续走] + -> house == house == - Before long, we arrived at his house. - -> END + 不久后,我们到达了他的房子。 + -> END -It allows multiple sections of story to combined together into a single section: +这就让故事的多个部分组合到一起成了一个独立的部分: - I had a headache; threading is hard to get your head around. - It was a tense moment for Monty and me. - We continued to walk down the dusty road. - 1: "What did you have for lunch today?" - 2: "Nice weather, we're having," - 3: Continue walking + 我有点头疼;缝合操作实在是有点难以理解。 + 对于蒙蒂和我来说,这真是一个紧张的时刻。 + 我们继续沿着尘土飞扬的道路走。 + 1: “你今天午餐吃了什么?” + 2: “天气不错啊,” + 3: 继续走 -On encountering a thread statement such as `<- conversation`, the compiler will fork the story flow. The first fork considered will run the content at `conversation`, collecting up any options it finds. Once it has run out of flow here it'll then run the other fork. +当遇到类似 `<- conversation` 这样的缝合语句时,编译器就会将故事流分叉过来。首个缝口 (fork) 将运行 `conversation` 中的内容,并收集其中的所有选项。一旦该缝口 (fork) 的内容结束,编译器将继续运行其他缝口 (fork) 。 -All the content is collected and shown to the player. But when a choice is chosen, the engine will move to that fork of the story and collapse and discard the others. +所有的内容都会收集并展示给玩家。但当玩家选择之后,引擎就会跳转到那个分叉后折叠并丢弃其它分叉。 -Note that global variables are *not* forked, including the read counts of knots and stitches. +另外需要注意的是,全局变量*不会*被分叉,包括结点和针脚的读取计数。 ### Uses of threads From 2baaf616f643191696b4491f97da8738b64b33a9 Mon Sep 17 00:00:00 2001 From: Ander-Index Date: Wed, 7 May 2025 15:31:11 +0800 Subject: [PATCH 15/22] Update to CP4-4 --- .../WritingWithInk_Simplified-Chinese.md | 268 +++++++++--------- 1 file changed, 140 insertions(+), 128 deletions(-) diff --git a/Documentation/WritingWithInk_Simplified-Chinese.md b/Documentation/WritingWithInk_Simplified-Chinese.md index f6b8c959..0bbd6030 100644 --- a/Documentation/WritingWithInk_Simplified-Chinese.md +++ b/Documentation/WritingWithInk_Simplified-Chinese.md @@ -16,12 +16,29 @@ 翻译仍在更新。 +## 此外 +根据语义,将部分专有词汇进行翻译: + +| 原文 | 翻译 | +| ----------- | --- | +| Knot | 结点 | +| Divert | 转向 | +| Branch | 分支 | +| Stitch | 针脚 | +| Weave | 织体 | +| Gather | 收束 | +| Scope | 界限 | +| Tunnel | 隧道 | +| Thread | 缝合线 | +| Side-Thread | 旁缝合线 | + ## 内容目录
内容目录 - [使用 Ink 进行写作](#使用-ink-进行写作) - [声明](#声明) + - [此外](#此外) - [内容目录](#内容目录) - [介绍](#介绍) - [第 1 部分:基础|Part One: The Basics](#第-1-部分基础part-one-the-basics) @@ -37,9 +54,9 @@ - [内容的片段被称为结点|Pieces of content are called knots](#内容的片段被称为结点pieces-of-content-are-called-knots) - [撰写一个结点|Writing a knot](#撰写一个结点writing-a-knot) - [进阶:一个结点更复杂的“你好世界”|Advanced: a knottier "hello world"](#进阶一个结点更复杂的你好世界advanced-a-knottier-hello-world) - - [4) 分道|Diverts](#4-分道diverts) - - [从结点分道到结点|Knots divert to knots](#从结点分道到结点knots-divert-to-knots) - - [分道是不可见的|Diverts are invisible](#分道是不可见的diverts-are-invisible) + - [4) 转向|Diverts](#4-转向diverts) + - [从结点转向到结点|Knots divert to knots](#从结点转向到结点knots-divert-to-knots) + - [转向是不可见的|Diverts are invisible](#转向是不可见的diverts-are-invisible) - [胶合|Glue](#胶合glue) - [5) 为故事流程进行分支|Branching The Flow](#5-为故事流程进行分支branching-the-flow) - [基本分支|Basic branching](#基本分支basic-branching) @@ -47,10 +64,10 @@ - [故事流|The story flow](#故事流the-story-flow) - [进阶:循环|Advanced: Loops](#进阶循环advanced-loops) - [6) 包含和针脚|Includes and Stitches](#6-包含和针脚includes-and-stitches) - - [结点可以是次级分道|Knots can be subdivided](#结点可以是次级分道knots-can-be-subdivided) + - [结点可以是次级转向|Knots can be subdivided](#结点可以是次级转向knots-can-be-subdivided) - [针脚需要有独一无二的名称|Stitches have unique names](#针脚需要有独一无二的名称stitches-have-unique-names) - [默认为第一个针脚|The first stitch is the default](#默认为第一个针脚the-first-stitch-is-the-default) - - [内部分道|Local diverts](#内部分道local-diverts) + - [内部转向|Local diverts](#内部转向local-diverts) - [脚本文件可组合|Script files can be combined](#脚本文件可组合script-files-can-be-combined) - [7) 可变选项|Varying Choices](#7-可变选项varying-choices) - [选项只能被使用一次|Choices can only be used once](#选项只能被使用一次choices-can-only-be-used-once) @@ -73,8 +90,8 @@ - [9) 游戏查询和函数|Game Queries and Functions](#9-游戏查询和函数game-queries-and-functions) - [选项计数函数|CHOICE\_COUNT()](#选项计数函数choice_count) - [总回合计数函数|TURNS()](#总回合计数函数turns) - - [分道计数函数|TURNS\_SINCE(-\> knot)](#分道计数函数turns_since--knot) - - [功能预览:在功能中使用分道计数函数|Sneak preview: using TURNS\_SINCE in a function](#功能预览在功能中使用分道计数函数sneak-preview-using-turns_since-in-a-function) + - [转向计数函数|TURNS\_SINCE(-\> knot)](#转向计数函数turns_since--knot) + - [功能预览:在功能中使用转向计数函数|Sneak preview: using TURNS\_SINCE in a function](#功能预览在功能中使用转向计数函数sneak-preview-using-turns_since-in-a-function) - [种子随机函数|SEED\_RANDOM()](#种子随机函数seed_random) - [进阶:更多查询|Advanced: more queries](#进阶更多查询advanced-more-queries) - [第 2 部分:织体|Part 2: Weave](#第-2-部分织体part-2-weave) @@ -94,13 +111,13 @@ - [界限|Scope](#界限scope) - [进阶:所有的选项都可以打标签|Advanced: all options can be labelled](#进阶所有的选项都可以打标签advanced-all-options-can-be-labelled) - [进阶:在织体里循环|Advanced: Loops in a weave](#进阶在织体里循环advanced-loops-in-a-weave) - - [进阶:分道指向到选项|Advanced: diverting to options](#进阶分道指向到选项advanced-diverting-to-options) + - [进阶:转向指向到选项|Advanced: diverting to options](#进阶转向指向到选项advanced-diverting-to-options) - [进阶:在一个选项后直接收束|Advanced: Gathers directly after an option](#进阶在一个选项后直接收束advanced-gathers-directly-after-an-option) - [第 3 部分:变量和逻辑|Part 3: Variables and Logic](#第-3-部分变量和逻辑part-3-variables-and-logic) - [1) 全局变量|Global Variables](#1-全局变量global-variables) - [定义全局变量|Defining Global Variables](#定义全局变量defining-global-variables) - [使用全局变量|Using Global Variables](#使用全局变量using-global-variables) - - [进阶:将分道存储为变量|Advanced: storing diverts as variables](#进阶将分道存储为变量advanced-storing-diverts-as-variables) + - [进阶:将转向存储为变量|Advanced: storing diverts as variables](#进阶将转向存储为变量advanced-storing-diverts-as-variables) - [进阶:全局变量是对外可见的|Advanced: Global variables are externally visible](#进阶全局变量是对外可见的advanced-global-variables-are-externally-visible) - [打印输出变量|Printing variables](#打印输出变量printing-variables) - [叠加态字符串|Evaluating strings](#叠加态字符串evaluating-strings) @@ -122,7 +139,7 @@ - [临时变量用于临时计算|Temporary variables are for scratch calculations](#临时变量用于临时计算temporary-variables-are-for-scratch-calculations) - [结点和针脚可接收参数|Knots and stitches can take parameters](#结点和针脚可接收参数knots-and-stitches-can-take-parameters) - [示例:定义一个递归结点|Example: a recursive knot definition](#示例定义一个递归结点example-a-recursive-knot-definition) - - [进阶:将分道目标作为参数来传递|Advanced: sending divert targets as parameters](#进阶将分道目标作为参数来传递advanced-sending-divert-targets-as-parameters) + - [进阶:将转向目标作为参数来传递|Advanced: sending divert targets as parameters](#进阶将转向目标作为参数来传递advanced-sending-divert-targets-as-parameters) - [5) 函数|Functions](#5-函数functions) - [定义和调用函数|Defining and calling functions](#定义和调用函数defining-and-calling-functions) - [函数不一定非要有个返回值|Functions don't have to return anything](#函数不一定非要有个返回值functions-dont-have-to-return-anything) @@ -138,13 +155,13 @@ - [隧道运行子故事|Tunnels run sub-stories](#隧道运行子故事tunnels-run-sub-stories) - [进阶:隧道可以返回到其它位置|Advanced: Tunnels can return elsewhere](#进阶隧道可以返回到其它位置advanced-tunnels-can-return-elsewhere) - [进阶:隧道是使用调用栈的|Advanced: Tunnels use a call-stack](#进阶隧道是使用调用栈的advanced-tunnels-use-a-call-stack) - - [2) 缝合|Threads](#2-缝合threads) - - [缝合把多个部分合并到一起|Threads join multiple sections together](#缝合把多个部分合并到一起threads-join-multiple-sections-together) - - [Uses of threads](#uses-of-threads) - - [When does a side-thread end?](#when-does-a-side-thread-end) - - [Using `-> DONE`](#using---done) - - [Example: adding the same choice to several places](#example-adding-the-same-choice-to-several-places) - - [Example: organisation of wide choice points](#example-organisation-of-wide-choice-points) + - [2) 缝合线|Threads](#2-缝合线threads) + - [缝合线把多个部分合并到一起|Threads join multiple sections together](#缝合线把多个部分合并到一起threads-join-multiple-sections-together) + - [缝合线的用法|Uses of threads](#缝合线的用法uses-of-threads) + - [何时结束一个旁缝合线?|When does a side-thread end?](#何时结束一个旁缝合线when-does-a-side-thread-end) + - [使用 `-> DONE`|Using `-> DONE`](#使用---doneusing---done) + - [示例:在多个位置添加相同选项|Example: adding the same choice to several places](#示例在多个位置添加相同选项example-adding-the-same-choice-to-several-places) + - [示例:大规模选项的组织管理|Example: organisation of wide choice points](#示例大规模选项的组织管理example-organisation-of-wide-choice-points) - [第 5 部分:进阶状态追踪|Part 5: Advanced State Tracking](#第-5-部分进阶状态追踪part-5-advanced-state-tracking) - [Note: New feature alert!](#note-new-feature-alert) - [1) Basic Lists](#1-basic-lists) @@ -396,7 +413,7 @@ #### 进阶:一个结点更复杂的“你好世界”|Advanced: a knottier "hello world" -在启动 Ink 文件时,结点以外的内容会自动运行。但节点不会。因此,如果你开始使用节点来管理内容,就需要告诉游戏该去哪里。我们可以使用分道箭头 `->`来做到这一点,下一部分将对此进行详细介绍。 +在启动 Ink 文件时,结点以外的内容会自动运行。但节点不会。因此,如果你开始使用节点来管理内容,就需要告诉游戏该去哪里。我们可以使用转向箭头 `->`来做到这一点,下一部分将对此进行详细介绍。 这是一个简单的结点跳转脚本: @@ -427,11 +444,11 @@ `-> END` 是一个同时给写作者和编译器用的标记,表示 "故事流程现在应该停止"。 -## 4) 分道|Diverts +## 4) 转向|Diverts -### 从结点分道到结点|Knots divert to knots +### 从结点转向到结点|Knots divert to knots -您可以使用“分道箭头”`->`来让故事从一个结点分道到另一个结。无需任何用户输入,分道会立即发生。 +您可以使用“转向箭头”`->`来让故事从一个结点转向到另一个结。无需任何用户输入,转向会立即发生。 === back_in_london === @@ -441,9 +458,9 @@ === hurry_home === 我们以最快的速度赶回萨维尔街。 -#### 分道是不可见的|Diverts are invisible +#### 转向是不可见的|Diverts are invisible -分道甚至可以在句子中可以无缝衔接: +转向甚至可以在句子中可以无缝衔接: === hurry_home === 我们赶回萨维尔街, -> as_fast_as_we_could @@ -480,7 +497,7 @@ ### 基本分支|Basic branching -将结点、选项和分道结合起来,就形成了的自助游戏 (choose-your-own game) 的基本结构。 +将结点、选项和转向结合起来,就形成了的自助游戏 (choose-your-own game) 的基本结构。 === paragraph_1 === 你站在安纳兰德城墙边,手持长剑。 @@ -495,7 +512,7 @@ ### 分支与合并|Branching and joining -利用分道,作者可以将故事流分支,然后再次合并起来,且不会让玩家看到流程已经重新连接。 +利用转向,作者可以将故事流分支,然后再次合并起来,且不会让玩家看到流程已经重新连接。 === back_in_london === @@ -528,7 +545,7 @@ ### 故事流|The story flow -结点和分道相结合就形成了游戏的基本故事流程。但这种流程是“扁平”的——既没有调用堆栈,分道也不会从某处“折返”。 +结点和转向相结合就形成了游戏的基本故事流程。但这种流程是“扁平”的——既没有调用堆栈,转向也不会从某处“折返”。 在大多数水墨脚本中,故事流程从顶部开始,像意大利面条一样乱蹦乱跳,最终,希望能到达"->结束"。 在大部分 Ink 脚本中,故事流从顶部开始,然后就像是一盘意面一样,最终到达一个 `-> END`。 @@ -537,7 +554,7 @@ #### 进阶:循环|Advanced: Loops -您可以使用分道来创建循环内容,**Ink** 有多种可以利用这一点的功能,包括使内容自行变化的方法,以及控制选项选择频率的方法。 +您可以使用转向来创建循环内容,**Ink** 有多种可以利用这一点的功能,包括使内容自行变化的方法,以及控制选项选择频率的方法。 更多信息请参阅这些章节: * [可变文本|Variable Text](#8-可变文本variable-text) @@ -553,7 +570,7 @@ ## 6) 包含和针脚|Includes and Stitches -### 结点可以是次级分道|Knots can be subdivided +### 结点可以是次级转向|Knots can be subdivided 随着故事越来越长,如果没有一些额外的结构,就会变得越来越难以组织。 @@ -573,7 +590,7 @@ ### 针脚需要有独一无二的名称|Stitches have unique names -针脚可以使用它的“地址”(Address) 来进行分道。 +针脚可以使用它的“地址”(Address) 来进行转向。 * [乘坐三等座] -> the_orient_express.in_third_class @@ -598,7 +615,7 @@ (……除非我们在结点内移动了针脚的顺序!) -您也可以在结点内的那些针脚上方加入任何内容。然而你需要记得为针脚进行分道。因为引擎在有针脚前有内容的时候*不会*自动进入第一个针脚,举个例子: +您也可以在结点内的那些针脚上方加入任何内容。然而你需要记得为针脚进行转向。因为引擎在有针脚前有内容的时候*不会*自动进入第一个针脚,举个例子: === the_orient_express === @@ -612,9 +629,9 @@ ... -### 内部分道|Local diverts +### 内部转向|Local diverts -如果你要在结点内进行分道,那么您不需要使用完整的地址就可以进行内部针脚。 +如果你要在结点内进行转向,那么您不需要使用完整的地址就可以进行内部针脚。 -> the_orient_express @@ -641,7 +658,7 @@ 包含语句应始终放在文件头,而不是在结点内。 -把文件分割开不会影响到分道跳转。(换句话说,只要你在文件头声明过了要用到的文件,那么就可以进行跨文件分道。) +把文件分割开不会影响到转向跳转。(换句话说,只要你在文件头声明过了要用到的文件,那么就可以进行跨文件转向。) ## 7) 可变选项|Varying Choices @@ -745,7 +762,7 @@ 您还可以手动打开或关闭选择。**Ink** 有很多可用的逻辑,但最简单的检测是“玩家是否看过某个特定内容”。 -游戏中的每个结点与针脚都有一个唯一的地址(这样它就可以被分道到),我们使用相同的地址来检测该内容是否被查看过。 +游戏中的每个结点与针脚都有一个唯一的地址(这样它就可以被转向到),我们使用相同的地址来检测该内容是否被查看过。 * { not visit_paris } [去巴黎] -> visit_paris + { visit_paris } [回到巴黎] -> visit_paris @@ -845,7 +862,7 @@ 鼠熊{&{&一下子就|}挠|抓}{&伤了你|到了你的{&腿|胳膊|脸颊}}。 -替文可以嵌套分道声明: +替文可以嵌套转向声明: 我{就这么等着。|继续等着。|都等睡着了。|都睡醒了还没有等到。|放弃并离开了。-> leave_post_office} @@ -983,7 +1000,7 @@ And here's a bit of lifestyle advice. Note the sticky choice - the lure of the t `TURNS()` 这个函数会返回游戏开始后的游戏回合数。 -### 分道计数函数|TURNS_SINCE(-> knot) +### 转向计数函数|TURNS_SINCE(-> knot) `TURNS_SINCE` 返回自上次访问某个结点或针脚之后,玩家操作了多少次。(玩家操作在形式上来说就是玩家的交互输入)。 @@ -992,12 +1009,12 @@ And here's a bit of lifestyle advice. Note the sticky choice - the lure of the t * {TURNS_SINCE(-> sleeping.intro) > 10} 你感到疲乏……-> sleeping * {TURNS_SINCE(-> laugh) == 0} 你尝试不再笑。 -请注意:传递参数给 `TURNS_SINCE` 的是具体的“分道目标”,而不是简单的结点地址本身(因为结点地址在程序那边是一串数字,是一个读数,而不是一个故事中的某个位置) +请注意:传递参数给 `TURNS_SINCE` 的是具体的“转向目标”,而不是简单的结点地址本身(因为结点地址在程序那边是一串数字,是一个读数,而不是一个故事中的某个位置) TODO: (向编译器传递 `-c` 的要求) (译者注:上面这个 TODO 是 Ink 的开发者给他们自己写的版本计划。) -#### 功能预览:在功能中使用分道计数函数|Sneak preview: using TURNS_SINCE in a function +#### 功能预览:在功能中使用转向计数函数|Sneak preview: using TURNS_SINCE in a function `TURNS_SINCE(->x) == 0` 检测是一个非常有用的函数,通常值得将其单独包装成一个 Ink 的功能。 @@ -1047,7 +1064,7 @@ TODO: (向编译器传递 `-c` 的要求) * “我说,这次的冒险真的很可怕[……”],我真的不想再继续了…… “啊……别这样。”他安慰着我:“看起来你现在有些累了。明天,事情一定会有所好转的。” -在实际游戏中,这三个选项都可能导致相同的结果——福格先生离开了房间。我们可以用“收束”而无需创建任何新的结点或分道而完成这一点。 +在实际游戏中,这三个选项都可能导致相同的结果——福格先生离开了房间。我们可以用“收束”而无需创建任何新的结点或转向而完成这一点。 “你说什么?”我的老大问我。 * “我有点累了[。”],老大……”我重复着。 @@ -1092,11 +1109,11 @@ TODO: (向编译器传递 `-c` 的要求) - 路上空无一人。麦基不见踪影。 -这是组织织体最基本的方式。本节的其余部分将详细介绍一些附加功能,这些功能可以用来制作织体嵌套、内容的旁道 (Side-Track) 和分道 (Diversions)、在自身内部进行分道,最重要的是,还根据前面的选择来影响后面的内容。 +这是组织织体最基本的方式。本节的其余部分将详细介绍一些附加功能,这些功能可以用来制作织体嵌套、内容的旁道 (Side-Track) 和转向 (Diversions)、在自身内部进行转向,最重要的是,还根据前面的选择来影响后面的内容。 #### 织体的理念(方法论)|The weave philosophy -织体不仅是对分支的方便封装,也是编写更经得起推敲的内容的一种方法。上面的 `escape` 示例就已经有四种可能的路径了,而更复杂的序列可能会有更多更多的路径。如果使用普通的分道,就必须挨个检查结点链接,这样很容易出现错误。 +织体不仅是对分支的方便封装,也是编写更经得起推敲的内容的一种方法。上面的 `escape` 示例就已经有四种可能的路径了,而更复杂的序列可能会有更多更多的路径。如果使用普通的转向,就必须挨个检查结点链接,这样很容易出现错误。 在织体中,流程保证从顶部开始,然后一路走到底。在基本的织体结构中,流程错误是不可能发生的,而且输出文本可以很容易地略读。 这就意味着无需在游戏中实际测试所有分支也能确保它们按预期运行。 @@ -1176,7 +1193,7 @@ TODO: (向编译器传递 `-c` 的要求) #### 进阶:收束这个操作做了什么|Advanced: What gathers do -收束是直观的,但它们的行为却很难用语言表达:一般来说,在一个选项被选中后,故事会找到下一个收束点并向其分道而去。 +收束是直观的,但它们的行为却很难用语言表达:一般来说,在一个选项被选中后,故事会找到下一个收束点并向其转向而去。 基本原理是:选项将故事情节分开,而收束则将它们重新聚拢。(这一套下来得名“织体”) @@ -1193,7 +1210,7 @@ TODO: (向编译器传递 `-c` 的要求) * “不行,你们该上床睡觉了。” - 船员们打起了哈欠。 -过一段时间后,这种嵌套就会变得难以阅读和操作,因此,如果嵌套会变得臃肿的话,将其分道到一个新的针脚会是一个好的操作。 +过一段时间后,这种嵌套就会变得难以阅读和操作,因此,如果嵌套会变得臃肿的话,将其转向到一个新的针脚会是一个好的操作。 但至少在理论上,你可以把整个故事只写成一个织体。 @@ -1276,7 +1293,7 @@ TODO: (向编译器传递 `-c` 的要求) ### 织体是庞大且没有地址索引的|Weaves are largely unaddressed -默认情况下,织体结构中的内容行都没有地址或标签,这意味着它们无法被分道到其他地方,也就无法进行测试。 +默认情况下,织体结构中的内容行都没有地址或标签,这意味着它们无法被转向到其他地方,也就无法进行测试。 在最基本的织体结构中,玩家的选择会改变织体的路径和他们所看到的内容,但一旦织体看完了,这些选择和路径就会被遗忘。 不过如果我们想记住玩家看过的内容,也是可以的——我们可以使用 `(label_name)` 语法在需要的地方添加标签。 @@ -1287,7 +1304,7 @@ TODO: (向编译器传递 `-c` 的要求) - (top) -一旦贴上标签,收束点就可以像结点和针脚一样被分道或是测试。这意味着您可以利用之前的决定来改变织体中的后续结果,同时继续保持清晰、可靠且继续发展等织体的所有优点。 +一旦贴上标签,收束点就可以像结点和针脚一样被转向或是测试。这意味着您可以利用之前的决定来改变织体中的后续结果,同时继续保持清晰、可靠且继续发展等织体的所有优点。 选项也可以用括号来打标签,就像收束点一样。但是标签括号需要写在每个选项的文本之前。 @@ -1309,7 +1326,7 @@ TODO: (向编译器传递 `-c` 的要求) * {get_out} [把他推到一边] // 当你选择了威胁他时 你把他粗暴地推到一边,他瞪着眼睛看着你,然后拔出了剑! - -> fight_guard // 这个路径将分道出这个织体 + -> fight_guard // 这个路径将转向出这个织体 - “哦……”警卫回应着,然后递给你一个小纸袋。“太妃糖?” @@ -1370,9 +1387,9 @@ TODO: (向编译器传递 `-c` 的要求) - (done) 你谢过了警卫,然后离开了。 -#### 进阶:分道指向到选项|Advanced: diverting to options +#### 进阶:转向指向到选项|Advanced: diverting to options -选项也可以被分道指向:但会直接分道到该选项的输出,*就像选择了该选项一样*。因此,打印的内容将忽略方括号内的文字,如果该选项只能使用一次,它将被标记为次数用尽。 +选项也可以被转向指向:但会直接转向到该选项的输出,*就像选择了该选项一样*。因此,打印的内容将忽略方括号内的文字,如果该选项只能使用一次,它将被标记为次数用尽。 - (opts) * [向警卫做鬼脸] @@ -1407,7 +1424,7 @@ TODO: (向编译器传递 `-c` 的要求) * 我什么也没说[],我的老大也什么都没说。 - 我们彼此再次陷入了沉默。 -注意上方示例中的二级收束点:这里其实真没什么好收束的,但是它为我们提供了一个方便的地方来分道第二个选项。 +注意上方示例中的二级收束点:这里其实真没什么好收束的,但是它为我们提供了一个方便的地方来转向第二个选项。 # 第 3 部分:变量和逻辑|Part 3: Variables and Logic @@ -1439,9 +1456,9 @@ TODO: (向编译器传递 `-c` 的要求) * { not knows_about_wager } “先生,我们为什么要旅行?”[]我问到。 * { knows_about_wager } 我认真思考着我们奇怪的冒险[],这件事真的可行吗? -#### 进阶:将分道存储为变量|Advanced: storing diverts as variables +#### 进阶:将转向存储为变量|Advanced: storing diverts as variables -“分道”语句本身实际上也是一种值,可以被存储、更改和转道。 +“转向”语句本身实际上也是一种值,可以被存储、更改和转道。 VAR current_epilogue = -> everybody_dies @@ -1812,7 +1829,7 @@ if 语法查询从开始到当前所产生的所有文本、选项还有结果 (事实上,因为这种定义足够有用,所以 **Ink** 提供了一种特殊的结点类型,称为“函数 (Function)”,对它进行一些限制,就可以返回一个值。详见函数章节。) -#### 进阶:将分道目标作为参数来传递|Advanced: sending divert targets as parameters +#### 进阶:将转向目标作为参数来传递|Advanced: sending divert targets as parameters 结点和针脚的地址是一种值,用 `->` 字符表示,可以被存储和传递。因此以下代码是合规的,常用且非常有用: @@ -1842,7 +1859,7 @@ ChatGPT 解析: 总结:这种结构的目的是让 generic_sleep 结点可以根据传入的参数跳转到不同的“醒来”位置,使其能够在不同场景中复用,增强代码的灵活性。 -译者注:这段说人话的意思就是,临时使用上方结点给出的分道参数替换下方的分道来做到临时接入不同的结点。 +译者注:这段说人话的意思就是,临时使用上方结点给出的转向参数替换下方的转向来做到临时接入不同的结点。 ……请注意 `generic_sleep` 定义中的 `->`:这是 **Ink** 中唯一一个需要将参数类型化的情况,因为否则很容易犯如下错误: @@ -1850,7 +1867,7 @@ ChatGPT 解析: 你躺下并闭上了眼睛。 -> generic_sleep (waking_in_the_hut) -……这将会让 waking_in_the_hut 的读取计数传递到 sleeping 结点,然后试图分道跳转到它。 +……这将会让 waking_in_the_hut 的读取计数传递到 sleeping 结点,然后试图转向跳转到它。 ## 5) 函数|Functions @@ -1860,7 +1877,7 @@ ChatGPT 解析: 一个函数: - 不能包含针脚 (Stitchs) -- 不能使用分道或提供选择 +- 不能使用转向或提供选择 - 可以调用其他函数 - 可以包含已打印输出的内容 - 可以返回任何类型的值 @@ -1888,7 +1905,7 @@ ChatGPT 解析: * {say_yes_to_everything()} 'Yes.' -与其他编程语言蕾丝,一个函数再一次执行完毕后,要将流程返回到调用它的位置——尽管函数不能进行分道,但是仍然函数仍然可以调用其它函数。 +与其他编程语言蕾丝,一个函数再一次执行完毕后,要将流程返回到调用它的位置——尽管函数不能进行转向,但是仍然函数仍然可以调用其它函数。 === function say_no_to_nothing === ~ return say_yes_to_everything() @@ -1904,7 +1921,7 @@ ChatGPT 解析: ~ stamina = stamina - x } -……要记得函数是不能进行分道的,所以上面这些代码虽然可以防止耐力值 (Stamina) 变为负数,但是不会让耐力归零的玩家死亡。 +……要记得函数是不能进行转向的,所以上面这些代码虽然可以防止耐力值 (Stamina) 变为负数,但是不会让耐力归零的玩家死亡。 ### 函数可以直接在同一行内被调用|Functions can be called inline @@ -2146,13 +2163,13 @@ ChatGPT 解析: 然而,如果被调用的故事段更加复杂——比如它跨越了多个结点 (knots) 怎么办?按照上述方法,我们不得不在结点之间不断传递“返回位置”的参数,以确保每次都知道返回到哪里。 -译者注:以上的示例是表示,如果不使用“隧道”的写法,要怎么在分道到另一个地方并执行完毕后分道回原来的位置。接下来才要说 **Ink** 为这个操作提供的“隧道”语法。 +译者注:以上的示例是表示,如果不使用“隧道”的写法,要怎么在转向到另一个地方并执行完毕后转向回原来的位置。接下来才要说 **Ink** 为这个操作提供的“隧道”语法。 -为了解决这一问题,**Ink** 将这一功能集成到了语言本身,提供了一种新类型的分道 (Divert),其功能类似于子流程,被称为“隧道(Tunnel)”。 +为了解决这一问题,**Ink** 将这一功能集成到了语言本身,提供了一种新类型的转向 (Divert),其功能类似于子流程,被称为“隧道(Tunnel)”。 ### 隧道运行子故事|Tunnels run sub-stories -隧道的语法看起来就像是一个分道,只是在分到的最后再另一个分道: +隧道的语法看起来就像是一个转向,只是在分到的最后再另一个转向: -> crossing_the_date_line -> @@ -2167,7 +2184,7 @@ ChatGPT 解析: 请注意,隧道结点并不以特殊的方式声明,因此编译器并不会在编译时检查隧道是否确实以 `->->` 语句结束,这种检查只会在运行时进行。因此,你需要仔细检查,以确保所有进入了隧道的流程都能再正确的返回出来。 -隧道可以串联在一起,也可以使用普通分道结束: +隧道可以串联在一起,也可以使用普通转向结束: ... // 运行隧道后跳转到 'done' @@ -2259,20 +2276,20 @@ ChatGPT 解析: 隧道是基于调用栈的,因此可以安全地递归调用。 -## 2) 缝合|Threads +## 2) 缝合线|Threads 到目前为止,尽管 **Ink** 中有大量分支和跳转,但一切都是线性的。然而,作者实际上可以将故事“分叉”为不同的子部分,以涵盖更多可能的玩家行为。 -我们称这种机制为“缝合 (Thread)”,尽管它并不完全符合计算机科学中“线程(也是 Thread)”的定义:因为这更像是从不同地方“缝合”新内容到当前故事中。 +我们称这种机制为“缝合线 (Thread)”,尽管它并不完全符合计算机科学中“线程(也是 Thread)”的定义:因为这更像是从不同地方“缝合线”新内容到当前故事中。 -需要注意的是,这是一个高级功能:一旦涉及缝合,故事的设计会变得更加复杂! +需要注意的是,这是一个高级功能:一旦涉及缝合线,故事的设计会变得更加复杂! -### 缝合把多个部分合并到一起|Threads join multiple sections together +### 缝合线把多个部分合并到一起|Threads join multiple sections together -缝合操作允许你将多个来源的内容一次性组合成一个部分,例如: +缝合线操作允许你将多个来源的内容一次性组合成一个部分,例如: == thread_example == - 我有点头疼;缝合操作实在是有点难以理解。 + 我有点头疼;缝合线操作实在是有点难以理解。 <- conversation <- walking @@ -2295,24 +2312,24 @@ ChatGPT 解析: 这就让故事的多个部分组合到一起成了一个独立的部分: - 我有点头疼;缝合操作实在是有点难以理解。 + 我有点头疼;缝合线操作实在是有点难以理解。 对于蒙蒂和我来说,这真是一个紧张的时刻。 我们继续沿着尘土飞扬的道路走。 1: “你今天午餐吃了什么?” 2: “天气不错啊,” 3: 继续走 -当遇到类似 `<- conversation` 这样的缝合语句时,编译器就会将故事流分叉过来。首个缝口 (fork) 将运行 `conversation` 中的内容,并收集其中的所有选项。一旦该缝口 (fork) 的内容结束,编译器将继续运行其他缝口 (fork) 。 +当遇到类似 `<- conversation` 这样的缝合线语句时,编译器就会将故事流分叉过来。首个缝口 (fork) 将运行 `conversation` 中的内容,并收集其中的所有选项。一旦该缝口 (fork) 的内容结束,编译器将继续运行其他缝口 (fork) 。 所有的内容都会收集并展示给玩家。但当玩家选择之后,引擎就会跳转到那个分叉后折叠并丢弃其它分叉。 另外需要注意的是,全局变量*不会*被分叉,包括结点和针脚的读取计数。 -### Uses of threads +### 缝合线的用法|Uses of threads -In a normal story, threads might never be needed. +在常规故事中,可能永远不需要用到缝合线。 -But for games with lots of independent moving parts, threads quickly become essential. Imagine a game in which characters move independently around a map: the main story hub for a room might look like the following: +但对于拥有大量独立移动部件的游戏,缝合线很快会变得不可或缺。想象一个角色在地图上独立移动的游戏:某个房间的主故事结点可能如下所示: CONST HALLWAY = 1 CONST OFFICE = 2 @@ -2328,14 +2345,14 @@ But for games with lots of independent moving parts, threads quickly become esse == hallway == <- characters_present(HALLWAY) - * [Drawers] -> examine_drawers - * [Wardrobe] -> examine_wardrobe - * [Go to Office] -> go_office + * [抽屉] -> examine_drawers + * [衣柜] -> examine_wardrobe + * [前往办公室] -> go_office - -> run_player_location = examine_drawers - // etc... + // 等等…… - // Here's the thread, which mixes in dialogue for characters you share the room with at the moment. + // 这里是缝合线,它会混入当前同房间角色的对话 == characters_present(room) { generals_location == room: @@ -2347,33 +2364,30 @@ But for games with lots of independent moving parts, threads quickly become esse -> DONE == general_conversation - * [Ask the General about the bloodied knife] - "It's a bad business, I can tell you." + * [询问将军关于带血的刀] + “这事可不简单,我告诉你。” - -> run_player_location == doctor_conversation - * [Ask the Doctor about the bloodied knife] - "There's nothing strange about blood, is there?" + * [询问医生关于带血的刀] + “血迹有什么好奇怪的?” - -> run_player_location +特别要注意:我们需要明确的方法让进入旁缝合线的玩家返回主流程。大多数情况下,缝合线要么需要将参数告知返回位置,要么需要直接结束当前故事段落。 +### 何时结束一个旁缝合线?|When does a side-thread end? -Note in particular, that we need an explicit way to return the player who has gone down a side-thread to return to the main flow. In most cases, threads will either need a parameter telling them where to return to, or they'll need to end the current story section. - - -### When does a side-thread end? +当旁缝合线无流程可处理时便会结束:需注意,它们会暂存选项稍后显示(这与隧道不同,隧道会收集选项、立即显示并持续跟进,直到遇到明确的返回指令,该过程可能跨越多个步骤)。 -Side-threads end when they run out of flow to process: and note, they collect up options to display later (unlike tunnels, which collect options, display them and follow them until they hit an explicit return, possibly several moves later). +有时,一个缝合线没有内容可提供——也许是与某个角色的对话已经结束,也许是我们还没有写完。在这种情况下,我们必须明确标记缝合线的结束。 -Sometimes a thread has no content to offer - perhaps there is no conversation to have with a character after all, or perhaps we have simply not written it yet. In that case, we must mark the end of the thread explicitly. +如果我们不这样做,内容的结尾可能是一个故事漏洞或悬而未决的故事缝合线,我们希望编译器能告诉我们这些情况。 -If we didn't, the end of content might be a story-bug or a hanging story thread, and we want the compiler to tell us about those. +### 使用 `-> DONE`|Using `-> DONE` -### Using `-> DONE` +当需要显式标记缝合线已终结时,使用 `-> DONE` 指令:意为"流程在此处主动终止"。若未标记,可能触发警告——游戏虽可继续运行,但会提醒存在未闭合的的叙事单元。 -In cases where we want to mark the end of a thread, we use `-> DONE`: meaning "the flow intentionally ends here". If we don't, we might end up with a warning message - we can still play the game, but it's a reminder that we have unfinished business. - -The example at the start of this section will generate a warning; it can be fixed as follows: +本节开头的示例会生成警告,修正方式如下: == thread_example == I had a headache; threading is hard to get your head around. @@ -2381,69 +2395,68 @@ The example at the start of this section will generate a warning; it can be fixe <- walking -> DONE -The extra DONE tells ink that the flow here has ended and it should rely on the threads for the next part of the story. +此处添加的 `DONE` 会告知 ink 引擎:当前流程已终结,后续故事应依赖其他部分推进。 -Note that we don't need a `-> DONE` if the flow ends with options that fail their conditions. The engine treats this as a valid, intentional, end of flow state. +请注意:若流程因条件分支未满足而自然终止,则无需 `-> DONE`。引擎会将其视为合规的流程终止状态。 -**You do not need a `-> DONE` after an option has been chosen**. Once an option is chosen, a thread is no longer a thread - it is simply the normal story flow once more. +**当玩家做出选择后,无需再使用`-> DONE`**。因为一旦选项被选定,该旁缝合线则立即脱离跳转出了缝合线,重新融入主线叙事流。 -Using `-> END` in this case will not end the thread, but the whole story flow. (And this is the real reason for having two different ways to end flow.) +**在此场景中使用`-> END`不会终止当前缝合线,而是会直接终结整个叙事流**(这也正是我们需要两种不同流程终止方式的根本原因)。 -#### Example: adding the same choice to several places +#### 示例:在多个位置添加相同选项|Example: adding the same choice to several places -Threads can be used to add the same choice into lots of different places. When using them this way, it's normal to pass a divert as a parameter, to tell the story where to go after the choice is done. +缝合线可用于在多个不同位置复用相同的选项。这种用法通常需要传入转向作为参数,以指定选项执行完毕后故事应跳转的位置。 === outside_the_house - The front step. The house smells. Of murder. And lavender. + 门前台阶。屋子里飘出混杂薰衣草香气的凶案气息。 - (top) <- review_case_notes(-> top) - * [Go through the front door] - I stepped inside the house. + * [进入正门] + 我迈步走进屋内。 -> the_hallway - * [Sniff the air] - I hate lavender. It makes me think of soap, and soap makes me think about my marriage. + * [嗅闻空气] + 我讨厌薰衣草。它让我想起肥皂,而肥皂让我想起我的婚姻。 -> top === the_hallway - The hallway. Front door open to the street. Little bureau. + 门厅。正门通向街道,角落摆着小柜子。 - (top) <- review_case_notes(-> top) - * [Go through the front door] - I stepped out into the cool sunshine. + * [走出正门] + 我踏入门外凉爽的阳光中。 -> outside_the_house - * [Open the bureau] - Keys. More keys. Even more keys. How many locks do these people need? + * [打开柜子] + 钥匙。更多的钥匙。甚至还有钥匙。这家人到底需要多少把锁? -> top === review_case_notes(-> go_back_to) + {not done || TURNS_SINCE(-> done) > 10} - [Review my case notes] - // the conditional ensures you don't get the option to check repeatedly - {I|Once again, I} flicked through the notes I'd made so far. Still not obvious suspects. + [查阅案件笔记] + // 使用条件判断以确保不会频繁出现该选项 + {我|又一次,我} 快速翻看目前的调查记录。依然没有明显嫌疑人。 - (done) -> go_back_to -Note this is different than a tunnel, which runs the same block of content but doesn't give a player a choice. So a layout like: +需注意这与隧道的区别:隧道会执行相同内容块但不提供玩家选择权。例如以下两种写法效果相同: <- childhood_memories(-> next) - * [Look out of the window] - I daydreamed as we rolled along... - - (next) Then the whistle blew... + * [望向窗外] + 车轮滚动中,我陷入恍惚…… + - (next) 直到汽笛声响起…… -might do exactly the same thing as: +大致上是等价于: - * [Remember my childhood] + * [回忆童年] -> think_back -> - * [Look out of the window] - I daydreamed as we rolled along... - - (next) Then the whistle blew... - -but as soon as the option being threaded in includes multiple choices, or conditional logic on choices (or any text content, of course!), the thread version becomes more practical. + * [望向窗外] + 车轮滚动中,我陷入恍惚…… + - (next) T直到汽笛声响起…… +不过,当需要复用的选项包含多重选择、条件分支逻辑(或任何文本内容!)时,缝合线方案就会显示出其真正的优势。 -#### Example: organisation of wide choice points +#### 示例:大规模选项的组织管理|Example: organisation of wide choice points -A game which uses ink as a script rather than a literal output might often generate very large numbers of parallel choices, intended to be filtered by the player via some other in-game interaction - such as walking around an environment. Threads can be useful in these cases simply to divide up choices. +当游戏将 ink 脚本作为底层逻辑而非直接输出时,常会遇到需要生成大量并行选项的情况——这些选项通常需要通过游戏内交互(如环境探索)进行筛选。此时,缝合线就能有效发挥模块化分割的作用。 ``` === the_kitchen @@ -2452,17 +2465,16 @@ A game which uses ink as a script rather than a literal output might often gener <- cupboards(-> top) <- room_exits = drawers (-> goback) - // choices about the drawers... + // 抽屉相关选项…… ... = cupboards(-> goback) - // choices about cupboards + // 有关橱柜的选项 ... = room_exits - // exits; doesn't need a "return point" as if you leave, you go elsewhere + // 出口;不需要"返回点",因为离开就意味着前往其他地方 ... ``` - # 第 5 部分:进阶状态追踪|Part 5: Advanced State Tracking Games with lots of interaction can get very complex, very quickly and the writer's job is often as much about maintaining continuity as it is about content. From 63b25646d085cf7c1cd82e919022146b732bc0e6 Mon Sep 17 00:00:00 2001 From: Ander-Index Date: Wed, 7 May 2025 15:38:08 +0800 Subject: [PATCH 16/22] Update date --- Documentation/WritingWithInk_Simplified-Chinese.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/WritingWithInk_Simplified-Chinese.md b/Documentation/WritingWithInk_Simplified-Chinese.md index 0bbd6030..1ecf0f7e 100644 --- a/Documentation/WritingWithInk_Simplified-Chinese.md +++ b/Documentation/WritingWithInk_Simplified-Chinese.md @@ -4,15 +4,15 @@ 简体中文教程是从英文原文翻译而来。可能会存在版本滞后性,故入与英文版有出入,请与英文版为准。 -部分不重要的段落由 ChatGPT 或 DeepL 组合翻译。 +翻译时使用了 DeepSeek、ChatGPT 与 DeepL 辅助。 -重要的部分完全由人工翻译。 +我可以确保每句翻译我都校正过。 译者:王洛木 (Nomo_Wang@outlook.com),如果您有关于翻译的问题要提,且不想要占用 Discussion 资源,可以发邮件给译者。 翻译时的 Ink 版本:1.2.0 -翻译最后更新时间:2024 年 12 月 13 日 +翻译最后更新时间:2025 年 05 月 07 日 翻译仍在更新。 From 2cfb0e60bed2c3ca655ad1a003b80805af1170d8 Mon Sep 17 00:00:00 2001 From: Ander-Index Date: Mon, 12 May 2025 11:09:20 +0800 Subject: [PATCH 17/22] Update to CP5-2 --- .../WritingWithInk_Simplified-Chinese.md | 169 +++++++++--------- 1 file changed, 85 insertions(+), 84 deletions(-) diff --git a/Documentation/WritingWithInk_Simplified-Chinese.md b/Documentation/WritingWithInk_Simplified-Chinese.md index 1ecf0f7e..9e0dad4d 100644 --- a/Documentation/WritingWithInk_Simplified-Chinese.md +++ b/Documentation/WritingWithInk_Simplified-Chinese.md @@ -163,12 +163,12 @@ - [示例:在多个位置添加相同选项|Example: adding the same choice to several places](#示例在多个位置添加相同选项example-adding-the-same-choice-to-several-places) - [示例:大规模选项的组织管理|Example: organisation of wide choice points](#示例大规模选项的组织管理example-organisation-of-wide-choice-points) - [第 5 部分:进阶状态追踪|Part 5: Advanced State Tracking](#第-5-部分进阶状态追踪part-5-advanced-state-tracking) - - [Note: New feature alert!](#note-new-feature-alert) - - [1) Basic Lists](#1-basic-lists) - - [2) Reusing Lists](#2-reusing-lists) - - [States can be used repeatedly](#states-can-be-used-repeatedly) - - [List values can share names](#list-values-can-share-names) - - [Advanced: a LIST is actually a variable](#advanced-a-list-is-actually-a-variable) + - [注意:新功能提醒!|Note: New feature alert!](#注意新功能提醒note-new-feature-alert) + - [1) 基础列表|Basic Lists](#1-基础列表basic-lists) + - [2) 复用列表|Reusing Lists](#2-复用列表reusing-lists) + - [状态是可以被重复使用的|States can be used repeatedly](#状态是可以被重复使用的states-can-be-used-repeatedly) + - [列表的值可以共享名称|List values can share names](#列表的值可以共享名称list-values-can-share-names) + - [进阶:LIST 本质上是一个变量|Advanced: a LIST is actually a variable](#进阶list-本质上是一个变量advanced-a-list-is-actually-a-variable) - [3) List Values](#3-list-values) - [Converting values to numbers](#converting-values-to-numbers) - [Converting numbers to values](#converting-numbers-to-values) @@ -2477,159 +2477,160 @@ ChatGPT 解析: # 第 5 部分:进阶状态追踪|Part 5: Advanced State Tracking -Games with lots of interaction can get very complex, very quickly and the writer's job is often as much about maintaining continuity as it is about content. +交互密集的游戏会迅速变得异常复杂,作者的工作不仅关乎内容创作,同样需要维护叙事连贯性。 -This becomes particularly important if the game text is intended to model anything - whether it's a game of cards, the player's knowledge of the gameworld so far, or the state of the various light-switches in a house. -**Ink** does not provide a full world-modelling system in the manner of a classic parser IF authoring language - there are no "objects", no concepts of "containment" or being "open" or "locked". However, it does provide a simple yet powerful system for tracking state-changes in a very flexible way, to enable writers to approximate world models where necessary. +当游戏文本需要为任何事物建模时,这一点都尤为重要——无论是卡牌游戏规则、玩家当前对游戏世界的认知,还是房屋内各类电灯开关的状态。 -#### Note: New feature alert! +**Ink** 并未像传统交互小说创作语言那样提供完整的世界建模系统——这里既没有"对象"概念,也不支持"容器关系"或"开启与锁定"状态。但它通过一套简洁而强大的系统,以高度灵活的方式追踪状态变化,使作者能在必要时构建近似的世界模型。 -This feature is very new to the language. That means we haven't begun to discover all the ways it might be used - but we're pretty sure it's going to be useful! So if you think of a clever usage we'd love to know! +#### 注意:新功能提醒!|Note: New feature alert! +该功能是语言中的全新特性。这意味着我们尚未发掘其所有可能的用途——但我们非常确定它将会很有用!如果您想到了任何巧妙的用法,我们很乐意听取您的建议! -## 1) Basic Lists +## 1) 基础列表|Basic Lists -The basic unit of state-tracking is a list of states, defined using the `LIST` keyword. Note that a list is really nothing like a C# list (which is an array). +状态追踪的基本单位是状态列表,使用 LIST 关键字定义。请注意,此列表与 C# 中的列表(即数组)完全不同。 -For instance, we might have: +举个例子,假定: LIST kettleState = cold, boiling, recently_boiled -This line defines two things: firstly three new values - `cold`, `boiling` and `recently_boiled` - and secondly, a variable, called `kettleState`, to hold these states. +这行代码定义了两项内容:首先是三个新状态值——`cold`(冷)、`boiling`(沸腾)和 `recently_boiled`(刚煮沸)——其次是一个名为`kettleState`的变量,用于存储这些状态。 -We can tell the list what value to take: +然后,我们可以指定列表的初始值: ~ kettleState = cold -We can change the value: +可以改变状态的值: - * [Turn on kettle] - The kettle begins to bubble and boil. + * [打开水壶] + 水壶开始冒泡沸腾。 ~ kettleState = boiling -We can query the value: +可以查询当前状态: - * [Touch the kettle] + * [触摸水壶] { kettleState == cold: - The kettle is cool to the touch. + 水壶摸起来凉凉的。 - else: - The outside of the kettle is very warm! + 水壶外壁非常烫! } -For convenience, we can give a list a value when it's defined using a bracket: +为方便起见,可以在一开始定义列表时就用括号指定初始值: LIST kettleState = cold, (boiling), recently_boiled - // at the start of the game, this kettle is switched on. Edgy, huh? + // 游戏开始时,这个水壶就是开着的,嘻嘻。 -...and if the notation for that looks a bit redundant, there's a reason for that coming up in a few subsections time. +……如果这种语法看起来有点多余,我们将在后续小节解释原因。 +## 2) 复用列表|Reusing Lists - -## 2) Reusing Lists - -The above example is fine for the kettle, but what if we have a pot on the stove as well? We can then define a list of states, but put them into variables - and as many variables as we want. +上述水壶的例子已经足够,但如果炉子上还有个锅呢?所以我们可以先定义一个状态列表,然后将其赋值给任意数量的变量。 LIST daysOfTheWeek = Monday, Tuesday, Wednesday, Thursday, Friday VAR today = Monday VAR tomorrow = Tuesday -### States can be used repeatedly +译者注:总结来说就是用这个语法一次性创建多个变量,并使用一个带名字的容器给这批内容装起来。 -This allows us to use the same state machine in multiple places. +### 状态是可以被重复使用的|States can be used repeatedly - LIST heatedWaterStates = cold, boiling, recently_boiled - VAR kettleState = cold - VAR potState = cold +这样我们就可以在多个地方复用同一个状态机器。 - * {kettleState == cold} [Turn on kettle] - The kettle begins to boil and bubble. - ~ kettleState = boiling - * {potState == cold} [Light stove] - The water in the pot begins to boil and bubble. - ~ potState = boiling + LIST heatedWaterStates = cold, boiling, recently_boiled // 创建水的状态列表:冷的、沸腾、刚煮沸 + VAR kettleState = cold // 水壶是冷的 + VAR potState = cold // 锅是冷的 -But what if we add a microwave as well? We might want start generalising our functionality a bit: + * {kettleState == cold} [打开水壶] // 如果水壶是冷的 + 水壶开始沸腾冒泡。 + ~ kettleState = boiling // 将水壶设为沸腾 + * {potState == cold} [点燃炉灶] + 锅里的水开始沸腾冒泡。 + ~ potState = boiling // 将锅设为沸腾 - LIST heatedWaterStates = cold, boiling, recently_boiled - VAR kettleState = cold - VAR potState = cold - VAR microwaveState = cold +但如果再加个微波炉呢?那我们可能需要稍微做点功能泛化: - === function boilSomething(ref thingToBoil, nameOfThing) - The {nameOfThing} begins to heat up. - ~ thingToBoil = boiling + LIST heatedWaterStates = cold, boiling, recently_boiled // 与上面的列表相同 + VAR kettleState = cold // 水壶是冷的 + VAR potState = cold // 锅是冷的 + VAR microwaveState = cold // 微波炉也是冷的 - === do_cooking - * {kettleState == cold} [Turn on kettle] - {boilSomething(kettleState, "kettle")} - * {potState == cold} [Light stove] - {boilSomething(potState, "pot")} - * {microwaveState == cold} [Turn on microwave] - {boilSomething(microwaveState, "microwave")} + === function boilSomething(ref thingToBoil, nameOfThing) // 函数:煮沸某物(参数 要煮沸的物品, 物品名称) + 那个{nameOfThing}开始加热了。 + ~ thingToBoil = boiling // 设定要煮沸的物品状态为煮沸 -or even... + === do_cooking // 进行烹饪 + * {kettleState == cold} [打开水壶] // 水壶是冷的 + {boilSomething(kettleState, "kettle")} // 调用上面煮沸某物的函数(水壶状态,水壶) + * {potState == cold} [点燃炉灶] // 灶台是冷的 + {boilSomething(potState, "pot")} // 调用上面的函数,由函数把它的状态改为“煮沸” + * {microwaveState == cold} [打开微波炉] // 微波炉是冷的 + {boilSomething(microwaveState, "microwave")} // 同理 +甚至可以…… LIST heatedWaterStates = cold, boiling, recently_boiled VAR kettleState = cold VAR potState = cold VAR microwaveState = cold - === cook_with(nameOfThing, ref thingToBoil) - + {thingToBoil == cold} [Turn on {nameOfThing}] - The {nameOfThing} begins to heat up. - ~ thingToBoil = boiling - -> do_cooking.done + // 上面还是那个列表和初始状态 + + === cook_with(nameOfThing, ref thingToBoil) // 用某物煮沸(物品名称,参数 要煮的东西) + + {thingToBoil == cold} [打开{nameOfThing}] // 某个要煮的东西是冷的,打开对应的物品名称 + 那个{nameOfThing}开始加热了。 // 那个“物品名称”开始加热了。 + ~ thingToBoil = boiling // 把要煮的东西状态设定为沸腾 + -> do_cooking.done // 转到 do_cooking 结点中的 done 针脚 - === do_cooking - <- cook_with("kettle", kettleState) + === do_cooking // 烹饪(上面要煮的东西与器皿的对应关系在这里) + <- cook_with("kettle", kettleState) // <- cook_with("pot", potState) <- cook_with("microwave", microwaveState) - (done) -Note that the "heatedWaterStates" list is still available as well, and can still be tested, and take a value. +注意:"加热水状态"这个列表仍然可用,仍然可以被检测和赋值。 -#### List values can share names +#### 列表的值可以共享名称|List values can share names -Reusing lists brings with it ambiguity. If we have: +复用列表会带来命名歧义。如果我们有: - LIST colours = red, green, blue, purple - LIST moods = mad, happy, blue + LIST colours = red, green, blue, purple // 列表 颜色:红、绿、蓝、紫 + LIST moods = mad, happy, blue // 列表 情绪:愤怒、开心、忧郁 + // 译者注:英语里,blue 可以同时翻译为“蓝色”或“忧郁”,就和中文中的一词多义一样。 - VAR status = blue + VAR status = blue // 设定 状态:blue -... how can the compiler know which blue you meant? +……那编译器怎么知道您指的是哪个列表中的 blue? -We resolve these using a `.` syntax similar to that used for knots and stitches. +所以我们通过使用类似结点和针脚中用到的 `.` 语法来结局这个问题。 VAR status = colours.blue -...and the compiler will issue an error until you specify. +……编译器会明确要求您指明您在使用哪个列表中的哪个状态,不然就会一直报错。 -Note the "family name" of the state, and the variable containing a state, are totally separate. So +注意:状态组的"家族名"(合集名称)与包含状态的变量是完全独立的。因此 - { statesOfGrace == statesOfGrace.fallen: - // is the current state "fallen" + { statesOfGrace == statesOfGrace.fallen: + // 检查 恩典状态 是否为:恩典状态.堕落 } -... is correct. - +……它也是合规的。 -#### Advanced: a LIST is actually a variable +#### 进阶:LIST 本质上是一个变量|Advanced: a LIST is actually a variable -One surprising feature is the statement +有个令人惊讶的特性是 LIST statesOfGrace = ambiguous, saintly, fallen -actually does two things simultaneously: it creates three values, `ambiguous`, `saintly` and `fallen`, and gives them the name-parent `statesOfGrace` if needed; and it creates a variable called `statesOfGrace`. +这个语句实际上同时完成了两件事:它创建了三个值,`ambiguous`, `saintly` 和 `fallen`,然后声明了一个名为 `statesOfGrace` 的普通变量 -And that variable can be used like a normal variable. So the following is valid, if horribly confusing and a bad idea: +这意味着这个变量可以像普通变量一样被重新赋值。所以以下写法极易造成混淆且不推荐,但语法上是合规的: LIST statesOfGrace = ambiguous, saintly, fallen - ~ statesOfGrace = 3.1415 // set the variable to a number not a list value + ~ statesOfGrace = 3.1415 // 将变量赋值为了一个数字而不是列表中的某个值 -...and it wouldn't preclude the following from being fine: +……但这并不影响以下用法的正确性: ~ temp anotherStateOfGrace = statesOfGrace.saintly From f9465968a3016edb2ad280648698a8647c65a866 Mon Sep 17 00:00:00 2001 From: Ander-Index Date: Tue, 20 May 2025 15:50:00 +0800 Subject: [PATCH 18/22] Update WritingWithInk_Simplified-Chinese.md CP 5-3 DONE. --- .../WritingWithInk_Simplified-Chinese.md | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/Documentation/WritingWithInk_Simplified-Chinese.md b/Documentation/WritingWithInk_Simplified-Chinese.md index 9e0dad4d..16bf1dc5 100644 --- a/Documentation/WritingWithInk_Simplified-Chinese.md +++ b/Documentation/WritingWithInk_Simplified-Chinese.md @@ -169,10 +169,10 @@ - [状态是可以被重复使用的|States can be used repeatedly](#状态是可以被重复使用的states-can-be-used-repeatedly) - [列表的值可以共享名称|List values can share names](#列表的值可以共享名称list-values-can-share-names) - [进阶:LIST 本质上是一个变量|Advanced: a LIST is actually a variable](#进阶list-本质上是一个变量advanced-a-list-is-actually-a-variable) - - [3) List Values](#3-list-values) - - [Converting values to numbers](#converting-values-to-numbers) - - [Converting numbers to values](#converting-numbers-to-values) - - [Advanced: defining your own numerical values](#advanced-defining-your-own-numerical-values) + - [3) 值的顺序|List Values](#3-值的顺序list-values) + - [将值转换为数字|Converting values to numbers](#将值转换为数字converting-values-to-numbers) + - [将数字转换为值|Converting numbers to values](#将数字转换为值converting-numbers-to-values) + - [高级:自定义数值映射|Advanced: defining your own numerical values](#高级自定义数值映射advanced-defining-your-own-numerical-values) - [4) Multivalued Lists](#4-multivalued-lists) - [Lists are boolean sets](#lists-are-boolean-sets) - [Assiging multiple values](#assiging-multiple-values) @@ -2637,50 +2637,50 @@ ChatGPT 解析: -## 3) List Values +## 3) 值的顺序|List Values -When a list is defined, the values are listed in an order, and that order is considered to be significant. In fact, we can treat these values as if they *were* numbers. (That is to say, they are enums.) +当定义一个列表时,列出的值必然是有顺序的,这个顺序也是有意义的。实际上,我们可以把这些值当作数字来处理(也就是说,它们本质上是枚举类型)。 - LIST volumeLevel = off, quiet, medium, loud, deafening - VAR lecturersVolume = quiet - VAR murmurersVolume = quiet + LIST volumeLevel = off, quiet, medium, loud, deafening // 创建一个“音量级别”的列表,列表里有“关闭”、“安静”、“中等”、“响亮”、“震耳欲聋” + VAR lecturersVolume = quiet // 创建一个“讲师音量”的变量,并设定为“安静” + VAR murmurersVolume = quiet // 创建一个“窃窃私语音量”的变量,并设定为“安静” - { lecturersVolume < deafening: - ~ lecturersVolume++ + { lecturersVolume < deafening: // 如果“讲师音量”小于“震耳欲聋” + ~ lecturersVolume++ // 那就就提高一级“讲师音量” - { lecturersVolume > murmurersVolume: - ~ murmurersVolume++ - The murmuring gets louder. + { lecturersVolume > murmurersVolume: // 如果“讲师音量”大于“窃窃私语音量” + ~ murmurersVolume++ // 那么提高一级“窃窃私语音量” + 窃窃私语声变得更大了。 } } -The values themselves can be printed using the usual `{...}` syntax, but this will print their name. +这些值本身可以通过常规的 `{某种判定条件}` 语法输出,但将直接显示其名称。 - The lecturer's voice becomes {lecturersVolume}. + 讲师的声音变得{lecturersVolume}。 -### Converting values to numbers +### 将值转换为数字|Converting values to numbers -The numerical value, if needed, can be got explicitly using the LIST_VALUE function. Note the first value in a list has the value 1, and not the value 0. +如需获取数值,可使用 LIST_VALUE 函数显式转换。但请注意,列表中第一个值的数值会记录为 1(而非 0)。 - The lecturer has {LIST_VALUE(deafening) - LIST_VALUE(lecturersVolume)} notches still available to him. + 讲师还有{LIST_VALUE(deafening) - LIST_VALUE(lecturersVolume)}档音量可以调。 -### Converting numbers to values +### 将数字转换为值|Converting numbers to values -You can go the other way by using the list's name as a function: +您可以通过将列表名称作为函数来使用以进行反向转换: - LIST Numbers = one, two, three - VAR score = one - ~ score = Numbers(2) // score will be "two" + LIST Numbers = one, two, three // 创建一个名为“数字”的列表,里面有“一”、“二”、“三” + VAR score = one // 创建一个名叫“得分”的变量 + ~ score = Numbers(2) // 设定“得分”为“数字”列表中的第2个值,这样之后,“得分”的值就会是”二“。 -### Advanced: defining your own numerical values +### 高级:自定义数值映射|Advanced: defining your own numerical values -By default, the values in a list start at 1 and go up by one each time, but you can specify your own values if you need to. +默认情况下,列表中的值从1开始依次递增,但您也可以根据需要指定自定义数值。 - LIST primeNumbers = two = 2, three = 3, five = 5 + LIST primeNumbers = two = 2, three = 3, five = 5 // 创建一个”质数“列表,并使得“二”的顺序为 2,“三”的顺序为 3,但“五”的顺序为 5。 -If you specify a value, but not the next value, ink will assume an increment of 1. So the following is the same: +如果为某个值指定了数值但未指定下一个值的数值,ink 将默认按上一个值继续递增1号。因此以下定义和上方的例子是等效的: - LIST primeNumbers = two = 2, three, five = 5 + LIST primeNumbers = two = 2, three, five = 5 // 这其中,“三”没有被手动制定为第 3 个,但是由于前一个被指定为第 2 个,所以这里自动递增 1 号即为 3。 ## 4) Multivalued Lists From 30b726caefc758fb08a4c551b5870fe389673e4c Mon Sep 17 00:00:00 2001 From: Ander-Index Date: Wed, 21 May 2025 10:48:05 +0800 Subject: [PATCH 19/22] Update WritingWithInk_Simplified-Chinese.md --- .../WritingWithInk_Simplified-Chinese.md | 48 +++++++++---------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/Documentation/WritingWithInk_Simplified-Chinese.md b/Documentation/WritingWithInk_Simplified-Chinese.md index 16bf1dc5..80d232d5 100644 --- a/Documentation/WritingWithInk_Simplified-Chinese.md +++ b/Documentation/WritingWithInk_Simplified-Chinese.md @@ -12,7 +12,7 @@ 翻译时的 Ink 版本:1.2.0 -翻译最后更新时间:2025 年 05 月 07 日 +翻译最后更新时间:2025 年 05 月 20 日 翻译仍在更新。 @@ -173,10 +173,10 @@ - [将值转换为数字|Converting values to numbers](#将值转换为数字converting-values-to-numbers) - [将数字转换为值|Converting numbers to values](#将数字转换为值converting-numbers-to-values) - [高级:自定义数值映射|Advanced: defining your own numerical values](#高级自定义数值映射advanced-defining-your-own-numerical-values) - - [4) Multivalued Lists](#4-multivalued-lists) - - [Lists are boolean sets](#lists-are-boolean-sets) - - [Assiging multiple values](#assiging-multiple-values) - - [Adding and removing entries](#adding-and-removing-entries) + - [4) 多值列表|Multivalued Lists](#4-多值列表multivalued-lists) + - [列表的本质是布尔集合|Lists are boolean sets](#列表的本质是布尔集合lists-are-boolean-sets) + - [批量赋值|Assiging multiple values](#批量赋值assiging-multiple-values) + - [添加或删除条目|Adding and removing entries](#添加或删除条目adding-and-removing-entries) - [Basic Queries](#basic-queries) - [Testing for emptiness](#testing-for-emptiness) - [Testing for exact equality](#testing-for-exact-equality) @@ -2682,56 +2682,54 @@ ChatGPT 解析: LIST primeNumbers = two = 2, three, five = 5 // 这其中,“三”没有被手动制定为第 3 个,但是由于前一个被指定为第 2 个,所以这里自动递增 1 号即为 3。 +## 4) 多值列表|Multivalued Lists -## 4) Multivalued Lists +以下示例均包含一处刻意添加的不实信息,我们现在现予以修正。列表(以及包含列表值的变量)并非只能存储单一值。 -The following examples have all included one deliberate untruth, which we'll now remove. Lists - and variables containing list values - do not have to contain only one value. +### 列表的本质是布尔集合|Lists are boolean sets -### Lists are boolean sets +列表变量不是包含数字的变量。实际上,列表就像宿舍楼里的出入登记板。它包含一系列名字,每个名字都关联着一个房间号,并通过一个只有开或关状态的滑块来标记"有人"或"外出"状态。 -A list variable is not a variable containing a number. Rather, a list is like the in/out nameboard in an accommodation block. It contains a list of names, each of which has a room-number associated with it, and a slider to say "in" or "out". +可能没任何人在: -Maybe no one is in: + LIST DoctorsInSurgery = Adams, Bernard, Cartwright, Denver, Eamonn // “外科医生”列表,那些值都是人名 - LIST DoctorsInSurgery = Adams, Bernard, Cartwright, Denver, Eamonn - -Maybe everyone is: +可能所有人都在: LIST DoctorsInSurgery = (Adams), (Bernard), (Cartwright), (Denver), (Eamonn) -Or maybe some are and some aren't: +或者可能有些人在有些人不在: LIST DoctorsInSurgery = (Adams), Bernard, (Cartwright), Denver, Eamonn -Names in brackets are included in the initial state of the list. +括号中的名字会被包含在列表初始状态。 -Note that if you're defining your own values, you can place the brackets around the whole term or just the name: +注意:当你自定义数值时,你可以用括号来包裹整个条目或仅包裹名称: - LIST primeNumbers = (two = 2), (three) = 3, (five = 5) +LIST primeNumbers = (two = 2), (three) = 3, (five = 5) // “质数”列表 -#### Assiging multiple values +#### 批量赋值|Assiging multiple values -We can assign all the values of the list at once as follows: +我们可以一次性为列表赋值多个值: ~ DoctorsInSurgery = (Adams, Bernard) ~ DoctorsInSurgery = (Adams, Bernard, Eamonn) -We can assign the empty list to clear a list out: +也可以赋个空值来清空列表: ~ DoctorsInSurgery = () -#### Adding and removing entries - -List entries can be added and removed, singly or collectively. +#### 添加或删除条目|Adding and removing entries +列表的条目可以单独或批量地添加或移除。 ~ DoctorsInSurgery = DoctorsInSurgery + Adams - ~ DoctorsInSurgery += Adams // this is the same as the above + ~ DoctorsInSurgery += Adams // 这与上一行是等效的。 ~ DoctorsInSurgery -= Eamonn ~ DoctorsInSurgery += (Eamonn, Denver) ~ DoctorsInSurgery -= (Adams, Eamonn, Denver) -Trying to add an entry that's already in the list does nothing. Trying to remove an entry that's not there also does nothing. Neither produces an error, and a list can never contain duplicate entries. +尝试添加已存在的条目不会产生任何效果。尝试移除不存在的条目也不会有任何效果。但是前面提到的这两种操作也不会报错,列表永远不会包含重读的条目。 ### Basic Queries From bb8fefe4595df01d2e9f9362d06cc78701781c1f Mon Sep 17 00:00:00 2001 From: Ander-Index Date: Tue, 3 Jun 2025 17:30:52 +0800 Subject: [PATCH 20/22] Update WritingWithInk_Simplified-Chinese.md CP 5-4 DONE --- .../WritingWithInk_Simplified-Chinese.md | 332 ++++++++++-------- 1 file changed, 186 insertions(+), 146 deletions(-) diff --git a/Documentation/WritingWithInk_Simplified-Chinese.md b/Documentation/WritingWithInk_Simplified-Chinese.md index 80d232d5..6472f169 100644 --- a/Documentation/WritingWithInk_Simplified-Chinese.md +++ b/Documentation/WritingWithInk_Simplified-Chinese.md @@ -12,7 +12,7 @@ 翻译时的 Ink 版本:1.2.0 -翻译最后更新时间:2025 年 05 月 20 日 +翻译最后更新时间:2025 年 06 月 03 日 翻译仍在更新。 @@ -177,19 +177,19 @@ - [列表的本质是布尔集合|Lists are boolean sets](#列表的本质是布尔集合lists-are-boolean-sets) - [批量赋值|Assiging multiple values](#批量赋值assiging-multiple-values) - [添加或删除条目|Adding and removing entries](#添加或删除条目adding-and-removing-entries) - - [Basic Queries](#basic-queries) - - [Testing for emptiness](#testing-for-emptiness) - - [Testing for exact equality](#testing-for-exact-equality) - - [Testing for containment](#testing-for-containment) - - [Warning: no lists contain the empty list](#warning-no-lists-contain-the-empty-list) - - [Example: basic knowledge tracking](#example-basic-knowledge-tracking) - - [Example: a doctor's surgery](#example-a-doctors-surgery) - - [Advanced: nicer list printing](#advanced-nicer-list-printing) - - [Lists don't need to have multiple entries](#lists-dont-need-to-have-multiple-entries) - - [The "full" list](#the-full-list) - - [Advanced: "refreshing" a list's type](#advanced-refreshing-a-lists-type) - - [Advanced: a portion of the "full" list](#advanced-a-portion-of-the-full-list) - - [Example: Tower of Hanoi](#example-tower-of-hanoi) + - [基本查询|Basic Queries](#基本查询basic-queries) + - [空值检测|Testing for emptiness](#空值检测testing-for-emptiness) + - [精确相等性测试|Testing for exact equality](#精确相等性测试testing-for-exact-equality) + - [包含性测试|Testing for containment](#包含性测试testing-for-containment) + - [注意:空列表不被任何列表包含|Warning: no lists contain the empty list](#注意空列表不被任何列表包含warning-no-lists-contain-the-empty-list) + - [示例:基础信息追踪|Example: basic knowledge tracking](#示例基础信息追踪example-basic-knowledge-tracking) + - [示例:医生门诊系统|Example: a doctor's surgery](#示例医生门诊系统example-a-doctors-surgery) + - [进阶:优化列表显示|Advanced: nicer list printing](#进阶优化列表显示advanced-nicer-list-printing) + - [列表不是必须包含多个值|Lists don't need to have multiple entries](#列表不是必须包含多个值lists-dont-need-to-have-multiple-entries) + - [“完整”的列表|The "full" list](#完整的列表the-full-list) + - [进阶:“重置”列表的类型|Advanced: "refreshing" a list's type](#进阶重置列表的类型advanced-refreshing-a-lists-type) + - [进阶:获取“完整”列表的子集|Advanced: a portion of the "full" list](#进阶获取完整列表的子集advanced-a-portion-of-the-full-list) + - [示例:汉诺塔|Example: Tower of Hanoi](#示例汉诺塔example-tower-of-hanoi) - [5) Advanced List Operations](#5-advanced-list-operations) - [Comparing lists](#comparing-lists) - ["Distinctly bigger than"](#distinctly-bigger-than) @@ -2703,6 +2703,7 @@ ChatGPT 解析: LIST DoctorsInSurgery = (Adams), Bernard, (Cartwright), Denver, Eamonn 括号中的名字会被包含在列表初始状态。 +(译者注:简单来说,加了括号就是在列表初始化的时候一定在列表。加了括号就是创建了这么个值,但是一开始并不在列表中。) 注意:当你自定义数值时,你可以用括号来包裹整个条目或仅包裹名称: @@ -2732,302 +2733,341 @@ LIST primeNumbers = (two = 2), (three) = 3, (five = 5) // “质数”列表 尝试添加已存在的条目不会产生任何效果。尝试移除不存在的条目也不会有任何效果。但是前面提到的这两种操作也不会报错,列表永远不会包含重读的条目。 -### Basic Queries +### 基本查询|Basic Queries -We have a few basic ways of getting information about what's in a list: +我们有以下几种基本方式来获取列表信息: - LIST DoctorsInSurgery = (Adams), Bernard, (Cartwright), Denver, Eamonn + LIST DoctorsInSurgery = (Adams), Bernard, (Cartwright), Denver, Eamonn // “外科医生”列表,可能有些人在有些人不在。 - {LIST_COUNT(DoctorsInSurgery)} // "2" - {LIST_MIN(DoctorsInSurgery)} // "Adams" - {LIST_MAX(DoctorsInSurgery)} // "Cartwright" - {LIST_RANDOM(DoctorsInSurgery)} // "Adams" or "Cartwright" + {LIST_COUNT(DoctorsInSurgery)} // 查询现在列表中有几个值:"2" + {LIST_MIN(DoctorsInSurgery)} // 查询排序值最小的是谁:"Adams" + {LIST_MAX(DoctorsInSurgery)} // 查询排序值最大的是谁:"Cartwright" + {LIST_RANDOM(DoctorsInSurgery)} // 随机一个值出来:"Adams" 或 "Cartwright" -#### Testing for emptiness +#### 空值检测|Testing for emptiness -Like most values in ink, a list can be tested "as it is", and will return true, unless it's empty. +和 Ink 中的大多数值一样,列表可以直接作为条件测试,非空时会返回 true。 - { DoctorsInSurgery: The surgery is open today. | Everyone has gone home. } + {DoctorsInSurgery: 今日门诊开放。 | 所有人都回家了。} // 如果“外科医生”列表不为空则显示前半句,空则显示后半句。 -#### Testing for exact equality +#### 精确相等性测试|Testing for exact equality -Testing multi-valued lists is slightly more complex than single-valued ones. Equality (`==`) now means 'set equality' - that is, all entries are identical. +测试多值列表比单值列表稍微复杂些。想等运算符(`==`)在这种情况下表示“集合相等”——也就是说,所有的条目必须完全相同。 -So one might say: +所以我们可以这样: { DoctorsInSurgery == (Adams, Bernard): - Dr Adams and Dr Bernard are having a loud argument in one corner. + 亚当斯医生和伯纳德医生正在角落里大声争吵。 } -If Dr Eamonn is in as well, the two won't argue, as the lists being compared won't be equal - DoctorsInSurgery will have an Eamonn that the list (Adams, Bernard) doesn't have. +如果埃蒙 (Eamonn) 医生也在场,两人就不会争吵,因为比较的两个列表不相等——DoctorsInSurgery 列表中有埃蒙,而 (Adams, Bernard) 列表中没有。 -Not equals works as expected: +下面是按照预期运行的不等运算符: - { DoctorsInSurgery != (Adams, Bernard): - At least Adams and Bernard aren't arguing. + { DoctorsInSurgery != (Adams, Bernard): // 如果“外科医生”列表不等于“Adams, Bernard” + 至少亚当斯和伯纳德没在争吵。 } -#### Testing for containment +#### 包含性测试|Testing for containment -What if we just want to simply ask if Adams and Bernard are present? For that we use a new operator, `has`, otherwise known as `?`. +那如果我们只是想知道亚当斯和伯纳德医当时是否在场呢?这个时候我们就可以使用新的运算符 `has`,也可以写作 `?`。 { DoctorsInSurgery ? (Adams, Bernard): Dr Adams and Dr Bernard are having a hushed argument in one corner. } -And `?` can apply to single values too: +`?` 运算符同时适用于单值列表。 - { DoctorsInSurgery has Eamonn: - Dr Eamonn is polishing his glasses. + { DoctorsInSurgery has Eamonn: // 如果“外科医生”列表包含 "Eamonn" + 埃蒙医生正在擦拭他的眼镜。 } -We can also negate it, with `hasnt` or `!?` (not `?`). Note this starts to get a little complicated as +我们也可以用否定形式,也就是 `hasnt` 或 `!?`(而不是 `?`),来查询列表是否不包含要查询的内容中的某一项。也就是说: - DoctorsInSurgery !? (Adams, Bernard) + DoctorsInSurgery !? (Adams, Bernard) // 查询“外科医生”列表中是否不包含 "Adams" 或 "Bernard" 中的一个或更多。 -does not mean neither Adams nor Bernard is present, only that they are not *both* present (and arguing). +这并不意味着亚当斯和伯纳德都不在场,仅表示他们不会同时在场(并发生争执)。 -#### Warning: no lists contain the empty list +#### 注意:空列表不被任何列表包含|Warning: no lists contain the empty list -Note that the test +所以如果你想要测试: - SomeList ? () + SomeList ? () // 查询“某个列表”是否包含 “<什么也没有>” -will always return false, regardless of whether `SomeList` itself is empty. In practice this is the most useful default, as you'll often want to do tests like: +无论 `SomeList` 本身的值是否为空,这个查询都会返回 false,这种设计在实际应用中最合理,比如这样的检测: - SilverWeapons ? best_weapon_to_use + SilverWeapons ? best_weapon_to_use // 查询“银制武器”列表中是否有 best_weapon_to_use(最好的武器) -to fail if the player is empty-handed. +那如果“best_weapon_to_use”(最好的武器)是空的,则返回失败。 + +#### 示例:基础信息追踪|Example: basic knowledge tracking -#### Example: basic knowledge tracking +多值列表在游戏中最简单的用途就是整洁地追踪“游戏标记”: -The simplest use of a multi-valued list is for tracking "game flags" tidily. + LIST Facts = (Fogg_is_fairly_odd), first_name_phileas, (Fogg_is_English) + // 创建“事实”列表,初始化“福格是个相当古怪的人(并留在列表中)”,“名叫菲利亚斯”,“福格是英国人” - LIST Facts = (Fogg_is_fairly_odd), first_name_phileas, (Fogg_is_English) + {Facts ? Fogg_is_fairly_odd:我礼貌地笑了笑。|他是个疯子吗?} + // 查询 Fogg_is_fairly_odd 是否在“事实”列表中。 - {Facts ? Fogg_is_fairly_odd:I smiled politely.|I frowned. Was he a lunatic?} - '{Facts ? first_name_phileas:Phileas|Monsieur}, really!' I cried. + “{Facts ? first_name_phileas:斐利亚斯|先生},真的!”我喊道。 + // 查询 first_name_phileas 是否在“事实”列表中。 -In particular, it allows us to test for multiple game flags in a single line. +特别是,这个语法还允许我们在一行中就测试多个标志。 { Facts ? (Fogg_is_English, Fogg_is_fairly_odd): - <> 'I know Englishmen are strange, but this is *incredible*!' + <> ”我知道英国人很奇怪,但这也太*不可思议*了!“ } +#### 示例:医生门诊系统|Example: a doctor's surgery -#### Example: a doctor's surgery - -We're overdue a fuller example, so here's one. +让我们来看一个完整示例: LIST DoctorsInSurgery = (Adams), Bernard, Cartwright, (Denver), Eamonn - + // 创建一个”外科医生“列表,初始化:亚当斯(留在列表), 伯纳德, 卡特赖特, 丹佛(留在列表), 埃蒙 + -> waiting_room === function whos_in_today() - In the surgery today are {DoctorsInSurgery}. + 今日坐诊医生有:{DoctorsInSurgery}。 === function doctorEnters(who) { DoctorsInSurgery !? who: ~ DoctorsInSurgery += who - Dr {who} arrives in a fluster. + {who} 医生匆匆赶到诊室。 } + // 函数功能结点,当调用 doctorEnters(who) 时添加这个医生并输出”一个"{who} 医生匆匆赶到诊室。“ === function doctorLeaves(who) { DoctorsInSurgery ? who: ~ DoctorsInSurgery -= who - Dr {who} leaves for lunch. + {who} 医生外出午餐。 } + // 函数功能结点,当调用 doctorLeaves(who) 时移除这个医生并输出”一个"{who} 医生外出午餐。“ === waiting_room {whos_in_today()} - * [Time passes...] + * [时间流逝……] {doctorLeaves(Adams)} {doctorEnters(Cartwright)} {doctorEnters(Eamonn)} {whos_in_today()} + // 在这里为之前结点中的 "who" 赋值并调用函数。 -This produces: - - In the surgery today are Adams, Denver. +这将会输出: - > Time passes... + 今日坐诊医生有:Adams, Denver。 - Dr Adams leaves for lunch. Dr Cartwright arrives in a fluster. Dr Eamonn arrives in a fluster. + > 时间流逝... - In the surgery today are Cartwright, Denver, Eamonn. + Adams 医生外出午餐。Cartwright 医生匆匆赶到诊室。Eamonn 医生匆匆赶到诊室。 -#### Advanced: nicer list printing + 今日坐诊医生有:Cartwright, Denver, Eamonn。 -The basic list print is not especially attractive for use in-game. The following is better: +#### 进阶:优化列表显示|Advanced: nicer list printing +基础的列表在游戏中可能实用,所以可以这样来优化一下: === function listWithCommas(list, if_empty) - {LIST_COUNT(list): - - 2: - {LIST_MIN(list)} and {listWithCommas(list - LIST_MIN(list), if_empty)} - - 1: - {list} - - 0: - {if_empty} - - else: - {LIST_MIN(list)}, {listWithCommas(list - LIST_MIN(list), if_empty)} - } + // 功能函数 listWithCommas,预留 list 和 if_empty 变量 + + {LIST_COUNT(list): + // 判断列表中有几个值 + - 2: + {LIST_MIN(list)} 和 {listWithCommas(list - LIST_MIN(list), if_empty)} + // 如果有 2 个,那么就先说“<列表中已存在的值中排序值最小的那个值>和<列表中去掉最小排序值之后的新列表的值>”,然后递归再调用自身,直到这个满足终止条件(0、1 或 2 个)后调用 if_empty 的替代文本。 + - 1: + {list} + // 只有 1 个就直接报出恐龙名字 + - 0: + {if_empty} + // 没有(为 0 个)就显示 {if_empty} 的内容 + - else: + {LIST_MIN(list)}、{listWithCommas(list - LIST_MIN(list), if_empty)} + // 和上面的 2 是一样的,只是 2 一开始就达到了递归的终止条件,这里可能比 2 更多,所以输出的时候考虑了语言逻辑而进行了调整。 + } LIST favouriteDinosaurs = (stegosaurs), brachiosaur, (anklyosaurus), (pleiosaur) + // 在这里创建了”我最喜欢的恐龙“列表,并初始化:<那些恐龙的名字> - My favourite dinosaurs are {listWithCommas(favouriteDinosaurs, "all extinct")}. + 我最喜欢的恐龙是{listWithCommas(favouriteDinosaurs, "已全部灭绝")}。 + // 调用 listWithCommas 函数,并传递参数“我最喜欢的恐龙列表给“listWithCommas”,并将“已全部灭绝”这个替代文本代入 if_empty -It's probably also useful to have an is/are function to hand: +再配上一个单复数判断函数: +(译者注:英语单数用 is,多个用 are,但是翻译过来都是“是”,就像汉语中的“一个”和“一群”的区别,请举一反三的应用) === function isAre(list) - {LIST_COUNT(list) == 1:is|are} + {LIST_COUNT(list) == 1:是一个(is)|是一群(are)} + + 最喜欢的恐龙{isAre(favouriteDinosaurs)}{listWithCommas(favouriteDinosaurs, "已全部灭绝")}。 - My favourite dinosaurs {isAre(favouriteDinosaurs)} {listWithCommas(favouriteDinosaurs, "all extinct")}. +再严谨一些的话(名次单复数的区别,举一反三地实用即可): -And to be pendantic: + 我最喜欢的恐龙{LIST_COUNT(favouriteDinosaurs) != 1:们}{isAre(favouriteDinosaurs)}{listWithCommas(favouriteDinosaurs, “已全部灭绝”)}。 - My favourite dinosaur{LIST_COUNT(favouriteDinosaurs) != 1:s} {isAre(favouriteDinosaurs)} {listWithCommas(favouriteDinosaurs, "all extinct")}. + My favourite dinosaur{LIST_COUNT(favouriteDinosaurs) != 1:s} {isAre(favouriteDinosaurs)} {listWithCommas(favouriteDinosaurs,s “已全部灭绝”)}. -#### Lists don't need to have multiple entries +#### 列表不是必须包含多个值|Lists don't need to have multiple entries -Lists don't *have* to contain multiple values. If you want to use a list as a state-machine, the examples above will all work - set values using `=`, `++` and `--`; test them using `==`, `<`, `<=`, `>` and `>=`. These will all work as expected. +列表不一定需要包含多个值。如果要将列表用作状态机,上述所有示例仍然适用——你可以继续使用 `=`、`++` 和 `--` 设置值;使用 `==`、`<`、`<=`、`>` 和 `>=` 进行判定测试。这些操作都将按预期工作。 -### The "full" list +### “完整”的列表|The "full" list -Note that `LIST_COUNT`, `LIST_MIN` and `LIST_MAX` are refering to who's in/out of the list, not the full set of *possible* doctors. We can access that using +注意:`LIST_COUNT`,`LIST_MIN` 和 `LIST_MAX` 参考的是当前列表中的内容,而非所有有可能的医生名单。要访问完整列表,可以使用: - LIST_ALL(element of list) + LIST_ALL(<列表中的元素>) -or +或 - LIST_ALL(list containing elements of a list) + LIST_ALL(<包含列表元素的列表>) {LIST_ALL(DoctorsInSurgery)} // Adams, Bernard, Cartwright, Denver, Eamonn {LIST_COUNT(LIST_ALL(DoctorsInSurgery))} // "5" {LIST_MIN(LIST_ALL(Eamonn))} // "Adams" -Note that printing a list using `{...}` produces a bare-bones representation of the list; the values as words, delimited by commas. +请注意:使用 `{<要判定的内容>}` 打印列表会产生最基本的列表表示形式,即用逗号分隔的值。 -#### Advanced: "refreshing" a list's type +#### 进阶:“重置”列表的类型|Advanced: "refreshing" a list's type -If you really need to, you can make an empty list that knows what type of list it is. +如果您需要的话,您可以创建一个没有内容的空列表(这个列表只是一个类型为列表的空列表)。 LIST ValueList = first_value, second_value, third_value + // 整一个有三个值的 ValueList VAR myList = () - + // 整一个名叫 myList 的变量,里面是空的。 + ~ myList = ValueList() + // 把 ValueList 的值填入 myList -You'll then be able to do: +这之后可以执行: { LIST_ALL(myList) } + // 列出完整的 myList -#### Advanced: a portion of the "full" list +#### 进阶:获取“完整”列表的子集|Advanced: a portion of the "full" list -You can also retrieve just a "slice" of the full list, using the `LIST_RANGE` function. There are two formulations, both valid: +使用 `LIST_RANGE` 函数可以获取完整列表的特定区间,有两种等效写法: LIST_RANGE(list_name, min_integer_value, max_integer_value) + // LIST_RANGE(想截取的列表名称,你想取得的那个区间的最小排序值的整数值,你想取得的那个区间的最大排序值的整数值) -and - - LIST_RANGE(list_name, min_value, max_value) +或者 + {LIST_RANGE(LIST_ALL(primeNumbers), 10, 20)} + // LIST_RANGE(想截取的列表名称,你想取得的那个区间的最小排序值的整数值,你想取得的那个区间的最大排序值的整数值,你想取得的那个区间的最小值值本身,你想取得的那个区间的最大值值本身) -Min and max values here are inclusive. If the game can’t find the values, it’ll get as close as it can, but never go outside the range. So for example: +其中最小值和最大值都是包含的。如果找不到精确匹配值,系统会返回最接近但不超出范围的数值。例如: - {LIST_RANGE(LIST_ALL(primeNumbers), 10, 20)} + {LIST_RANGE(LIST_ALL(质数列表), 10, 20)} + // 从完整的质数列表中截取,最小截到 10,最大截到 20 -will produce - - 11, 13, 17, 19 +将输出: + 11, 13, 17, 19 -### Example: Tower of Hanoi -To demonstrate a few of these ideas, here's a functional Tower of Hanoi example, written so no one else has to write it. +### 示例:汉诺塔|Example: Tower of Hanoi +为了展示其中的一些想法,这里有一个功能性的汉诺塔示例,写这个示例的目的是为了让其他人不用再写了。 +(译者注:汉诺塔是一种经典益智游戏,如果不清楚这是什么请用搜索引擎查一下,规则相当简单,这里不再赘述。) - LIST Discs = one, two, three, four, five, six, seven - VAR post1 = () - VAR post2 = () - VAR post3 = () + LIST Discs = 一, 二, 三, 四, 五, 六, 七 // 创建列表“圆盘”,并初始化一二三四五六七。 + VAR post1 = () // 创建变量“柱子1” + VAR post2 = () // 创建变量“柱子2” + VAR post3 = () // 创建变量“柱子3” - ~ post1 = LIST_ALL(Discs) + ~ post1 = LIST_ALL(Discs) // 在 柱子1 上按列表 Discs(圆盘)的顺序放上所有的 7 个圆盘 -> gameloop + // 转向 游戏循环 === function can_move(from_list, to_list) === + // 功能函数 can_move(检查是否可以移动),等待参数 from_list(来源柱)和 to_list(目标柱) { - LIST_COUNT(from_list) == 0: - // no discs to move + // 如果待会 来源柱 中的值为 0(也就是没有圆盘) ~ return false + // 返回 false - LIST_COUNT(to_list) > 0 && LIST_MIN(from_list) > LIST_MIN(to_list): - // the moving disc is bigger than the smallest of the discs on the new tower + // 如果待会 目标柱 同时大于 0 和 来源柱 中最小的排序值,而这两个值又大于 目标柱 中的最小排序值 + // 实际来说就是要移动的圆盘比目标柱最上面的圆盘大 ~ return false + // 返回 false - else: - // nothing stands in your way! + //其他情况都可以移动! ~ return true - + // 返回 true } === function move_ring( ref from, ref to ) === - ~ temp whichRingToMove = LIST_MIN(from) - ~ from -= whichRingToMove - ~ to += whichRingToMove + // 功能函数 move_ring(移动圆盘),传参 来源,传参 目标 + ~ temp whichRingToMove = LIST_MIN(from) // 将 from(来源)列表中最小排序值代入临时变量 whichRingToMove(移动哪号圆盘) + ~ from -= whichRingToMove // 从 来源 列表中移除 whichRingToMove + ~ to += whichRingToMove // 在 目标 列表中增加 whichRingToMove + == function getListForTower(towerNum) + // 功能参数 获取柱子列表(等待传入柱子编号) { towerNum: - - 1: ~ return post1 - - 2: ~ return post2 - - 3: ~ return post3 + - 1: ~ return post1 // 是 1 就返回 柱子1 + - 2: ~ return post2 // 是 2 就…… + - 3: ~ return post3 // …… } === function name(postNum) - the {postToPlace(postNum)} temple + // 功能函数 柱子名转换(小写),等待 postNum 参数 + 那{postToPlace(postNum)}座 === function Name(postNum) - The {postToPlace(postNum)} temple + // 功能函数 柱子名转换(首字母大写),等待 postNum 参数 + 那{postToPlace(postNum)}座 === function postToPlace(postNum) + // 功能函数 编号到次序转换 { postNum: - - 1: first - - 2: second - - 3: third + - 1: 第一 + - 2: 第二 + - 3: 第三 } === function describe_pillar(listNum) == + // 功能函数 描述柱子,等待 listNum 参数 ~ temp list = getListForTower(listNum) + // 将 获取柱子列表(柱子编号)填入 临时参数 list { - - LIST_COUNT(list) == 0: - {Name(listNum)} is empty. - - LIST_COUNT(list) == 1: - The {list} ring lies on {name(listNum)}. - - else: - On {name(listNum)}, are the discs numbered {list}. + - LIST_COUNT(list) == 0: // 如果 list 最小排序值为 0 + {Name(listNum)}是空的。 + - LIST_COUNT(list) == 1: // 如果 list 最小排序值为 1 + 只有{list}号圆盘在{name(listNum)}上。 + - else: // 其他情况 + 在{name(listNum)}上,摆放着{list}号圆盘。 } === gameloop - Staring down from the heavens you see your followers finishing construction of the last of the great temples, ready to begin the work. + 从天上俯瞰,你看到你的追随者们正在准备开始完成最后一座大神庙的建设。 - (top) - + [ Regard the temples] - You regard each of the temples in turn. On each is stacked the rings of stone. {describe_pillar(1)} {describe_pillar(2)} {describe_pillar(3)} + + [查看圣殿] + 你依次检视每座圣殿。每座圣殿上都堆叠着石环。{describe_pillar(1)} {describe_pillar(2)} {describe_pillar(3)} <- move_post(1, 2, post1, post2) <- move_post(2, 1, post2, post1) <- move_post(1, 3, post1, post3) <- move_post(3, 1, post3, post1) <- move_post(3, 2, post3, post2) <- move_post(2, 3, post2, post3) + // move_post 函数在下一针脚 -> DONE = move_post(from_post_num, to_post_num, ref from_post_list, ref to_post_list) + // 移动柱子(并代入了对应的参数) + { can_move(from_post_list, to_post_list) } - [ Move a ring from {name(from_post_num)} to {name(to_post_num)} ] + [将圆盘从{name(from_post_num)}移动到{name(to_post_num)}。] { move_ring(from_post_list, to_post_list) } - { stopping: - - The priests far below construct a great harness, and after many years of work, the great stone ring is lifted up into the air, and swung over to the next of the temples. - The ropes are slashed, and in the blink of an eye it falls once more. - - Your next decree is met with a great feast and many sacrifices. After the funeary smoke has cleared, work to shift the great stone ring begins in earnest. A generation grows and falls, and the ring falls into its ordained place. - - {cycle: - - Years pass as the ring is slowly moved. - - The priests below fight a war over what colour robes to wear, but while they fall and die, the work is still completed. + { stopping: // 按顺序显示下方文本并停在最后一项 + - 下方的祭司们建造了巨大的吊架,经过多年的努力,巨大的石环被吊起,缓缓移向下一座圣殿。 + 绳索被斩断,转瞬间石环便稳稳落下。 + - 你的谕令引发了盛大的庆典和祭祀。当葬仪的烟雾散去,移动石环的工程郑重展开。一代人成长又逝去,石环终于归位。 + - { cycle: // 循环显示下方文本 + - 石环在岁月流转中缓慢移动。 + - 祭司们为袍服颜色爆发战争,虽死伤无数,工程却仍在继续。 } } -> top From 5bd538cedbc3c93716645f38dbff70da2a840398 Mon Sep 17 00:00:00 2001 From: Ander-Index Date: Wed, 2 Jul 2025 16:38:39 +0800 Subject: [PATCH 21/22] Translation completed. --- .../WritingWithInk_Simplified-Chinese.md | 956 +++++++++--------- 1 file changed, 472 insertions(+), 484 deletions(-) diff --git a/Documentation/WritingWithInk_Simplified-Chinese.md b/Documentation/WritingWithInk_Simplified-Chinese.md index 6472f169..7a14ccfc 100644 --- a/Documentation/WritingWithInk_Simplified-Chinese.md +++ b/Documentation/WritingWithInk_Simplified-Chinese.md @@ -2,7 +2,7 @@ ## 声明 -简体中文教程是从英文原文翻译而来。可能会存在版本滞后性,故入与英文版有出入,请与英文版为准。 +简体中文教程是从英文原文翻译而来。可能会存在版本滞后性,或与英文版有出入,请与英文版为准。 翻译时使用了 DeepSeek、ChatGPT 与 DeepL 辅助。 @@ -12,25 +12,27 @@ 翻译时的 Ink 版本:1.2.0 -翻译最后更新时间:2025 年 06 月 03 日 +翻译最后更新时间:2025 年 07 月 01 日 -翻译仍在更新。 +已全部翻译完毕。如果您在阅读时发现问题,请你用上面的联系方式联系译者反馈,感谢您的阅读~ ## 此外 根据语义,将部分专有词汇进行翻译: -| 原文 | 翻译 | -| ----------- | --- | -| Knot | 结点 | -| Divert | 转向 | -| Branch | 分支 | -| Stitch | 针脚 | -| Weave | 织体 | -| Gather | 收束 | -| Scope | 界限 | -| Tunnel | 隧道 | +| 原文 | 翻译 | +| ----------- | ---- | +| Knot | 结点 | +| Divert | 转向 | +| Branch | 分支 | +| Stitch | 针脚 | +| Weave | 织体 | +| Gather | 收束 | +| Scope | 界限 | +| Tunnel | 隧道 | | Thread | 缝合线 | | Side-Thread | 旁缝合线 | +| Flags | 标志 | +| Nodes | 小节 | ## 内容目录
@@ -104,7 +106,7 @@ - [收束点也可以嵌套|Gather points can be nested too](#收束点也可以嵌套gather-points-can-be-nested-too) - [进阶:收束这个操作做了什么|Advanced: What gathers do](#进阶收束这个操作做了什么advanced-what-gathers-do) - [你可以根据你自己的需要设置多层嵌套|You can nest as many levels are you like](#你可以根据你自己的需要设置多层嵌套you-can-nest-as-many-levels-are-you-like) - - [示例:用嵌套节点写的对话|Example: a conversation with nested nodes](#示例用嵌套节点写的对话example-a-conversation-with-nested-nodes) + - [示例:用嵌套小节编写的对话|Example: a conversation with nested nodes](#示例用嵌套小节编写的对话example-a-conversation-with-nested-nodes) - [3) 追踪织体|Tracking a Weave](#3-追踪织体tracking-a-weave) - [织体是庞大且没有地址索引的|Weaves are largely unaddressed](#织体是庞大且没有地址索引的weaves-are-largely-unaddressed) - [收束和选项也可以打标签|Gathers and options can be labelled](#收束和选项也可以打标签gathers-and-options-can-be-labelled) @@ -190,25 +192,25 @@ - [进阶:“重置”列表的类型|Advanced: "refreshing" a list's type](#进阶重置列表的类型advanced-refreshing-a-lists-type) - [进阶:获取“完整”列表的子集|Advanced: a portion of the "full" list](#进阶获取完整列表的子集advanced-a-portion-of-the-full-list) - [示例:汉诺塔|Example: Tower of Hanoi](#示例汉诺塔example-tower-of-hanoi) - - [5) Advanced List Operations](#5-advanced-list-operations) - - [Comparing lists](#comparing-lists) - - ["Distinctly bigger than"](#distinctly-bigger-than) - - ["Definitely never smaller than"](#definitely-never-smaller-than) - - [Health warning!](#health-warning) - - [Inverting lists](#inverting-lists) - - [Footnote](#footnote) - - [Intersecting lists](#intersecting-lists) - - [6) Multi-list Lists](#6-multi-list-lists) - - [Lists to track objects](#lists-to-track-objects) - - [Lists to track multiple states](#lists-to-track-multiple-states) - - [How does this affect queries?](#how-does-this-affect-queries) - - [7) Long example: crime scene](#7-long-example-crime-scene) - - [8) Summary](#8-summary) - - [Flags](#flags) - - [State machines](#state-machines) - - [Properties](#properties) + - [5) 进阶列表操作|Advanced List Operations](#5-进阶列表操作advanced-list-operations) + - [比较列表|Comparing lists](#比较列表comparing-lists) + - [“严格的大于”](#严格的大于) + - [“绝不会小于”](#绝不会小于) + - [健康忠告!](#健康忠告) + - [反转列表](#反转列表) + - [脚注](#脚注) + - [交集列表](#交集列表) + - [6) 多列表列表(表中表)](#6-多列表列表表中表) + - [用列表追踪物品](#用列表追踪物品) + - [用列表追踪多重状态](#用列表追踪多重状态) + - [这对查询有何影响?](#这对查询有何影响) + - [7) 长示例:犯罪现场](#7-长示例犯罪现场) + - [8) 总结](#8-总结) + - [标志(Flags)](#标志flags) + - [状态机(State machines)](#状态机state-machines) + - [属性(Properties)](#属性properties) - [第 6 部分:标识符中的国际字符支持|Part 6: International character support in identifiers](#第-6-部分标识符中的国际字符支持part-6-international-character-support-in-identifiers) - - [Supported Identifier Characters](#supported-identifier-characters) + - [支持的标识符字符](#支持的标识符字符)
@@ -397,7 +399,7 @@ === top_knot ==top_knot -需要注意的是: +译者注:关于结点还有以后的函数和列表等部分的命名 * 区分大小写。 * 末尾的等号是可选项。 * 无法使用连字符 "-"。还有其他一些特殊的标点符号也无法使用。在出现不可使用的标点符号时,编辑器会报错提醒。 @@ -413,7 +415,7 @@ #### 进阶:一个结点更复杂的“你好世界”|Advanced: a knottier "hello world" -在启动 Ink 文件时,结点以外的内容会自动运行。但节点不会。因此,如果你开始使用节点来管理内容,就需要告诉游戏该去哪里。我们可以使用转向箭头 `->`来做到这一点,下一部分将对此进行详细介绍。 +在启动 Ink 文件时,结点以外的内容会自动运行。但结点不会。因此,如果你开始使用结点来管理内容,就需要告诉游戏该去哪里。我们可以使用转向箭头 `->`来做到这一点,下一部分将对此进行详细介绍。 这是一个简单的结点跳转脚本: @@ -1172,7 +1174,7 @@ TODO: (向编译器传递 `-c` 的要求) ### 收束点也可以嵌套|Gather points can be nested too -有时,问题不在于选项数量的增加,而在于故事节点的增加。我们可以通过嵌套收束点和选项来实现这一点。 +有时,问题不在于选项数量的增加,而在于故事要点的增加。我们可以通过嵌套收束点和选项来实现这一点。 - “波洛?你认为这是谋杀还是自杀?” * “谋杀!” @@ -1214,7 +1216,7 @@ TODO: (向编译器传递 `-c` 的要求) 但至少在理论上,你可以把整个故事只写成一个织体。 -### 示例:用嵌套节点写的对话|Example: a conversation with nested nodes +### 示例:用嵌套小节编写的对话|Example: a conversation with nested nodes 这个示例有点长: @@ -3073,170 +3075,168 @@ LIST primeNumbers = (two = 2), (three) = 3, (five = 5) // “质数”列表 -> top +## 5) 进阶列表操作|Advanced List Operations -## 5) Advanced List Operations +前文已涵盖基础的比较操作。除此之外还有一些更强大的功能,但正如熟悉数学集合的人所知——事情开始变得有些复杂了。因此本节内容标注为"进阶"警示。 -The above section covers basic comparisons. There are a few more powerful features as well, but - as anyone familiar with mathematical sets will know - things begin to get a bit fiddly. So this section comes with an 'advanced' warning. +本节的多数功能对大多数游戏开发并非必需。 -A lot of the features in this section won't be necessary for most games. +### 比较列表|Comparing lists -### Comparing lists +我们可以使用 >、<、>= 和 <= 来比较列表大小。需要注意!这里使用的定义并不完全符合常见标准,它们是基于被比较列表中元素的数值来进行比较的。 -We can compare lists less than exactly using `>`, `<`, `>=` and `<=`. Be warned! The definitions we use are not exactly standard fare. They are based on comparing the numerical value of the elements in the lists being tested. +#### “严格的大于” -#### "Distinctly bigger than" +`LIST_A > LIST_B` 的含义是:“A 中的最小值大于 B 中的最大值”。换句话说,如果放在数轴上,A 的全部内容都在 B 的全部内容的右侧。`<` 则是相反的比较。 -`LIST_A > LIST_B` means "the smallest value in A is bigger than the largest values in B": in other words, if put on a number line, the entirety of A is to the right of the entirety of B. `<` does the same in reverse. +#### “绝不会小于” -#### "Definitely never smaller than" +`LIST_A >= LIST_B` 的含义是——(请你做好心理准备……)——“A 中的最小值至少与 B 中的最小值相等,且 A 中的最大值至少与 B 中的最大值相等”。换句话说,如果画在数轴上,A 的整体要么在 B 之上,要么与 B 重叠,但 B 不会高于 A。 -`LIST_A >= LIST_B` means - take a deep breath now - "the smallest value in A is at least the smallest value in B, and the largest value in A is at least the largest value in B". That is, if drawn on a number line, the entirety of A is either above B or overlaps with it, but B does not extend higher than A. +需要注意的是,`LIST_A > LIST_B` 意味着 `LIST_A != LIST_B`,而 `LIST_A >= LIST_B` 则允许 `LIST_A == LIST_B` 但会排除 `LIST_A < LIST_B`,这也许正如你所希望的那样。 -Note that `LIST_A > LIST_B` implies `LIST_A != LIST_B`, and `LIST_A >= LIST_B` allows `LIST_A == LIST_B` but precludes `LIST_A < LIST_B`, as you might hope. +#### 健康忠告! -#### Health warning! +`LIST_A >= LIST_B` 并*不*等同于 `LIST_A > LIST_B` 或 `LIST_A == LIST_B`。 -`LIST_A >= LIST_B` is *not* the same as `LIST_A > LIST_B or LIST_A == LIST_B`. +这个道理是:为了您的脑细胞着想,除非你在脑中有非常清晰的理解,否则不要使用这些比较。 -The moral is, don't use these unless you have a clear picture in your mind. +### 反转列表 -### Inverting lists - -A list can be "inverted", which is the equivalent of going through the accommodation in/out name-board and flipping every switch to the opposite of what it was before. +列表可以被“反转”,就像是住宿登记处的进出名牌板,将每个开关都翻转成相反的状态。 LIST GuardsOnDuty = (Smith), (Jones), Carter, Braithwaite === function changingOfTheGuard ~ GuardsOnDuty = LIST_INVERT(GuardsOnDuty) - -Note that `LIST_INVERT` on an empty list will return a null value, if the game doesn't have enough context to know what invert. If you need to handle that case, it's safest to do it by hand: +请注意,如果对一个空列表使用 `LIST_INVERT`,而游戏又没有足够的上下文来确定到底要反转什么内容,那么它将返回空值。如果需要处理这种情况,最安全的做法是手动处理: === function changingOfTheGuard - {!GuardsOnDuty: // "is GuardsOnDuty empty right now?" + {!GuardsOnDuty: // 查询 GuardsOnDuty 列表现在是不是空的 ~ GuardsOnDuty = LIST_ALL(Smith) - else: ~ GuardsOnDuty = LIST_INVERT(GuardsOnDuty) } -#### Footnote +#### 脚注 -The syntax for inversion was originally `~ list` but we changed it because otherwise the line +在 Ink 诞生之初时,反转的语法最初是 `~ list`,但后来更改了,否则以下这行 ~ list = ~ list -was not only functional, but actually caused list to invert itself, which seemed excessively perverse. +不仅可以正常运行,而且会真的让 list 自我反转,这看起来过于反常。 -### Intersecting lists +(译者注:这是一个已经不再可用的语法,图一乐看看就行。) -The `has` or `?` operator is, somewhat more formally, the "are you a subset of me" operator, ⊇, which includes the sets being equal, but which doesn't include if the larger set doesn't entirely contain the smaller set. +### 交集列表 -To test for "some overlap" between lists, we use the overlap operator, `^`, to get the *intersection*. +`has` 或 `?` 运算符用通俗的语言来表述就是“你是我的子集吗”运算符,也就是“⊇”的意思,它包含集合相等的情况,但当大集合未完全包含小集合时则不成立。 + +若要检测两个列表是否“有交集”,可以使用重叠运算符 `^` 来获取*交集*。 LIST CoreValues = strength, courage, compassion, greed, nepotism, self_belief, delusions_of_godhood VAR desiredValues = (strength, courage, compassion, self_belief ) VAR actualValues = ( greed, nepotism, self_belief, delusions_of_godhood ) - {desiredValues ^ actualValues} // prints "self_belief" - -The result is a new list, so you can test it: + {desiredValues ^ actualValues} // 这会输出 "self_belief" - {desiredValues ^ actualValues: The new president has at least one desirable quality.} +译者注:两个 LIST 之间似乎不能直接比较,他们这里也是把 LIST 里的值搞到了两个 VAR 里再进行比较的。 - {LIST_COUNT(desiredValues ^ actualValues) == 1: Correction, the new president has only one desirable quality. {desiredValues ^ actualValues == self_belief: It's the scary one.}} +结果是一个新的列表,因此可以进行判定: + {desiredValues ^ actualValues: 新总统至少有一个值得称道的品质。} // 如果两个列表有交集。 + {LIST_COUNT(desiredValues ^ actualValues) == 1: 更正,新总统实际上只有一个值得称道的品质。{desiredValues ^ actualValues == self_belief: 而且是那个最可怕的品质。}} // 如果两个列表只有一个交集就“吃了吐”。如果那个交集完全等于 self_belief 则输出…… -## 6) Multi-list Lists +## 6) 多列表列表(表中表) +到目前为止,我们在所有示例中都使用了一个简化假设:列表变量中的值必须全部来自同一个列表族。但其实并不需要。(译者注:这是说,前面的例子最多只创建了一个列表,但其实 LIST 并不是只能有一个) -So far, all of our examples have included one large simplification, again - that the values in a list variable have to all be from the same list family. But they don't. +这使得列表除了用作状态机和标志追踪器外,还可以用来作为通用属性,非常适合用于建模你的世界。 -This allows us to use lists - which have so far played the role of state-machines and flag-trackers - to also act as general properties, which is useful for world modelling. +这就是我们的“盗梦空间”时刻(从这里开始进入更复杂、强大但真实的“嵌套”世界(嵌套状态、嵌套结构、嵌套复杂性)。这种结果非常强大,但也更接近“真正的代码”,比之前讲过的任何内容都更像。 -This is our inception moment. The results are powerful, but also more like "real code" than anything that's come before. +### 用列表追踪物品 -### Lists to track objects +举个例子,我们可以定义: -For instance, we might define: - - LIST Characters = Alfred, Batman, Robin - LIST Props = champagne_glass, newspaper + LIST Characters = Alfred, Batman, Robin // 角色列表 + LIST Props = champagne_glass, newspaper // 道具列表:香槟杯、报纸 VAR BallroomContents = (Alfred, Batman, newspaper) VAR HallwayContents = (Robin, champagne_glass) -We could then describe the contents of any room by testing its state: +接着,我们可以通过状态检测来描述房间内的内容: === function describe_room(roomState) - { roomState ? Alfred: Alfred is here, standing quietly in a corner. } { roomState ? Batman: Batman's presence dominates all. } { roomState ? Robin: Robin is all but forgotten. } - <> { roomState ? champagne_glass: A champagne glass lies discarded on the floor. } { roomState ? newspaper: On one table, a headline blares out WHO IS THE BATMAN? AND *WHO* IS HIS BARELY-REMEMBERED ASSISTANT? } + { roomState ? Alfred: 阿尔弗雷德正静静地站在角落里。}{ roomState ? Batman: 蝙蝠侠的存在让所有人都感到压迫。}{ roomState ? Robin: 罗宾几乎被遗忘。} + <> { roomState ? champagne_glass: 地板上丢这一个香槟杯。}{ roomState ? newspaper: 桌子上的头条新闻用超大的字号写着“谁是蝙蝠侠?他那几乎被遗忘的助手又是*谁*?”} -So then: +那么: { describe_room(BallroomContents) } -produces: - - Alfred is here, standing quietly in a corner. Batman's presence dominates all. +就会输出: - On one table, a headline blares out WHO IS THE BATMAN? AND *WHO* IS HIS BARELY-REMEMBERED ASSISTANT? + 阿尔弗雷德正静静地站在角落里。蝙蝠侠的存在让所有人都感到压迫。罗宾几乎被遗忘。 + + 桌子上的头条新闻用超大的字号写着“谁是蝙蝠侠?他那几乎被遗忘的助手又是谁?” -While: +而: { describe_room(HallwayContents) } -gives: +则会输出: - Robin is all but forgotten. + Robin 几乎被遗忘。 - A champagne glass lies discarded on the floor. + 地板上丢弃着一个香槟杯。 -And we could have options based on combinations of things: +我们还可以基于组合状态来提供选项: - * { currentRoomState ? (Batman, Alfred) } [Talk to Alfred and Batman] - 'Say, do you two know each other?' + * { currentRoomState ? (Batman, Alfred) } [与阿尔弗雷德和蝙蝠侠对话] + “嘿,你们两个互相认识吗?” -### Lists to track multiple states +### 用列表追踪多重状态 -We can model devices with multiple states. Back to the kettle again... +我们还可以用它来建模具有多个状态的设备。回到水壶那个例子…… LIST OnOff = on, off LIST HotCold = cold, warm, hot - VAR kettleState = (off, cold) // we need brackets because it's a proper, multi-valued list now + VAR kettleState = (off, cold) // 这回这里需要一个括号,因为他们现在是一个真正的多值列表了 === function turnOnKettle() === { kettleState ? hot: - You turn on the kettle, but it immediately flips off again. + 你打开水壶,但它立刻又跳闸关闭。 - else: - The water in the kettle begins to heat up. + 水壶里的水开始加热。 ~ kettleState -= off ~ kettleState += on - // note we avoid "=" as it'll remove all existing states + // 注意不要使用“=”赋值,而应该使用操作列表的办法,否则会移除所有已存在的状态,也就是直接覆盖掉了列表而替换成了一个值。 } === function can_make_tea() === ~ return kettleState ? (hot, off) -These mixed states can make changing state a bit trickier, as the off/on above demonstrates, so the following helper function can be useful. +这种混合状态会让状态变更稍微复杂,如上面 on/off 的示例所示,因此以下辅助函数会很有用: === function changeStateTo(ref stateVariable, stateToReach) - // remove all states of this type - ~ stateVariable -= LIST_ALL(stateToReach) - // put back the state we want - ~ stateVariable += stateToReach + ~ stateVariable -= LIST_ALL(stateToReach) // 移除此类别的所有状态 + ~ stateVariable += stateToReach // 添加需要到达的那个状态 + // 译者注:相当于是先清除,表中所有状态,再重新给定对应状态,避免了手动开关导致遗漏了某些值的状态。 - which enables code like: +这样就可以写出如下代码: ~ changeState(kettleState, on) ~ changeState(kettleState, warm) -#### How does this affect queries? +#### 这对查询有何影响? -The queries given above mostly generalise nicely to multi-valued lists +上述查询基本可以自然地以此类推到多值列表上: LIST Letters = a,b,c LIST Numbers = one, two, three @@ -3246,8 +3246,7 @@ The queries given above mostly generalise nicely to multi-valued lists {LIST_ALL(mixedList)} // a, one, b, two, c, three {LIST_COUNT(mixedList)} // 3 {LIST_MIN(mixedList)} // a - {LIST_MAX(mixedList)} // three or c, albeit unpredictably - + {LIST_MAX(mixedList)} // three 或 c,结果不固定 {mixedList ? (a,b) } // false {mixedList ^ LIST_ALL(a)} // a, c @@ -3257,459 +3256,448 @@ The queries given above mostly generalise nicely to multi-valued lists { LIST_INVERT(mixedList) } // one, b, two -## 7) Long example: crime scene +## 7) 长示例:犯罪现场 -Finally, here's a long example, demonstrating a lot of ideas from this section in action. You might want to try playing it before reading through to better understand the various moving parts. +最后,这里给出一个长示例以展示本节中许多概念在实际中的运作方式。建议在阅读之前把这段代码复制到 Ink 中先试玩一下,以便更好理解地理解各个环节的运作。 + +(译者注:这一节的代码翻译由 ChatGPT 完成。已在 Ink 中验证其可靠性。) -> murder_scene - // Helper function: popping elements from lists + // 辅助函数:从列表中弹出元素 === function pop(ref list) - ~ temp x = LIST_MIN(list) - ~ list -= x - ~ return x - + ~ temp x = LIST_MIN(list) + ~ list -= x + ~ return x + // - // System: items can have various states - // Some are general, some specific to particular items + // 系统:物品可具有不同状态 + // 有些是通用状态,有些是特定物品的专有状态 // - LIST OffOn = off, on LIST SeenUnseen = unseen, seen - + LIST GlassState = (none), steamed, steam_gone LIST BedState = (made_up), covers_shifted, covers_off, bloodstain_visible - + // - // System: inventory + // 系统:库存 // - + LIST Inventory = (none), cane, knife - + === function get(x) - ~ Inventory += x - + ~ Inventory += x + // - // System: positioning things - // Items can be put in and on places + // 系统:物品位置管理 + // 物品可被放入或放置在不同位置 // - + LIST Supporters = on_desk, on_floor, on_bed, under_bed, held, with_joe - + === function move_to_supporter(ref item_state, new_supporter) === - ~ item_state -= LIST_ALL(Supporters) - ~ item_state += new_supporter - - - // System: Incremental knowledge. - // Each list is a chain of facts. Each fact supersedes the fact before + ~ item_state -= LIST_ALL(Supporters) + ~ item_state += new_supporter + + + // 系统:递增式知识管理 + // 每个列表都是一条事实链,每个事实会取代之前的事实 // - + VAR knowledgeState = () - + === function reached (x) - ~ return knowledgeState ? x - + ~ return knowledgeState ? x + === function between(x, y) - ~ return knowledgeState? x && not (knowledgeState ^ y) - + ~ return knowledgeState? x && not (knowledgeState ^ y) + === function reach(statesToSet) - ~ temp x = pop(statesToSet) - { - - not x: - ~ return false - - - not reached(x): - ~ temp chain = LIST_ALL(x) - ~ temp statesGained = LIST_RANGE(chain, LIST_MIN(chain), x) - ~ knowledgeState += statesGained - ~ reach (statesToSet) // set any other states left to set - ~ return true // and we set this state, so true - - - else: - ~ return false || reach(statesToSet) - } + ~ temp x = pop(statesToSet) + { + - not x: + ~ return false + + - not reached(x): + ~ temp chain = LIST_ALL(x) + ~ temp statesGained = LIST_RANGE(chain, LIST_MIN(chain), x) + ~ knowledgeState += statesGained + ~ reach (statesToSet) // 设置列表中剩余状态 + ~ return true // 成功设置该状态,返回 true + - else: + ~ return false || reach(statesToSet) + } + // - // Set up the game + // 游戏初始化 // - + VAR bedroomLightState = (off, on_desk) - + VAR knifeState = (under_bed) - - + + // - // Knowledge chains + // 知识链 // - - + LIST BedKnowledge = neatly_made, crumpled_duvet, hastily_remade, body_on_bed, murdered_in_bed, murdered_while_asleep - - LIST KnifeKnowledge = prints_on_knife, joe_seen_prints_on_knife,joe_wants_better_prints, joe_got_better_prints - + + LIST KnifeKnowledge = prints_on_knife, joe_seen_prints_on_knife, joe_wants_better_prints, joe_got_better_prints + LIST WindowKnowledge = steam_on_glass, fingerprints_on_glass, fingerprints_on_glass_match_knife - - + // - // Content + // 内容 // - + === murder_scene === - The bedroom. This is where it happened. Now to look for clues. + 卧室。这就是案发地。现在该寻找线索了。 - (top) - { bedroomLightState ? seen: <- seen_light } - <- compare_prints(-> top) - - * (dobed) [The bed...] - The bed was low to the ground, but not so low something might not roll underneath. It was still neatly made. - ~ reach (neatly_made) - - - (bedhub) - * * [Lift the bedcover] - I lifted back the bedcover. The duvet underneath was crumpled. - ~ reach (crumpled_duvet) - ~ BedState = covers_shifted - * * (uncover) {reached(crumpled_duvet)} - [Remove the cover] - Careful not to disturb anything beneath, I removed the cover entirely. The duvet below was rumpled. - Not the work of the maid, who was conscientious to a point. Clearly this had been thrown on in a hurry. - ~ reach (hastily_remade) - ~ BedState = covers_off - * * (duvet) {BedState == covers_off} [ Pull back the duvet ] - I pulled back the duvet. Beneath it was a sheet, sticky with blood. - ~ BedState = bloodstain_visible - ~ reach (body_on_bed) - Either the body had been moved here before being dragged to the floor - or this is was where the murder had taken place. - * * {BedState !? made_up} [ Remake the bed ] - Carefully, I pulled the bedsheets back into place, trying to make it seem undisturbed. - ~ BedState = made_up - * * [Test the bed] - I pushed the bed with spread fingers. It creaked a little, but not so much as to be obnoxious. - * * (darkunder) [Look under the bed] - Lying down, I peered under the bed, but could make nothing out. - - * * {TURNS_SINCE(-> dobed) > 1} [Something else?] - I took a step back from the bed and looked around. - -> top - - - -> bedhub - - * {darkunder && bedroomLightState ? on_floor && bedroomLightState ? on} - [ Look under the bed ] - I peered under the bed. Something glinted back at me. - - - (reaching) - * * [ Reach for it ] - I fished with one arm under the bed, but whatever it was, it had been kicked far enough back that I couldn't get my fingers on it. - -> reaching - * * {Inventory ? cane} [Knock it with the cane] - -> knock_with_cane - - * * {reaching > 1 } [ Stand up ] - I stood up once more, and brushed my coat down. - -> top - - * (knock_with_cane) {reaching && TURNS_SINCE(-> reaching) >= 4 && Inventory ? cane } [Use the cane to reach under the bed ] - Positioning the cane above the carpet, I gave the glinting thing a sharp tap. It slid out from the under the foot of the bed. - ~ move_to_supporter( knifeState, on_floor ) - * * (standup) [Stand up] - Satisfied, I stood up, and saw I had knocked free a bloodied knife. - -> top - - * * [Look under the bed once more] - Moving the cane aside, I looked under the bed once more, but there was nothing more there. - -> standup - - * {knifeState ? on_floor} [Pick up the knife] - Careful not to touch the handle, I lifted the blade from the carpet. - ~ get(knife) - - * {Inventory ? knife} [Look at the knife] - The blood was dry enough. Dry enough to show up partial prints on the hilt! - ~ reach (prints_on_knife) - - * [ The desk... ] - I turned my attention to the desk. A lamp sat in one corner, a neat, empty in-tray in the other. There was nothing else out. - Leaning against the desk was a wooden cane. - ~ bedroomLightState += seen - - - - (deskstate) - * * (pickup_cane) {Inventory !? cane} [Pick up the cane ] - ~ get(cane) - I picked up the wooden cane. It was heavy, and unmarked. - - * * { bedroomLightState !? on } [Turn on the lamp] - -> operate_lamp -> - - * * [Look at the in-tray ] - I regarded the in-tray, but there was nothing to be seen. Either the victim's papers were taken, or his line of work had seriously dried up. Or the in-tray was all for show. - - + + (open) {open < 3} [Open a drawer] - I tried {a drawer at random|another drawer|a third drawer}. {Locked|Also locked|Unsurprisingly, locked as well}. - - * * {deskstate >= 2} [Something else?] - I took a step away from the desk once more. - -> top - - - - -> deskstate - - * {(Inventory ? cane) && TURNS_SINCE(-> deskstate) <= 2} [Swoosh the cane] - I was still holding the cane: I gave it an experimental swoosh. It was heavy indeed, though not heavy enough to be used as a bludgeon. - But it might have been useful in self-defence. Why hadn't the victim reached for it? Knocked it over? - - * [The window...] - I went over to the window and peered out. A dismal view of the little brook that ran down beside the house. - - - - (window_opts) - <- compare_prints(-> window_opts) - * * (downy) [Look down at the brook] - { GlassState ? steamed: - Through the steamed glass I couldn't see the brook. -> see_prints_on_glass -> window_opts - } - I watched the little stream rush past for a while. The house probably had damp but otherwise, it told me nothing. - * * (greasy) [Look at the glass] - { GlassState ? steamed: -> downy } - The glass in the window was greasy. No one had cleaned it in a while, inside or out. - * * { GlassState ? steamed && not see_prints_on_glass && downy && greasy } - [ Look at the steam ] - A cold day outside. Natural my breath should steam. -> see_prints_on_glass -> - + + {GlassState ? steam_gone} [ Breathe on the glass ] - I breathed gently on the glass once more. { reached (fingerprints_on_glass): The fingerprints reappeared. } - ~ GlassState = steamed - - + + [Something else?] - { window_opts < 2 || reached (fingerprints_on_glass) || GlassState ? steamed: - I looked away from the dreary glass. - {GlassState ? steamed: - ~ GlassState = steam_gone - <> The steam from my breath faded. - } - -> top - } - I leant back from the glass. My breath had steamed up the pane a little. - ~ GlassState = steamed - - - - -> window_opts + { bedroomLightState ? seen: <- seen_light } + <- compare_prints(-> top) + + * (dobed) [床……] + 床离地不高,但也不至于什么都滚不进去。它依旧被整齐地铺好。 + ~ reach (neatly_made) + - - (bedhub) + * * [掀开被子] + 我掀开了被子。被褥已经被压皱。 + ~ reach (crumpled_duvet) + ~ BedState = covers_shifted + * * (uncover) {reached(crumpled_duvet)} + [拿掉被子] + 小心翼翼地,我完全移开了被子,下方的被褥一片凌乱。 + 这并非一位尽职的女仆所为,显然是匆忙间丢上的。 + ~ reach (hastily_remade) + ~ BedState = covers_off + * * (duvet) {BedState == covers_off} [拉开被褥] + 我拉开了被褥,下面的床单上粘着血迹。 + ~ BedState = bloodstain_visible + ~ reach (body_on_bed) + 不是尸体先被移到这里,就是这里正是案发地。 + * * {BedState !? made_up} [重新整理床铺] + 我小心翼翼地把床单铺回原状,试图让它看起来毫无动过的痕迹。 + ~ BedState = made_up + * * [测试床铺] + 我张开手指按了按床,床吱呀作响,但声响并不大。 + * * (darkunder) [查看床下] + 我躺下来,往床下看去,但什么都看不清。 + + * * {TURNS_SINCE(-> dobed) > 1} [看看别处?] + 我从床边退后一步,环顾四周。 + -> top + - - -> bedhub + + * {darkunder && bedroomLightState ? on_floor && bedroomLightState ? on} + [查看床下] + 我往床下看去,有什么东西在闪光。 + - - (reaching) + * * [伸手去拿] + 我伸手到床下去够,但无论那是什么,已经被踢得太远够不到。 + -> reaching + * * {Inventory ? cane} [用手杖够] + -> knock_with_cane + + * * {reaching > 1 } [站起来] + 我再次站起身,拍了拍大衣。 + -> top + + * (knock_with_cane) {reaching && TURNS_SINCE(-> reaching) >= 4 && Inventory ? cane } [用手杖够床下的东西] + 我用手杖对准地毯轻轻一挑,闪光的东西从床脚滑了出来。 + ~ move_to_supporter( knifeState, on_floor ) + * * (standup) [站起来] + 我满意地站起身,看到被挑出来的是一把带血的刀。 + -> top + + * * [再次查看床下] + 我移开手杖,再次查看床下,但那里已经没有其他东西。 + -> standup + + * {knifeState ? on_floor} [捡起刀] + 我小心翼翼地避开刀柄,将刀从地毯上拾起。 + ~ get(knife) + + * {Inventory ? knife} [查看刀] + 刀上的血迹已经干了,足够显露出刀柄上的部分指纹! + ~ reach (prints_on_knife) + + * [书桌……] + 我把注意力转向书桌。一盏台灯放在一角,另一角是空空的收纳盘,桌面没有其他东西。 + 一根木手杖斜靠在桌边。 + ~ bedroomLightState += seen + + - - (deskstate) + * * (pickup_cane) {Inventory !? cane} [捡起手杖] + ~ get(cane) + 我捡起了这根木手杖,它很沉,却没有任何标记。 + + * * { bedroomLightState !? on } [打开台灯] + -> operate_lamp -> + + * * [查看收纳盘] + 我看了看收纳盘,但里面什么都没有。要么是死者的文件被拿走了,要么他根本没什么业务,又或只是摆设。 + + + + (open) {open < 3} [打开抽屉] + 我{随便抽开一个|又拉开另一个|拉开第三个}抽屉,{锁着|也是锁着|果然也是锁着}。 + + * * {deskstate >= 2} [看看别处?] + 我再次从桌边退后一步。 + -> top + + - - -> deskstate + + * {(Inventory ? cane) && TURNS_SINCE(-> deskstate) <= 2} [挥动手杖] + 我仍握着手杖,轻轻挥了挥。它确实很沉,但不足以当作钝器使用。 + 不过若是自卫时用上倒也合适。可死者当时为什么没有抓起它?或者碰倒它? + + * [窗户……] + 我走到窗户旁,往外看去,只能见到房子旁潺潺流过的小溪。 + + - - (window_opts) + <- compare_prints(-> window_opts) + * * (downy) [往下看小溪] + { GlassState ? steamed: + 透过被雾气笼罩的玻璃,我看不清小溪。 -> see_prints_on_glass -> window_opts + } + 我看着那条小溪匆匆流过。这栋房子大概有点潮湿,但除此之外,这景象并没有告诉我什么。 + * * (greasy) [查看玻璃] + { GlassState ? steamed: -> downy } + 窗户上的玻璃很脏。里面外面都没人清理过。 + * * { GlassState ? steamed && not see_prints_on_glass && downy && greasy } + [查看雾气] + 外面很冷,自然我的呼吸会在玻璃上起雾。 -> see_prints_on_glass -> + + + {GlassState ? steam_gone} [对着玻璃哈气] + 我轻轻对着玻璃哈了口气。{ reached (fingerprints_on_glass): 指纹又重新显现出来。 } + ~ GlassState = steamed + + + + [看看别处?] + { window_opts < 2 || reached (fingerprints_on_glass) || GlassState ? steamed: + 我从昏暗的玻璃上移开了视线。 + {GlassState ? steamed: + ~ GlassState = steam_gone + <> 我呼出的雾气渐渐散去。 + } + -> top + } + 我从玻璃上靠了回去,我的呼吸在玻璃上凝起了一层雾。 + ~ GlassState = steamed + + - - -> window_opts + + * {top >= 5} [离开房间] + 我看得够多了。我{bedroomLightState ? on:关掉了台灯,然后}转身离开了房间。 + -> joe_in_hall + + - -> top - * {top >= 5} [Leave the room] - I'd seen enough. I {bedroomLightState ? on:switched off the lamp, then} turned and left the room. - -> joe_in_hall - - -> top - - = operate_lamp - I flicked the light switch. - { bedroomLightState ? on: - <> The bulb fell dark. - ~ bedroomLightState += off - ~ bedroomLightState -= on - - else: - { bedroomLightState ? on_floor: <> A little light spilled under the bed.} { bedroomLightState ? on_desk : <> The light gleamed on the polished tabletop. } - ~ bedroomLightState -= off - ~ bedroomLightState += on - } - ->-> - - + 我按下了灯的开关。 + { bedroomLightState ? on: + <> 灯泡熄灭了。 + ~ bedroomLightState += off + ~ bedroomLightState -= on + - else: + { bedroomLightState ? on_floor: <> 灯光透过床下洒出一丝微光。} { bedroomLightState ? on_desk : <> 灯光在抛光的桌面上闪烁着光芒。 } + ~ bedroomLightState -= off + ~ bedroomLightState += on + } + ->-> + + = compare_prints (-> backto) - * { between ((fingerprints_on_glass, prints_on_knife), fingerprints_on_glass_match_knife) } - [Compare the prints on the knife and the window ] - Holding the bloodied knife near the window, I breathed to bring out the prints once more, and compared them as best I could. - Hardly scientific, but they seemed very similar - very similiar indeed. - ~ reach (fingerprints_on_glass_match_knife) - -> backto - + * { between ((fingerprints_on_glass, prints_on_knife), fingerprints_on_glass_match_knife) } + [对比刀上的指纹和窗户上的指纹] + 我拿着带血的刀靠近窗户,对着玻璃哈了口气让指纹再次显现,尽力进行对比。 + 虽说这并不科学,但它们看起来非常相似——非常相似。 + ~ reach (fingerprints_on_glass_match_knife) + -> backto + = see_prints_on_glass - ~ reach (fingerprints_on_glass) - {But I could see a few fingerprints, as though someone hadpressed their palm against it.|The fingerprints were quite clear and well-formed.} They faded as I watched. - ~ GlassState = steam_gone - ->-> - + ~ reach (fingerprints_on_glass) + {但我能看见一些指纹,就像有人用手掌按过似的。|指纹非常清晰完整。} 当我注视时,它们渐渐消散。 + ~ GlassState = steam_gone + ->-> + = seen_light - * {bedroomLightState !? on} [ Turn on lamp ] - -> operate_lamp -> - - * { bedroomLightState !? on_bed && BedState ? bloodstain_visible } - [ Move the light to the bed ] - ~ move_to_supporter(bedroomLightState, on_bed) - - I moved the light over to the bloodstain and peered closely at it. It had soaked deeply into the fibres of the cotton sheet. - There was no doubt about it. This was where the blow had been struck. - ~ reach (murdered_in_bed) - - * { bedroomLightState !? on_desk } {TURNS_SINCE(-> floorit) >= 2 } - [ Move the light back to the desk ] - ~ move_to_supporter(bedroomLightState, on_desk) - I moved the light back to the desk, setting it down where it had originally been. - * (floorit) { bedroomLightState !? on_floor && darkunder } - [Move the light to the floor ] - ~ move_to_supporter(bedroomLightState, on_floor) - I picked the light up and set it down on the floor. - - -> top - + * {bedroomLightState !? on} [打开台灯] + -> operate_lamp -> + + * { bedroomLightState !? on_bed && BedState ? bloodstain_visible } + [把灯移到床上] + ~ move_to_supporter(bedroomLightState, on_bed) + + 我把灯移到血迹处仔细观察。血已经渗透进棉质床单的纤维中。 + 毫无疑问,凶手是在这里行凶的。 + ~ reach (murdered_in_bed) + + * { bedroomLightState !? on_desk } {TURNS_SINCE(-> floorit) >= 2 } + [把灯移回桌子] + ~ move_to_supporter(bedroomLightState, on_desk) + 我把灯移回桌子,放回它原来的位置。 + * (floorit) { bedroomLightState !? on_floor && darkunder } + [把灯移到地上] + ~ move_to_supporter(bedroomLightState, on_floor) + 我把灯拾起,放到了地上。 + - -> top + === joe_in_hall - My police contact, Joe, was waiting in the hall. 'So?' he demanded. 'Did you find anything interesting?' + 我的警察联系人乔正站在走廊里等我。“怎么样?”他问道,“你发现了什么有趣的东西吗?” - (found) - * {found == 1} 'Nothing.' - He shrugged. 'Shame.' - -> done - * { Inventory ? knife } 'I found the murder weapon.' - 'Good going!' Joe replied with a grin. 'We thought the murderer had gotten rid of it. I'll bag that for you now.' - ~ move_to_supporter(knifeState, with_joe) - - * {reached(prints_on_knife)} { knifeState ? with_joe } - 'There are prints on the blade[.'],' I told him. - He regarded them carefully. - 'Hrm. Not very complete. It'll be hard to get a match from these.' - ~ reach (joe_seen_prints_on_knife) - * { reached((fingerprints_on_glass_match_knife, joe_seen_prints_on_knife)) } - 'They match a set of prints on the window, too.' - 'Anyone could have touched the window,' Joe replied thoughtfully. 'But if they're more complete, they should help us get a decent match!' - ~ reach (joe_wants_better_prints) - * { between(body_on_bed, murdered_in_bed)} - 'The body was moved to the bed at some point[.'],' I told him. 'And then moved back to the floor.' - 'Why?' - * * 'I don't know.' - Joe nods. 'All right.' - * * 'Perhaps to get something from the floor?' - 'You wouldn't move a whole body for that.' - * * 'Perhaps he was killed in bed.' - 'It's just speculation at this point,' Joe remarks. - * { reached(murdered_in_bed) } - 'The victim was murdered in bed, and then the body was moved to the floor.' - 'Why?' - * * 'I don't know.' - Joe nods. 'All right, then.' - * * 'Perhaps the murderer wanted to mislead us.' - 'How so?' - * * * 'They wanted us to think the victim was awake[.'], I replied thoughtfully. 'That they were meeting their attacker, rather than being stabbed in their sleep.' - * * * 'They wanted us to think there was some kind of struggle[.'],' I replied. 'That the victim wasn't simply stabbed in their sleep.' - - - - 'But if they were killed in bed, that's most likely what happened. Stabbed, while sleeping.' - ~ reach (murdered_while_asleep) - * * 'Perhaps the murderer hoped to clean up the scene.' - 'But they were disturbed? It's possible.' - - * { found > 1} 'That's it.' - 'All right. It's a start,' Joe replied. - -> done - - -> found + * {found == 1} “没有。” + 他耸了耸肩:“可惜。” + -> done + * { Inventory ? knife } “我找到了凶器。” + “干得好!”乔笑着回答,“我们以为凶手已经处理掉了它。我现在帮你封存起来。” + ~ move_to_supporter(knifeState, with_joe) + + * {reached(prints_on_knife)} { knifeState ? with_joe } + “刀上有指纹。”我告诉他。 + 他仔细查看。 + “唔,不太完整,要比对起来有点困难。” + ~ reach (joe_seen_prints_on_knife) + * { reached((fingerprints_on_glass_match_knife, joe_seen_prints_on_knife)) } + “刀上的指纹和窗户上的指纹是同一人留下的。” + “谁都可能碰过窗户。”乔若有所思地回答,“但如果窗户上的指纹更完整,或许能帮我们找到匹配!” + ~ reach (joe_wants_better_prints) + * { between(body_on_bed, murdered_in_bed)} + “尸体曾被移到床上,然后又被移回地面。”我告诉他。 + “为什么?” + * * “我不知道。” + 乔点点头:“好吧。” + * * “可能是为了从地上拿东西?” + “没必要为了拿东西而搬动整具尸体。” + * * “可能是死在床上的。” + “现在说什么都是猜测。”乔说。 + * { reached(murdered_in_bed) } + “受害者是在床上被谋杀的,随后尸体被移到了地上。” + “为什么?” + * * “我不知道。” + 乔点点头:“好吧。” + * * “可能凶手想误导我们。” + “怎么误导?” + * * * “想让我们以为受害者是清醒着遇害的。”我若有所思地回答,“好像他是见到了凶手才被杀。” + * * * “想让我们以为曾经发生过搏斗。”我回答,“让我们以为他不是在睡梦中被杀的。” + - - - “但如果真是在床上被杀,那很可能他是在睡觉时被刺杀的。” + ~ reach (murdered_while_asleep) + * * “可能凶手想清理现场。” + “然后被打断了?也有可能。” + + * { found > 1} “就这些。” + “好吧,总算是个开始。”乔回答。 + -> done + - -> found - (done) - { - - between(joe_wants_better_prints, joe_got_better_prints): - ~ reach (joe_got_better_prints) - <> 'I'll get those prints from the window now.' - - reached(joe_seen_prints_on_knife): - <> 'I'll run those prints as best I can.' - - else: - <> 'Not much to go on.' - } - -> END - + { + - between(joe_wants_better_prints, joe_got_better_prints): + ~ reach (joe_got_better_prints) + <> “我现在去把窗户上的指纹提取下来。” + - reached(joe_seen_prints_on_knife): + <> “我会尽量比对这些指纹。” + - else: + <> “线索不多。” + } + -> END -## 8) Summary +## 8) 总结 -To summarise a difficult section, **Ink**'s list construction provides: +现在,我们来总结一下这个困难的章节,**Ink**的列表构造提供了: -### Flags -* Each list entry is an event -* Use `+=` to mark an event as having occurred -* Test using `?` and `!?` +### 标志(Flags) +* 每个列表条目是一个事件 +* 使用 `+=` 来标记事件已发生 +* 使用 `?` 和 `!?` 进行测试 -Example: +示例: LIST GameEvents = foundSword, openedCasket, metGorgon { GameEvents ? openedCasket } { GameEvents ? (foundSword, metGorgon) } ~ GameEvents += metGorgon -### State machines -* Each list entry is a state -* Use `=` to set the state; `++` and `--` to step forward or backward -* Test using `==`, `>` etc +### 状态机(State machines) +* 每个列表条目是一个状态 +* 使用 `=` 设置状态;使用 `++` 和 `--` 前进或后退 +* 使用 `==`、`>` 等进行判定 -Example: +示例: LIST PancakeState = ingredients_gathered, batter_mix, pan_hot, pancakes_tossed, ready_to_eat { PancakeState == batter_mix } { PancakeState < ready_to_eat } ~ PancakeState++ -### Properties -* Each list is a different property, with values for the states that property can take (on or off, lit or unlit, etc) -* Change state by removing the old state, then adding in the new -* Test using `?` and `!?` +### 属性(Properties) +* 每个列表是不同的属性,包含该属性可取的状态值(on/off,lit/unlit 等) +* 通过先移除旧状态,再添加新状态来改变状态 +* 使用 `?` 和 `!?` 进行判定 -Example: +示例: LIST OnOffState = on, off LIST ChargeState = uncharged, charging, charged VAR PhoneState = (off, uncharged) - * {PhoneState !? uncharged } [Plug in phone] + * { PhoneState !? uncharged } [插上手机充电] ~ PhoneState -= LIST_ALL(ChargeState) ~ PhoneState += charging - You plug the phone into charge. - * { PhoneState ? (on, charged) } [ Call my mother ] - - + 你将手机插上开始充电。 + * { PhoneState ? (on, charged) } [给妈妈打电话] # 第 6 部分:标识符中的国际字符支持|Part 6: International character support in identifiers -By default, ink has no limitations on the use of non-ASCII characters inside the story content. However, a limitation currently exsits -on the characters that can be used for names of constants, variables, stictches, diverts and other named flow elements (a.k.a. *identifiers*). - -Sometimes it is inconvenient for a writer using a non-ASCII language to write a story because they have to constantly switch to naming identifiers in ASCII and then switching back to whatever language they are using for the story. In addition, naming identifiers in the author's own language could improve the overal readibility of the raw story format. - -In an effort to assist in the above scenario, ink *automatically* supports a list of pre-defined non-ASCII character ranges that can be used as identifiers. In general, those ranges have been selected to include the alpha-numeric subset of the official unicode character range, which would suffice for naming identifiers. The below section gives more detailed information on the non-ASCII characters that ink automatically supports. - -### Supported Identifier Characters - -The support for the additional character ranges in ink is currently limited to a predefined set of character ranges. - -Below is a listing of the currently supported identifier ranges. - - - **Arabic** - - Enables characters for languages of the Arabic family and is a subset of the official *Arabic* unicode range `\u0600`-`\u06FF`. - - - - **Armenian** - - Enables characters for the Armenian language and is a subset of the official *Armenian* unicode range `\u0530`-`\u058F`. - - - - **Cyrillic** - - Enables characters for languages using the Cyrillic alphabet and is a subset of the official *Cyrillic* unicode range `\u0400`-`\u04FF`. - - - - **Greek** - - Enables characters for languages using the Greek alphabet and is a subset of the official *Greek and Coptic* unicode range `\u0370`-`\u03FF`. - - - - **Hebrew** - - Enables characters in Hebrew using the Hebrew alphabet and is a subset of the official *Hebrew* unicode range `\u0590`-`\u05FF`. - - - - **Latin Extended A** - - Enables an extended character range subset of the Latin alphabet - completely represented by the official *Latin Extended-A* unicode range `\u0100`-`\u017F`. - - - - **Latin Extended B** - - Enables an extended character range subset of the Latin alphabet - completely represented by the official *Latin Extended-B* unicode range `\u0180`-`\u024F`. - -- **Latin 1 Supplement** - - Enables an extended character range subset of the Latin alphabet - completely represented by the official *Latin 1 Supplement* unicode range `\u0080` - `\u00FF`. - - -**NOTE!** ink files should be saved in UTF-8 format, which ensures that the above character ranges are supported. - -If a particular character range that you would like to use within identifiers isn't supported, feel free to open an [issue](/inkle/ink/issues/new) or [pull request](/inkle/ink/pulls) on the main ink repo. +默认情况下,Ink 在故事内容中使用非 ASCII 字符没有任何限制。然而,目前对常量、变量、针脚(Stitch)、转向(Divert)以及其他具名流程元素(即 标识符)的命名字符存在限制。 + +对于使用非 ASCII 语言(可以简单认为是 26 个英文字母和常见标点符号内)写作的作者来说,这意味着他们在编写故事时需要不断在 ASCII 命名与故事语言之间切换,十分不便。此外,使用作者本身语言为标识符命名,也有助于提升原始故事格式的整体可读性。 + +为帮助解决上述问题,Ink 自动支持一系列预定义的可用于标识符的非 ASCII 字符范围。一般来说,这些范围包含了官方 Unicode 字符范围中字母数字的子集,足以用于标识符命名。以下部分给出了 Ink 自动支持的非 ASCII 可用字符的详细信息。 + + +### 支持的标识符字符 + +Ink 对额外字符范围的支持目前仅限于预定义的一组字符范围。 + +以下是当前支持的标识符字符范围列表: + +- **阿拉伯语(Arabic)** + + 启用阿拉伯语系语言的字符,是官方 *Arabic* Unicode 范围 `\u0600-\u06FF` 的子集。 + +- **亚美尼亚语(Armenian)** + + 启用亚美尼亚语言的字符,是官方 *Armenian* Unicode 范围 `\u0530-\u058F` 的子集。 + +- **西里尔字母(Cyrillic)** + + 启用使用西里尔字母语言的字符,是官方 *Cyrillic* Unicode 范围 `\u0400-\u04FF` 的子集。 + +- **希腊语(Greek)** + + 启用使用希腊字母语言的字符,是官方 *Greek and Coptic* Unicode 范围 `\u0370-\u03FF` 的子集。 + +- **希伯来语(Hebrew)** + + 启用使用希伯来字母语言的希伯来语字符,是官方 *Hebrew* Unicode 范围 `\u0590-\u05FF` 的子集。 + +- **拉丁字母扩展 A(Latin Extended A)** + + 启用拉丁字母扩展范围的字符,完整对应官方 *Latin Extended-A* Unicode 范围 `\u0100-\u017F`。 + +- **拉丁字母扩展 B(Latin Extended B)** + + 启用拉丁字母扩展范围的字符,完整对应官方 *Latin Extended-B* Unicode 范围 `\u0180-\u024F`。 + +- **拉丁字母补充(Latin 1 Supplement)** + + 启用拉丁字母扩展范围的字符,完整对应官方 *Latin 1 Supplement* Unicode 范围 `\u0080 - \u00FF`。 + +**注意!** Ink 文件应以 UTF-8 格式保存,以确保支持上述字符范围。 + +果您希望在标识符中使用的特定但目前尚未支持的字符范围,欢迎在 Ink 主代码库提交 [issue](/inkle/ink/issues/new) 或提交 [pull request](/inkle/ink/pulls)。 \ No newline at end of file From 47c3b2702824b4c21d249dc132b587366b537aeb Mon Sep 17 00:00:00 2001 From: Ander-Index Date: Sun, 6 Jul 2025 23:37:47 +0800 Subject: [PATCH 22/22] Save --- .../RunningYourInk_Simplified-Chinese.md | 104 ++++++++++++------ 1 file changed, 68 insertions(+), 36 deletions(-) diff --git a/Documentation/RunningYourInk_Simplified-Chinese.md b/Documentation/RunningYourInk_Simplified-Chinese.md index a8661a01..6c5011d5 100644 --- a/Documentation/RunningYourInk_Simplified-Chinese.md +++ b/Documentation/RunningYourInk_Simplified-Chinese.md @@ -1,6 +1,38 @@ # 运行您的 Ink -## Quick Start +
+内容目录 + +- [运行您的 Ink](#运行您的-ink) + - [快速开始](#快速开始) + - [进一步了解](#进一步了解) + - [从运行时的 API 开始](#从运行时的-api-开始) + - [保存与加载](#保存与加载) + - [错误处理](#错误处理) + - [就这?](#就这) + - [引擎使用与理念](#引擎使用与理念) + - [使用标签标记您的 ink 内容](#使用标签标记您的-ink-内容) + - [逐行标签](#逐行标签) + - [结点标签](#结点标签) + - [全局标签](#全局标签) + - [选项标签](#选项标签) + - [进阶:动态标签](#进阶动态标签) + - [跳转到特定的“场景”](#跳转到特定的场景) + - [设置或获取 ink 的变量](#设置或获取-ink-的变量) + - [阅读与访问计数](#阅读与访问计数) + - [变量观察器](#变量观察器) + - [运行函数](#运行函数) + - [外部函数](#外部函数) + - [Alternatives to external functions](#alternatives-to-external-functions) + - [动作与纯函数的比较](#动作与纯函数的比较) + - [外部函数的回退方案](#外部函数的回退方案) + - [多个并行流程(测试版)](#多个并行流程测试版) + - [使用 LIST](#使用-list) + - [使用编译器](#使用编译器) + +
+ +## 快速开始 *Note that although these instructions are written with Unity in mind, it's possible (and straightforward) to run your ink in a non-Unity C# environment.* @@ -9,7 +41,7 @@ * Click it, and you should get an Editor window that lets you play (preview) your story. * To integrate into your game, see **Getting started with the runtime API**, below. -## Further information +## 进一步了解 Ink uses an intermediate `.json` format, which is compiled from the original `.ink` files. ink's Unity integration package automatically compiles ink files for you, but you can also compile them on the command line. See **Using inklecate on the command line** in the [README](http://www.github.com/inkle/ink) for more information. @@ -29,7 +61,7 @@ public class Script : MonoBehaviour { Story _inkStory; ``` -## Getting started with the runtime API +## 从运行时的 API 开始 As mentioned above, your `.ink` file(s) are compiled to a single `.json` file. This is treated by Unity as a TextAsset, that you can then load up in your game. @@ -74,7 +106,7 @@ while (_inkStory.canContinue) { //And now you're ready to return to step 1, and present content again. ``` -### Saving and loading +### 保存与加载 To save the state of your story within your game, call: @@ -84,7 +116,7 @@ To save the state of your story within your game, call: `_inkStory.state.LoadJson(savedJson);` -### Error handling +### 错误处理 If you made a mistake in your ink that the compiler can't catch, then the story will throw an exception. To avoid this and get standard Unity errors instead, you can use an error handler that you should assign when you create your story: @@ -98,13 +130,13 @@ _inkStory.onError += (msg, type) => { Debug.LogError(msg); }; ``` -### Is that it? +### 就这? That's it! You can achieve a lot with just those simple steps, but for more advanced usage, including deep integration with your game, read on. For a sample Unity project in action with minimal UI, see [Aaron Broder's Blot repo](https://github.com/abroder/blot). -## Engine usage and philosophy +## 引擎使用与理念 In Unity, we recommend using your own component class to wrap `Ink.Runtime.Story`. The runtime **ink** engine has been designed to be reasonably general purpose and have a simple API. We also recommend wrapping rather than inheriting from `Story`, so that you can expose to your game only the functionality that you need. @@ -128,47 +160,47 @@ The above approach is used in our current game for the writer to declare the pro To mark up content more explicitly, you may want to use *tags* or *external functions* - see below. At inkle, we find that we use a mixture, but we actually find the above approach useful for a very large portion of our interaction with the game - it's very flexible. -## Marking up your ink content with tags +## 使用标签标记您的 ink 内容 -Tags can be used to add metadata to your game content that isn't intended to appear to the player. Within ink, add a `#` character followed by any string content you want to pass over to the game. There are three main places where you can add these hash tags: +标签可用于向游戏内容添加元数据,这些信息并不会显示给玩家。在 ink 中,只需添加一个 `#` 字符,后跟任意想要传递给游戏的字符串内容即可。有三个主要位置可以添加这些井号标签: -### Line by line tags +### 逐行标签 -One use case is for a graphic adventure that has different art for characters depending on their facial expression. So, you could do: +假定使用场景是图像冒险类游戏,要根据角色的面部表情切换不同的立绘。那么你可以这样写: Passepartout: Really, Monsieur. # surly -On the game side, every time you get content with `_inkStory.Continue()`, you can get a list of tags with `_inkStory.currentTags`, which will return a `List`, in the above case with just one element: `"surly"`. +在游戏端,每次使用 `_inkStory.Continue()` 获取内容时,都可以通过 `_inkStory.currentTags` 获取标签列表,它会返回一个 `List`。在上述例子中只包含一个元素:`"surly"`。 -To add more than one tag, simply delimit them with more `#` characters: +如果需要添加多个标签,只需使用更多的 `#` 字符分隔即可: Passepartout: Really, Monsieur. # surly # really_monsieur.ogg -The above demonstrate another possible use case: providing full voice-over for your game, by marking up your ink with the audio filenames. +上述示例同时演示了另一个使用场景:为游戏提供完整的配音,通过在 ink 中标记音频文件名来实现。 -Tags for a line can be written above it, or on the end of the line: +标签可以写在该行内容的上方,或写在行尾: - # the first tag - # the second tag - This is the line of content. # the third tag + # 第一个标签 + # 第二个标签 + 这行是故事的内容。# 第三个标签 -All of the above tags will be included in the `currentTags` list. +以上所有标签都会包含在 `currentTags` 列表中。 -### Knot tags +### 结点标签 -Any tags that you include at the very top of a knot: +只需要在结点的一开头就写入标签,例如: === Munich == # location: Germany # overview: munich.ogg # require: Train ticket - First line of content in the knot. + 这个结点内容的第一行。 ...are accessible by calling `_inkStory.TagsForContentAtPath("your_knot")`, which is useful for getting metadata from a knot before you actually want the game to go there. Note that these tags will also appear in the `currentTags` list for the first line of content in the knot. -### Global tags +### 全局标签 Any tags provided at the very top of the main ink file are accessible via the `Story`'s `globalTags` property, which also returns a `List`. Any top level story metadata can be included there. @@ -179,7 +211,7 @@ We suggest the following by convention, if you wish to share your ink stories pu Note that [Inky](https://github.com/inkle/inky) will use the title tag in this format as the `

` tag in a story exported for web. -#### Choice tags +#### 选项标签 Tags can also be applied to choices. Depending where the tag is placed inside the ink line, the tag will appear on both the choice and the content generated by that choice; on just the choice; or just on the output: @@ -193,7 +225,7 @@ You can use all three in the same ink line: The choice tags are stored inside the `List tags` on the choice object. -#### Advanced: Tags are dynamic +#### 进阶:动态标签 Note that the content of a tag can contain any inline **ink**, such as shuffles, cycles, function calls and variable replacements. @@ -201,7 +233,7 @@ Note that the content of a tag can contain any inline **ink**, such as shuffles, I open the door. #suspense_music{RANDOM(1, 4)}.mp3 -## Jumping to a particular "scene" +## 跳转到特定的“场景” Top level named sections in **ink** are called knots (see [the writing tutorial](WritingWithInk.md)). You can tell the runtime engine to jump to a particular named knot: @@ -219,7 +251,7 @@ _inkStory.ChoosePathString("myKnotName.theStitchWithin"); (Note that this path string is a *runtime* path rather than the path as used within the **ink** format. It's just been designed so that for the basics of knots and stitches, the format works out the same. Unfortunately however, you can't reference gather or choice labels this way.) -## Setting/getting ink variables +## 设置或获取 ink 的变量 The state of the variables in the **ink** engine is, appropriately enough, stored within the `variablesState` object within the `story`. You can both get and set variables directly on this object: @@ -229,7 +261,7 @@ _inkStory.variablesState["player_health"] = 100 int health = (int) _inkStory.variablesState["player_health"] ``` -## Read/Visit counts +## 阅读与访问计数 To find out the number of times that a knot or stitch has been visited by the ink engine, you can use this API: @@ -239,7 +271,7 @@ _inkStory.state.VisitCountAtPathString("..."); The path string is in the form `"yourKnot"` for knots, and `"yourKnot.yourStitch"` for stitches. -## Variable observers +## 变量观察器 You can register a delegate function to be called whenever a particular variable changes. This can be useful to reflect the state of certain **ink** variables directly in the UI. For example: @@ -252,7 +284,7 @@ _inkStory.ObserveVariable ("health", (string varName, object newValue) => { The reason that the variable name is passed in is so that you can have a single observer function that observes multiple different variables. -## Running functions +## 运行函数 You can run ink functions directly from C# using `EvaluationFunction`. @@ -265,7 +297,7 @@ You do not need to Continue() over any text lines that may exist in the function var returnValue = _inkStory.EvaluationFunction("myFunctionName", out textOutput, params); ``` -## External functions +## 外部函数 You can define game-side functions in C# that can be called directly from **ink**. To do so: @@ -301,7 +333,7 @@ Remember that in addition to external functions, there are other good ways to co `>>> SHOT: view_over_bridge` -#### Actions v.s. Pure functions +#### 动作与纯函数的比较 **Warning:** The following section is subtly complex! However, don't worry - you can probably ignore it and use default behaviour. If you find a situation where glue isn't working the way you expect and there's an external function in there somewhere, or if you're just plain curious, read on... @@ -333,7 +365,7 @@ public void BindExternalFunction(string funcName, Func func, bool lookah * **Actions** should have `lookaheadSafe = false` * **Pure functions** should have `lookaheadSafe = true` -### Fallbacks for external functions +### 外部函数的回退方案 When testing your story, either in [Inky](/inkle/inky) or in the [ink-unity integration](/inkle/ink-unity-integration/) player window, you don't get an opportunity to bind a game function before running the story. To get around this, you can define a *fallback function* within ink, which is run if the `EXTERNAL` function can't be found. To do so, simply create an ink function with the same name and parameters. For example, for the above `multiply` example, create the ink function: @@ -344,7 +376,7 @@ When testing your story, either in [Inky](/inkle/inky) or in the [ink-unity inte ~ return 1 ``` -## Multiple parallel flows (BETA) +## 多个并行流程(测试版) It is possible to have multiple parallel "flows" - allowing the following examples: @@ -361,7 +393,7 @@ The API is relatively simple: - `story.currentFlowName` — a string containing the name of the currently active flow. May contain internal identifier for default flow, so use `currentFlowIsDefault` to check first. ) -## Working with LISTs +## 使用 LIST Ink lists are a more complex type used in the ink engine, so interacting with them is a bit more involved than with ints, floats and strings. @@ -430,7 +462,7 @@ list.Without(otherList) // equivalent to (list - otherList) in ink list.Contains(otherList) // equivalent to (list ? otherList) in ink ``` -## Using the compiler +## 使用编译器 Precompiling your stories is more efficient than loading .ink at runtime. That said, it's a useful approach for some situations, and can be done with the following code: