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>