colorful rat Ratfactor.com > Dave's Repos

hiss

An HTML text adventure game creator
git clone http://ratfactor.com/repos/hiss/hiss.git

hiss/hiss.html

Download raw file: hiss.html

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">&#9654; 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, '&lt;'); 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>