1 <!DOCTYPE html>
     
2 <html>
     
3 <!--              Welcome to
     
4               _   _ ___ ____ ____  
     
5              | | | |_ _/ ___/ ___| 
     
6              | |_| || |\___ \___ \ 
     
7        +-----|  _  || | ___) |__) | -----+
     
8        |     |_| |_|___|____/____/       |
     
9        |                                 |
    
10        | Copyright 2024 David Gauer      |
    
11        | http://ratfactor.com/hiss/      |
    
12        +---------------------------------+
    
13        |                                 |
    
14        | Table of Contents               |
    
15        | -----------------               |
    
16        | 0. EditorHtml <--(You are here) |
    
17        | 1. ExampleScript                |
    
18        | 2. ColorPreviewSVG              |
    
19        | 3. EditorJS                     |
    
20        | 4. ExportedPlayerHtml           |
    
21        | 5. SharedPlayerJS               |
    
22        | 6. HissDocumentation            |
    
23        |                                 |
    
24        +---------------------------------+
    
25 -->
    
26 <head>
    
27     <meta charset="utf-8">
    
28     <meta name="viewport" content="width=device-width, initial-scale=1">
    
29     <!-- Hiss "H" favicon in base64-encoded SVG data --> 
    
30     <link rel="shortcut icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='115' height='115'%3E%3Cg style='fill:%23D2D; stroke: %23000; stroke-width: 1.5;'%3E%3Cpath d='m 71,16 c 2,2 4,3 5,6 V 38 H 61 V 21 c 1,-2 3,-4 6,-6 l -25,1 c -58,3 -3,50 2,28 1,-6 -6,-7 -10,-3 7,2 3,3 3,3 C 6,51 38,3 47,23 l -1,34 c 0,3 -3,5 -5,7 L 66,63 C 64,62 62,59 61,57 V 41 h 15 l 1,27.5 c 0,3 2,9 7,12 6,4 15,4 22,2 3,-1 6,-7 5,-11 -2,-7 -15,-5 -19,-4 4,1 15,10 11,13 -4,3 -11,-6 -11,-11 0,-17 0,-49 0,-49 0,-3 2,-4 4,-6 z' /%3E%3Cpath d='m 77,67 c -23,1 -47,24 -63,8 -4,-4 -2,-14 3,-14 8,0 3,8 0,8 3,2 10,4 13,0 3,-5 -5,-17 -12,-15 -32,7 -2,39 17,37 15,0 19,-12 41,-24 z' /%3E%3C/g%3E%3C/svg%3E%0A">
    
31     <title>Hiss Game Editor</title>
    
32     <style>
    
33     :root {
    
34         /* Editor theme variables with high-contrast defaults.
    
35            If everything works, you'll never actually see these defaults. */
    
36         --bg-page: #FFF; --bg-main: #FFF; --fg-main: #000; --a-color: #00F;
    
37         --bg-list: #FFF; --bg-title: #FFF; --fg-title: #000; --borders: #000;
    
38         --doc-border: none; --doc-h2: #000; --doc-h3: #000; --bg-menu: #FFF;
    
39         --fg-menu: #00F; --fg-values: #000; --fg-script: #000; --hl-script: #FBD;
    
40         --svg-hiss-stroke: #000; --svg-hiss-fill: #FFF; --svg-decos-stroke: #000;
    
41     }
    
42 
    43     /* Main Styles */
    
44     body { background: var(--bg-page); max-width: 1500px; margin: auto;
    
45        padding: 4px; color: var(--fg-main); }
    
46     #container { display: flex; min-height: 300px; }
    
47     code, pre, textarea, #el_edit_highlights { font-size: 1rem; } /* fix monospace */
    
48 
    49     /* Top Editor Toolbar and Title */
    
50     header{ border: 1px solid var(--borders); box-sizing: border-box; }
    
51     header .title { background: var(--bg-title); color: var(--fg-title);
    
52         text-align: center; border-bottom: 1px solid var(--borders);
    
53     }
    
54     header .title h1 { font-size: 1.6em; font-weight: bold; margin: 0; }
    
55     header .menu { background: var(--bg-menu); }
    
56     header .menu a { display: inline-block; margin: 3px 10px;
    
57         color: var(--fg-menu);}
    
58     #el_export_game { font-weight: bold; margin-right: 30px; }
    
59     #el_new_game_area { width: 400px; padding: 1em; margin: auto;
    
60         border: 4px solid var(--borders);
    
61     }
    
62 
    63     /* Left Column: script list and game variable values */
    
64     .list { max-width: 200px; padding: 5px; background: var(--bg-list);
    
65         border: 1px solid var(--borders); overflow-x: hidden;
    
66         overflow-y: auto; flex: 1; }
    
67     .list a { display: block; }
    
68     .list a.indent { margin-left: 10px; }
    
69     .list a.selected { font-weight: bold; font-style: italic; }
    
70     .list .section { margin: 10px 0 10px 0; font-weight: bold;
    
71         border-bottom: 1px solid var(--borders); }
    
72     .list a.add { float: right; font-weight: normal; }
    
73     .list .var span { color: var(--fg-values) }
    
74 
    75     /* Middle Column: script editing text area */
    
76     .editor { flex: 2; display: flex; flex-direction: column;
    
77         background: var(--bg-main); }
    
78     .editor .script-name { padding: 5px; }
    
79     #el_edit_container { position: relative; width: 100%; height: 100%; border: 0;
    
80         margin: 0; padding: 0; }
    
81     #el_edit_textarea, #el_edit_highlights { font-family: monospace; white-space: pre-wrap;
    
82         word-wrap: break-word; padding: 10px; position: absolute;
    
83         box-sizing: border-box; border: 0; width: 100%; height: 100%;
    
84         border: 1px solid var(--borders); }
    
85     #el_edit_textarea { background-color: transparent; color: var(--fg-script);
    
86         z-index: 2; resize: none; }
    
87     #el_edit_highlights { background-color: var(--bg-main); z-index: 1; overflow: auto;
    
88         pointer-events: none; color: transparent; }
    
89     #el_edit_highlights i { font-style: normal; background-color: var(--hl-script); }
    
90     #el_name_edit, #el_add_script { min-height: 300px; background: var(--bg-main);
    
91         color: var(--fg-script); padding: 1em; }
    
92     .inset { margin: 15px; }
    
93     p.del { background: #FDD; border: 1px solid #F00; color: #000; padding: 1em; }
    
94     button.del { background: #B00; color: #FFF; font-weight: bold; }
    
95     .error { background: #000; color: #F77; padding: 5px; font-weight: bold; }
    
96 
    97     /* Right Column: Game player render/preview area */
    
98     .player { flex: 2; padding: 10px; font-size: 1.2em; color: var(--fg-main);
    
99         border: 1px solid var(--borders); background: var(--bg-main);
   
100     }
   
101     .player h1 { font-weight: bold; font-size: larger; text-align: center;
   
102         color: var(--fg-main);
   
103     }
   
104     .box { border: 4px solid var(--borders); padding: 4px; }
   
105     a { text-decoration: underline; color: var(--a-color); cursor: pointer; }
   
106     .deco svg, #svg_hiss { stroke-width: 2; stroke: var(--svg-decos-stroke);
   
107         fill: none; display: block; margin: 10px auto; }
   
108 
   109     /* Hiss User Guide (documentation) area */
   
110     .docs { background: var(--bg-main); max-width: 1500px; margin: 10px auto;
   
111         padding: 1em; box-sizing: border-box; border: var(--doc-border); }
   
112     .docs .inner { max-width: 800px; margin: auto; }
   
113     .docs h2 { color: var(--doc-h2); text-align: center; }
   
114     .docs h3 { color: var(--doc-h3); margin-top: 3em;
   
115                border-bottom: 1px solid var(--doc-h3); }
   
116     .docs h4 { margin-top: 4em; }
   
117     .svg_hiss_styles { stroke-width: 1; stroke: var(--svg-hiss-stroke);
   
118         fill: var(--svg-hiss-fill); }
   
119     .docs table { width: 100%; border-collapse: collapse; }
   
120     .docs table td { border: 6px solid var(--borders); padding: 10px;
   
121         vertical-align: top; }
   
122     .docs table h4 { margin-top: 0; }
   
123     .docs table ul { padding-left: 10px; }
   
124     .docs pre { word-wrap: break-word; padding: 10px;
   
125         border: 1px solid var(--borders); }
   
126     .diagram { display: block; margin: auto; }
   
127     </style>
   
128 </head>
   
129 <body>
   
130 <header>
   
131     <div class="title" id="ed_title"><h1>Hiss Game Editor</h1></div>
   
132     <div class="menu">
   
133         <a id="el_export_game">▶ Export Game</a>
   
134         <a id="el_export_script">Save File</a>
   
135         <a id="el_import_script">Load File</a>
   
136         <a id="el_new_game_btn">New Game</a>
   
137         <a id="el_cycle_scheme">Editor Color Scheme</a>
   
138         <div id="el_new_game_area">
   
139             <p>You can make a new game from scratch or re-load the initial sample
   
140                game that comes with Hiss.</p>
   
141             <p>Both options will delete the current game scripts and replace them.</p>
   
142             <button id="el_new_blank_btn">Make New Blank Game</button>
   
143             <button id="el_load_sample_btn">Load Sample Game</button>
   
144             <button id="el_new_cancel_btn">Cancel</button>
   
145         </div>
   
146     </div>
   
147 </header>
   
148 <div id="container">
   
149     <div class="list">
   
150         <div class="section">Scripts
   
151             <a class="add" id="el_show_add_script">(add+)</a>
   
152         </div>
   
153         <div id="el_script_list"> <!-- draw_script_list() here --> </div>
   
154         <div id="el_var_list_section" class="section">Values</div>
   
155         <div id="el_var_list"><!-- draw_vars() here --></div>
   
156         <div id="el_color_preview"> <!-- draw_color_preview() here --> </div>
   
157     </div>
   
158     <div class="editor">
   
159         <div id="el_script_name_area" class="script-name">
   
160             <span id="el_script_name" class="name">?</span>
   
161             <a id="el_edit_script_name">(edit)</a>
   
162         </div>
   
163         <p id="el_edit_start">
   
164             This the first script in the game. You can't delete it, or
   
165             rename it because then the game player wouldn't know where to
   
166             start!
   
167         </p>
   
168         <div id="el_name_edit">
   
169             <input type="text" id="el_rename_field">
   
170             <button id="el_rename_current">Change name</button>
   
171             <span id="el_rename_msg"></span>
   
172             <hr>
   
173             <button id="el_del_btn" class="del">Delete</button>
   
174             <div id="el_del_prompt">
   
175                 <p class="del">
   
176                     Are you sure you wish to delete script
   
177                     '<span id="el_del_script_name"></span>'?
   
178                 </p>
   
179                 <button id="el_del_btn2" class="del">DELETE!</button>
   
180                 <button id="el_del_cancel">No, Cancel</button>
   
181             </div>
   
182         </div>
   
183         <div id="el_add_script">
   
184             <p>Add a new script named:</p>
   
185             <input type="text" id="el_name_field">
   
186             <button id="el_add_script_btn">Add</button>
   
187             <div id="el_add_error_msg" class="error"></div>
   
188             <p>Special:</p>
   
189             <a href="#" id="el_create_before">Create '*Always Before</a>
   
190             <p class="inset">
   
191                 A special script that always runs before every other script.</p>
   
192             <a href="#" id="el_create_after">Create '*Always After</a>
   
193             <p class="inset">
   
194                 Another special script that always runs after every other script.</p>
   
195         </div>
   
196         <div id="el_edit_container">
   
197             <div id="el_edit_highlights" aria-hidden="true"></div> <!-- highlights -->
   
198             <textarea id="el_edit_textarea" spellcheck="false"></textarea>
   
199         </div>
   
200         <!-- draw_edit_area() here -->
   
201     </div>
   
202     <div class="player">
   
203         <h1><!-- game title --></h1>
   
204         <div class="box"><!-- play_obj() here --></div>
   
205     </div>
   
206 </div>
   
207 <input id="el_file_elem" type="file" accept="text/plain" style="display:none;">
   
208 
   209 
   210       <!-- +---------------------------------+
   
211            |                                 |
   
212            |          ExampleScript          |
   
213            |                                 |
   
214            +---------------------------------+ -->
   
215 
   216 <script id="el_sample_script" type="hiss">
   
217 [*Start]:
   
218 Hello world!
   
219 
   220 set title to My Game
   
221 
   222 deco:  o<<==[.o][<<O*=|][.o]<<==o
   
223 
   224 Welcome to Hiss! This is a work in progress, but before I'm done, you'll be
   
225 able to learn how to use this with documentation below.
   
226 
   227 link frog to frog
   
228 
   229 [frog]:
   
230 I am a frog!
   
231 
   232 deco: =O=O=
   
233 
   234 Ribbit ribbit ribbit ribbit ribbit ribbit ribbit ribbit ribbit ribbit ribbit ribbit ribbit ribbit ribbit ribbit ribbit ribbit ribbit ribbit ribbit.
   
235 
   236 set title to Frog Time!
   
237 set page color to #ff0
   
238 set title color to #055
   
239 set border color to #8f8
   
240 
   241 set box color to #f4fff0
   
242 set text color to #2d6f00
   
243 set link color to #678c00
   
244 set deco color to #addd00
   
245 
   246 link Chomp to frog.eat
   
247 
   248 link Hop to frog.jump
   
249 
   250 [frog.eat]:
   
251 Frog eats!
   
252 
   253 link Hop to frog.jump
   
254 [frog.jump]:
   
255 JUMP!
   
256 [frog.ribbit]:
   
257 Ribbit!
   
258 </script>
   
259 
   260 
   261       <!-- +---------------------------------+
   
262            |                                 |
   
263            |        ColorPreviewSVG          |
   
264            |                                 |
   
265            +---------------------------------+ -->
   
266 
   267 <script id="el_color_preview_svg" type="hiss-stuff">
   
268 <svg width="170" height="100">
   
269     <!-- Page box -->
   
270     <rect x="71" y="1" width="98" height="98" class="page_color" />
   
271 
   272     <!-- Player box -->
   
273     <rect x="78" y="29" width="85" height="61" class="box_color"
   
274        style="stroke-width:1; stroke:#000;" />
   
275 
   276     <!-- Text boxes, dots, lines, and text -->
   
277     <g style="fill:#000; stroke: none;">
   
278         <rect x="0" y="4" width="62" height="15" />
   
279         <rect x="0" y="34" width="55" height="15" />
   
280         <rect x="0" y="69" width="70" height="15" />
   
281         <circle cx="84" cy="12" r="3" />
   
282         <circle cx="86" cy="42" r="3" />
   
283         <circle cx="78" cy="77" r="3" />
   
284     </g>
   
285     <g style="fill:none; stroke: #000; stroke-width: 2;">
   
286         <path d="M 30,12 86,12" />
   
287         <path d="M 30,42 88,42" />
   
288         <path d="M 30,77 78,77" />
   
289     </g>
   
290     <g style="font-size:12px;font-family:sans-serif;fill:#6beeff;stroke:none;font-weight:normal;">
   
291         <text x="2" y="15">page color</text>
   
292         <text x="99" y="20" style="font-weight:bold;" class="title_color">title color</text>
   
293         <text x="2" y="45">box color</text>
   
294         <text x="2" y="80">border color</text>
   
295         <text x="95" y="45" class="text_color">text color</text>
   
296         <text x="84" y="63" class="deco_color">~ deco color ~</text>
   
297         <text x="95" y="82" class="link_color">link color</text>
   
298     </g>
   
299 </svg>
   
300 </script>
   
301 
   302 
   303       <!-- +---------------------------------+
   
304            |                                 |
   
305            |            EditorJS             |
   
306            |                                 |
   
307            +---------------------------------+ -->
   
308 
   309 <script>
   
310 // Editor variables (not used in the game runtime)
   
311 var objs = {};
   
312 var editing_script = false;
   
313 var adding_script = false;
   
314 var current_obj = null;
   
315 var display_script = null;
   
316 var highlight_script = null;
   
317 var delete_prompt = false;
   
318 var autosave_timer; // for debounce
   
319 var color_scheme_changed = false;
   
320 
   321 // Script item array positions
   
322 var TYPE=0, TXT=1, KEY=1, FRIEND=1, VAL=2, TO=2;
   
323 
   324 // Put references to elements with 'el_*' IDs in the global namespace.
   
325 document.querySelectorAll('[id^=el_]').forEach(function(e){
   
326     window[e.id] = e;
   
327 });
   
328 
   329 // DOM convenience functions.
   
330 function hide(el){ el.style.display = 'none'; }
   
331 function show(el){ el.style.display = ''; }
   
332 
   333 el_edit_script_name.onclick = function(){
   
334     editing_script = !editing_script;
   
335     hide(el_rename_msg);
   
336     draw();
   
337 }
   
338 
   339 el_show_add_script.onclick = function(){
   
340     adding_script = true;
   
341     editing_script = false;
   
342     draw();
   
343 }
   
344 
   345 el_del_btn.onclick = function(){
   
346     delete_prompt = true;
   
347     draw();
   
348     return;
   
349 }
   
350 
   351 el_del_btn2.onclick = function(){
   
352     delete objs[current_name];
   
353     autosave_script();
   
354     current_name = '*Start';
   
355     draw();
   
356 }
   
357 
   358 el_del_cancel.onclick = function(){
   
359     delete_prompt = false;
   
360     draw();
   
361 }
   
362 
   363 el_create_before.onclick = function(){
   
364     el_name_field.value = '*Always Before';
   
365     el_add_script_btn.click();
   
366 }
   
367 
   368 el_create_after.onclick = function(){
   
369     el_name_field.value = '*Always After';
   
370     el_add_script_btn.click();
   
371 }
   
372 
   373 el_rename_current.onclick = function(){
   
374     show(el_rename_msg);
   
375     var nn = el_rename_field.value;
   
376 
   377     if(nn in objs){
   
378         el_rename_msg.classList.add("error");
   
379         el_rename_msg.textContent = "There is already a script named '"+nn+"'.";
   
380         return;
   
381     }
   
382 
   383     objs[nn] = objs[current_name];
   
384     delete objs[current_name];
   
385 
   386     // Loop through all lines in all scripts, rename links and inserts
   
387     var links_changed = 0;
   
388     var inserts_changed = 0;
   
389     Object.keys(objs).forEach(function(k){
   
390         objs[k].forEach(function(x){
   
391             if(x[TYPE] === 'link' && x[TO] === current_name){
   
392                 x[TO] = nn;
   
393                 links_changed++;
   
394             }
   
395             if(x[TYPE] === 'insert' && x[FRIEND] === current_name){
   
396                 x[FRIEND] = nn;
   
397                 inserts_changed++;
   
398             }
   
399         });
   
400     });
   
401 
   402     el_rename_msg.classList.remove("error");
   
403     el_rename_msg.textContent = "Renamed script. "+links_changed+" link(s), and "
   
404         +inserts_changed+" inserts(s) were also renamed.";
   
405 
   406     current_name = nn;
   
407     autosave_script();
   
408     draw();
   
409 }
   
410 
   411 el_add_script_btn.onclick = function(){
   
412     var nn = el_name_field.value;
   
413     if(nn in objs){
   
414         show(el_add_error_msg);
   
415         el_add_error_msg.textContent = "There is already a script named '"+nn+"'.";
   
416         return;
   
417     }
   
418 
   419     create_script(nn);
   
420 }
   
421 
   422 
   423 
   424 function update_script(){
   
425     display_script = el_edit_textarea.value;
   
426     objs[current_name] = script_to_obj(display_script);
   
427     current_obj = objs[current_name];
   
428     highlight_script = script_to_highlights(display_script);
   
429     draw();
   
430 
   431     // Autosave script changes, debounced to a 1 second pause
   
432     clearTimeout(autosave_timer);
   
433     autosave_timer = setTimeout(autosave_script, 1000);
   
434 }
   
435 
   436 el_edit_textarea.oninput = update_script;
   
437 
   438 function autosave_script(){
   
439     autosave("hiss-script", make_whole_script());
   
440 }
   
441 
   442 function autosave(key, value){
   
443     // Wrapping localStorage with a try block just in case browser
   
444     // doesn't support it at all or in the current context.
   
445     try {
   
446         localStorage.setItem(key, value);
   
447     }
   
448     catch(error){
   
449         console.error("Hiss cannot auto-save '"+key+"':", error);
   
450     }
   
451 }
   
452 
   453 function autoload(key){
   
454     try {
   
455         var whole_script = localStorage.getItem(key);
   
456         if(whole_script !== null){
   
457             return whole_script;
   
458         }
   
459     }
   
460     catch(error){
   
461         console.error("Hiss cannot auto-load '"+key+"':", error);
   
462     }
   
463     return null;
   
464 }
   
465 
   466 var syntax = {
   
467     'insert': ['insert', FRIEND, 'here'],
   
468     'link'  : ['link', TXT, 'to', TO],
   
469     'var'   : ['set', KEY, 'to', VAL],
   
470     'if'    : ['if', KEY, 'is', VAL],
   
471     'print' : ['print', KEY],
   
472     'deco'  : ['deco:', TXT],
   
473     'inc'   : ['inc', KEY],
   
474     'dec'   : ['dec', KEY],
   
475     'endif' : ['endif'],
   
476     'stop'  : ['STOP!'],
   
477     'else'  : ['else'],
   
478     'break' : [''],
   
479     'txt'   : [TXT],
   
480 };
   
481 
   482 // Turn the above list into an array of RegExp "matchers".
   
483 // This is a list of "tuples" containing the script element
   
484 // type name and a regexp that matches it. Order matters!
   
485 var matchers = Object.keys(syntax).map(function(k){
   
486 	var syn = syntax[k];
   
487 	
   488     // Regular syntax matcher (parser)
   
489     var m = RegExp('^\\s*' + syn.map(function(s){
   
490         return (typeof s === 'string') ? s : '(.*)';
   
491     }).join('\\s+') + '\\s*$');
   
492 
   493     // Highlight matcher and replacer
   
494     var hm, hr;
   
495     // There's a pattern to these, but it's noisy to automate.
   
496     switch(syn.length){
   
497     case 4:
   
498         hm = RegExp('^(\\s*)'+syn[0]+'(\\s+.*\\s)'+syn[2]+'(\\s+\\S.*\s*)');
   
499         hr = '$1<i>'+syn[0]+'</i>$2<i>'+syn[2]+'</i>$3';
   
500         break;
   
501     case 3:
   
502         hm = RegExp('^(\\s*)'+syn[0]+'(\\s+.*\\s)'+syn[2]+'(\s*)');
   
503         hr = '$1<i>'+syn[0]+'</i>$2<i>'+syn[2]+'</i>$3';
   
504         break;
   
505     case 2:
   
506         hm = RegExp('^(\\s*)'+syn[0]+'(\\s+.*)');
   
507         hr = '$1<i>'+syn[0]+'</i>$2';
   
508         break;
   
509     case 1:
   
510         hm = RegExp('^(\\s*)'+syn[0]+'(\s*)');
   
511         hr = '$1<i>'+syn[0]+'</i>$2';
   
512     }
   
513 
   514     // highlighter function
   
515     var highlighter = function(s){
   
516         if(k === 'break'){
   
517             // Adding just a space to breaks fixes highlight
   
518             // off-by-one line if is last element
   
519             return (s === '') ?  s+' ': s;
   
520         }
   
521         if(k === 'txt'){
   
522             // If it's regular paragraph text, do nothing!
   
523             return s;
   
524         }
   
525         return s.replace(hm, hr);
   
526     };
   
527 
   528     return {
   
529         'type': k,
   
530         'matcher': m,
   
531         'highlighter': highlighter
   
532     };
   
533 });
   
534 
   535 function obj_to_script(obj){
   
536     var s = '';
   
537     var indent = 0, i = 0;
   
538     var i, t;
   
539 
   540     obj.forEach(function(c){
   
541         t = c[TYPE];
   
542 
   543 		if(t==='endif' || t==='else'){ indent--; }
   
544 		for(i=0; i<indent; i++){ s+= "  "; }
   
545 		if(t==='else' || t==='if'){ indent++; }
   
546 
   547         s += syntax[t].map(function(tp){
   
548             return (typeof tp === 'string')? tp : c[tp];
   
549         }).join(' ');
   
550         s += "\n";
   
551     });
   
552     return s.trim() + "\n"; // Just one newline
   
553 }
   
554 
   555 function script_to_obj(script){
   
556     // See https://ratfactor.com/cards/js-string-parsing
   
557     var lines = script.split(/\r\n|\r|\n/);
   
558 
   559     // The array of matched regexes *is* the final object.
   
560     return lines.map(function(line){
   
561         line = line.trim();
   
562         var m;
   
563         for(var i=0; i<matchers.length; i++){
   
564             if(m = matchers[i].matcher.exec(line)){
   
565                 m[0] = matchers[i].type;
   
566                 return m;
   
567             }
   
568         }
   
569     });
   
570 }
   
571 
   572 function script_to_highlights(script){
   
573     var lines = script.split(/\r\n|\r|\n/);
   
574     var hlights = lines.map(function(line){
   
575         line = line.replace(/</g, '<');
   
576         for(var i=0; i<matchers.length; i++){
   
577             line = matchers[i].highlighter(line);
   
578         }
   
579         return line;
   
580     });
   
581     return hlights.join("\n");
   
582 }
   
583 
   584 function create_script(name){
   
585     current_name = name;
   
586     current_obj = [['txt', name]]; // Put name as text in script.
   
587     objs[name] = current_obj;
   
588     display_script = obj_to_script(current_obj);
   
589     highlight_script = script_to_highlights(display_script);
   
590     adding_script = false;
   
591     autosave_script();
   
592     draw();
   
593 }
   
594 
   595 function draw_script_list(){
   
596     var script_names = Object.keys(objs).sort();
   
597     var parent_script = null;
   
598 
   599     // TODO: force redraw if a different script is
   
600     //       selected (so it'll be bold)
   
601 
   602     // Memoization
   
603     var current_memo = JSON.stringify(script_names);
   
604     if(draw_script_list.memo == current_memo){
   
605         // No change since last call. Leave DOM alone.
   
606         return;
   
607     }
   
608     draw_script_list.memo = current_memo;
   
609 
   610     el_script_list.replaceChildren();
   
611     script_names.forEach(function(n){
   
612         var m = /^(.+)(\..+)$/.exec(n);
   
613         var disp_name = n;
   
614 
   615         var el = document.createElement('a');
   
616         el.onclick = link(n);
   
617 
   618         // Do fake "tree" structure for foo.bar items:
   
619         if(m && m[1] == parent_script){
   
620             disp_name = m[2];
   
621             el.classList.add('indent');
   
622         }
   
623         else { parent_script = n; }
   
624 
   625         if(n === current_name){ el.classList.add('selected'); }
   
626 
   627         el.textContent = disp_name;
   
628         el_script_list.appendChild(el);
   
629     });
   
630 }
   
631 
   632 function draw_edit_area(){
   
633     hide(el_edit_start);
   
634     hide(el_name_edit);
   
635     hide(el_del_btn);
   
636     hide(el_del_prompt);
   
637     hide(el_add_script);
   
638     hide(el_edit_container);
   
639 
   640     if(adding_script){
   
641         hide(el_script_name_area);
   
642     }else{
   
643         show(el_script_name_area);
   
644     }
   
645 
   646     el_script_name.textContent = current_name;
   
647 
   648     if(editing_script){
   
649         if(current_name === '*Start'){ show(el_edit_start); return; }
   
650 
   651         show(el_name_edit);
   
652         el_rename_field.value = current_name;
   
653 
   654         if(delete_prompt){
   
655             show(el_del_prompt);
   
656             el_del_script_name.textContent = current_name;
   
657         }
   
658         else{
   
659             show(el_del_btn);
   
660         }
   
661         return;
   
662     }
   
663 
   664     if(adding_script){
   
665         show(el_add_script);
   
666         hide(el_add_error_msg);
   
667         el_name_field.value = '';
   
668         return;
   
669     }
   
670 
   671     show(el_edit_container); // Show the script edit container
   
672     el_edit_textarea.value = display_script;
   
673 }
   
674 
   675 // TODO: somehow deleting the final character of a variable is leaving
   
676 //       that last value
   
677 function clean_vars(){
   
678     // Collect the variable names set in all scripts
   
679     var exist_vars = {};
   
680     Object.values(objs).forEach(function(o){
   
681         o.forEach(function(x){
   
682             if(x[TYPE] === 'var' && x[KEY]){ // '' is a falsy value
   
683                 exist_vars[x[KEY]] = true;
   
684             }
   
685         });
   
686     });
   
687 
   688     Object.keys(vars).forEach(function(k){
   
689         // Now remove vars not in above list
   
690         if (!(k in exist_vars)) {
   
691             delete vars[k];
   
692         }
   
693     });
   
694 }
   
695 
   696 function draw_vars(){
   
697     // Memoization
   
698     var current_memo = JSON.stringify(vars);
   
699     if(draw_vars.memo == current_memo){
   
700         // No change since last call. Leave DOM alone.
   
701         return;
   
702     }
   
703     draw_vars.memo = current_memo;
   
704 
   705     var var_list = Object.keys(vars);
   
706     if (var_list.length < 1) {
   
707         hide(el_var_list);
   
708         hide(el_var_list_section);
   
709         return;
   
710     }
   
711 
   712     show(el_var_list);
   
713     show(el_var_list_section);
   
714 
   715     el_var_list.replaceChildren(); // clear the list
   
716     var_list.forEach(function(k){
   
717         var d = document.createElement('div');
   
718         d.classList.add('var');
   
719         d.textContent = k+": ";
   
720         var s = document.createElement('span');
   
721         s.textContent = vars[k];
   
722         d.appendChild(s);
   
723         el_var_list.appendChild(d);
   
724     });
   
725 }
   
726 
   727 function draw(){
   
728     // Reset insert recursion limiter
   
729     inserts = 0;
   
730 
   731     clean_vars();
   
732     play_obj(current_obj);
   
733     clean_vars();
   
734 
   735     document.title = "Hiss Editor: " + document.title;
   
736     draw_script_list();
   
737     draw_vars();
   
738 
   739     var game_colors = get_game_colors();
   
740     if(game_colors.has_custom){
   
741         draw_color_preview(el_color_preview, game_colors);
   
742     }
   
743 
   744     draw_edit_area();
   
745 
   746     // Re-dimension and fill editor highlight div
   
747     // NOTE! This has to be after the color preview for height reasons.
   
748     if(el_edit_highlights){
   
749         el_edit_highlights.innerHTML = highlight_script;
   
750         ed_redimension();
   
751         ed_register_scroll();
   
752     }
   
753 }
   
754 
   755 function editor_link(to){
   
756     return function(){
   
757         el_rename_msg.textContent = "";
   
758         delete_prompt = false;
   
759         editing_script = false;
   
760         adding_script = false;
   
761         current_name = to;
   
762         current_obj = objs[to];
   
763         display_script = obj_to_script(current_obj);
   
764         highlight_script = script_to_highlights(display_script);
   
765         draw();
   
766     };
   
767 }
   
768 
   769 function editor_create(name, current_el){
   
770     if(name == ''){ return; } // No thanks!
   
771     var btn = document.createElement('button');
   
772     btn.textContent = "Create '" + name + "'";
   
773     btn.onclick = function(){ create_script(name); };
   
774     current_el.appendChild(btn); // TODO: oh boy, this needs to append to the current p
   
775 }
   
776 
   777 function get_game_colors(){
   
778     var c = {
   
779         has_custom: false,
   
780         css_styles: {
   
781             'box color':    '--bg-page',
   
782             'title color':  '--fg-title',
   
783             'border color': '--borders',
   
784             'page color':   '--bg-main',
   
785             'text color':   '--fg-main',
   
786             'deco color':   '--svg-decos-stroke',
   
787             'link color':   '--a-color',
   
788         },
   
789         names: [],
   
790         colors: {},
   
791     };
   
792     c.names = Object.keys(c.css_styles);
   
793 
   794     var docstyle = document.documentElement.style;
   
795 
   796     // Set defaults from CSS and override with any currently
   
797     // set variables of the same name.
   
798     c.names.forEach(function(k){
   
799         c.colors[k] = docstyle.getPropertyValue(c.css_styles[k]);
   
800         if(k in vars){
   
801             c.colors[k] = vars[k];
   
802             c.has_custom = true;
   
803         }
   
804     });
   
805 
   806     return c;
   
807 }
   
808 
   809 function draw_color_preview(container, game_colors){
   
810     // Memoization
   
811     var current_memo = JSON.stringify(game_colors);
   
812     if(!color_scheme_changed && draw_color_preview.memo == current_memo){
   
813         // No change since last call. Leave DOM alone.
   
814         return;
   
815     }
   
816     draw_color_preview.memo = current_memo;
   
817 
   818     color_scheme_changed = false;
   
819 
   820     // Write a copy of the SVG source for the preview into the container.
   
821     container.innerHTML = el_color_preview_svg.textContent;
   
822 
   823     // Set those colors!
   
824     game_colors.names.forEach( function(color){
   
825         var cname = "." + color.replace(' ', '_');
   
826         if(color === 'border color'){
   
827             // This one is special - the stroke of the page rect.
   
828             container.querySelector('.page_color').style.stroke = game_colors.colors[color];
   
829         }
   
830         else{
   
831             container.querySelector(cname).style.fill = game_colors.colors[color];
   
832         }
   
833     });
   
834 }
   
835 
   836 function make_whole_script(){
   
837     var text = '';
   
838     Object.keys(objs).forEach(function(name){
   
839         var obj = objs[name];
   
840         var script = obj_to_script(obj);
   
841         text += "["+name+"]:\n";
   
842         text += script + "\n\n";
   
843     });
   
844 
   845     return text;
   
846 }
   
847 
   848 el_export_script.onclick = function(){
   
849     var text = make_whole_script();
   
850 
   851     // Create anchor with "data:" url/uri
   
852     var dl = document.createElement('a');
   
853     dl.href = 'data:text/plain;charset=utf-8,'+encodeURIComponent(text);
   
854     dl.download = 'hiss_script.txt';
   
855     dl.style.display = 'none';
   
856     document.body.appendChild(dl);
   
857     dl.click();
   
858 };
   
859 
   860 el_import_script.onclick = function(){
   
861     el_file_elem.click();
   
862     el_file_elem.addEventListener("change", function(){
   
863         if(el_file_elem.files.length < 1){
   
864             // Assume we hit cancel in file picker
   
865             return;
   
866         }
   
867         var fr = new FileReader();
   
868         // callback when it finishes reading
   
869         fr.addEventListener("load", function(){
   
870             parse_whole_script(fr.result);
   
871             draw();
   
872         });
   
873         fr.readAsText(el_file_elem.files[0]);
   
874     });
   
875 };
   
876 
   877 hide(el_new_game_area);
   
878 el_new_game_btn.onclick = function(){ show(el_new_game_area); }
   
879 el_new_cancel_btn.onclick = function(){ hide(el_new_game_area); }
   
880 el_load_sample_btn.onclick = function(){
   
881     hide(el_new_game_area);
   
882     parse_whole_script(el_sample_script.textContent);
   
883     draw();
   
884 }
   
885 
   886 
   887 el_new_blank_btn.onclick = function(){
   
888     hide(el_new_game_area);
   
889     parse_whole_script('[*Start]:\nHello World!');
   
890     draw();
   
891 }
   
892 
   893 el_export_game.onclick = function(){
   
894     // Note: Getting elements now. They didn't exist on first pass.
   
895     var html = document.getElementById('exported_player_html').textContent;
   
896     var script = document.getElementById('exportable_game_js').textContent;
   
897 
   898     // Add game's JSON data and shared player JS in a script tag
   
899     // See https://html.spec.whatwg.org/multipage/scripting.html
   
900     html += "\x3Cscript>";
   
901     html += "var objs = " + JSON.stringify(objs) + ";";
   
902     html += script;
   
903     html += "\x3C/script></body></html>";
   
904 
   905     // Create anchor with "data:" url/uri
   
906     var dl = document.createElement('a');
   
907     dl.href = 'data:text/html;charset=utf-8,'+encodeURIComponent(html);
   
908     dl.download = 'mygame.html';
   
909     dl.style.display = 'none';
   
910     document.body.appendChild(dl);
   
911     dl.click();
   
912 };
   
913 
   914 function parse_whole_script(txt){
   
915     objs = {};
   
916     
   917     var lines = txt.split(/\r\n|\r|\n/);
   
918     var script = null;
   
919     var name = null;
   
920 
   921     // Collect the lines of each obj...
   
922     lines.forEach(function(line){
   
923         var m = null;
   
924         if(m = /^\[(.*)\]:$/.exec(line)){
   
925             // Got a new name, did we have a previous?
   
926             if(name !== null){
   
927                 // Yes, save the previous one:
   
928                 objs[name] = script_to_obj(script);
   
929             }
   
930             // start the new one
   
931             name = m[1];
   
932             script = "";
   
933             return;
   
934         }
   
935         // not a name, append line of script
   
936         script += line + "\n";
   
937     });
   
938     // Save the last one:
   
939     objs[name] = script_to_obj(script);
   
940 
   941     if(!("*Start" in objs)){
   
942         // No start script somehow. Let's make one.
   
943         objs["*Start"] = {c:[]};
   
944     }
   
945 
   946     current_obj = objs["*Start"];
   
947     display_script = obj_to_script(current_obj);
   
948     highlight_script = script_to_highlights(display_script);
   
949 }
   
950 
   951 function ed_redimension(){
   
952     // Get dimensions of the textarea and apply them to the highlight div.
   
953     var tb = el_edit_textarea.getBoundingClientRect();
   
954     el_edit_highlights.style.height = tb.height+"px";
   
955     el_edit_highlights.style.width = tb.width+"px";
   
956 }
   
957 addEventListener('resize', ed_redimension);
   
958 
   959 function ed_register_scroll(elem){
   
960     if(el_edit_textarea.has_scroll_event){ return; }
   
961 
   962     el_edit_textarea.addEventListener('scroll', function(){
   
963          el_edit_highlights.scrollTop = el_edit_textarea.scrollTop;
   
964     });
   
965     el_edit_textarea.has_scroll_event = true;
   
966 }
   
967 
   968 
   969 // Editor Color Schemes
   
970 var schemes = [
   
971     { 'scheme-title': 'Agent Smith',
   
972     'bg-page': '#000', 'bg-main': '#000', 'fg-main': '#0F2', 'a-color': '#AF0',
   
973     'bg-list': '#000', 'borders': '#0F2', 'doc-border': 'none', 'doc-h2':
   
974     '#0FF', 'doc-h3': '#F0E', 'bg-title': '#000', 'fg-title': '#0FF',
   
975     'bg-menu': '#000', 'fg-menu': '#AF0', 'fg-values': '#F5F', 'fg-script':
   
976     '#FE0', 'hl-script': '#720', 'svg-hiss-stroke': '#0F2', 'svg-hiss-fill':
   
977     '#000', 'svg-decos-stroke': '#0CF', },
   
978     { 'scheme-title': "Dolly '74",
   
979     'bg-page': '#AAA', 'bg-main': '#FFF', 'fg-main': '#333', 'a-color': '#03B',
   
980     'bg-list': '#fffccf', 'borders': '#567', 'doc-border': '10px solid #DFF',
   
981     'doc-h2': '#90B', 'doc-h3': '#199', 'bg-title': '#F99', 'fg-title': '#FFF',
   
982     'bg-menu': '#FEE', 'fg-menu': '#03B', 'fg-values': '#B0C', 'fg-script':
   
983     '#000', 'hl-script': '#FDD', 'svg-hiss-stroke': '#CC3', 'svg-hiss-fill':
   
984     '#F99', 'svg-decos-stroke': '#333', },                                
   
985     { 'scheme-title': 'Hotdog Stand',
   
986     'bg-page': '#FF0', 'bg-main': '#F00', 'fg-main': '#FFF', 'a-color': '#FF0',
   
987     'bg-list': '#F00', 'borders': '#000', 'doc-border': '10px solid #000',
   
988     'doc-h2': '#FFF', 'doc-h3': '#FFF', 'bg-title': '#000', 'fg-title': '#FFF',
   
989     'bg-menu': '#FFF', 'fg-menu': '#000', 'fg-values': '#000', 'fg-script':
   
990     '#FFF', 'hl-script': '#000', 'svg-hiss-stroke': '#000', 'svg-hiss-fill':
   
991     '#FF0', 'svg-decos-stroke': '#FF0', },
   
992     // Solar light and dark schemes based on
   
993     // https://ethanschoonover.com/solarized/
   
994     { 'scheme-title': 'Solar Light',
   
995     'bg-page': '#eee8d5', 'bg-main': '#fdf6e3', 'fg-main': '#002b36',
   
996     'a-color': '#268bd2', 'bg-list': '#fdf6e3', 'borders': '#657b83',
   
997     'doc-border': '10px solid #93a1a1', 'doc-h2': '#859900', 'doc-h3':
   
998     '#6c71c4', 'bg-title': '#93a1a1', 'fg-title': '#fdf6e3', 'bg-menu':
   
999     '#586e75', 'fg-menu': '#fdf6e3', 'fg-values': '#cb4b16', 'fg-script':
  
1000     '#002b36', 'hl-script': '#c6f0ed', 'svg-hiss-stroke': '#b58900',
  
1001     'svg-hiss-fill': '#93a1a1', 'svg-decos-stroke': '#657b83', },
  
1002     { 'scheme-title': 'Solar Dark',
  
1003     'bg-page': '#002b36', 'bg-main': '#073642', 'fg-main': '#fdf6e3',
  
1004     'a-color': '#268bd2', 'bg-list': '#073642', 'borders': '#657b83',
  
1005     'doc-border': '4px solid #2aa198', 'doc-h2': '#859900', 'doc-h3':
  
1006     '#6c71c4', 'bg-title': '#93a1a1', 'fg-title': '#fdf6e3', 'bg-menu':
  
1007     '#586e75', 'fg-menu': '#fdf6e3', 'fg-values': '#cb4b16', 'fg-script':
  
1008     '#fdf6e3', 'hl-script': '#145550', 'svg-hiss-stroke': '#2aa198',
  
1009     'svg-hiss-fill': 'none', 'svg-decos-stroke': '#657b83', },
  
1010 ];
  
1011 
  1012 // Cycle color schemes
  
1013 function cycle_scheme(){
  
1014     current_scheme++;
  
1015     if(current_scheme >= schemes.length){
  
1016         current_scheme = 0;
  
1017     }
  
1018     apply_scheme();
  
1019 
  1020     autosave('hiss-color-scheme', current_scheme);
  
1021 }
  
1022 el_cycle_scheme.onclick = cycle_scheme;
  
1023 cycle_btn_label = el_cycle_scheme.textContent;
  
1024 
  1025 function apply_scheme(){
  
1026     color_scheme_changed = true;
  
1027     var my_scheme = schemes[current_scheme];
  
1028     var scheme_txt = ' (' + my_scheme['scheme-title'] + ')';
  
1029     el_cycle_scheme.textContent = cycle_btn_label + scheme_txt;
  
1030     var docstyle = document.documentElement.style;
  
1031     Object.keys(my_scheme).forEach(function(key){
  
1032         docstyle.setProperty('--'+key, my_scheme[key]);
  
1033     });
  
1034     draw(); // Re-renders color preview if needed
  
1035 };
  
1036 
  1037 // Try to autoload any script we might have autosaved
  
1038 var loaded_whole_script = autoload("hiss-script");
  
1039 if(loaded_whole_script === null){
  
1040     // There was no autosaved script, use the embedded test script.
  
1041     loaded_whole_script = el_sample_script.textContent;
  
1042 }
  
1043 parse_whole_script(loaded_whole_script);
  
1044 
  1045 // Setup editor color scheme.
  
1046 var current_scheme = 4; // start with a scheme
  
1047 // Try to autoload the last color scheme used
  
1048 var prev_color_scheme = autoload("hiss-color-scheme");
  
1049 if(prev_color_scheme !== null){
  
1050     current_scheme = Number(prev_color_scheme);
  
1051 }
  
1052 
  1053 // When everything's loaded, start the editor.
  
1054 window.addEventListener('load', function(){
  
1055     link = editor_link; // Replace player's link() function.
  
1056     apply_scheme();
  
1057     draw();
  
1058 });
  
1059 </script>
  
1060 
  1061 
  1062       <!-- +---------------------------------+
  
1063            |                                 |
  
1064            |       ExportedPlayerHtml        |
  
1065            |                                 |
  
1066            +---------------------------------+ -->
  
1067 
  1068 <script id="exported_player_html" type="text/html">
  
1069 <!DOCTYPE html>
  
1070 <html>
  
1071 <head>
  
1072   <meta charset="utf-8">
  
1073   <title></title>
  
1074   <meta name="viewport" content="width=device-width, initial-scale=1">
  
1075   <style>
  
1076     body { background-color: #333; }
  
1077     h1 { color: #FFF; margin: 5px; text-align: center; }
  
1078     a { text-decoration: underline; color: #00F; cursor: pointer; }
  
1079     .box { border: 10px solid #FDA; margin: 1em auto; padding: 1em;
  
1080         background: #fed; font-size: 1.2em; max-width: 35em; }
  
1081     .deco { text-align: center; }
  
1082     .deco svg { stroke-width: 2; stroke: #000; fill: none; }
  
1083     .error { background: #C00; color: #FFF; padding: 5px; }
  
1084   </style>
  
1085 </head>
  
1086 <body class="player">
  
1087 <h1></h1>
  
1088 <div class="box"></div>
  
1089 </script>
  
1090 
  1091 
  1092       <!-- +---------------------------------+
  
1093            |                                 |
  
1094            |         SharedPlayerJS          |
  
1095            |                                 |
  
1096            +---------------------------------+ -->
  
1097 
  1098 <script id="exportable_game_js">
  
1099 var player_el = document.querySelector(".box"); // Game renders here.
  
1100 var title_el = document.querySelector(".player h1");
  
1101 var default_title = 'My Game';
  
1102 var current_name = '*Start';
  
1103 var inserts = 0;
  
1104 var insert_limit = 100; // Prevents infinite recursion.
  
1105 var is_stopped = false; // The 'STOP' command sets to true.
  
1106 var vars = {}; // Runtime game variables.
  
1107 
  1108 var game_colors = [
  
1109 //  Var Name       Query Selector   CSS Property
  
1110 // ----------------------------------------------------
  
1111 {v:'page color',   q:'.player',    p:'background-color', },
  
1112 {v:'box color',    q:'.box',       p:'background-color', }, 
  
1113 {v:'border color', q:'.box',       p:'border-color', },
  
1114 {v:'text color',   q:'.box',       p:'color'},
  
1115 {v:'title color',  q:'.player h1', p:'color'},
  
1116 {v:'deco color',   q:'.box svg',   p:'stroke'},
  
1117 {v:'link color',   q:'.box a',     p:'color'},
  
1118 ];
  
1119 
  1120 function play_obj(obj){
  
1121     is_stopped = false;
  
1122     player_el.replaceChildren(); // Clear the container.
  
1123     if("*Always Before" in objs){ render_obj(objs['*Always Before']); }
  
1124     render_obj(obj);
  
1125     if("*Always After" in objs){ render_obj(objs['*Always After']); }
  
1126 }
  
1127 
  1128 function link(to){ return function(){ play_obj(objs[to]); }; }
  
1129 function getval(key){ if(!(key in vars)){ return 'EMPTY'; } return vars[key]; }
  
1130 
  1131 function handle_not_exist(cmd, name, current_el){
  
1132     if(objs[name]){ return false; } // DOES exist
  
1133 
  1134     // If editor exists, let it handle this.
  
1135     if(typeof editor_create === 'function'){
  
1136         editor_create(name, current_el);
  
1137         return true;
  
1138     }
  
1139 
  1140     // Otherwise, display an error.
  
1141     var e = document.createElement('div');
  
1142     e.classList.add('error');
  
1143     e.textContent = "Sorry, no '" + name + "' script to " + cmd + " here.";
  
1144     current_el.append(e);
  
1145     return true;
  
1146 }
  
1147 
  1148 // Logic Stack for if/else/endif instruction rules:
  
1149 //   * 'if' pushes true or false on the logic stack
  
1150 //   * 'else' inverts the last state on the stack
  
1151 //   * 'endif' pops the last state on the stack
  
1152 // State is "true" if all states on the stack are true.
  
1153 var LS = [];
  
1154 function LS_true(){
  
1155     if(LS.length < 1) return true;
  
1156     return LS.every(function(s){ return s; });
  
1157 }
  
1158 
  1159 function render_obj(obj){
  
1160     // Always start with a new paragraph.
  
1161     var p = document.createElement('p');
  
1162     LS = []; // Clear the logic stack
  
1163 
  1164     obj.forEach(function(c){
  
1165         if(is_stopped) return;
  
1166         var cmd = c[0];
  
1167 
  1168         // Perform any logic commands first.
  
1169         if(cmd === 'if'){
  
1170             v = getval(c[1]);
  
1171             LS.push(v == c[2]); // Loose equality comparison.
  
1172             return;
  
1173         }
  
1174         if(cmd === 'else'){
  
1175             if(LS.length < 1){
  
1176                 LS.push(false);
  
1177                 return;
  
1178             }
  
1179             var last = LS.length-1;
  
1180             LS[last] = !LS[last]; // Invert last state.
  
1181             return;
  
1182         }
  
1183         if(cmd === 'endif'){
  
1184             if(LS.length > 0){
  
1185                 LS.pop();
  
1186             }
  
1187             return;
  
1188         }
  
1189         if(!LS_true()){ return; } // A "false" logic state, skip forward.
  
1190 
  1191         if(cmd === 'stop'){ is_stopped = true; return; }
  
1192         if(cmd === 'var'){ vars[c[1]] = c[2]; return; }
  
1193         if(cmd === 'inc'){
  
1194             if(!vars[c[1]]){ vars[c[1]] = 1; }
  
1195             else if(isNaN(vars[c[1]])){ vars[c[1]] += " plus one"; }
  
1196             else{ vars[c[1]] = Number(vars[c[1]])+1; }
  
1197             return;
  
1198         }
  
1199         if(cmd === 'dec'){
  
1200             if(!vars[c[1]]){ vars[c[1]] = -1; }
  
1201             else if(isNaN(vars[c[1]])){ vars[c[1]] += " minus one"; }
  
1202             else{ vars[c[1]] = Number(vars[c[1]])-1; }
  
1203             return;
  
1204         }
  
1205         if(cmd === 'insert'){
  
1206             player_el.append(p); // End previous paragraph.
  
1207             p = document.createElement('p');
  
1208 
  1209             if(handle_not_exist('insert', c[1], player_el)){ return; }
  
1210 
  1211             // Limit insert recursion
  
1212             if(inserts++ > insert_limit){
  
1213                 p.textContent = 'Et cetera...';
  
1214                 return;
  
1215             }
  
1216             render_obj(objs[c[1]]);
  
1217             return;
  
1218         }
  
1219         if(cmd === 'link'){
  
1220             if(handle_not_exist('link', c[2], p)){ return; }
  
1221 
  1222             alink = document.createElement('a');
  
1223             alink.textContent = c[1];
  
1224             alink.onclick = link(c[2]);
  
1225             p.append(alink);
  
1226             return;
  
1227         }
  
1228         if(cmd === 'deco'){
  
1229             deco_el = document.createElement('div');
  
1230             deco_el.classList.add('deco');
  
1231             Decos.makesvg(c[1], deco_el);
  
1232             p.append(deco_el);
  
1233             return;
  
1234         }
  
1235         if(cmd === 'txt'){
  
1236             // Add spaces to either end if not punctuation
  
1237             var before = c[1].match(/^\W/) ? "" : " ";
  
1238             var after = c[1].match(/\W$/) ? "" : " ";
  
1239 
  1240             p.appendChild(document.createTextNode(
  
1241                 before + c[1] + after
  
1242             ));
  
1243 
  1244             return;
  
1245         }
  
1246         if(cmd === 'break'){ // blank line
  
1247             // Append existing paragraph and create a new one.
  
1248             player_el.append(p);
  
1249             p = document.createElement('p');
  
1250             return;
  
1251         }
  
1252         if(cmd === 'print'){
  
1253             p.textContent += getval(c[1]);
  
1254         }
  
1255     });
  
1256 
  1257     player_el.append(p); // Always append, even if empty.
  
1258 
  1259     game_colors.forEach(function(c){
  
1260         if(typeof vars[c.v] === 'undefined'){ return; }
  
1261         document.querySelectorAll(c.q).forEach(function (el){
  
1262             el.style[c.p] = vars[c.v]; // Set property to color value
  
1263         });
  
1264     });
  
1265 
  1266     var set_title = vars['title'] ? vars['title'] : default_title;
  
1267     title_el.innerHTML = set_title;
  
1268     document.title = set_title;
  
1269 }
  
1270 
  1271 var Decos = {
  
1272     dim: 32, // 32x32 px
  
1273     named: {}, // named group storage
  
1274     visited: [], // stack of visited groups
  
1275     glyphs: {
  
1276 '-': '<path d="M 0,16 H 16" />',
  
1277 '=': '<path d="M 0,16 H 32" />',
  
1278 '|': '<path d="M 16,32 V 0" />',
  
1279 '_': '<path d="M 0,31 H 32" />',
  
1280 'c': '<path d="M 16,23 A 7,7 0 0 1 9,16 7,7 0 0 1 16,9" />',
  
1281 '(': '<path d="m 16,31 a 15,15 0 0 1 -13,-7 15,15 0 0 1 0,-15 A 15,15 0 0 1 16,1" />',
  
1282 ')': '<path d="M 16,0 C 16,8 8,16 0,16" />',
  
1283 '.': '<circle cx="8" cy="16" r="3" />',
  
1284 'o': '<circle cx="8" cy="16" r="7" />',
  
1285 'O': '<circle cx="16" cy="16" r="15" />',
  
1286 '^': '<path d="m 19,21 -3,-5 -3,5 3,-1 z" />',
  
1287 's': '<path d="m 0,16 c 5,0 4,16 9,14 C 15,26 1,6 8,2 13,-1 12,16 16,16" />',
  
1288 'S': '<path d="m 0,16 c 5,0 1,14 8,14 7,0 9,-28 16,-28 7,0 4,14 8,14" />',
  
1289 '$': '<path d="m 26,17 c -3,2 0,4 2,3 1,-1 3,-2 3,-5 -0,-1 -2,-4 -4,-4 -3,-1 -6,2 -9,5 -3,3 -3,9 -9,4 M 22,12 C 15,4 14,22 6,22 2,22 1,18 1,16 1,15 3,12 5,12 9,11 9,14 6,15" />',
  
1290 '#': '<path d="m 18,27 v 5 m -4,-5 v 5 M 10,31 V 27 L 6,29 4,26 5,22 C 4,19 3,18 3,12 c 0,-7 5,-10 13,-10 8,-0 13,3 13,10 0,6 -1,7 -2,10 l 1,4 -2,3 -4,-2 v 3" />',
  
1291 'p': '<path d="M 16,16 C 1,16 2,4 11,4 c 6,0 5,8 0,8 -5,0 -3,-6 1,-4" />',
  
1292 'P': '<path d="m 32,16 c 0,0 -2,-0 -7,0 -2,0 -7,3 -8,6 -2,3 -5,4 -8,4 -4,0 -7,-4 -7,-8 0,-4 2,-8 6,-8 5,0 7,3 7,6 -0,5 -7,6 -8,1 -1,-3 6,-5 4,1" />',
  
1293 '@': '<path d="m 20,17 c 1,-0 1,-2 1,-2 -1,-2 -3,-2 -4,-2 -3,1 -3,5 -2,7 2,4 7,5 11,3 5,-3 6,-10 3,-15 C 25,0 16,-1 9,3 4,7 1,13 1,19" />',
  
1294 "v": '<path d="M 3,15 11,10 17,8 M 1,15 c 0,0 5,-11 8,-12 C 14,0 32,1 32,1 30,1 20,11 18,14 14,20 1,16 1,16 Z" />',
  
1295 'w': '<path d="m 14,16 c 2,2 7,1 11,-1 m -15,3 c 0,0 4,4 9,5 5,1 7,-3 13,-7 L 31,15 C 28,12 26,10 23,11 l -7,2 M 0,16 C 7,16 5,8 12,7 M 2,14 c -2,9 9,4 11,2 2,-3 6,-11 9,-9 0,0 1,-1 1,-1 C 22,-0 12,0 8,3 3,6 -1,11 2,14 Z" />',
  
1296 '!': '<path d="m 25,12 7,4 M 18,6 27,3 28,1 M 13,13 l 7,2 5,-3 4,-5 M 0,16 6,16 14,13 17,6 16,0" />',
  
1297 '%': '<path d="m 13,13 7,2 6,-3 M 0,16 l 6,0 7,-3 4,-7 M 14,11 C 23,11 21,5 20,1 13,2 11,4 14,11 Z m 6,4 c 7,3 8,-2 10,-6 -7,-3 -9,-2 -10,6 z" />',
  
1298 '*': '<path d="M 15,16 C 10,25 8,20 2,16 8,13 9,7 15,16 Z m 1,1 c -9,5 -4,7 -0,13 4,-6 9,-7 0,-13 z m 1,-1 c 5,-9 7,-4 13,-0 -6,4 -7,9 -13,0 z M 16,15 C 7,10 12,8 16,2 c 4,6 9,7 0,13 z" />',
  
1299     },
  
1300 };
  
1301 
  1302 Decos.makesvg = function (pattern, destination){
  
1303     var t = Decos.makepattern(pattern, 0);
  
1304     var svgtxt = t[0];
  
1305     var width = t[1];
  
1306 
  1307     Decos.named = [];
  
1308     Decos.visited = [];
  
1309 
  1310     destination.innerHTML =
  
1311         '<svg id="destination" width="'+width+'" height="'+Decos.dim+'">'
  
1312       + svgtxt
  
1313       + '</svg>';
  
1314 }
  
1315 
  1316 Decos.makepattern = function (pattern, x){
  
1317     var combo = false;
  
1318     var rotate = 0;
  
1319     var mirrorh = false;
  
1320     var mirrorv = false;
  
1321     var repeat = 0;
  
1322     var svg = "";
  
1323     var tf, c, patbuff, action;
  
1324 
  1325     // For each character in pattern...
  
1326     var i = 0;
  
1327     for(; i<pattern.length; i++){
  
1328         c = pattern[i];
  
1329         action = false;
  
1330 		tf = "translate("+x+", 0)"; // transform
  
1331 
  1332         // Cycle detected!
  
1333         if(Decos.visited.includes(c)){
  
1334             return ['<rect width="150" height="32" style="fill: #000;" /><text x="4" y="24" style="stroke: none; fill: #fff; font-size: 20px;">RECURSION!</text>',150];
  
1335         }
  
1336 
  1337         // If this char is a named pattern, use it!
  
1338         if(c in Decos.named){
  
1339             Decos.visited.push(c);
  
1340             t = Decos.makepattern(Decos.named[c], x);
  
1341             svg += t[0];
  
1342             x = t[1];
  
1343             continue;
  
1344         }
  
1345 
  1346         if(c >= '2' && c <= '9'){ repeat = parseInt(c)-1; continue; }
  
1347         if(c === '<'){ x -= Decos.dim/4; action=true; }
  
1348         if(c === 'r'){ rotate += 45; action=true; }
  
1349         if(c === 'R'){ rotate += 180; action=true; }
  
1350         if(c === 'm'){ mirrorh = true; action=true; }
  
1351         if(c === 'M'){ mirrorv = true; action=true; }
  
1352         if(c === ' '){ x += Decos.dim/2; action=true; }
  
1353         if(c === '['){ combo = true; action=true; }
  
1354         if(c === ']'){ combo = false; x += Decos.dim; action=true; }
  
1355         if(c === '{' && i+4 <= pattern.length){
  
1356             name = pattern[i+1];
  
1357             i += 2;
  
1358             while(pattern[i] === ' '){ i++; } // eat spaces
  
1359             ends = pattern.indexOf("}", i); // find end
  
1360             if(ends < 0) continue;
  
1361             Decos.named[name] = pattern.substring(i, ends);
  
1362             i = ends;
  
1363             while(pattern[i+1] === ' '){ i++; } // eat spaces
  
1364             continue;
  
1365         }
  
1366 
  1367         if(repeat > 0){ repeat--; i--; }
  
1368         if(action){ continue; }
  
1369 		if(rotate != 0){ tf += ' rotate('+rotate+', 16, 16)'; }
  
1370         if(mirrorh){ tf += ' scale(-1,1) translate(-'+Decos.dim+', 0)'; }
  
1371         if(mirrorv){ tf += ' scale(1,-1) translate(0, -'+Decos.dim+')'; }
  
1372 
  1373         svg += '<g transform="' + tf + '">';
  
1374         svg += Decos.glyphs[c];
  
1375         svg += '</g>';
  
1376 
  1377         // After printing a glyph, turn off special actions
  
1378         rotate = 0;
  
1379 		mirrorh = mirrorv = false;
  
1380 		tf = "";
  
1381         // Don't advance "print head" if in combo
  
1382         if(!combo){ x += Decos.dim; }
  
1383     }
  
1384 
  1385     if(combo) x += Decos.dim; // Add space for an "open" combo
  
1386 
  1387     // Pop any pattern visited
  
1388     Decos.visited.pop();
  
1389 
  1390     return [svg, x];
  
1391 }
  
1392 
  1393 play_obj(objs['*Start']); // Start the game!
  
1394 </script>
  
1395 
  1396 
  1397       <!-- +---------------------------------+
  
1398            |                                 |
  
1399            |        HissDocumentation        |
  
1400            |                                 |
  
1401            +---------------------------------+ -->
  
1402 
  1403 <div class="docs">
  
1404 <div class="inner">
  
1405 
  1406 <svg id="svg_hiss" width="200" height="100" role="img">
  
1407 <title>Hiss Logo</title>
  
1408 <g class="svg_hiss_styles">
  
1409 <path d="m 71,16 c 2,2 4,3 5,6 l 0,16 -15,0 0,-17 c 1,-2 3,-4 6,-6 L 42,16 c -58,3 -3,50 2,28 1,-6 -6,-7 -10,-3 7,2 3,3 3,3 C 6,51 38,3 47,23 l -1,34 c -0,3 -3,5 -5,7 l 25,-1 C 64,62 62,59 61,57 L 61,41 76,41 77,68.5 c 0,3 2,9 7,12 6,4 15,4 22,2 3,-1 6,-7 5,-11 -2,-7 -15,-5 -19,-4 4,1 15,10 11,13 -4,3 -11,-6 -11,-11 0,-17 -0,-49 -0,-49 0,-3 2,-4 4,-6 z" />
  
1410 <path d="m 77,67 c -23,1 -47,24 -63,8 -4,-4 -2,-14 3,-14 8,0 3,8 0,8 3,2 10,4 13,-0 3,-5 -5,-17 -12,-15 -32,7 -2,39 17,37 15,-0 19,-12 41,-24 z" />
  
1411 <path d="m 92,27 21,0 c 2,0 4,1 4,4 l -0,24 c 0,2 2,4 2,4 l 1,2 H 95 c 2,-1 4,-2 5,-5 V 31 c -2,-3 -6,-3 -8,-4 z" />
  
1412 <path d="m 150,26 0,12 c -1,-2 -2,-5 -5,-6 -3,-2 -10,-5 -11,-3 -6,5 18,12 19,25 1,15 -22,10 -26,9 -2,-1 -4,2 -5,3 V 53 c 2,3 3,4 4,5 8,7 15,5 13,0 -5,-8 -21,-10 -20,-21 2,-16 18,-8 28,-8 1,0 3,-1 3,-3 z" />
  
1413 <path d="m 184,30 0,12 c -1,-2 -2,-5 -5,-6 -3,-2 -10,-5 -11,-3 -6,5 18,12 19,25 1,15 -22,10 -26,9 -2,-1 -4,2 -5,3 V 57 c 2,3 3,4 4,5 8,7 15,5 13,0 -5,-8 -21,-10 -20,-21 2,-16 18,-8 28,-8 1,0 3,-1 3,-3 z" />
  
1414 <circle cx="109" cy="18" r="7" />
  
1415 </g>
  
1416 </svg>
  
1417 
  1418 
  1419 <h2>Hiss User Guide</h2>
  
1420 
  1421 
  1422     <p>This is a <b>***WORK IN PROGRESS***</b>
  
1423     Check out the
  
1424     <a href="http://ratfactor.com/hiss/log">devlog</a> to see where I'm at.</p>
  
1425 
  1426 
  1427 
  1428 <h3 id="toc">Table of Contents</h3>
  
1429 
  1430 <ul>
  
1431     <li><a href="#introduction">Introduction</a>
  
1432         <ul>
  
1433             <li>How to get started
  
1434             <li><a href="#what-is">What is Hiss?</a>
  
1435         </ul>
  
1436     </li>
  
1437     <li>The editor interface
  
1438     <li>Basic tutorial: your first game
  
1439     <li>Introduction to variables and logic
  
1440     <li><a href="#lang-ref">Language reference</a>
  
1441         <ul>
  
1442             <li><a href="#lang-set">set [variable] to [value]</a></li>
  
1443             <li><a href="#lang-print">print [variable]</a></li>
  
1444             <li><a href="#lang-if">if [variable] is [value]</a></li>
  
1445             <li><a href="#lang-else">else</a></li>
  
1446             <li><a href="#lang-endif">endif</a></li>
  
1447             <li>...</li>
  
1448         </ul>
  
1449     </li>
  
1450     <li>Scripts
  
1451     <li>Scripts (Advanced)
  
1452     <li>No such thing as errors
  
1453     <li>Making Decorations with "deco"
  
1454 </ul>
  
1455 
  1456 
  1457 <h3 id="introduction">Introduction</h3>
  
1458 
  1459 <p>Welcome to <b>Hiss</b>, The <u>H</u>ypertext <u>I</u>nteractive <u>S</u>tory
  
1460 <u>S</u>cribe!</p>
  
1461 
  1462 <h4>What is Hiss?</h4>
  
1463 
  1464 <p>Hiss is a tool for creating text-based games such as "choose your own
  
1465 adventure" stories, textual puzzles, or something else entirely.
  
1466 All game interaction is performed through written text and hyperlinks.
  
1467 Other than that, the only limit is your imagination. 
  
1468 </p>
  
1469 
  1470 <p>Exported Hiss games are a single HTML file which can be played in any
  
1471 reasonably modern Web Browser. The game file can be shared and played "offline"
  
1472 or hosted on a server.
  
1473 </p>
  
1474 
  1475 <h4>How to get started</h4>
  
1476 
  1477 <p>One way to learn how to use Hiss is to read this manual.
  
1478 </p>
  
1479 
  1480 <p>The other way to learn is to just mess around with the initial sample game
  
1481 and see what happens. (You can't hurt anything and you can always reset
  
1482 the sample.  It's one of the options under the "New Game" menu item.)
  
1483 </p>
  
1484 
  1485 <p><a href="#toc">^ Table of Contents</a></p>
  
1486 
  1487 
  1488 <h3>The editor interface</h3>
  
1489 
  1490 <p>The Hiss editor is divided into these sections:</p>
  
1491 
  1492 <table>
  
1493     <tr><td colspan="3"><h4>File Menu</h4>
  
1494         <ul><li>"Export Game" - save your game as a stand-alone HTML file
  
1495             <li>"Save File" - save your game as a text file
  
1496             <li>"Load File" - import a game text file
  
1497             <li>"New Game" - replace whatever you current have with a new blank game
  
1498                  <i>or</i> the initial sample game
  
1499             <li>"Editor Color Scheme" - change the <i>editor</i> colors (does not affect game colors)
  
1500         </ul>
  
1501     </td></tr>
  
1502     <tr>
  
1503     <td><h4>List Panel</h4>
  
1504         <ul><li>Click "(add+)" to create a new script in your game
  
1505             <li>Click on the name of a script to edit it
  
1506         </ul>
  
1507         <p>If you've set any variables in your game, they will show
  
1508         up in this panel with their current values. Likewise, any
  
1509         colors you've set will show up in a visual preview.
  
1510     </td>
  
1511     <td><h4>Script Editor Panel</h4>
  
1512         <ul><li>Click "(edit)" to rename or delete the current script.</ul>
  
1513         <p>This is where you'll edit the content of each game script.
  
1514            All changes show up immediately in the Player Preview to the right.
  
1515     </td>
  
1516     <td><h4>Player Preview Panel</h4>
  
1517         <p>The current script plays here as it will in the exported
  
1518         HTML game.
  
1519         <p>As you navigate the scripts in your game, the current script in
  
1520         the Script Editor Panel will update to match.
  
1521     </td>
  
1522     <tr>
  
1523 </table>
  
1524 
  1525 <p><a href="#toc">^ Table of Contents</a></p>
  
1526 
  1527 
  1528 <h3>Basic tutorial: your first game</h3>
  
1529 
  1530 <p>To start your first game from scratch, select *New Game* from the top
  
1531 menu and then the *Make New Blank Game*.
  
1532 </p>
  
1533 
  1534 <p>Hiss was created with storytelling in mind. The story begins with the
  
1535 script named <i>*Start</i>. Let's make a game with a few actions and
  
1536 places to go. Think of a character and a setting.
  
1537 </p>
  
1538 
  1539 <p>(If you can't think of anything, how about: "Once upon a time,
  
1540 there was a Space Gerbil named Starfuzz who was returning home...")
  
1541 </p>
  
1542 
  1543 <p>As you type text in the Script Editor Panel (the center panel in the
  
1544 editor), the player (right panel) will continuously update to display the text
  
1545 as it will appear in the final game. Note that you can make paragraphs of text
  
1546 by separating them with one or more blank lines.
  
1547 </p>
  
1548 
  1549 <p>Got the start of a story in the <i>*Start</i> script? Now we can add some
  
1550 choices in the form of links to other scripts.
  
1551 </p>
  
1552 
  1553 <p>Scripts can represent actions, or places, or just more story.
  
1554 </p>
  
1555 
  1556 <p>A convenient way to make new scripts is to create links to them.
  
1557 The player will display a "Create ..." button for any script that doesn't yet
  
1558 exist.
  
1559 </p>
  
1560 
  1561 <p>Example:
  
1562 </p>
  
1563 
  1564 <pre>
  
1565 Once upon a time, there was a Space Gerbil
  
1566 named Starfuzz who was returning home.
  
1567 
  1568 But fuel was running low and the usual route
  
1569 was too far.
  
1570 
  1571 Starfuzz can:
  
1572 
  1573 link Look at the map to map1
  
1574 
  1575 link Have lunch to lunch
  
1576 </pre>
  
1577 
  1578 <p>If your script looked like the above, the player would show two buttons:
  
1579 "Create 'map1'" and "Create 'lunch'".
  
1580 </p>
  
1581 
  1582 <p>Try it with your own story. Or copy the one above.
  
1583 </p>
  
1584 
  1585 <p>When you click one of the "Create" buttons, two things will happen: 1. The
  
1586 new script will appear in the Scripts list in the left panel and will be
  
1587 selected;
  
1588 2. The editor in the middle panel will display the new script so you can
  
1589 start writing it.
  
1590 </p>
  
1591 
  1592 <p>Write your own story text for your game's script(s), or use these:
  
1593 </p>
  
1594 
  1595 <p>Script "map1":
  
1596 </p>
  
1597 
  1598 <pre>
  
1599 Starfuzz looks at the galactic star map. Aha! It's all
  
1600 so clear now. The route can be plotted through a dense star
  
1601 cluster.
  
1602 
  1603 link Plot the route to map2
  
1604 </pre>
  
1605 
  1606 <p>Script "lunch":
  
1607 </p>
  
1608 
  1609 <pre>
  
1610 Starfuzz is hungry. You know how hard it is to plot interstellar
  
1611 travel on an empty stomach.
  
1612 
  1613 Mmmm, that was a good sandwich. Now it's time to check that map.
  
1614 
  1615 link Look at the map with a full belly to map1
  
1616 </pre>
  
1617 
  1618 <p>As you can see, there can be more than one link to a script and
  
1619 the text on the link can be anything you want. Only the script name
  
1620 needs to be consistent. The words "link" and "to" are <i>keywords</i> and
  
1621 must be entered in the correct order for the link to work.
  
1622 </p>
  
1623 
  1624 <p><b>Wisdom of the ages:</b> To preserve your work as you create your game, it
  
1625 is highly recommended that you frequently make a backup using the "Save File"
  
1626 option in the top menu.  This will save your game as a text file which can be
  
1627 re-imported with "Load File".
  
1628 (Hiss saves your game using your browser's "local storage" as you type, but
  
1629 it's better to be safe than sorry.)
  
1630 </p>
  
1631 
  1632 <p>Add as many scripts as you like to complete your first game. Here's
  
1633 the rest of the Space Gerbil game:
  
1634 </p>
  
1635 
  1636 <p>Script "map2":
  
1637 </p>
  
1638 
  1639 <pre>
  
1640 With the help of the ship's computer, Starfuzz plots the route
  
1641 through the star cluster. It will be a bumpy trip, but that's the
  
1642 only way to make it home with the remaining fuel.
  
1643 
  1644 link Pilot home! to go home
  
1645 </pre>
  
1646 
  1647 <p>Script "go home":
  
1648 </p>
  
1649 
  1650 <pre>
  
1651 The dense star cluster is notoriously difficult to navigate, but
  
1652 Starfuzz summoned Gerbil Resilience and piloted deftly through.
  
1653 
  1654 Upon entering the Home System, a familiar voice called over the
  
1655 communications channel: "Welcome home, Starfuzz. You are just in
  
1656 time to save us from the invading Space Lizards!
  
1657 
  1658 YOU WIN!
  
1659 
  1660 Story to be continued in Space Gerbil 2...
  
1661 </pre>
  
1662 
  1663 <p>Here is a visual diagram of the flow of the scripts in this tiny
  
1664 game:
  
1665 </p>
  
1666 
  1667 <svg width="300" height="440" class="diagram" alt="">
  
1668 <marker id="arrowhead" viewBox="0 0 10 10" refY="5" markerWidth="6"
  
1669         markerHeight="6" orient="auto-start-reverse">
  
1670     <path style="stroke: var(--svg-decos-stroke); fill: var(--svg-decos-stroke);"
  
1671           d="M 0 0 L 10 5 L 0 10 z" />
  
1672 </marker>
  
1673 <g style="fill:none; stroke: var(--svg-decos-stroke); stroke-width: 3;">
  
1674     <rect x="89" y="29" width="120" height="30" />
  
1675     <rect x="34" y="108" width="120" height="30" />
  
1676     <rect x="89" y="183" width="120" height="30" />
  
1677     <rect x="89" y="244" width="120" height="30" />
  
1678     <rect x="89" y="305" width="120" height="30" />
  
1679     <rect x="89" y="366" width="120" height="30" />
  
1680     <g style="marker-end:url(#arrowhead)">
  
1681         <path d="m 166,58 0,105" />
  
1682         <path d="m 150,58 c 0,26 -48,4 -48,30" />
  
1683         <path d="m 102,138 c 0,26 39,-1 39,25" />
  
1684         <path d="m 166,214 0,10" />
  
1685         <path d="m 166,275 0,10" />
  
1686         <path d="m 166,336 0,10" />
  
1687     </g>
  
1688 </g>
  
1689 <g style="fill: var(--fg-main); font-size: 20px;">
  
1690     <text x="130" y="50">*Start</text>
  
1691     <text x="80" y="130">lunch</text>
  
1692     <text x="135" y="204">map1</text>
  
1693     <text x="135" y="265">map2</text>
  
1694     <text x="122" y="325">go home</text>
  
1695     <text x="127" y="386">the end</text>
  
1696 </g>
  
1697 </svg>
  
1698 
  1699 <p>As you can see, the "lunch" script is the only alternative path and
  
1700 even that doesn't affect the rest of the game. Perhaps you can
  
1701 add some more interesting and consequential choices?
  
1702 </p>
  
1703 
  1704 <p>You can test your game at any point by selecting a script in the left
  
1705 panel and playing the game the right panel. You can play from the beginning
  
1706 by selecting the "*Start" script.
  
1707 </p>
  
1708 
  1709 <h4>It may be small, but this is a real game, let's export it:</h4>
  
1710 
  1711 <p>Once you've completed your game, click the "Export Game" link in the
  
1712 top menu. This will save your game as an HTML file which can be played
  
1713 in any modern browser. You can share your HTML game any way you normally
  
1714 share files, or host it on a website for anyone in the world to play.
  
1715 </p>
  
1716 
  1717 <p>Congratulations on creating your first game!
  
1718 </p>
  
1719 
  1720 <p>This completes the tutorial. Linking between scripts is all you need to
  
1721 create a choose-your-own-adventure game. But Hiss has features to support
  
1722 more advanced types of storytelling games. Read on to learn about them.
  
1723 </p>
  
1724 
  1725 <p><a href="#toc">^ Table of Contents</a></p>
  
1726 
  1727 
  1728 <h3>Introduction to variables and logic</h3>
  
1729 
  1730 <p>Hiss supports some basic computer programming concepts. Even simple
  
1731 text stories can benefit from a little logic here and there.</p>
  
1732 
  1733 <p>For example, the Space Gerbil game we made above currently ignores
  
1734 whether or not you choose to eat lunch before journeying home, which is
  
1735 kind of sad. Let's fix that
  
1736 </p>
  
1737 
  1738 <p>To keep track of whether or not we had lunch, let's add a variable
  
1739 called <code>lunch eaten</code> and set it to the value <code>true</code>
  
1740 in the <b>lunch</b> script like so:
  
1741 </p>
  
1742 
  1743 <pre>
  
1744 Starfuzz is hungry. You know how hard it is to plot interstellar
  
1745 travel on an empty stomach.
  
1746 
  1747 <b>set lunch eaten to true</b>
  
1748 
  1749 Mmmm, that was a good sandwich. Now it's time to check that map.
  
1750 
  1751 link Look at the map with a full belly to map1
  
1752 </pre>
  
1753 
  1754 <p>You'll notice that as you type the new line in the script, it will appear in
  
1755 the game like any other story text <em>until</em> you've typed enough for Hiss
  
1756 to recognize it as a programming statement.
  
1757 </p>
  
1758 
  1759 <p>The moment you type "set lunch eaten to t", that's enough. The line will
  
1760 disappear from the story and a new section will appear in the List Panel:,
  
1761 <b>Values</b>.  As you type the rest of the value "true", that will appear as
  
1762 the variable's value in the List Panel.
  
1763 </p>
  
1764 
  1765 <p>The values shown in the List Panel are the result of whatever scripts you've
  
1766 run, including the one you're currently editing.
  
1767 </p>
  
1768 
  1769 NEXT: check the variable in one of the later scripts of the story
  
1770 
  1771 <p>Now let's add a little "easter egg" to the game that mentions the
  
1772 fact that you've eaten lunch. Here's the 
  
1773 
  1774 <pre>
  
1775 With the help of the ship's computer, Starfuzz plots the route
  
1776 through the star cluster. It will be a bumpy trip, but that's the
  
1777 only way to make it home with the remaining fuel.
  
1778 
  1779 <b>if lunch eaten is true
  
1780   (Good thing Starfuzz ate lunch. This is hardly a
  
1781   job for an empty belly.)
  
1782 endif</b>
  
1783 
  1784 link Pilot home! to go home
  
1785 </pre>
  
1786 
  1787 <p>The "if statement" ("if lunch eaten is true") should be fairly clear on its own.
  
1788 Anything after this statement will <em>only</em> show up if the statement is
  
1789 true...in this case, literally the value "true".</p>
  
1790 
  1791 <p>The "endif" statement ends the check, so anything after that line will show up
  
1792 whether or not lunch was eaten. In this case, the "Pilot home!" link will
  
1793 always be visible whether you ate lunch or not.</p>
  
1794 
  1795 <p>Go ahead and re-play the game in the editor or re-export it and play the
  
1796 exported game. The new sentence should show up depending on whether or not
  
1797 you eat lunch.</p>
  
1798 
  1799 <p>If it doesn't work, congratulations, you've created your first bug. Try
  
1800 debugging your game by watching the value of <code>lunch</code> in the List
  
1801 Panel to the left when you chose the option to eat lunch. Compare it to the
  
1802 value you're checking in the <code>map2</code> script. Spelling matters because
  
1803 Hiss has no way of knowing if you actually <em>want</em> "true" and "troo" to
  
1804 be the same thing.</p>
  
1805 
  1806 <p>(Aside: Indenting the text between the <code>if</code> and <code>endif</code> keywords
  
1807 is optional, but it makes the structure a little clearer to read. For this reason, Hiss
  
1808 will automatically indent statements like this for you when it loads a script into the
  
1809 editor panel.)</p>
  
1810 
  1811 <p><a href="#toc">^ Table of Contents</a></p>
  
1812 
  1813 
  1814 <h3 id="lang-ref">Language reference</h3>
  
1815 
  1816 <p>A Hiss game is made up of one or more "scripts". Generally speaking,
  
1817 each script represents a new page that will display in your game.
  
1818 You can use scripts to represent actions, areas, or simply more
  
1819 story.
  
1820 </p>
  
1821 
  1822 <p>If a line of Hiss script matches one of the statement patterns below, it
  
1823 will perform a special action. <strong>All other lines</strong> will appear
  
1824 verbatim in the game. For example, <code>if foo is bar</code> matches the pattern for
  
1825 an "if statement" and will be interpreted as such by Hiss. But
  
1826 <code>it is a duck if it quacks</code> does not match the pattern and
  
1827 will display as regular text.
  
1828 </p>
  
1829 
  1830 <p>The most important rule of the Hiss language is: <strong>every statement must be
  
1831 on its own line</strong>.</p>
  
1832 
  1833 <h4 id="lang-set">set [variable] to [value]</h4>
  
1834 
  1835 <p>Variables allow you to store things. What kind of things? Well, anything you
  
1836 can type. To set a variable to a value, use the keywords "set" and "to" like so:</p>
  
1837 
  1838 <pre>
  
1839 set x to 5
  
1840 set foo to bar
  
1841 set Cheese to Gouda
  
1842 set favorite hobbit to Samwise Gamgee
  
1843 </pre>
  
1844 
  1845 <p>As you can see in that last example, both the variable name and the value can span
  
1846 multiple words. You'll know Hiss understood what you wrote if the words "set" and "to"
  
1847 are highlighted in the code and the variable shows up in the "Values" section of the
  
1848 List Panel on the left.</p>
  
1849 
  1850 <h4 id="lang-print">print [variable]</h4>
  
1851 
  1852 <p>What can we do with variables? The simplest thing is to print them like so:</p>
  
1853 
  1854 <pre>
  
1855 set Cow Sound to Moo
  
1856 
  1857 print Cow Sound
  
1858 </pre>
  
1859 
  1860 The above example with print "Moo" on a line by itself.
  
1861 
  1862 You can also display a variable in a paragraph of text (no new line).
  
1863 Hiss will try to do the right thing with surrounding punctuation:
  
1864 
  1865 <pre>
  
1866 set Cow Sound to Moo
  
1867 
  1868 The cow says, "
  
1869 print Cow Sound
  
1870 !"
  
1871 </pre>
  
1872 
  1873 <p>Which will print 'The cow says, "Moo!"'.</p>
  
1874 
  1875 <p>Now is a good time to repeat the rule: <strong>every statement must be
  
1876 on its own line</strong>. Let's see what happens if we ignore the rule:</p>
  
1877 
  1878 <pre>
  
1879 set Cow Sound to Moo
  
1880 
  1881 The cow says, "print Cow Sound!"
  
1882 </pre>
  
1883 
  1884 <p>As you have perhaps guessed, this prints the line verbatim:
  
1885 'The cow says, "print Cow Sound!"'</p>
  
1886 
  1887 
  1888 <h4 id="lang-if">if [variable] is [value]</h4>
  
1889 
  1890 <p>Anything appearing after an "if statement" will be ignored <em>unless</em>
  
1891 the statement is "true". An if statement is true when the variable is set
  
1892 to the same value after "is".
  
1893 
  1894 <p>For example, this won't print anything:</p>
  
1895 
  1896 <pre>
  
1897 set Flavor to lime
  
1898 if Flavor is lemon
  
1899   I LOVE LEMON!
  
1900 </pre>
  
1901 
  1902 <p>But this will display the shouting message:</p>
  
1903 
  1904 <pre>
  
1905 set Flavor to lime
  
1906 if Flavor is lime
  
1907   I CHANGED MY MIND AND LOVE LIME INSTEAD!
  
1908 </pre>
  
1909 
  1910 <p>If is usually paired with "endif" and often with "else", which are described
  
1911 next.</p>
  
1912 
  1913 <h4 id="lang-else">else</h4>
  
1914 
  1915 <p>Anything appearing after an "else statement", which is just "else" on a line
  
1916 by itself, will be ignored <em>unless</em> it is a preceeded by an "if statement"
  
1917 which has been found to be false.</p>
  
1918 
  1919 <p>Example:</p>
  
1920 
  1921 <pre>
  
1922 set Flavor to lime
  
1923 if Flavor is lemon
  
1924   I LOVE LEMON!
  
1925 else
  
1926   I CHANGED MY MIND AND LOVE LIME INSTEAD!
  
1927 </pre>
  
1928 
  1929 <p>This will display the LIME message because the "if Flavor is lemon" statement
  
1930 was false.
  
1931 
  1932 <h4 id="lang-endif">endif</h4>
  
1933 
  1934 <p>As mentioned above, an "if statement" is typically paired with an "endif".
  
1935 An if or else without an endif will affect <em>everything</em> to the end of
  
1936 the script, including other ifs and elses.</p>
  
1937 
  1938 <p>Example <em>without</em> endif:</p>
  
1939 
  1940 <pre>
  
1941 set Flavor to lime
  
1942 
  1943 if Flavor is lemon
  
1944   I LOVE LEMON!
  
1945 
  1946 if Flavor is lime
  
1947   I CHANGED MY MIND AND LOVE LIME INSTEAD!
  
1948 </pre>
  
1949 
  1950 <p>The above example will <em>not print anything</em> because "if Flavor is lemon"
  
1951 was false. Programmers describe the relationship between these two if statements
  
1952 as "nested". In fact, if you leave and return to the above script, Hiss make the
  
1953 relationship easier to see by indenting the second if statement like the example
  
1954 below.</p>
  
1955 
  1956 <p>Same example <em>without</em> endif, but with proper indenting:</p>
  
1957 
  1958 <pre>
  
1959 set Flavor to lime
  
1960 
  1961 if Flavor is lemon
  
1962   I LOVE LEMON!
  
1963 
  1964   if Flavor is lime
  
1965     I CHANGED MY MIND AND LOVE LIME INSTEAD!
  
1966 </pre>
  
1967 
  1968 <p>But an <code>endif</code> can end the first <code>if</code> and kick the
  
1969 second one out of the nest.
  
1970 <em>Fly and be free little if!</em>
  
1971 </p>
  
1972 
  1973 <p>Note that in the following example, <em>both</em> <code>if</code> statements
  
1974 have been terminated with an <code>endif</code> because that's what you would
  
1975 normally do and is recommended.  (An <code>if</code> without an
  
1976 <code>endif</code> tends to produce a sense of unease and tension, like the
  
1977 <a href="http://en.wikipedia.org/wiki/Damocles">sword of Damocles</a>
  
1978 (wikipedia.org).)
  
1979 </p>
  
1980 
  1981 <pre>
  
1982 set Flavor to lime
  
1983 
  1984 if Flavor is lemon
  
1985   I LOVE LEMON!
  
1986 endif
  
1987 
  1988 if Flavor is lime
  
1989   I CHANGED MY MIND AND LOVE LIME INSTEAD!
  
1990 endif
  
1991 </pre>
  
1992 
  1993 
  1994 
  1995 <h4><code>link <em>[some text]</em> to <em>[script]</em></code></h4>
  
1996 
  1997 TODO
  
1998 
  1999 <h4><code>inc [variable]</code> and <code>dec [variable]</code></h4>
  
2000 
  2001 TODO
  
2002 
  2003 
  2004 <h4>STOP!</h4>
  
2005 
  2006 TODO
  
2007 
  2008 <h4>deco: [decoration pattern]</h4>
  
2009 
  2010 TODO
  
2011 
  2012 <h4>insert [script] here</h4>
  
2013 
  2014 TODO
  
2015 
  2016 <h3>Scripts</h3>
  
2017 
  2018 <p>A Hiss game is made up of one or more "scripts". Each script
  
2019 represents a new page that will display in your game.
  
2020 </p>
  
2021 
  2022 <p>You can use scripts to represent actions, areas, or simply more
  
2023 story.
  
2024 </p>
  
2025 
  2026 <p><a href="#toc">^ Table of Contents</a></p>
  
2027 
  2028 
  2029 <h3>Scripts (Advanced)</h3>
  
2030 
  2031 <p>When you export your game as a text file with the "Save File" option
  
2032 in the file menu, the game's scripts appear as names in square brackets
  
2033 like so:
  
2034 </p>
  
2035 
  2036 <pre>
  
2037 [*Start]:
  
2038 Hello world.
  
2039 link foo to Foo
  
2040 
  2041 [Foo]:
  
2042 This is foo.
  
2043 </pre>
  
2044 
  2045 <p>If you have a favorite text editor program, you are encouraged to
  
2046 edit your game there and re-import it to the Hiss editor for testing.
  
2047 </p>
  
2048 
  2049 <p><a href="#toc">^ Table of Contents</a></p>
  
2050 
  2051 
  2052 <h3>No such thing as errors</h3>
  
2053 
  2054 <p>HissScript has a fundamental rule: there are no errors. A mis-typed command
  
2055 just displays as regular text. Hopefully, this makes Hiss friendly for
  
2056 game-makers of all skill levels.
  
2057 </p>
  
2058 
  2059 <p><a href="#toc">^ Table of Contents</a></p>
  
2060 
  2061 
  2062 <h3>Making Decorations with "deco"</h3>
  
2063 
  2064 <p>TODO: pull examples and such from
  
2065 the stand-alone deco editor, <a href="http://ratfactor.com/hiss/decos.html">decos.html</a>
  
2066 </p>
  
2067 
  2068 <p><a href="#toc">^ Table of Contents</a></p>
  
2069 
  2070 </div> <!-- end of .inner -->
  
2071 </div>  <!-- end of .docs -->
  
2072 </body>
  
2073 </html>