746 lines
33 KiB
HTML
746 lines
33 KiB
HTML
|
|
<!DOCTYPE HTML>
|
||
|
|
<html lang="en" class="light sidebar-visible" dir="ltr">
|
||
|
|
<head>
|
||
|
|
<!-- Book generated using mdBook -->
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<title>Expression Language - Storybook Language Guide</title>
|
||
|
|
|
||
|
|
|
||
|
|
<!-- Custom HTML head -->
|
||
|
|
|
||
|
|
<meta name="description" content="Comprehensive documentation for the Storybook narrative simulation language">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
|
|
<meta name="theme-color" content="#ffffff">
|
||
|
|
|
||
|
|
<link rel="icon" href="../favicon.svg">
|
||
|
|
<link rel="shortcut icon" href="../favicon.png">
|
||
|
|
<link rel="stylesheet" href="../css/variables.css">
|
||
|
|
<link rel="stylesheet" href="../css/general.css">
|
||
|
|
<link rel="stylesheet" href="../css/chrome.css">
|
||
|
|
<link rel="stylesheet" href="../css/print.css" media="print">
|
||
|
|
|
||
|
|
<!-- Fonts -->
|
||
|
|
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
|
||
|
|
<link rel="stylesheet" href="../fonts/fonts.css">
|
||
|
|
|
||
|
|
<!-- Highlight.js Stylesheets -->
|
||
|
|
<link rel="stylesheet" href="../highlight.css">
|
||
|
|
<link rel="stylesheet" href="../tomorrow-night.css">
|
||
|
|
<link rel="stylesheet" href="../ayu-highlight.css">
|
||
|
|
|
||
|
|
<!-- Custom theme stylesheets -->
|
||
|
|
|
||
|
|
|
||
|
|
<!-- Provide site root to javascript -->
|
||
|
|
<script>
|
||
|
|
var path_to_root = "../";
|
||
|
|
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
|
||
|
|
</script>
|
||
|
|
<!-- Start loading toc.js asap -->
|
||
|
|
<script src="../toc.js"></script>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<div id="body-container">
|
||
|
|
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||
|
|
<script>
|
||
|
|
try {
|
||
|
|
var theme = localStorage.getItem('mdbook-theme');
|
||
|
|
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||
|
|
|
||
|
|
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||
|
|
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||
|
|
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||
|
|
}
|
||
|
|
} catch (e) { }
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<!-- Set the theme before any content is loaded, prevents flash -->
|
||
|
|
<script>
|
||
|
|
var theme;
|
||
|
|
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||
|
|
if (theme === null || theme === undefined) { theme = default_theme; }
|
||
|
|
const html = document.documentElement;
|
||
|
|
html.classList.remove('light')
|
||
|
|
html.classList.add(theme);
|
||
|
|
html.classList.add("js");
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
||
|
|
|
||
|
|
<!-- Hide / unhide sidebar before it is displayed -->
|
||
|
|
<script>
|
||
|
|
var sidebar = null;
|
||
|
|
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
|
||
|
|
if (document.body.clientWidth >= 1080) {
|
||
|
|
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||
|
|
sidebar = sidebar || 'visible';
|
||
|
|
} else {
|
||
|
|
sidebar = 'hidden';
|
||
|
|
}
|
||
|
|
sidebar_toggle.checked = sidebar === 'visible';
|
||
|
|
html.classList.remove('sidebar-visible');
|
||
|
|
html.classList.add("sidebar-" + sidebar);
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||
|
|
<!-- populated by js -->
|
||
|
|
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
|
||
|
|
<noscript>
|
||
|
|
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
|
||
|
|
</noscript>
|
||
|
|
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
|
||
|
|
<div class="sidebar-resize-indicator"></div>
|
||
|
|
</div>
|
||
|
|
</nav>
|
||
|
|
|
||
|
|
<div id="page-wrapper" class="page-wrapper">
|
||
|
|
|
||
|
|
<div class="page">
|
||
|
|
<div id="menu-bar-hover-placeholder"></div>
|
||
|
|
<div id="menu-bar" class="menu-bar sticky">
|
||
|
|
<div class="left-buttons">
|
||
|
|
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||
|
|
<i class="fa fa-bars"></i>
|
||
|
|
</label>
|
||
|
|
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||
|
|
<i class="fa fa-paint-brush"></i>
|
||
|
|
</button>
|
||
|
|
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||
|
|
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
||
|
|
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||
|
|
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||
|
|
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||
|
|
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||
|
|
</ul>
|
||
|
|
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||
|
|
<i class="fa fa-search"></i>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<h1 class="menu-title">Storybook Language Guide</h1>
|
||
|
|
|
||
|
|
<div class="right-buttons">
|
||
|
|
<a href="../print.html" title="Print this book" aria-label="Print this book">
|
||
|
|
<i id="print-button" class="fa fa-print"></i>
|
||
|
|
</a>
|
||
|
|
<a href="https://github.com/r3t-studios/storybook" title="Git repository" aria-label="Git repository">
|
||
|
|
<i id="git-repository-button" class="fa fa-github"></i>
|
||
|
|
</a>
|
||
|
|
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div id="search-wrapper" class="hidden">
|
||
|
|
<form id="searchbar-outer" class="searchbar-outer">
|
||
|
|
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||
|
|
</form>
|
||
|
|
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||
|
|
<div id="searchresults-header" class="searchresults-header"></div>
|
||
|
|
<ul id="searchresults">
|
||
|
|
</ul>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||
|
|
<script>
|
||
|
|
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||
|
|
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||
|
|
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||
|
|
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||
|
|
});
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<div id="content" class="content">
|
||
|
|
<main>
|
||
|
|
<h1 id="expression-language"><a class="header" href="#expression-language">Expression Language</a></h1>
|
||
|
|
<p>The Storybook expression language enables conditions, queries, and logic throughout the system. Expressions appear in life arc transitions, behavior tree guards, decorator conditions, and relationship queries. This chapter provides a complete reference for expression syntax and semantics.</p>
|
||
|
|
<h2 id="what-are-expressions"><a class="header" href="#what-are-expressions">What are Expressions?</a></h2>
|
||
|
|
<p>Expressions are logical statements that evaluate to <code>true</code> or <code>false</code>. They combine:</p>
|
||
|
|
<ul>
|
||
|
|
<li><strong>Literals</strong>: Numbers, strings, booleans</li>
|
||
|
|
<li><strong>Identifiers</strong>: References to fields and entities</li>
|
||
|
|
<li><strong>Comparisons</strong>: <code>==</code>, <code>!=</code>, <code><</code>, <code><=</code>, <code>></code>, <code>>=</code></li>
|
||
|
|
<li><strong>Logical operators</strong>: <code>and</code>, <code>or</code>, <code>not</code></li>
|
||
|
|
<li><strong>Field access</strong>: <code>self.field</code>, <code>other.field</code></li>
|
||
|
|
<li><strong>Quantifiers</strong>: <code>forall</code>, <code>exists</code> (for collections)</li>
|
||
|
|
</ul>
|
||
|
|
<h2 id="syntax"><a class="header" href="#syntax">Syntax</a></h2>
|
||
|
|
<pre><code class="language-bnf"><expression> ::= <literal>
|
||
|
|
| <identifier>
|
||
|
|
| <field-access>
|
||
|
|
| <comparison>
|
||
|
|
| <logical>
|
||
|
|
| <unary>
|
||
|
|
| <quantifier>
|
||
|
|
| "(" <expression> ")"
|
||
|
|
|
||
|
|
<literal> ::= <int> | <float> | <string> | <bool>
|
||
|
|
|
||
|
|
<identifier> ::= <simple-name>
|
||
|
|
| <qualified-path>
|
||
|
|
|
||
|
|
<field-access> ::= <expression> "." <identifier>
|
||
|
|
| "self" "." <identifier>
|
||
|
|
| "other" "." <identifier>
|
||
|
|
|
||
|
|
<comparison> ::= <expression> <comp-op> <expression>
|
||
|
|
|
||
|
|
<comp-op> ::= "==" | "!=" | "<" | "<=" | ">" | ">="
|
||
|
|
|
||
|
|
<logical> ::= <expression> "and" <expression>
|
||
|
|
| <expression> "or" <expression>
|
||
|
|
|
||
|
|
<unary> ::= "not" <expression>
|
||
|
|
| "-" <expression>
|
||
|
|
|
||
|
|
<quantifier> ::= ("forall" | "exists") <identifier> "in" <expression> ":" <expression>
|
||
|
|
</code></pre>
|
||
|
|
<h2 id="literals"><a class="header" href="#literals">Literals</a></h2>
|
||
|
|
<h3 id="integer-literals"><a class="header" href="#integer-literals">Integer Literals</a></h3>
|
||
|
|
<pre><code class="language-storybook">42
|
||
|
|
-7
|
||
|
|
0
|
||
|
|
1000
|
||
|
|
</code></pre>
|
||
|
|
<h3 id="float-literals"><a class="header" href="#float-literals">Float Literals</a></h3>
|
||
|
|
<pre><code class="language-storybook">3.14
|
||
|
|
-0.5
|
||
|
|
0.0
|
||
|
|
100.25
|
||
|
|
</code></pre>
|
||
|
|
<h3 id="string-literals"><a class="header" href="#string-literals">String Literals</a></h3>
|
||
|
|
<pre><code class="language-storybook">"Martha"
|
||
|
|
"Sourdough takes patience."
|
||
|
|
"active"
|
||
|
|
</code></pre>
|
||
|
|
<p>Strings are enclosed in double quotes. Escape sequences: <code>\n</code>, <code>\t</code>, <code>\\</code>, <code>\"</code>.</p>
|
||
|
|
<h3 id="boolean-literals"><a class="header" href="#boolean-literals">Boolean Literals</a></h3>
|
||
|
|
<pre><code class="language-storybook">true
|
||
|
|
false
|
||
|
|
</code></pre>
|
||
|
|
<h2 id="identifiers"><a class="header" href="#identifiers">Identifiers</a></h2>
|
||
|
|
<p>Identifiers reference fields or entities.</p>
|
||
|
|
<h3 id="simple-identifiers"><a class="header" href="#simple-identifiers">Simple Identifiers</a></h3>
|
||
|
|
<pre><code class="language-storybook">health
|
||
|
|
enemy_count
|
||
|
|
is_ready
|
||
|
|
</code></pre>
|
||
|
|
<h3 id="qualified-paths"><a class="header" href="#qualified-paths">Qualified Paths</a></h3>
|
||
|
|
<pre><code class="language-storybook">Martha.skill_level
|
||
|
|
Character.emotional_state
|
||
|
|
</code></pre>
|
||
|
|
<h2 id="comparison-operators"><a class="header" href="#comparison-operators">Comparison Operators</a></h2>
|
||
|
|
<h3 id="equality-"><a class="header" href="#equality-">Equality: <code>==</code></a></h3>
|
||
|
|
<p>Tests if two values are equal.</p>
|
||
|
|
<pre><code class="language-storybook">name == "Martha"
|
||
|
|
count == 5
|
||
|
|
status == active
|
||
|
|
</code></pre>
|
||
|
|
<p><strong>Type compatibility:</strong></p>
|
||
|
|
<ul>
|
||
|
|
<li>Both operands must be the same type</li>
|
||
|
|
<li>Works with: int, float, string, bool, enum values</li>
|
||
|
|
</ul>
|
||
|
|
<h3 id="inequality-"><a class="header" href="#inequality-">Inequality: <code>!=</code></a></h3>
|
||
|
|
<p>Tests if two values are not equal.</p>
|
||
|
|
<pre><code class="language-storybook">name != "Gregory"
|
||
|
|
health != 0
|
||
|
|
ready != true
|
||
|
|
</code></pre>
|
||
|
|
<h3 id="less-than"><a class="header" href="#less-than">Less Than: <code><</code></a></h3>
|
||
|
|
<p>Tests if left operand is less than right.</p>
|
||
|
|
<pre><code class="language-storybook">health < 20
|
||
|
|
age < 18
|
||
|
|
distance < 10.0
|
||
|
|
</code></pre>
|
||
|
|
<p><strong>Valid types:</strong> int, float</p>
|
||
|
|
<h3 id="less-than-or-equal-"><a class="header" href="#less-than-or-equal-">Less Than or Equal: <code><=</code></a></h3>
|
||
|
|
<pre><code class="language-storybook">health <= 50
|
||
|
|
count <= max_count
|
||
|
|
</code></pre>
|
||
|
|
<h3 id="greater-than"><a class="header" href="#greater-than">Greater Than: <code>></code></a></h3>
|
||
|
|
<pre><code class="language-storybook">strength > 10
|
||
|
|
bond > 0.5
|
||
|
|
</code></pre>
|
||
|
|
<h3 id="greater-than-or-equal-"><a class="header" href="#greater-than-or-equal-">Greater Than or Equal: <code>>=</code></a></h3>
|
||
|
|
<pre><code class="language-storybook">age >= 21
|
||
|
|
score >= 100
|
||
|
|
</code></pre>
|
||
|
|
<h2 id="logical-operators"><a class="header" href="#logical-operators">Logical Operators</a></h2>
|
||
|
|
<h3 id="and-and"><a class="header" href="#and-and">AND: <code>and</code></a></h3>
|
||
|
|
<p>Both operands must be true.</p>
|
||
|
|
<pre><code class="language-storybook">health < 50 and has_potion
|
||
|
|
is_ready and not is_busy
|
||
|
|
age >= 18 and age < 65
|
||
|
|
</code></pre>
|
||
|
|
<p><strong>Evaluation:</strong> Short-circuit (if left is false, right is not evaluated)</p>
|
||
|
|
<h3 id="or-or"><a class="header" href="#or-or">OR: <code>or</code></a></h3>
|
||
|
|
<p>At least one operand must be true.</p>
|
||
|
|
<pre><code class="language-storybook">is_day or is_lit
|
||
|
|
health < 20 or surrounded
|
||
|
|
enemy_count == 0 or all_enemies_dead
|
||
|
|
</code></pre>
|
||
|
|
<p><strong>Evaluation:</strong> Short-circuit (if left is true, right is not evaluated)</p>
|
||
|
|
<h3 id="operator-precedence"><a class="header" href="#operator-precedence">Operator Precedence</a></h3>
|
||
|
|
<p>From highest to lowest:</p>
|
||
|
|
<ol>
|
||
|
|
<li><strong>Parentheses</strong>: <code>(...)</code></li>
|
||
|
|
<li><strong>Unary</strong>: <code>not</code>, <code>-</code></li>
|
||
|
|
<li><strong>Comparisons</strong>: <code>==</code>, <code>!=</code>, <code><</code>, <code><=</code>, <code>></code>, <code>>=</code></li>
|
||
|
|
<li><strong>AND</strong>: <code>and</code></li>
|
||
|
|
<li><strong>OR</strong>: <code>or</code></li>
|
||
|
|
</ol>
|
||
|
|
<p><strong>Examples:</strong></p>
|
||
|
|
<pre><code class="language-storybook">not is_ready and is_awake
|
||
|
|
// Equivalent to: (not is_ready) and is_awake
|
||
|
|
|
||
|
|
health < 50 or is_poisoned and has_antidote
|
||
|
|
// Equivalent to: (health < 50) or (is_poisoned and has_antidote)
|
||
|
|
|
||
|
|
// Use parentheses for clarity:
|
||
|
|
(health < 50 or is_poisoned) and has_antidote
|
||
|
|
</code></pre>
|
||
|
|
<h2 id="unary-operators"><a class="header" href="#unary-operators">Unary Operators</a></h2>
|
||
|
|
<h3 id="not-not"><a class="header" href="#not-not">NOT: <code>not</code></a></h3>
|
||
|
|
<p>Inverts a boolean value.</p>
|
||
|
|
<pre><code class="language-storybook">not is_ready
|
||
|
|
not (health < 20)
|
||
|
|
not enemy_nearby and safe
|
||
|
|
</code></pre>
|
||
|
|
<h3 id="negation--"><a class="header" href="#negation--">Negation: <code>-</code></a></h3>
|
||
|
|
<p>Negates a numeric value.</p>
|
||
|
|
<pre><code class="language-storybook">-health
|
||
|
|
-10
|
||
|
|
-(max_value - current_value)
|
||
|
|
</code></pre>
|
||
|
|
<h2 id="field-access"><a class="header" href="#field-access">Field Access</a></h2>
|
||
|
|
<h3 id="direct-field-access"><a class="header" href="#direct-field-access">Direct Field Access</a></h3>
|
||
|
|
<pre><code class="language-storybook">health
|
||
|
|
bond
|
||
|
|
emotional_state
|
||
|
|
</code></pre>
|
||
|
|
<p>References a field on the current entity.</p>
|
||
|
|
<h3 id="dot-access"><a class="header" href="#dot-access">Dot Access</a></h3>
|
||
|
|
<pre><code class="language-storybook">Martha.skill_level
|
||
|
|
Character.emotional_state
|
||
|
|
enemy.health
|
||
|
|
</code></pre>
|
||
|
|
<p>Access fields on other entities.</p>
|
||
|
|
<h3 id="self-access"><a class="header" href="#self-access">Self Access</a></h3>
|
||
|
|
<p>In relationships and certain contexts, <code>self</code> refers to the current participant:</p>
|
||
|
|
<pre><code class="language-storybook">self.bond
|
||
|
|
self.responsibility
|
||
|
|
self.trust
|
||
|
|
</code></pre>
|
||
|
|
<p><strong>Use case:</strong> Relationship transitions, symmetric queries</p>
|
||
|
|
<h3 id="other-access"><a class="header" href="#other-access">Other Access</a></h3>
|
||
|
|
<p>In relationships, <code>other</code> refers to other participants:</p>
|
||
|
|
<pre><code class="language-storybook">other.bond
|
||
|
|
other.aware_of_mentor
|
||
|
|
other.respect
|
||
|
|
</code></pre>
|
||
|
|
<p><strong>Use case:</strong> Relationship queries with perspective</p>
|
||
|
|
<h3 id="example-in-life-arcs"><a class="header" href="#example-in-life-arcs">Example in Life Arcs</a></h3>
|
||
|
|
<pre><code class="language-storybook">life_arc RelationshipState {
|
||
|
|
state new {
|
||
|
|
on self.bond > 0.7 and other.bond > 0.7 -> stable
|
||
|
|
}
|
||
|
|
|
||
|
|
state stable {
|
||
|
|
on self.bond < 0.3 -> troubled
|
||
|
|
on other.bond < 0.3 -> troubled
|
||
|
|
}
|
||
|
|
|
||
|
|
state troubled {
|
||
|
|
on self.bond < 0.1 or other.bond < 0.1 -> broken
|
||
|
|
}
|
||
|
|
|
||
|
|
state broken {}
|
||
|
|
}
|
||
|
|
</code></pre>
|
||
|
|
<h2 id="quantifiers"><a class="header" href="#quantifiers">Quantifiers</a></h2>
|
||
|
|
<p>Quantifiers test conditions over collections.</p>
|
||
|
|
<h3 id="forall-forall"><a class="header" href="#forall-forall">ForAll: <code>forall</code></a></h3>
|
||
|
|
<p>Tests if a condition holds for all elements in a collection.</p>
|
||
|
|
<pre><code class="language-storybook">forall e in enemies: e.defeated
|
||
|
|
forall item in inventory: item.weight < 10
|
||
|
|
</code></pre>
|
||
|
|
<p><strong>Syntax:</strong></p>
|
||
|
|
<pre><code class="language-bnf">forall <variable> in <collection>: <predicate>
|
||
|
|
</code></pre>
|
||
|
|
<p><strong>Semantics:</strong></p>
|
||
|
|
<ul>
|
||
|
|
<li>Returns <code>true</code> if predicate is true for every element</li>
|
||
|
|
<li>Returns <code>true</code> for empty collections (vacuously true)</li>
|
||
|
|
</ul>
|
||
|
|
<p><strong>Examples:</strong></p>
|
||
|
|
<pre><code class="language-storybook">// All enemies defeated?
|
||
|
|
forall enemy in enemies: enemy.health <= 0
|
||
|
|
|
||
|
|
// All party members ready?
|
||
|
|
forall member in party: member.is_ready
|
||
|
|
|
||
|
|
// All doors locked?
|
||
|
|
forall door in doors: door.is_locked
|
||
|
|
</code></pre>
|
||
|
|
<h3 id="exists-exists"><a class="header" href="#exists-exists">Exists: <code>exists</code></a></h3>
|
||
|
|
<p>Tests if a condition holds for at least one element.</p>
|
||
|
|
<pre><code class="language-storybook">exists e in enemies: e.is_hostile
|
||
|
|
exists item in inventory: item.is_healing_potion
|
||
|
|
</code></pre>
|
||
|
|
<p><strong>Syntax:</strong></p>
|
||
|
|
<pre><code class="language-bnf">exists <variable> in <collection>: <predicate>
|
||
|
|
</code></pre>
|
||
|
|
<p><strong>Semantics:</strong></p>
|
||
|
|
<ul>
|
||
|
|
<li>Returns <code>true</code> if predicate is true for any element</li>
|
||
|
|
<li>Returns <code>false</code> for empty collections</li>
|
||
|
|
</ul>
|
||
|
|
<p><strong>Examples:</strong></p>
|
||
|
|
<pre><code class="language-storybook">// Any enemy nearby?
|
||
|
|
exists enemy in enemies: enemy.distance < 10
|
||
|
|
|
||
|
|
// Any door unlocked?
|
||
|
|
exists door in doors: not door.is_locked
|
||
|
|
|
||
|
|
// Any ally wounded?
|
||
|
|
exists ally in allies: ally.health < ally.max_health * 0.5
|
||
|
|
</code></pre>
|
||
|
|
<h3 id="nested-quantifiers"><a class="header" href="#nested-quantifiers">Nested Quantifiers</a></h3>
|
||
|
|
<p>Quantifiers can nest:</p>
|
||
|
|
<pre><code class="language-storybook">forall team in teams: exists player in team: player.is_leader
|
||
|
|
// Every team has at least one leader
|
||
|
|
|
||
|
|
exists room in dungeon: forall enemy in room.enemies: enemy.defeated
|
||
|
|
// At least one room has all enemies defeated
|
||
|
|
</code></pre>
|
||
|
|
<h2 id="usage-in-context"><a class="header" href="#usage-in-context">Usage in Context</a></h2>
|
||
|
|
<h3 id="life-arc-transitions"><a class="header" href="#life-arc-transitions">Life Arc Transitions</a></h3>
|
||
|
|
<pre><code class="language-storybook">life_arc CombatState {
|
||
|
|
state idle {
|
||
|
|
on enemy_count > 0 -> combat
|
||
|
|
}
|
||
|
|
|
||
|
|
state combat {
|
||
|
|
on health < 20 -> fleeing
|
||
|
|
on enemy_count == 0 -> victorious
|
||
|
|
}
|
||
|
|
|
||
|
|
state fleeing {
|
||
|
|
on distance_from_enemies > 100 -> safe
|
||
|
|
}
|
||
|
|
|
||
|
|
state victorious {
|
||
|
|
on celebration_complete -> idle
|
||
|
|
}
|
||
|
|
|
||
|
|
state safe {
|
||
|
|
on health >= 50 -> idle
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</code></pre>
|
||
|
|
<h3 id="behavior-tree-conditions"><a class="header" href="#behavior-tree-conditions">Behavior Tree Conditions</a></h3>
|
||
|
|
<pre><code class="language-storybook">behavior GuardedAction {
|
||
|
|
if(health > 50 and has_weapon) {
|
||
|
|
AggressiveAttack
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
behavior ConditionalChoice {
|
||
|
|
choose tactics {
|
||
|
|
then melee {
|
||
|
|
if(distance < 5 and weapon_type == "sword")
|
||
|
|
MeleeAttack
|
||
|
|
}
|
||
|
|
|
||
|
|
then ranged {
|
||
|
|
if(distance >= 5 and has_arrows)
|
||
|
|
RangedAttack
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</code></pre>
|
||
|
|
<h3 id="behavior-tree-conditions-1"><a class="header" href="#behavior-tree-conditions-1">Behavior Tree Conditions</a></h3>
|
||
|
|
<pre><code class="language-storybook">behavior SmartAI {
|
||
|
|
choose strategy {
|
||
|
|
then aggressive {
|
||
|
|
if(health > 70 and enemy_count < 3)
|
||
|
|
Attack
|
||
|
|
}
|
||
|
|
|
||
|
|
then defensive {
|
||
|
|
if(health < 30 or enemy_count >= 5)
|
||
|
|
Defend
|
||
|
|
}
|
||
|
|
|
||
|
|
then balanced {
|
||
|
|
if(health >= 30 and health <= 70)
|
||
|
|
TacticalManeuver
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</code></pre>
|
||
|
|
<h2 id="type-system"><a class="header" href="#type-system">Type System</a></h2>
|
||
|
|
<h3 id="type-compatibility"><a class="header" href="#type-compatibility">Type Compatibility</a></h3>
|
||
|
|
<p>Comparisons require compatible types:</p>
|
||
|
|
<div class="table-wrapper"><table><thead><tr><th>Operator</th><th>Left Type</th><th>Right Type</th><th>Valid?</th></tr></thead><tbody>
|
||
|
|
<tr><td><code>==</code>, <code>!=</code></td><td>int</td><td>int</td><td>✓</td></tr>
|
||
|
|
<tr><td><code>==</code>, <code>!=</code></td><td>float</td><td>float</td><td>✓</td></tr>
|
||
|
|
<tr><td><code>==</code>, <code>!=</code></td><td>string</td><td>string</td><td>✓</td></tr>
|
||
|
|
<tr><td><code>==</code>, <code>!=</code></td><td>bool</td><td>bool</td><td>✓</td></tr>
|
||
|
|
<tr><td><code>==</code>, <code>!=</code></td><td>enum</td><td>same enum</td><td>✓</td></tr>
|
||
|
|
<tr><td><code>==</code>, <code>!=</code></td><td>int</td><td>float</td><td>✗</td></tr>
|
||
|
|
<tr><td><code><</code>, <code><=</code>, <code>></code>, <code>>=</code></td><td>int</td><td>int</td><td>✓</td></tr>
|
||
|
|
<tr><td><code><</code>, <code><=</code>, <code>></code>, <code>>=</code></td><td>float</td><td>float</td><td>✓</td></tr>
|
||
|
|
<tr><td><code><</code>, <code><=</code>, <code>></code>, <code>>=</code></td><td>string</td><td>string</td><td>✗</td></tr>
|
||
|
|
</tbody></table>
|
||
|
|
</div>
|
||
|
|
<h3 id="implicit-coercion"><a class="header" href="#implicit-coercion">Implicit Coercion</a></h3>
|
||
|
|
<p><strong>None.</strong> Storybook has no implicit type coercion. All comparisons must be between compatible types.</p>
|
||
|
|
<p><strong>Error:</strong></p>
|
||
|
|
<pre><code class="language-storybook">count == "5" // Error: int vs string
|
||
|
|
health < true // Error: int vs bool
|
||
|
|
</code></pre>
|
||
|
|
<p><strong>Correct:</strong></p>
|
||
|
|
<pre><code class="language-storybook">count == 5
|
||
|
|
health < 50
|
||
|
|
</code></pre>
|
||
|
|
<h2 id="special-keyword-is"><a class="header" href="#special-keyword-is">Special Keyword: <code>is</code></a></h2>
|
||
|
|
<p>The <code>is</code> keyword provides syntactic sugar for equality with enum values:</p>
|
||
|
|
<pre><code class="language-storybook">// Instead of:
|
||
|
|
status == active
|
||
|
|
|
||
|
|
// You can write:
|
||
|
|
status is active
|
||
|
|
</code></pre>
|
||
|
|
<p><strong>More examples:</strong></p>
|
||
|
|
<pre><code class="language-storybook">name is "Martha"
|
||
|
|
skill_level is master
|
||
|
|
emotional_state is focused
|
||
|
|
</code></pre>
|
||
|
|
<p>This is purely syntactic—<code>is</code> and <code>==</code> are equivalent.</p>
|
||
|
|
<h2 id="complete-examples"><a class="header" href="#complete-examples">Complete Examples</a></h2>
|
||
|
|
<h3 id="simple-conditions"><a class="header" href="#simple-conditions">Simple Conditions</a></h3>
|
||
|
|
<pre><code class="language-storybook">health < 20
|
||
|
|
enemy_nearby
|
||
|
|
not is_ready
|
||
|
|
count > 5
|
||
|
|
</code></pre>
|
||
|
|
<h3 id="complex-conditions"><a class="header" href="#complex-conditions">Complex Conditions</a></h3>
|
||
|
|
<pre><code class="language-storybook">(health < 20 and not has_potion) or surrounded
|
||
|
|
forall e in enemies: e.defeated
|
||
|
|
exists item in inventory: item.is_healing_potion and item.quantity > 0
|
||
|
|
</code></pre>
|
||
|
|
<h3 id="life-arc-with-complex-conditions"><a class="header" href="#life-arc-with-complex-conditions">Life Arc with Complex Conditions</a></h3>
|
||
|
|
<pre><code class="language-storybook">life_arc CharacterMood {
|
||
|
|
state content {
|
||
|
|
on health < 30 or hunger > 80 -> distressed
|
||
|
|
on social_interaction > 0.8 -> happy
|
||
|
|
}
|
||
|
|
|
||
|
|
state distressed {
|
||
|
|
on health >= 50 and hunger < 30 -> content
|
||
|
|
on (health < 10 or hunger > 95) and help_available -> desperate
|
||
|
|
}
|
||
|
|
|
||
|
|
state happy {
|
||
|
|
on social_interaction < 0.3 -> content
|
||
|
|
on received_bad_news -> distressed
|
||
|
|
}
|
||
|
|
|
||
|
|
state desperate {
|
||
|
|
on help_received -> distressed
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</code></pre>
|
||
|
|
<h3 id="behavior-with-quantifiers"><a class="header" href="#behavior-with-quantifiers">Behavior with Quantifiers</a></h3>
|
||
|
|
<pre><code class="language-storybook">behavior SquadLeader {
|
||
|
|
choose leadership {
|
||
|
|
then regroup {
|
||
|
|
if(squad_has_wounded)
|
||
|
|
OrderRetreat
|
||
|
|
}
|
||
|
|
|
||
|
|
then advance {
|
||
|
|
if(squad_all_ready)
|
||
|
|
OrderAdvance
|
||
|
|
}
|
||
|
|
|
||
|
|
then hold_position {
|
||
|
|
if(not squad_all_ready)
|
||
|
|
OrderHold
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</code></pre>
|
||
|
|
<h3 id="relationship-query"><a class="header" href="#relationship-query">Relationship Query</a></h3>
|
||
|
|
<pre><code class="language-storybook">life_arc FriendshipQuality {
|
||
|
|
state new_friends {
|
||
|
|
on self.bond > 0.7 and other.bond > 0.7 -> strong_bond
|
||
|
|
on self.trust < 0.3 or other.trust < 0.3 -> shaky
|
||
|
|
}
|
||
|
|
|
||
|
|
state strong_bond {
|
||
|
|
on self.bond < 0.5 -> weakening
|
||
|
|
}
|
||
|
|
|
||
|
|
state weakening {
|
||
|
|
on self.bond < 0.2 or other.bond < 0.2 -> ended
|
||
|
|
on self.bond > 0.7 and other.bond > 0.7 -> strong_bond
|
||
|
|
}
|
||
|
|
|
||
|
|
state shaky {
|
||
|
|
on self.trust > 0.6 and other.trust > 0.6 -> new_friends
|
||
|
|
on self.trust < 0.1 or other.trust < 0.1 -> ended
|
||
|
|
}
|
||
|
|
|
||
|
|
state ended {}
|
||
|
|
}
|
||
|
|
</code></pre>
|
||
|
|
<h2 id="validation-rules"><a class="header" href="#validation-rules">Validation Rules</a></h2>
|
||
|
|
<ol>
|
||
|
|
<li><strong>Type consistency</strong>: Both sides of comparison must be compatible types</li>
|
||
|
|
<li><strong>Boolean context</strong>: Logical operators (<code>and</code>, <code>or</code>, <code>not</code>) require boolean operands</li>
|
||
|
|
<li><strong>Field existence</strong>: Referenced fields must exist on the entity</li>
|
||
|
|
<li><strong>Collection validity</strong>: Quantifiers require collection-typed expressions</li>
|
||
|
|
<li><strong>Variable scope</strong>: Quantifier variables only valid within their predicate</li>
|
||
|
|
<li><strong>No division by zero</strong>: Arithmetic operations must not divide by zero</li>
|
||
|
|
<li><strong>Enum validity</strong>: Enum comparisons must reference defined enum values</li>
|
||
|
|
</ol>
|
||
|
|
<h2 id="best-practices"><a class="header" href="#best-practices">Best Practices</a></h2>
|
||
|
|
<h3 id="1-use-parentheses-for-clarity"><a class="header" href="#1-use-parentheses-for-clarity">1. Use Parentheses for Clarity</a></h3>
|
||
|
|
<p><strong>Avoid:</strong></p>
|
||
|
|
<pre><code class="language-storybook">health < 50 or is_poisoned and has_antidote
|
||
|
|
</code></pre>
|
||
|
|
<p><strong>Prefer:</strong></p>
|
||
|
|
<pre><code class="language-storybook">(health < 50 or is_poisoned) and has_antidote
|
||
|
|
</code></pre>
|
||
|
|
<h3 id="2-break-complex-conditions"><a class="header" href="#2-break-complex-conditions">2. Break Complex Conditions</a></h3>
|
||
|
|
<p><strong>Avoid:</strong></p>
|
||
|
|
<pre><code class="language-storybook">on (health < 20 and not has_potion) or (surrounded and not has_escape) or (enemy_count > 10 and weapon_broken) -> desperate
|
||
|
|
</code></pre>
|
||
|
|
<p><strong>Prefer:</strong></p>
|
||
|
|
<pre><code class="language-storybook">state combat {
|
||
|
|
on health < 20 and not has_potion -> desperate
|
||
|
|
on surrounded and not has_escape -> desperate
|
||
|
|
on enemy_count > 10 and weapon_broken -> desperate
|
||
|
|
}
|
||
|
|
</code></pre>
|
||
|
|
<h3 id="3-name-complex-conditions"><a class="header" href="#3-name-complex-conditions">3. Name Complex Conditions</a></h3>
|
||
|
|
<p>For repeated complex conditions, consider using intermediate fields:</p>
|
||
|
|
<p><strong>Instead of:</strong></p>
|
||
|
|
<pre><code class="language-storybook">on health < (max_health * 0.2) and enemy_count > 5 -> flee
|
||
|
|
</code></pre>
|
||
|
|
<p><strong>Consider:</strong></p>
|
||
|
|
<pre><code class="language-storybook">// In character definition:
|
||
|
|
critically_wounded: health < (max_health * 0.2)
|
||
|
|
outnumbered: enemy_count > 5
|
||
|
|
|
||
|
|
// In life arc:
|
||
|
|
on critically_wounded and outnumbered -> flee
|
||
|
|
</code></pre>
|
||
|
|
<h3 id="4-use-is-for-enums"><a class="header" href="#4-use-is-for-enums">4. Use <code>is</code> for Enums</a></h3>
|
||
|
|
<p><strong>Prefer:</strong></p>
|
||
|
|
<pre><code class="language-storybook">status is active
|
||
|
|
emotional_state is focused
|
||
|
|
</code></pre>
|
||
|
|
<p><strong>Over:</strong></p>
|
||
|
|
<pre><code class="language-storybook">status == active
|
||
|
|
emotional_state == focused
|
||
|
|
</code></pre>
|
||
|
|
<h3 id="5-quantifiers-for-collections"><a class="header" href="#5-quantifiers-for-collections">5. Quantifiers for Collections</a></h3>
|
||
|
|
<p><strong>Avoid:</strong></p>
|
||
|
|
<pre><code class="language-storybook">// Manual checks for each element
|
||
|
|
if enemy1.defeated and enemy2.defeated and enemy3.defeated
|
||
|
|
</code></pre>
|
||
|
|
<p><strong>Prefer:</strong></p>
|
||
|
|
<pre><code class="language-storybook">if forall enemy in enemies: enemy.defeated
|
||
|
|
</code></pre>
|
||
|
|
<h2 id="cross-references"><a class="header" href="#cross-references">Cross-References</a></h2>
|
||
|
|
<ul>
|
||
|
|
<li><a href="./13-life-arcs.html">Life Arcs</a> - Transition conditions</li>
|
||
|
|
<li><a href="./11-behavior-trees.html">Behavior Trees</a> - Guard and condition nodes</li>
|
||
|
|
<li><a href="./12-decorators.html">Decorators</a> - Guard decorator</li>
|
||
|
|
<li><a href="./15-relationships.html">Relationships</a> - Self/other field access</li>
|
||
|
|
<li><a href="./18-value-types.html">Value Types</a> - Literal value types</li>
|
||
|
|
</ul>
|
||
|
|
<h2 id="related-concepts"><a class="header" href="#related-concepts">Related Concepts</a></h2>
|
||
|
|
<ul>
|
||
|
|
<li><strong>Type safety</strong>: Strong typing prevents type errors at compile time</li>
|
||
|
|
<li><strong>Short-circuit evaluation</strong>: AND/OR operators optimize evaluation</li>
|
||
|
|
<li><strong>Quantifiers</strong>: Enable expressive collection queries</li>
|
||
|
|
<li><strong>Field access</strong>: Context-sensitive (<code>self</code>, <code>other</code>) for relationships</li>
|
||
|
|
<li><strong>Boolean algebra</strong>: Standard logical operators with expected semantics</li>
|
||
|
|
</ul>
|
||
|
|
|
||
|
|
</main>
|
||
|
|
|
||
|
|
<nav class="nav-wrapper" aria-label="Page navigation">
|
||
|
|
<!-- Mobile navigation buttons -->
|
||
|
|
<a rel="prev" href="../reference/16-other-declarations.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||
|
|
<i class="fa fa-angle-left"></i>
|
||
|
|
</a>
|
||
|
|
|
||
|
|
<a rel="next prefetch" href="../reference/18-value-types.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||
|
|
<i class="fa fa-angle-right"></i>
|
||
|
|
</a>
|
||
|
|
|
||
|
|
<div style="clear: both"></div>
|
||
|
|
</nav>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||
|
|
<a rel="prev" href="../reference/16-other-declarations.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||
|
|
<i class="fa fa-angle-left"></i>
|
||
|
|
</a>
|
||
|
|
|
||
|
|
<a rel="next prefetch" href="../reference/18-value-types.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||
|
|
<i class="fa fa-angle-right"></i>
|
||
|
|
</a>
|
||
|
|
</nav>
|
||
|
|
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Livereload script (if served using the cli tool) -->
|
||
|
|
<script>
|
||
|
|
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||
|
|
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
|
||
|
|
const socket = new WebSocket(wsAddress);
|
||
|
|
socket.onmessage = function (event) {
|
||
|
|
if (event.data === "reload") {
|
||
|
|
socket.close();
|
||
|
|
location.reload();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
window.onbeforeunload = function() {
|
||
|
|
socket.close();
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
<script>
|
||
|
|
window.playground_copyable = true;
|
||
|
|
</script>
|
||
|
|
|
||
|
|
|
||
|
|
<script src="../elasticlunr.min.js"></script>
|
||
|
|
<script src="../mark.min.js"></script>
|
||
|
|
<script src="../searcher.js"></script>
|
||
|
|
|
||
|
|
<script src="../clipboard.min.js"></script>
|
||
|
|
<script src="../highlight.js"></script>
|
||
|
|
<script src="../book.js"></script>
|
||
|
|
|
||
|
|
<!-- Custom JS scripts -->
|
||
|
|
|
||
|
|
|
||
|
|
</div>
|
||
|
|
</body>
|
||
|
|
</html>
|