Hiss Dev Log
"Welcome, adventurer!"
Title was: "December Adventure 2023 (The Hiss Log)" but now it’s two weeks into January 2024. (And now it’s August.)
hiss.html ← this link will always have the current state of development
Today begins my December Adventure, 2023.
What’s a December Adventure, you ask? Read about it here and see what other adventurers are up to (eli.li).
Mumbling: So you want to peek under the ol' rat dome, eh? Wanna see how the squishy parts happen? This will be written either late at night after I’ve stumbled off into my night chamber to do a last bit of recreational programming before the sleep clowns pull me into the dark realm or early in the morning, moments after I wake, screaming, sword already in hand, to face the fresh hecks of the day. Thus, it may be a bit stranger than your average developer log.
Prepare your tender eyes for spelling errors, typos, and grammar unfortunatenesses.
Day 1
Not bad. Not bad at all. I didn’t have as much time as I’d expected, but, wait…I should probably talk about what I’m even building here.
Two words: choose-your-own-hyperlink-adventure engine (Yeah, that first hyphenated word is pretty long).
Okay, I think it would be easiest if I showed the things that came before this. Also, it would amuse me, so that is what we are going to do.
Exhibit One: "The Iron Guppy" (2003, PHP)
I have a little eulogy for my hyperlink-driven text-based space adventure game HERE. It died in 2017.
Exhibit Two: "Hoot" (2012-2014, JavaScript)
Holy cow, I’m looking at this thing now and it was kinda cool! Check out Hoot HERE.
So this was an all-in-one JavaScript "game engine" with documentation and a tutorial on the left and a big old text area on the right where you type your game.
It still works because backward compatibility is the one great thing about browsers.
If content like this won’t scar you for life, you can click the link in the second paragraph of the documentation that loads a sample script.
Here’s what I mean by content that might scar you for life:
[find a place to relieve your bladder]: /Having been a dental hygienist for most of your life, you reason that the best course of action is to find a place to relieve your bladder. Unfortunately, the sheep are upon you too quickly and they are far too insane for your tactic to work! You scream as they use their poisoned knives upon your face and neck area! You are dying. ...
The game script is a complete enough language that I included a "99 Bottles of Beer" solution in the "Docs" tab!
[start]: set [bottle count] to 99 /Let's _drink and sing_./ [drink and sing]: /^[count] bottle[s] of beer on the wall, [count] bottle[s] of beer. / if [bottle count] greater than 0 then decrease [bottle count] /Take one down and pass it around, [count] bottle[s] of beer on the wall.__/ run [drink and sing] else /Go to the store and buy some more, 99 bottles of beer on the wall./ end [count]: if [bottle count] equals 0 then /no more/ else /[bottle count]/ end [s]: if [bottle count] equals 1 then // else /s/ end
(You paste that into the script area.)
I invite you to play with the four tabs across the top. The "Structure" tab attempts to help you see how the game engine sees your game. The "Play" tab plays the game! The "Build" tab is the interesting part. This was my best effort, in 2017, to let you save your game!
I think I can do better in 2019.
Here’s an example game as rendered by the stand-alone engine. Warning, it’s just as insane and violent as the other bit I quoted above. Oh, and another warning: it’s BRIGHT ORANGE! Okay here it is: "My Awesome Game"
Just for the sake of completeness, I’ll also link to the README, from which I just learned that "HOOT" stood for "Hypertext Once upOn a Time". Nice.
Exhibit Three: "Hoot2 / HootScript2" (2018, JavaScript)
So Hoot was fine and well, but I had this idea for a much cooler interactive game-building experience. It was 2018 for flippin' heck’s sake.
See what I wrought HERE.
"Immediate mode rendering" had been discovered on the DOM. I was into Mithril.js (mithril.js.org) and it blew my mind. Leo Horie remains my hero for making Mithril.
Anyway, a lot happened for me in 2018 and it stalled. But I thought it was going rather well. For such a tiny thing, you could sure do a lot with it.
The game editor is divided into three columns:
-
Left: a list of the objects you’ve created to the left
-
Middle: a text area for the current object’s game script
-
Right: the live game running the current object
You can navigate by either playing the game or clicking on the object list.
Click on stuff and you’ll see a Goat
variable get updated (it flashes purple).
For a good time, try typing stuff in the script box. As you can see, the game updates immediately as you type. Pretty cool, right?
Exhibit Four (this new thing): "Hiss" (2023, JavaScript)
Which brings us to today. I never forgot about my abandoned game thingy, but re-starting abandoned projects used to be my Achilles heel.
A couple weeks ago, my youngest was making up a choose-your-own-adventure type of game/story to tell us and I thought, "I bet he’d really love to actually see that come to life in the browser." Which, of course, made me think of my abandoned Hootscript2 project.
I got all excited. But I didn’t really have a chance to do much with it 'cause
I was doing NaNoWriMo (nanowrimo.org) and, why yes, I
did hit my reduced word count goal, thank you for asking! Also, you know,
$day_job
, right? So that was the rest of November.
Then Eli mentions over on Mastodon that it was time for a December Adventure and I’m like, "holy sweet Yule goat, this is the perfect time to do that game thing!"
So what have we here? Is it a "hoot"? Nope, sorry owl fans. This is now a snake.
I present the beginnings of HISS: Hypertext Interactive Story Scribe:
If you looked at Exhibit Three above, then this layout will seem familiar. The difference so far is that I’m making use of a CSS media query to detect portrait-ish and landscape-ish screen dimensions and either displaying the editor and player right-left or top-bottom.
And, you know, I messed around with that for quite a while. Thank goodness for my CSS flexbox tutorial/cheatsheet/example page. I don’t do this "new" stuff often enough to remember it off the top of my head like I know the older stuff. (And I realize calling flexbox "new" makes me sound like a very old web dev, which is true. Centuries in web years.)
The less visible thing that’s different is that in 2018, I used Mithril for projects with a lot of interactive UI rendering because that was my go-to rendering library.
But it’s 2023 and I’ve got my very own rendering library: RetroV
and heck yeah I’m gonna use that in the editor. So I’ve plopped it
straight into a <script>
tag. It’s small enough to do that.
(In this house, we do not use JS outside of browsers. But don’t take that to mean I don’t like JS, because I actually like the awesome tiny core of JavaScript.)
I’ve got a little parseScript()
function that does just about nothing
right now. But you can type in the text field and see the string change
in the player box.
Nothing else works.
Okay, that’s it for today. Way more writing than programming. In fact, I’m just shy of a NaNoWriMo day’s worth of text in this first entry. At this rate, this page would be a book by the end of December. Thankfully, most entries will be waaaaaaaaaaay smaller. I hope.
P.S. I said above that I didn’t get to do much with Hiss before today, but last Saturday morning, I did do a little string parsing test because I wanted to see if my old regex habits were still viable. It looks like they are.
"I sleep now." --The Skeleton, The Lost Skeleton of Cadavra (2001)
Day 2
Got links working! I put one in the little default "hello world" script. Here’s the link again:
hiss.html ← this link will always have the current state of development
The "Bite" link in the play area pops up an alert box
that tells you what the link "points to". (In this
case, a thing called bitten
.)
You can change the link and see it update in the play area.
Next: Make clicking the link take you to the named "thing".
(That’s trivial - just update the current_obj
by name, but
I’ll need at least one other test "thing" to go to.)
Day 3
Even thought it’s the weekend, I did a ton of stuff today and didn’t get to this until the end of the night. So I kept the actual Hissing as simple as possible.
The trivial part was making links go to "things" by setting the current thing and redrawing. This is the entire handler (it’s a closure that’s pre-loaded with the thing name):
function link(to){ return function(){ current_obj = to; draw(); }; }
I needed a "thing" to actually go to, so I hacked the silliest little thing possible. First, I made a temporary script for the new thing and stored it in a string:
var bitten_script = "You have bitten your own hand. Oh no!\nlink Retry to start";
And then I manually added it to the object (thing) list by parsing the script:
var objs = { ... 'bitten':parse_script(bitten_script), };
And it worked! The "Bite" link in the start
thing takes me to bitten
, and
the "Retry" link takes me back to start
. Yay!
I probably would have done a little more, but I ended up nerd-sniping (xkcd) myself!
I’ve got a minified copy of my RetroV library pasted right in the page. That’s so the whole editor will be a single document you can download to your local computer to make games offline (or preserve for your grandchildren or whatever).
But having a line with 2000+ characters can be really inconvenient in the text editor.
So I thought, "How hard could it be to break this minified source at 80 columns?" Classic self-snipe!
Here’s the resulting Ruby program to line-break minified JS source. LOL.
Next: Convert ("decompile") thing AST object back into script.
Day 4
Well, that was easy. Going from JavaScript object to HissScript (Hisscript? Hissscript? Hisssssssscript?) string is delightfully simple. Here’s an example:
if(c.type==='var'){ s += "set " + c.key + " to " + c.val + "\n"; }
So now, when you click back and forth between the two
"objects" start
and bitten
, the script that appears
in the editor textarea is reconstituted from the object.
Speaking of textareas, I re-discovered the old HTML attribute versus property confusion. At first, I was worried that I would need to add something to RetroV, but once I got it sorted, all I needed was to add another example to my demo.html page. (Scroll or Ctrl+F to "textarea" to see it.) So that was a nice bit of project maintenance.
I got two other little things working: setting variables and the object list in the left pane.
The variables use set <var> to <value>
syntax. A special
variable is title
, which sets the title of the player.
Try changing the title in either script and see it change
immediately.
The object list in the left pane simply lists all current objects and when you click on one, it uses it as the current script and player target.
These easy gains have brightened up my day considerably!
Next: Make proper paragraphs, using blank lines as paragraph separators. I think this is where the objects become trees? (Unless I can think of a sensible to represent the children of paragraphs and if statements in flat lists?)
Day 5
Barely sneaking this in today. So not tackling my planned feature.
But I did have this fun and easy one in mind:
It’s fun to set the foreground (fg) and background (bg) colors and see them change immediately. Silly, but it works and I imagine people having fun with it. I guess if I have that, I’ll need to allow setting the link color as well.
Next: Make proper paragraphs, using blank lines as paragraph separators.
Day 6
I was really feeling some, what would you call this? A 1990’s desktop application aesthetic? I got rid of the portrait vs landscape CSS query. It just wasn’t worth the headache and I was never really happy with the player under the editor anyway. This will still display just fine on some surprisingly skinny displays.
I’ve been planning for the area below the game builder to be the documentation for using it (the ideas is for the whole thing to be completely self-contained). So this was a great opportunity to add the beginnings of that as well. I’m not sure where the save/export buttons will be. I have to see how many types of actions I end up needing.
I also did the paragraph thing…kinda. Actually, I’m glad I slept on that.
The "obvious" way to represent a script as data is a tree:
* Paragraph * Text * Link * More text
And "if" statements would be further nesting:
* If (condition) * Condition true * Paragraph * etc... * Condition false * Paragraph * etc...
And nested ifs (if I support them) would go even deeper.
But it occurred to me that I can store a flat list for the same reason I can parse a flat text file: storing a paragraph break (empty line) gives me the same info. I just act on it programatically when I render:
* Text (implicit paragraph starts) * Link * Break (end paragraph, start a new one) * Text * etc...
And here’s the "crazy" part: I think it’s even better for ifs!
* If (condition) * Text, etc... * Else * Text, etc...
I can test the If condition and do the right thing with that information. Incredibly, nested ifs are no problem. I can use a stack to store the current nested state.
If this turns out to be harder to deal with than a tree, then I’ll just go ahead and do the tree. But for now, I’m loving the simplicity of a flat list.
I have paragraphs working.
Here’s an interesting problem that came up, though: now that I can have multiple lines of script ending up in the same paragraph, I have to deal with spaces between the items.
For example:
Foo Bar Baz
Becomes "FooBarBaz" when rendered. :-(
So I’m currently solving that by just adding spaces around text elements when I send them off to be rendered. I’ll probably need to do something better in the future.
The other thing I did was move my two sample script items from JS strings to
<script type="hiss">
tags. I’ve used that trick to store text and data in other
projects. As long as the type isn’t the JavaScript MIME type, the browser won’t
try to execute it. Now they’re much easier to read and edit.
Next: Refactor the "current_script" nonsense.
Day 7
As has been the case, I was pretty exhausted going into this tonight. But after noodling around on some easy stuff, I got into it and decided to tackle something more interesting.
So I’ve got three new things tonight:
-
The "user guide" section now exists below the editor. It’s mostly placeholder, but does contain the No such thing as errors section. Check it out.
-
I did the refactor I’d written in yesterday’s "next note". Nothing too exciting there, just some code that makes more sense now.
-
And the fun one: You can add objects to your game now!
The last one is fun. Here’s what it looks like:
The way you create an object that doesn’t exist yet by linking to it is inspired by wikis. It’s how I solved the problem in my previous "HootScript2" game thingy.
Next: Perhaps "If" statements?
Day 8
I’ve got "if" statements partially working. It’s been a very, very long day and I need to go to bed. No problem, I’ll probably have a cleaner solution after sleeping on it anyway. :-)
Next: Finish up if/else/endif (with nesting!)
Day 9
It’s the weekend! I spent most of the day on another project. But I did work out what I believe to be a pretty cool system for if/else/endif logic. It’s a stack with three possible values on the top: true, false, and null.
Update: This ended up being wrong! I got the correct implementation on Day 11.
I had to write it out on paper:
UPDATE: It’s the next night and I realized this doesn’t work. So don’t base anything off of this table. :-)
Here’s the table I worked out in comment form from the JavaScript (actual logic not implemented yet):
// Logic Stack table of actions. ToS=Top of Stack // --------+----------+--------+------------------- // ToS | Input | Do | Result // --------+----------+--------+------------------- // - | if true | +true | true // - | if false | +null | null // true | if true | +true | true,true // true | if false | +null | true,null // false | if <any> | +null | false,null // null | if <any> | +null | null,null // - | else | +false | false // true | else | NOT | false // false | else | - | false // null | else | +true | true // - | endif | - | - // <any> | endif | pop | (ToS popped) // --------+----------+--------+-------------------
I don’t like the tri-state logic any more than you do, but it was the only way to make nested if/else work out for every case (at least I hope every case).
Remember: the idea is to have no errors in HissScript.
So you can type just an else
on its own and that’s
perfectly valid. Hiss will try to do the most logical thing
it can with it.
As for actual programming, I didn’t want to forget an idea I’d had the previous night, so I added it and partially implemented it: a "debug" or "internals" viewer. You check the "Show internals!" checkbox and it’ll show you where it found paragraph breaks, variables being set, and (in the future) if/else logic being enacted.
Speaking of variables being set, I added a "Values" view to the thing list panel on the left.
You can see both of these features in this screenshot:
Next: Same as last time: finish if/else/endif.
Day 10
Well, I implemented the logic from yesterday and guess what? It’s not right. The nested if/else block doesn’t do the right thing - else still has no way of knowing which of the two situations it’s in:
if true if false else I should show up!!! endif endif if false if false else I should NOT show up!!! endif endif
I had to implement it to see the flaw. I stand by the paper method to think it out. But my best ideas don’t always do what I think they will on an actual computer. :-)
Not to worry, I think the "logic stack" with a series of booleans containing the current if/else results is still correct and I think I see the right answer now.
It’s actually glaringly obvious, I think/hope, in
hindsight: only true
values all the way up the logic
stack indicate we’re in a "true" branch of logic that
should be executed. I mean, "duh", right? :-)
If I’m right, I’ll explain it better!
Next: Again: finish if/else/endif.
Day 11
So, what I realized last night is that there is no single value I could possible put at the top of the "logic stack" which could represent where you’re at in an if/else tree.
It’s completely obvious to me in retrospect and a perfect example of the advantage of breaking up challenging tasks over time to fully internalize them. (In other words, "sleep on it," repeatedly.)
Anyway, the following pseudocode tree is obviously a tree. And it’s pretty
easy for a seasoned programmer to see where the logic adds up to true
:
<--- default state is 'true' if false if true if true else endif endif else <--- true if false if true else endif else <--- true if true <--- true else endif <--- true endif <--- true endif <--- back to default 'true'
But in HissScript, it’s actually a flat list of instructions.
Let’s see how that works for the deeply nested example above:
HissScript | Logic stack | Logic result |
---|---|---|
- | - | true (default) |
if false | false | false |
if true | false, true | false |
if true | false, true, true | false |
else | false, true, false | false |
endif | false, true | false |
endif | false | false |
else | true | true |
if false | true, false | false |
if true | true, false, true | false |
else | true, false, false | false |
endif | true, false | false |
else | true, true | true |
if true | true, true, true | true |
else | true, true, false | false |
endif | true, true | true |
endif | true | true |
endif | - | true (default) |
- | - | true (default) |
See, the stack is still the right mechanism. But instead of just checking
the top of the stack, I need to look at the whole stack. If any of the
items on the stack are false
, the whole current "branch" is false.
(In other words, the whole stack has to be true
for us to be "in" a
true
"branch" of logic.)
The operations are almost identical to what I had before:
-
if
pushes true or false on the logic stack -
else
inverts the last item on the stack -
endif
pops the last item on the stack
This is actually way simpler than the true/false/null thing I had before and that big logic table for each operation.
Again, why am I re-inventing how to store source code? There’s two reasons:
-
To see if storing a flat list is even remotely viable. If it is, then I believe it will be easier to examine and debug in the long run.
-
To help me make a language where there are no such thing as errors.
That second one is the important one. My example above makes a nice tree of logic that would be fine in any language. But I want Hiss to do something reasonable if you have this:
if true ...
(No matching endif
. The end of the script implicitly ends the true block.)
Or even:
else ... endif
(No if
at all! Everything in this block is "false", right?)
Later in the evening…
It worked! I can’t promise it will read your mind and give you the behavior you want, but I can promise that there are no errors no matter what weird combination of if/else/endif you type.
And the nested logic is absolutely correct. :-)
Next: Maybe something fun as a reward - like my SVG deco block idea!
Day 12
I started this morning by breaking out yesterday’s triumph into its own page so it’ll be easier for others to find: Interpreting if/else logic with a simple flat list of booleans.
I hinted at this "SVG deco" thing in my "next" note to myself yesterday. I’ve got a start on it:
As you can see, -hiss-
is now rendered as semi-cursive
text. That’s tiny set of SVG paths I drew in Inkscape
and then copied over to the source of the Hiss document.
The initial instance is in the user guide below. It gets
copied (as the outerHTML
property of the element) to
the player as needed.
Similarly, three hyphens ---
turns into a line
decoration like you might see as a section break in
a novel. At least, that’s the idea.
I’m imagining a small set of these that you can cycle through by adding more or less hyphens. I think it’s fun to see the text turn into a graphic instantly and I think my kids are going to enjoy this feature when I show them.
The thing I’m wondering is: can I somehow have combinations of little ASCII-art graphics that turn into little SVG elements that you can combine so they can be used creatively in ways I haven’t pre-planned? I don’t know. I don’t want to get too side-tracked from the main text adventure goals, but this could bring a whole fun visual element to the thing as well.
I uploaded my SVGs to one of those online "compressor"
services and I see how they’re doing it. You can
combine styles with <g>
groups and clean almost
everything out of the <path>
elements.
The little swoosh drawing was down to just 600 bytes when it was compressed (it’s not currently, so don’t bother checking). Which is super reasonable.
I have very specific requirements, so I can actually beat the online tool (I have an unfair advantage). And uploading files is too round-about for rapid testing anyway. So I think I’ll write my own SVG squisher in Ruby. (Because that’s a fun side quest and I think it’ll be pretty quick.)
Includes
But I actually have something way more exciting in mind today: insert … here
The thing I realized yesterday when I made that monkey and banana logic test was that I had to keep linking to the same "script" thing after each action so you could actually see the effect of the action.
There are a couple solutions to this problem, but the one that immediately came to mind when I woke this morning was that rather than linking to the thing, I could include it.
Includes will be easy to implement and I already have the wording I’d like to use:
insert <name of thing> here
But here’s the deal: as soon as you can include one thing inside another, somebody is going to realize that they can do it recursively.
And at that moment, the browser is going to attempt to fill the computer’s memory with copies of the rendered player output and Hiss will crash.
Now, I have no intention of doing cycle detection and preventing either self-recursion or mutual-recursion because there are so many cool things you could do with it (like, ,maybe this is how loops are done in Hiss…).
But crashing the browser isn’t very nice either.
WWSPD? (What Would Seymour Papert Do?)
I think I’ll simply limit the total number of things that can be drawn. They can all be different or they can all be the same. Maybe 100 is appropriate? Gotta have enough to be fun to play with.
Next: insert … here. And/or SVG compressor utility.
Day 13
Incredible power for such an easy feature to implement.
By being able to include a common script rather than just link to it, you get immediate feedback when you change the state of the monkey or the banana:
But the really fun part is when you make a recursive script that includes itself like this one:
Rather than prevent this, I simply put a hard limit of 100 inserts on it. That’s still enough to scroll way off the screen, but not enough to crash the browser. In fact, the browser renders it instantly:
Let’s take a moment to reflect on how amazing Web browsers are. Yeah, Google is evil and Mozilla is insane. But the sheer performance and backward compatibility of browsers like Firefox are incredible engineering achievements and we can just use them any time we want from anywhere in the world. You can run an application like Hiss in milliseconds without "installing" anything in the traditional sense.
I’m completely confident that Hiss will still be functioning 20 years from now with no changes. I believe that because the stuff I wrote 20 years ago in pure HTML and JavaScript still works today. It might even work 100 years from now - who knows?
The idea that all of this came from a networked documentation sharing concept from a nuclear research laboratory is just bonkers. Everything about the Web is bonkers.
Where was I?
Oh yeah, so like I mentioned somewhere in my ramble yesterday, this may be how loops are "officially" done in Hiss.
I have no idea why someone would need loops in a choose-your-own-adventure game, but I don’t want to have any artificial barriers to game-maker’s creativity, either.
Assuming it’s not meant to be infinite, a loop, to me, implies a condition to keep the loop going. And the most natural condition for a loop is either to iterate over a list or increment (or decrement) a counter.
I don’t have lists yet and I’m not sure I want to have them.
Incrementing and decrementing a numeric value is easy enough…but in a language where errors are not possible, I need to handle incrementing and decrementing things that are not numbers, like the word "bicycle".
I two options for that:
-
I simply ignore increment/decrement on non-numbers
-
I increment/decrement the encoding value of one or more of the characters in the string.
The first option is easy and sensible.
The second option is potentially funny, but difficult to do correctly (especially outside the 7-bit ASCII range).
I got the above feature working this morning and this evening, I’m getting started on the SVG minifier utility in Ruby.
Next: SVG line-art minification.
Day 14
Well, sometimes a program works out just like you’re hoping it will. And today was one of those days.
The SVG line art minifier for Hiss is fully operational.
Here is one of the decorations. It’s just 203 bytes after being processed by my script:
Here’s the exact source of the above:
<svg id="svg_deco1" width="200" height="50"> <g style="fill:none; stroke: #000000; stroke-width: 3;"> <path d="M 103,3 C 103,19 2,-8 2,8" /> <path d="M 58,2 52,8" /> <path d="M 52,2 45,8" /> </g> </svg>
SVG is so cool. I wrote a report about it in 1999 when the W3C first came out with the spec. But it was years and years before we could use it (thanks Microsoft, grrrrrr).
Here’s my full writeup with examples and stuff: SVG Minifier in Ruby
Next: Try increment/decrement numeric values. (Oooh, I just had a funny idea for what happens when you do it to strings…)
Day 15
Increment and decrement work. Oh, and a print statement. These all operate on values:
-
inc foo
increments (+1) the value of foo -
dec foo
decrements (-1) the value of foo -
print foo
prints the value of foo
Between that and insert thing here
, controlled
loops are not pretty, but possible:
if x is EMPTY This will loop just once! set x to 5 endif if x is 0 ALL DONE! else print x dec x insert loop-num here endif
To be clear, I consider this hideous loop script example to be just fine because I can’t actually think of any good uses for such a thing in a choose-your-own-adventure game. But I do want to invite experimentation and play, and I think this encourages that. Besides, just because I can’t think of a good use for it doesn’t mean somebody else won’t.
Okay, so incrementing and decrementing numbers is all fine and well, but how does a language without the concept of errors deal with a request to increment or decrement something that isn’t a number, like a string?
There’s no one answer to this. I considered ignoring the request, which is boring and not helpful. Or incrementing/decrementing the binary value of the encoded string, but that would be difficult to get right and probably flat-out confusing to most people.
But before I went to bed last night, I had what I think is the funniest and most visual solution. Here it is:
Does your language do this?
Next: I have no idea! I am so tired. But I’m off work for the holidays now, yay!
Day 16
I’ve begun decompressing for the holidays. So I picked what I hoped would be an easy feature today. And it was!
Now you can click the Export Script link in a new menu in the Hiss header and it’ll save the whole game script as a plain text file. Check it out:
(I’ll spare you a massive rant which would go right here about finding information on the Web in the year 2023.)
I made a stand-alone example for future reference: Downloading files from JavaScript with Data URLs.
The downloaded script contains all of the "things" in your
game. The format has the things listed by name in square
brackets ([foo]:
). As an example, here’s some bits from my
current test script:
[Start]: -hiss- set monkey to caged set banana to locked ... [situation]: if monkey is free if banana is unlocked ...
Next: Import script files!
Day 17
Importing a whole game script (in the format established by the export from yesterday) works.
So now you can work on your script in your favorite text editor and import it.
Oh yeah, and now that I have this ability, I changed the initial test scripts from being separate chunks loaded separately to one big script (as exported), which is so much less annoying to manage. Yay!
Next: Group things by dot syntax (you’ll see what I mean by this when I do it. But I gotta see if it’s a good idea before I invest any effort in describing it.)
Day 18
First off, made a little stand-alone write-up for HTML/JS file imports like I did with the export.
As for the "group things by dot syntax", it was easy to implement and it works great. So now I’ll explain it.
I anticipate that having a ton of "things" to manage all of the possible actions you can take in a game will get unpleasant after a while.
You can put them in alphabetical order, but that feels pretty flimsy and doesn’t aid finding things quickly visually.
Here’s my solution:
In your script, these are referenced like so:
banana.eat banana.peel banana.sniff banana.throw away
The banana thing now has four sub-items. So it appears that I’ve added some sort of name-spacing to Hiss. Ooh, that must be pretty advanced, right?
Wrong! I’m just sorting them alphabetically and if I see a dot (".") in a name, I chop off the first part and indent the item. It looks like the list "understands" the relationship, but that’s just an illusion.
Here’s the entirety of the list drawing code:
var names = Object.keys(objs).sort(); return names.map(function(n){ if(m = /^.+(\..+)$/.exec(n)){ return ["a.indent", {onclick:link(n)}, m[1]]; } return ["a", {onclick:link(n)}, n]; });
I’ve used similar indent tricks for tree-looking lists in actual paid software products before. It looks like some complicated nested relationship, but it’s not. I learned the hard way long ago: more often than not, people don’t actually want complicated trees, they just want the appearance of them.
(In databases, you can also store an "indent level" along with sort order to make visual trees of items without having to make parent-child relationships. It’s usually way simpler - especially to query!!!.)
And in this case, the appearance of a tree is what we want. I could also easily adapt this to multiple levels of nesting, but I think one will be fine…for now.
Next: Maybe get back to those SVG decos. I actually need to start building a test game, too. Gotta dogfood this thing for a bit before I start working on the stand-alone player.
I’ve always had a hard time working on both the technology and the story aspects of a game at the same time…
Update, Dec 29: It occurred to me that I’ve never written the above anywhere else. So here’s a new card with the above as a stand-alone idea: Fake Trees: Using Indents For Simpler UIs
Day 19
No SVG decos today. Just did a bug fix for yesterday’s feature:
foo.bar
Should not indent .bar
if there’s no foo
.
Easy fix.
Day 20
Kids are off school and "holiday stuff" is in full swing, so it’s been way harder to work on this.
I started on the SVG decos this morning, but for some reason, my heart just wasn’t in it.
So I’ve started on the stand-alone player export feature.
I believe I’ll be able to put the entire player’s HTML document in a <script> tag with a "type" attribute set to something other than "javascript". I’ve been using this technique to store the test hiss scripts since the beginning of this project.
But is any HTML okay and…what about a <script> tag in that HTML document??? I have questions.
Day 21
I didn’t even bother to do a Web search for answers to my <script> tag questions. Instead, I read the HTML Standard, Section 4.12.1.3 Restrictions for contents of script elements (whatwg.org).
What this makes very clear is that I could, indeed, have a <script> tag inside a <script> tag and the browser will check for balanced closing tags before ending the original block.
But just because it’s legal doesn’t make it "right" and I don’t see any need to push these limits this hard. I think I’ll split my player output code into these parts:
-
The bulk of the HTML and CSS
-
The game script, as JSON (same structure as the in-memory game in the editor)
-
The JS, as text to be put into a script tag in the player.
-
Close the HTML body (just a trivial string).
And to keep things from being overwhelming (as I alluded yesterday, concentration is in short supply during the holidays), I’m going to split the work into these parts:
-
Export the JSON game script as a file
-
Write a stand-alone player in a separate HTML document
-
Combine the player and the JSON output into a full game export as described above.
And I got the JSON game script exporter working tonight. Took about 5 minutes. I already had the text script exporter and JSON.stringify() gives me the content of the game. So there wasn’t much to do.
I think it’ll be fun to work on the stand-alone player because that’ll be a new implementation of the player I already have in the editor. So it should be pretty straight forward. Back to basics. I like basics.
Next: Start on the stand-alone player HTML + JS. (Use the JSON exported from tonight’s feature.)
Day 22
Started work on the stand-alone player. Nothing much to show for it yet except a red error box that shows up if you have the gall to try to run it with a game that somehow doesn’t have the required 'Start' thing.
Day 23
The game export needs to be as compact as possible because I want it to be extremely portable. Like, "write your game to a floppy disk" portable.
And something that has been bothering me is how much bigger the JSON version of a game script is. There’s so much wasted space naming the properties of the objects.
The text version of "banana" starts off like this:
if banana is locked set banana to unlocked You unlock the banana, whatever that means! else ...
But that bloats up to this in my current JSON incarnation (edited for readability):
"banana":{"c":[ {"type":"if","key":"banana","val":"locked"}, {"type":"var","key":"banana","val":"unlocked"}, {"type":"txt","txt":"You unlock the banana, whatever that means!"}, {"type":"else"},...
This JSON style is more-or-less comprehensible by humans. But for the computer, it could just as well be encoded as positional lists:
"banana":[ [7,"banana","locked"], [3,"banana","unlocked"], [9,"You unlock the banana, whatever that means!"], [12],...
(Above example is representative, but not actually valid in the final form because I haven’t actually done this conversion in the editor yet).
Here’s the sizes as a chart, in case the numbers aren’t obvious enough:
3000 +------------------------------------------------+ | | | Tiny game example in bytes. | 2500 |-+ +-| | | | | | 1923 | 2000 |-+ ********** | | * * | | * * | 1500 |-+ * * +-| | * * | | * * 980 | 1000 |-+ 886 * * ********** +-| | ********** * * * * | | * * * * * * | | * * * * * * | 500 |-+ * * * * * * +-| | * * * * * * | | * * * * * * | 0 +------------------------------------------------+ script.txt objs.json lists.json
(I spent far too long on the above ASCII bar graph. It’s written with gnuplot (wikipedia.org) using this and this (both stackoverflow.com). But I never could get it quite right, so it’s heavily hand-edited.)
The list version is much closer to the original human-written script in terms of size, but is, of course, harder to understand because the object type is encoded with a number.
So I’m thinking this is an acceptable compromise:
"banana":[ ["if","banana","locked"], ["var","banana","unlocked"], ["txt","You unlock the banana, whatever that means!"], ["else"],...
Annnnnnnnd okay, I’m freaking out. I just made over 300 changes to use the above format. I had to do it all at once because there wasn’t any way to test it in pieces.
But it just worked. It just worked. I had to check and double-check that I was actually looking at the new code because I was expecting to have quite a number of errors after all of those changes.
But, nope, it’s the new game object format. This is one of those cases where not having errors is deeply disconcerting.
Later in the day: well, I got this far with the new format in the stsand-alone player. It renders a bit of the Start thing before it dies on the insert. I gotta figure out how to handle those with this DOm renderer.
You’ll laugh, but my favorite resource for vanilla JS DOM manipulation is:
I’m not kidding.
Today has been good! I love it when I can delete code and/or make things smaller and still have the same functionality.
Next: Continue on stand-alone player!
Day 24
Another invisible editor improvement has come out of making the player: I was rendering the inserted thing inside the current thing’s open paragraph tag:
Hello insert Foo here World.
Which would result in
<p> Hello! <p>Foo stuff...</p> World. </p>
But that’s bad HTML. Paragraphs can only contain inline elements, not other block elements like other paragraphs. (Of course, browsers just put up with this kind of garbage and rendering nested paragraphs all day, but that doesn’t mean we should do something wrong when it can be fixed.)
Now I’m ending the previous paragraph and rendering the inserted thing into the parent container. Needless to say, I’m also doing it that way in the player, where it’s much more explicit what is happening to the DOM.
And now I’ve got the stand-alone player working to the point where it can perform this basic situation exported from the editor and manually pasted into the player:
Did a lot of nice family holiday stuff yesterday and expecting more today. Thankfully, the cat got me up early today so I could make this progress before everyone else is up. I’m really happy with this progress and I’m gonna call it a day while I’m ahead. :-)
player.html ←- You can see the stand-alone player here. The source is very much a work-in-progress.
Next: More refactoring in the player and giving it SVG decos and the silly inc/dec font size feature.
Days 25, 26
These are big deal days here in this house with Yuletide celebrations followed by a birthday!
So no hissing today. :-)
Day 27
Apparently I felt like doing some cosmetic things. First to go: the harsh 90’s productivity application colors. In their place, this!!!
New logo SVG, too. I added the <circle> element to my SVG minifier because that dot over the "i" is a circle.
Basically, I’m trying to make the least intimidating interface I can and I think this appearance helps.
In addition to the visual cosmetics, I also added a cosmetic code feature I’ve been wanting to add: automatic indenting of if/else/endif blocks.
It turned out to be ridiculously easy. Could it be this easy?
if(c[TYPE]==='endif' || c[TYPE]==='else') indent--; for(i=0; i<indent; i++) s+= " "; if(c[TYPE]==='else' || c[TYPE]==='if') indent++;
Yeah, just three lines in obj_to_script()
.
Anyway, it seems to work. Here’s the "situation" thing from my test script:
if monkey is free if banana is unlocked The monkey eats the banana! Yay, you win! else The monkey wants to have the banana but it can't. endif else There is a monkey in a cage here. endif if banana is locked There is a banana here, but it is locked somehow. :-( endif
Next: I’ve discovered a problem with the way RetroV displays HTML literals (including inline <svg> tags) - it doesn’t update them. This is actually intended behavior…but I’m starting to doubt that it’s the current behavior. Not sure if I need to add a new feature or what.
Day 28
Apparently I hadn’t gotten the coloring out of my system yet. So now there are four color schemes and a switcher in the menu:
The top two are based on Solarized (ethanschoonover.com), and the bottom two are my own: yesterday’s pink and pastel (which I’m calling "Dolly '74" - a reference to Dolly Parton’s 1974 album, Jolene, an initially unconscious inspiration for the colors) and an ultra hacker-1337 scheme ("Agent Smith").
The big deal here for me was using CSS variables (or "custom properties" as they are formally called) for the first time…and changing their values dynamically with JavaScript. Never done either of those things before. I’ve wanted variables in CSS for decades and now we have them! Yay!
This comes in three parts. First, you define a variable in CSS. This particular
method (the :root
part) was used in an MDN example:
:root { --fg-main: #000; }
Then you use the variable wherever you would use that value in the stylesheet:
body { color: var(--fg-main); }
Looks weird, but it works!
Then, the value of the variable (and thereby anything that uses that value) can be changed in JavaScript at "runtime" like so:
document.documentElement.style.setProperty('--fg-main', '#FFF');
Before doing this variable excursion, I was doing color schemes the "old fashioned" way by defining a top-level class on the body tag and defining cascading color styles for each element. It was redundant and horrible, but it certainly worked and it’s what I’m used to. This is way better, more compact, more understandable, and more maintainable.
Then I got an SVG "favicon" working for the page as a data url:
<link rel="shortcut icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' %3Cpath d='m 71,16 ...%3C/svg%3E%0A">
Total cost for the favicon: 731 bytes.
Aaaaaand…I did a quick and dirty proof of concept of the whole game export thingy and it works!
I made this "game" in 10 seconds in the editor and exported it as an HTML file and opened the file and it works!!!
But the best part is that I called my youngest over to check it out and we made a little puzzle adventure game together with about five rooms and I had all of the features needed to do all of the logic requested. And we got the rest of the family to play it!
This has been a huge day of success for the Hiss!
(Still tons of bugs and little things to add, but I really feel like this is getting somewhere!)
By the way, this single HTML page contains all editor functionality, all graphics, two separate players, two separate stylesheets, four color schemes, and a little test "game" in a whopping 38Kb. And I know for a fact that I have some cruft that’s accumulated during the churn.
Next: I guess get the SVG decos finally working in the player? Or fix the SVG update bug in the editor? Or maybe implement the "Always" thing?
Day 29
Okay, it’s super janky, but I did create the ability to have an "Always" thing that always runs at the end of every other thing (except ones which have been inserted because that would cause it to run twice…).
I made this "die from bad smells simulator" to test it out. Every bad smell
increments a stinks
counter. The "Always" thing displays the current count
and/or checks to see if you have died from too many.
At first, I had "Always" running at the beginning/top of every thing. But that didn’t seem right because it was displaying before the stink count was incremented.
But running "Always" at the bottom/end doesn’t do everything I want it to either.
So I think I need to have an "Always Before" and "Always After" maybe?
I also currently have no good way to create these things in the editor without making links to them (and then removing the links). But that should be pretty easy to solve. I still haven’t created a way to rename or delete existing things, either. There’s plenty of missing UI stuff.
I’m gonna have to sleep on this feature. My mini-game is a total mess, but that might have more to do with distractedly making it in 10 minutes…
Next: Try always before/after?
Day 30
Today was pretty awesome. The "Always Before" and "Always After" things work great.
I also added a STOP! directive that ends all processing.
To make the three of those work cleanly, I did some medium refactoring on the player rendering.
It was one of those funny cases where using a global variable was way cleaner than going through a dozen contortions to write "pure functional" code that returns values up the call stack.
The exported player renderer was already using global state because rendering to the DOM directly was way more sensible than collecting elements and passing them around. But I had some refactoring to do there.
The last feature is subtle: I now only add spaces between text lines if they don’t start or end with punctuation.
So this:
You have $ print inventory bucks .00 in your pocket.
will print as You have $15.00 in your pocket.
But
You have print inventory bucks in your pocket.
will print as You have 15 in your pocket.
Like I said, it’s subtle. I’m pretty sure nobody but programmers will even notice that the spaces are just "doing what you expect." :-)
Rather than a screenshot for this entry, I’m just gonna post the entire test game script I wrote for this.
I call it "Smells In Paradise Simulator":
[Always Before]: if stink is 3 You have died from the bad smells! You are deceased. You can no longer navigate the house. :-( STOP! endif [Always After]: You have smelled print stink stinky things. if stink is 3 You have died from the bad smells! You are deceased. You can no longer navigate the house. :-( endif [Start]: set stink to 0 You have arrived at a tropical island. This is going to be great! You are renting a cute little house on the beach. Oh no, there are some bad smells in this house! insert room list here [room list]: link Living room to living room link Kitchen to kitchen link Bedroom to bedroom [living room]: The couch smells terrible! You are taking damage! inc stink insert room list here [kitchen]: This is a kitchen. Everything is old-fashioned, with enamel paint and chrome on the applicances. It's small, but serviceable. link Check the fridge to kitchen.fridge link Check the trash to kitchen.trash Or just link go back to the living room to living room [kitchen.fridge]: Oh, it's so bad! This is a smelly fridge! inc stink You are in the link kitchen to kitchen . [kitchen.trash]: You fool! Of COURSE the trash stinks! What were you expecting? inc stink You are in the link kitchen to kitchen . [bedroom]: The bedroom is decorated in neutral beige tones. Sheer curtains billow in the breeze, inviting you to take in the view. There is a perfectly made link bed to bedroom.sleep in the corner. Unfortunately, there is also a link small stain to bedroom.sniff in the other corner. insert room list here [bedroom.sleep]: Ahhhh, this is so nice. if stink is 0 You have a relaxing sleep. else The sleep restores your mind and olefactory senses. dec stink endif You are in the link bedroom to bedroom . [bedroom.sniff]: Why did you do that? You knew that stain was going to smell bad, but you sniffed it anyway. You have taken more damage! inc stink You are in the link bedroom to bedroom .
And guess what? You can play the game in my experimental stand-alone player here: Smells In Paradise Simulator.
(Hint: Sleep in the bed to restore one stinky smell’s worth of health. There is no win condition. Eventually you’ll probably smell too many bad things and die. Sorry.)
As you’ll note, the stand-alone exported game clocks in at 9.5Kb (2.7Kb of which is the game data itself).
hiss.html ← This link will always have the current state of the editor’s development.
Day 31
Well, this is the final day of December Adventure 2023!
(Actually, I’m writing this in the very first hours of 2024. We stayed up for it and I just finished this.)
So I’ve had the germ of this idea for a while: allowing game creators to actually fashion SVG decorations out of primitive parts by editing a simple single-line "ASCII art" pattern.
The final insight was remembering the PLATO Emoticons (platopeople.com) from the 1970s. These worked by "overprinting" multiple characters on top of each other.
Very clever stuff. So I figured if I can make any shapes with SVG, I can tailor them specifically for this purpose so you can make your own little decorative dividers in your games.
So I started some really simple ones tonight. I made a little template in Inkscape. I have my svg-min-ruby program specifically ignoring my guide lines (the red plus) by ID, so I can easily get just the shape I drew.
Here’s my template in action:
Then I knew it would be a pain to prototype this feature in the full game editor, so I made this little stand-alone test bed:
decos.html ← Try it!!!
If you don’t want to try it or something goes horribly wrong with the link above, here’s a screenshot for posterity:
I’m kinda amazed I got this far in a single (long) evening.
While the December Adventure may officially be over, I will definitely be continuing work on Hiss immediately in January. I will be continuing this log as well.
Now it is way past my bedtime. Good night!
Day 32 (Jan 1)
Ahoy! It’s the year 2024 now and December may be over, but it’s full steam ahead for Hiss!
I’ve decided to make the deco pattern <
character move the "print head"
back by a quarter space. This quadruples the number of neat combinations
you can make.
Here’s a TIE fighter:
|<<-<<<o<-<<<<|
Which renders as:
And that’s…a lot of <
characters. Yuck! To compensate for these
quarter steps, I’ve made "overprinting" a series of symbols easier
by introducing a "group" concept with square brackets ([…]
):
o[O-|]o
Renders as:
Day 33 (Jan 2)
I’ve woken up the last couple mornings with patterns to add to the deco maker.
The funny thing is that none of these were what I was woke up thinking about:
I was planning to add some flowers and leaves. But I’m happy with the options available with these - more basic primitives and some fairly standard "swooshes" like you often see section breaks in books.
Again, here’s the link to the deco pattern test page so you can play with it: decos.html
Day 34 (Jan 3)
Didn’t post any updates today because there are bugs, but I’ve been having great
fun with a rotation operators: r
for 45 degree and R
for 180 degree rotations.
With these, you can make a full circle with just a left arc like so: [(R(]
.
So I’m contemplating removing the right arc entirely…
Also, this is so fun that there’s now a tendency to have an explosion of features and I’m afraid it’s going to balloon in complexity and size. The second one is easy to address by setting a size limit. I’m gonna say 5Kb.
At the start of the evening, it was 4603 bytes. At the end of the evening, I’d added rotation and it was down to 2005 bytes. But there was some really easy fat to remove. Further gains will be more difficult.
Day 35 (Jan 4)
The deco features continue. I’m battling the SVG width being incorrect in different circumstances. It’s one of those silly logic bugs. I see the problem and I see a solution, but I don’t see a clean solution. And that’s what I want.
Okay, that’s fixed. And I’ve added documentation to the deco demo/toy that is semi-automated (the glyph quick-reference and table are automatically drawn from the available list of glyphs) so I won’t run into trouble (already previously encountered) where I forget to document a new glyph.
The whole demo toy with documentation weighs in at 8.9Kb, but the current feature size (glyph renderer that will be shared between the editor and stand-alone player) is: 2354 bytes.
Next: Now I’m excited to start actually adding new glyphs! So maybe that will be the next thing I do.
Day 36 (Jan 5)
A new skull outline glyph and chevron are like a DIY skull kit. It also kind of looks like a guitar when rotated.
I’ve also added a mirror ("m"
) action.
And repeats! By putting a single-digit number (2-9) in front of a glyph or action, you repeat it:
7. 2r( 6<c
But I’m not planning to allow the repeating of any sort of groups of actions/glyphs. Repeat is bad enough. Grouping them simply exceeds the complexity budget of this decoration feature. (It’s already "bad" enough as it is!)
One hour later: I can’t stop thinking about how I would make named groups of patterns…
Three hours later: Okay, I’ve got named groups working now. What have I done?!
Example from documentation:
{x [r|r=]} {a <<o<<} xaxax
Produces:
This is nuts, right? But I’m currently sitting at 3.6Kb, which should leave me plenty of room for the florid designs I want to add.
And surely I’m done adding features to the decoration language itself, right?
I guess we’ll see.
Once again, here’s the link to the deco toy: decos.html
By the way, here’s what my desk notebook currently looks like:
Day 37 (Jan 6)
Took the day off. :-)
Day 38 (Jan 7)
The spirals I’d planned in my notebook didn’t quite work out in SVG form (not enough space given the 32px square size of my glyphs with a 2px line weight. But I got a pair of 'em that I’m happy with. They’re 'p' and 'P' and they combine nicely in a bunch of different ways. Here’s the most basic:
P<<p<<=
Really happy with these two. This is more like what I imagined with for book-like section divider decorations in the first place.
Gonna try for leaves and flowers next time, then maybe I can be done with this ridiculous side-quest? We’ll see!
Day 39 (Jan 8)
Really happy with this morning’s deco progress. 'M' now mirrors vertically ('m' mirrors horizontally).
And I added two kinds of leaves and a quarter-arc that can be combined into shapes like a star:
Definitely nearing the finish line on decorations.
Next: Flowers.
Day 40 (Jan 9)
Got a big spiral, some sticks and more leaves and a "splat" that can be turned into a flower. Not even gonna put any examples here. I’m done with computers for the day. Headed to the couch with a book.
Will probably clean up and maybe even integrate the currect decoration "engine" into the full game editor.
We’re sitting at… 5032 bytes!
So I guess I hit my budget on the nose. Glad I’ve got everything I want in it already. I’ll probably be able to shave some bytes while cleaning it up. Maybe.
Day 41 (Jan 10)
Lots of non-visual backend stuff in the deco "engine" today. A couple of them are visible, though.
Here’s the new glyph "cheat sheet" (which also shows the new glyphs available, though just a mere taste of some of the combinations which are now possible).
And here’s my named pattern recursion detection in action:
(I wanted to just have a JavaScript alert, but due to the recursive nature of the problem, it turned out to be surprisingly difficult to not have infinite alerts, which is awful. So we have this instead. A lot of work for something that seems very unlikely to ever happen and I can’t guarantee it’ll work perfectly every time. But at least I tried!)
Day 42 (Jan 11)
I did some more cleanup on the deco "engine". I don’t even remember what.
But the exciting news: the decoration SVG maker is now in the editor!!!
The new syntax is:
deco: o<<==[O*]==o
Which makes:
But if that wasn’t enough, I also added a new Hotdog Stand theme.
If you aren’t already screaming from this sharp stab in the nostalgia organs, you might need this link to help fill in the missing pieces:
Anyway, the merging of the deco "engine" with the editor is pretty huge deal and puts an end to a side quest that has now taken a third as long as the entire main project.
I’ve also uploaded a pair of handy files:
-
blank.txt - A (mostly) blank start script
-
stinks.txt - The Smells In Paradise Simulator script featured above.
Now the editor itself can get some much-needed love. For example, there is no way of deleting a thing. That’s nuts!
Day 43 (Jan 12)
Got the "decoration engine" working in the stand-alone player:
I’m particularly pleased with having made the decoration code modular enough that I was able to take it from the test-bed HTML document, put it into the game editor, and then use the same script in the player export (the contents of the script tag are copied along with the HTML and player JS).
Unlike the decoration "engine", the editor’s version of the game script player and stand-alone player are separate because I have other features like "debug" in the editor’s player. I also like the idea that a Hiss player is simple enough to implement that doing it twice is no big deal.
Now I’d like to focus on an under-served area of Hiss: the list of "things" in the left panel.
Off the top of my head (and kind of in order of severity):
-
There’s no way to delete a thing!
-
There’s no way to rename a thing.
-
There’s no way to create the "Always Before" and "Always After" things without first making links to them.
-
There’s no visual indication which thing you’re currently looking at.
-
Is "thing" really the best word I can come up with for these?
As for that last one, I’ve seriously thought about calling them "scripts" or "pages", which are so wildly different that it shows how nebulous these things are.
(I’m currently leaning towards "scripts", for what it’s worth. Especially now that I have a pair called "Always Before" and "Always After"…)
Day 44 (Jan 13)
Okay, I went ahead and renamed "things" to "scripts". I think it makes the most sense.
And now you can rename a script or delete it! There’s now an edit link for the currently selected script (which is also more clear now that it’s highlighted in the script list panel).
Renaming also renames all links and inserts (includes) that pointed to it, which is pretty cool.
The delete option has a confirmation step so you won’t accidentally destroy a script.
I’m still not sure how to add the "Always After" and "Always Before" scripts without just forcing them to always exist. I might make a "Add" feature to create any arbitrary new script and perhaps that could suggest the "Always" scripts? Yeah, that sounds pretty good…
Next: Put an "add" feature in the script list?
Day 45 (Jan 14)
Side quest! Ha ha, okay, I couldn’t help it. I did a few style tweaks and then was suddenly overcome with a desire to try syntax highlighting.
It turned out to be way easier to implement than I’d expected.
For now, it’s in yet another stand-alone test page: highlight.html
Give it a whirl!
I’ll see about getting the highlighting in the game editor tomorrow and then get back on track with the script "add" feature as planned. :-)
Day 46 (Jan 15)
I’m making a checklist for myself to get through the drudgery of actually putting syntax highlighting in the game editor:
-
DONE: Add the new HTML elements to the renderer
-
DONE: Port over the styles (CSS) needed for this effect
-
DONE: Do I still need a container element? (update: YES)
-
DONE: Port over the resize and scroll event code
-
DONE: Create the highlighted version of the script (with HTML tags around highlights)
-
DONE: Make sure both versions of script update in sync with each other always
(And yes, I did say "drudgery". This is where the high of pure, unfiltered creation is followed by the low of actual implementation. I already know it will work, it’s a question of doing all the things again in the real editor and fixing the inevitable bugs that will crop up in stuff that used to work that I’ll be breaking.)
Regarding making the highlighted version of the script, I can think of four ways to go about it:
-
Make a new separate function to produce highlighted script from object.
-
Make a new separate function to produce highlighted script from non-highlighted script. (!)
-
Make the object-to-script function only return a highlighted script, put its contents in the highlights div, and then get the textContent of that to put in textarea, which should strip the HTML highlighting. (!!)
-
Make the object-to-script function return a tuple with a highlighted and non-highlighted script.
The last one seems the most sane while also balancing performance and least amount of duplicated code. All four solutions have possible advantages.
Evening: Well, that turned out to be messy! I ended up going with option two above. I forgot how short my obj_to_script() function was! But, like I said, wow, messy.
And I forgot that having multiple editor color schemes was going to be a factor!
Day 47 (Jan 16)
Well, syntax highlighting works great after I did a re-write of how I convert the encoded script objects into scripts…but my clever auto-indenting wreaks havoc with it.
Dang it. I had one function making the highlighted (with HTML markup) and unhighlighted versions of the script in a pretty clever way.
But if you’re typing nested if/else statements, my highlighted version of the script was auto-indenting underneath what you are actually typing…which is likely wrong unless you happen to type the exact same amount of indent yourself!
Anyway, I’m on my third re-write of this part and, quite frankly, I really don’t mind. I like having multiple tries at it because I keep getting better and better at it. The code is getting more compact and succinct because I understand the problem better.
Day 48 (Jan 17)
So as of yesterday, I’m storing my Hiss "syntax" in an object keyed by the element name. It’s super short, so here’s the whole thing:
// Strings are script literals, constants (ALL CAPS) are numeric // indices indicating where in the resulting script element array // we'll store (and later find) the user-supplied value. var syntax = { 'insert': ['insert', FRIEND, 'here'], 'link' : ['link', TXT, 'to', TO], 'var' : ['set', KEY, 'to', VAL], 'if' : ['if', KEY, 'is', VAL], 'print' : ['print', KEY], 'deco' : ['deco:', TXT], 'inc' : ['inc', KEY], 'dec' : ['dec', KEY], 'endif' : ['endif'], 'stop' : ['STOP!'], 'else' : ['else'], 'break' : [''], 'txt' : [TXT], };
This works great for "decoding" the stored script element back to a written script, but I’m also planning to use it to auto-create regexps "matchers". In theory, this could mean that I could have just one source of truth for the game syntax. Very exciting!
Decoding back to a written script can run in any order because I’m just looking up the values above by key (the type name is stored as a string matching the keys above).
But if I go the other direction, the matchers above have to run in the order I wrote them above. Can you see why?
The reason is that the txt
item at the end will (as I’m imagining it) result
in a regexp that looks like this:
/^\s(.*)$/
In other words, it will match some whitespace followed by anything on a line.
So if that matches before any of the others, they’ll never be reached. That’s fatal.
My question becomes: can I rely on the key order of Object.keys()
when I enumerate over the object above?
I tried reading the relevant parts of the ECMAScript 2015 specification and threw in the towel.
And it turns out, putting more effort into reading it would have been a total waste of time. The various discussions on Stackoverflow were extremely useful for comparing the multiple relevant ECMAScript specs versus actual browser behavior!
Object.keys order for large numerical indexes? (stackoverflow.com)
Does ES6 introduce a well-defined order of enumeration for object properties? (stackoverflow.com)
Especially the answer by user "trincot":
"…from EcmaScript 2020 onwards, Object.keys enforces the same specific order as Object.getOwnPropertyNames and Reflect.ownKeys, namely the one specified in OrdinaryOwnPropertyKeys […] 2. Other own String properties, in ascending chronological order of property creation…"
And the answer ends with:
"…It should be noted that all major implementations had already aligned with this order years ago."
So there you have it (and this is confirmed by everything else I’ve read and
experienced): Object.keys()
returns string keys in the same order in which
they’re created (which is guaranteed to be the same order in which they’re
listed in source code).
P.S. I am aware of the Map
type in JS, but prefer to use the familiar Object.
Minor correction: I’ll be using the convenience of Object.entries()
since I need keys and values. I’m confident the order rules are the same.
Okay, looks like it works! I’m converting the "object" code to script text and highlighting a written script (highlighting keywords) using code generated from that simple syntax list.
So do I dare also re-writing the main script parser with the same technique? Heck yeah, just not tonight. I’m just happy to have this thing working again after two nights of various states of broken.
I also removed the debug ("Show Internals" checkbox) feature entirely. I don’t feel like it’s quite as important to have now that syntax highlighting shows that the editor recognizes the keywords in the script. I love deleting code! I just hope this was the right decision.
Anyway, I’m really excited that my four day excursion into syntax highlighting is done!
Next: Refactor script_to_obj()
to use new
"matchers" generated from my encoded syntax.
After That: Get back to "script management" and add the ability to create scripts, including the "always" scripts.
Day 49 (Jan 18)
Yahoo! Syntax highlighting is actually done now.
So, this morning, I did that refactoring I’d planned last night. And that went great!
obj_to_script()
went from 70 lines of source to 16. (Not counting the shared
syntax definition used by the two other new functions, of course.)
But testing that also exposed a fatal flaw with the highlighting code.
So I ended up having to rewrite that entirely. Well, maybe that’s
overstating it, but the part where I generated the "matcher" regexp
and "replacer" string with encoded $n
placeholders needed a complete
re-write. I had to resort to paper and a bunch of testing to eventually
get it right.
There’s clearly a pattern to it, but it was so hairy that accounting for all of the exceptions was clearly going to take more code than just writing out all four possibilities (if there had been more than four, I definitely woulda done the automation, which is always my first inclination). Here’s the resultant regexps and replacement strings to highlight every possible pattern of keyword and variable user input in my simple language.
These cases are based on the number of input tokens:
case 4: hm = RegExp('^(\\s*)'+syn[0]+'(\\s+\\S.*\\s)'+syn[2]+'(\\s+\\S.*\s*)'); hr = '$1<i>'+syn[0]+'</i>$2<i>'+syn[2]+'</i>$3'; break; case 3: hm = RegExp('^(\\s*)'+syn[0]+'(\\s+\\S.*\\s)'+syn[2]+'(\s*)'); hr = '$1<i>'+syn[0]+'</i>$2<i>'+syn[2]+'</i>$3'; break; case 2: hm = RegExp('^(\\s*)'+syn[0]+'(\\s+\\S.*)'); hr = '$1<i>'+syn[0]+'</i>$2'; break; case 1: hm = RegExp('^(\\s*)'+syn[0]+'(\s*)'); hr = '$1<i>'+syn[0]+'</i>$2';
That ain’t pretty, but it’s not as inscrutable as the code that would have generated it. (I suppose it’s pretty awful if you don’t like regular expressions as much as I do. I actually like them just fine because they’re small, so they fit on the dang screen!)
And…I got the Add Script feature working:
Being able to easily add those "always" scripts fixes a big hole in the functionality that I’ve wanted to fill since I added them!
Very exciting.
The whole thing is at 50Kb now. It’ll be interesting to see how much weight the documentation adds. For comparison, this log, with images, is currently just under 2Mb (2,000Kb)!.
Next: Values (variable values shown in first panel list). Need fix some really funny bugs with my intial low-effort pass on displaying values. Also debating the ability to edit the current value - not sure if that will actually be useful or even make sense for game dev…
Day 50 (Jan 19)
Re-wrote the original janky "Values" display (which shows current values of variables in the left panel while editing). The values reflect what’s happening in the current running game, but that wasn’t quite good enough because if you, say, renamed a variable in a script, every single change to that variable would become a new variable.
So now the values list first scans the entire game for places where you set variables just to see which ones are still in play. Then it checks for currently-set values for those variables. The currently-set values will already reflect whatever script is currently being displayed and may have just run and set some values.
Anyway, it still feels pretty natural - you can see variables and values change instantly as you type them in the current script, but there are no longer a bunch of silly artifacts lingering around as you rename variables.
I was debating letting you change variable values with some sort of editor, but I think I’m gonna sit on that and wait until it proves itself necessary.
Then I started on a fun one - an SVG "preview" box that will show any color settings you’ve made.
The new thing I’m adding to my SVG minifier for this is rectangles and text. I love how tiny a graphic like this can be!
Here’s my current SVG (work in progress) after minification:
<svg width="150" height="150"> <g style="fill:none; stroke: #000; stroke-width: 1;"> <rect x="1" y="1" width="149" height="149" /> <rect x="11" y="56" width="129" height="84" /> </g> <g style="fill: #000; stroke: none;"> <rect x="0" y="0" width="48" height="22" /> <rect x="10" y="55" width="35" height="21" /> </g> <g style="font-size:20px;font-family:sans-serif;fill:#6beeff;"> <text x="4" y="18" class="pbg">PBG</text> <text x="55" y="46" class="pfg">PFG</text> <text x="14" y="72" class="bg">BG</text> <text x="39" y="112" class="fg">FG</text> <text x="83" y="112" class="lfg">LFG</text> </g> <path d="m 85,115 35,0" style="fill:none; stroke-width: 2;" /> </svg>
And here’s what that looks like:
(Note that you could totally make this with just HTML, too, but have fun lining up everything just right!)
This’ll make a lot more sense when I have the right colors in there, but these all-caps abbreviations are the actual names of the variables you’ll set in the game to change the colors on the final exported player page!
-
PBG = Page background
-
PFG = Page foreground (title text)
-
BG = Player background
-
FG = Player foreground (main game text)
-
LFG = Link foreground (main game links)
I’ll have an interactive preview somewhere in the documentation, so it should be extremely clear how it all works (I hope!).
Day 51 (Jan 20)
Have SVG color preview box drawing correctly. Now working on variables…
Day 52 (Jan 21) - Skipped!
New plan is to take one weekend morning off for fiction writing. Today is that day. No hissing today.
Day 53 (Jan 22)
Workin' on that SVG color preview still. Getting colors from the color scheme by default. It shows up correctly initially, which is neat.
Day 54 (Jan 23)
More SVG color preview. It reflects the changes as I set the variables now!
Not sure how I feel about all of these cryptic variable names. Having 'fg' and 'bg' was okay, but adding 'dfg' for the "deco foreground" might have been a step too far. And I’m realizing that I need a main game border color too… Well, I’ll just live with it for now and see how I feel about it in a while. I might just have been staring at these too long.
Aside: As you can see, progress has slowed to a crawl. Sometimes the real world has a lot going on. Not as much time for the Hiss. But tiny progress is still better than no progress for keeping the project "alive"!
Day 55 (Jan 24)
Crawling along. I’ve added the main player border color to the preview and list of "color variables" as planned:
I think this is officially one too many cryptic 2-3 letter vars. I can barely keep track of them while I’m looking at them and I can only imagine what it would be like if I had, say, dyslexia. I’ll try to come up with some better names for these. I don’t want them to be too long, though! This is where good naming gets really difficult.
Day 56 (Jan 25)
Made the mistake of looking into the current state-of-the-art for storing
small amounts of data from local HTML documents. By local I mean
opened from the local filesytem and having a URI scheme starting with file://
.
File URI scheme (wikipedia.org).
For the actual URI scheme format, see Section 2 of
RFC 8089.
But, honestly, all you’re going to learn is that anything after file:
is
pretty much up to the browser and your operating system to work out.
(But these days, you’ll probably see file:///
with three slashes, which
indicates a file on your local filesystem. A file://foo/…;
URI would
be a file from a host ("authority") called "foo". Leaving off the authority
part and having just a single slash (file:/tmp/hello.html
) is also valid,
but way less common. I’ve never seen a browser display this when I open an
HTML file locally.)
Anyway…can we finally do something reliable to store data from local HTML documents?
Sadly, it appears to be the same as it was over a decade ago.
You can possibly use localStorage
, but it’s browser-dependent and you’re
cautioned against depending on it.
To be clear, I find all of this local security stuff disappointing because it tends to favor web applications over users. What I mean by that is that the hand-wringing about "security" and what-not has always been from the point of view of the application developer trying to ensure that other applications can’t read their data…but there is practically no concern about the basic ability for the user to be able to store data in some fashion. So…there is no ability.
We are in the ridiculous position where an HTML document on your local computer, that you wrote by hand, has WAY less access to your browser’s features (a program also running on your local computer) than some random evil website.
I mean, come on, that’s backwards. "We" (the people who actually decide the standards, not me) completely lost our way a decade ago.
Sorry about the rant, but this stuff really bothers me.
Moving on…
I’m going to "kick this can down the road". I want people to be able to save the entire Hiss editor locally and use it, but I’m not sure how much of it will be usable.
Days Nope (Jan 26-27)
Time passes.
Day 57 (Jan 29)
Let’s add that customizable title back. But this time, it will reflect the color settings. What’s nice about this is it also gives me a way to show the page background color and player border color without making a huge mess.
Note: I’m trying to show as little as possible in the editor when you first start it up. I plan to even have the "Values" and "Game Colors" sections only appear when you start making variables or setting colors.
Adding the title is super easy. Have a game variable, check it and either display the title in a div or not.
Next: Update the title, page bg, and border colors when you set them. (And maybe show the title when you do - even if there’s no title set…)
Nope (Jan 30)
Nope.
Day 58 (Jan 31)
Wrote two lines of code.
Day 59 (Feb 1)
Ten minutes: Started working out the variable display logic on paper…
Day 60 (Feb 2)
Another ten minutes: Finished working out the variable display logic on paper.
Day 61 (Feb 3)
Implemented the display logic! It’s so pleasing to have a clean solution to something like this after trashing my source code for so long with inelegant and not-quite-working solutions.
I have a passion for this pleasing feeling, so I’ve written a new card: Leopard-Free Programming.
Here’s the display rules I came up with:
-
Show vars with values in the current running game/edit session.
-
Don’t show vars that aren’t defined in the game’s scripts.
-
Don’t show empty vars.
-
Do show empty vars if they are generated as a result of running the current script being edited/played.
And here’s the (it turns out) very simple steps to accomplish the above:
-
Collect a list of all the vars set in all scripts
-
Strip out any set vars that aren’t in the list
-
Run current script
This will require a little explanation.
First, the reason I need to strip out variables that aren’t currently set in all game scripts is that variables with values can come into existence while you’re typing.
Say you’ve created a variable called Have Room Key
and set it to a value.
set Have Room Key to true
Then you go back and decide to change the variable name to just Have Key
:
set Have Key to true
Well, every time you edit, the script will re-run, which means that all of the following variables will be set:
Have Room Key: true Have Roo Key: true Have Ro Key: true Have R Key: true
There’s all sorts of ugly and complicated ways I could track this specific problem. But it’s fast, cheap, and way simpler just to strip out the "orphaned" variable names that no longer exist in the game.
Second, I want to display "EMPTY" for variables that are being checked in the current script (but not set). The hope is that it will make it clear that you can write…
if Foo is EMPTY
…to check if a variable that has not yet been set. As far as the game is
concerned, foo really is equal to the string "EMPTY" when you check it
because that’s what my getval()
function sets it to and returns.
Thus the requirement of the final step to play the current script after stripping any previous orphaned or empty vars from the list.
All of this is a lot of words for a tiny bit of functionality, but I think it’s worth celebrating these little wins!
There’s also a rule that doesn’t show up above: I’m no longer displaying the "Values" section in the list panel (leftmost) at all if you don’t have any variables set. The idea here is to make the interface as uncluttered and non-intimidating as possible when you first arrive.
Sick (Feb 4)
Have been sick the last couple days, but finally gave in on Sunday. Lay on the couch and read Maakies.
Day 62 (Feb 5)
More incremental progress. Got the title preview area in the game player working with colors.
Day 63 (Feb 6)
Finally have the game player displaying colors again. This is one the first features I implemented way back in the beginning, but it’s a whole different thing now!
Here’s some frog colors. You can see that the preview in the list panel and the actual player on the right have matching frog colors:
I’m excited to move on, but I do have one last tweak first…
Next: Rename color variables to something sensible and draw lines to make the SVG preview more easily understood!
Nooooope (Feb 7 - ???)
I’m still alive and I still think about this project regularly, but have been unable to work on it. Thank you for your patience.
To see what I’m up to (and get a sense for when I’ll get back to this), try checking my now page.
Day 64 (July 17)
Well, it’s been over five months and I’m more than ready to get back into this project.
It looks like I’ve left myself with a task to update the colors preview SVG. I wasn’t entirely sure what I meant by, "draw lines to make the SVG preview more easily understood." Luckily, I’d already started that task and I see now that the lines connect from labels to the elements in the preview. And it is much clearer, I think. (Update: Check out the screenshot for Day 66.)
The other thing I had decided to do was setup a Git repo. The original idea was to just have people "View source" on the editor to…view the source. But I recognize that browsers on phones, etc., don’t always make this easy. And a repo is nice for tracking changes and attributing a license, etc.
So here’s the new repo: http://ratfactor.com/repos/hiss/
And the other thing I’ve decided to do immediately after finishing that SVG preview is to make the game player JS code shared between the editor and exported games.
So that’s three TODOs:
-
Finish SVG color preview
-
Make repo for source (DONE)
-
Make shared player source between editor and exported games
Day 64 (July 18)
Did some more housekeeping stuff (setting up Apache httpd to serve hiss.html straight out of my projects directory now that I’ve got a repo for it).
And started the SVG color preview work. It’s a pretty menial task, but at least it’s an easy transition back into Hissing regularly. :-)
Day 65 (July 19)
SVG color preview looking pretty good. Think I’ll have it finished tomorrow. Perhaps a screenshot as well. :-)
Day 66 (July 20)
I think that’s got it. Here’s the new look for the preview:
The new variable names should be much clearer than the old ones:
bg color title color border color page color text color link color deco color
So that takes care of two TODOs from Day 63, now I can proceed to the one I really want to do…
Next: Make shared editor/exported player source.
Day 67 (July 21)
Time to wreak terrible havok on the whole thing by de-duplicating the game player script.
Currently, there is a single exportable chunk of JS that does nothing in the editor, but runs the player in the exported game. And in the editor, there is a tangled mess of functions that "plays" the current script in the player preview panel.
The goal today is to get rid of the duplication. When this task is complete, the editor will preview the current script object using the exact same code used in the exported player.
I predict it will be a fairly annoying and painful refactor, but the benefits are many: less code, less duplication, and I’ll be testing the player code live while working on the editor - no need to export a game to make sure everything will work.
This is also a great way to re-load the whole codebase back into my head after this long hiatus.
I also did a new housekeeping task today: adding a new update.sh script to the repo. Since I’m working out of a project directory instead of the website (where you’re presumably reading this now), I need to update the website and the Hiss repo mini-site.
Day 68 (July 25)
Skipped a couple days. I was waking up too late to work on it in the morning. There was too much to do and too many interruptions in the daytime.
Okay, the player code is shared between the editor and stand-alone exported games now! It has some horrendous hacks to make it work, but it works.
One thing that has become increasingly clear is that it’s time to stop using my RetroV rendering library in the editor. It saved me a ton of time in the beginning, but since so many parts of the game and editor now have raw DOM function calls to render them, it’s just getting in the way.
I also noticed that apparently I never got the game color variables working in the stand-alone player! I’m sure I knew that five months ago, but it’s news to me now.
This works well enough now to go ahead and update everything on the live site.
Next: Custom colors in exported games. After that: factor out RetroV!
Day 69 (Nice, also July 26)
Not a lot of time this morning. Made some small but very good progress on those custom colors. Excited about having this feature working in the editor and exported game player!
Next: See previous.
Day 70 (July 27)
Same as previous day. More small, incremental progress on those colors.
Next: See previous.
Day 71 (July 28)
The hacks to make the shared player work within the constraints of the
existing editor code are getting just absolutely hideous.
I’m holding my nose and plowing forward.
Custom run-time game colors are mostly working.
Really looking forward to factoring out RetroV so I can re-write the editor’s
draw()
function!
Next: See previous.
Day 72 (Aug 02)
Yeah, you’re counting right, I’ve missed some days. I really prefer to do at least some every day to keep a project fresh in my head. But I don’t always get that luxury. Just gotta keep coming back and keep trying.
Anyway, this painful custom color slog seems to be done enough to move on for now.
And I believe I may have promised a screenshot?
How about three in one:
-
The color preview graphic doesn’t show up until you set a custom color.
-
The editor with outlandish yellows and greens in the color and game previews.
-
The exported game showing the same outlandish colors.
Next: I am so ready to refactor the editor rendering code. It’s gonna hurt, but I can’t stand the stink anymore. :-)
Day 73 (Aug 03)
A super quick thing to get warmed up: I’ve got the custom title setting the browser as well as the H1 tag in the game preview and the exported game.
Now on to the refactoring. I’m going full "pull the bandage" on this one: deleting the minified copy of RetroV and then making the editor interface display again.
Later: Wow! Editor render refactoring done. That only took a couple hours total, scattered across the day.
In short, I turned my declarative RetroV interface data structures into a combination of raw HTML and JavaScript DOM manipulation methods. Conceptually messier, but in practice, it’s actually nicer.
I assumed the file size would go down indeed, here’s the stats and Git made me hopeful about the commit:
162 insertions, 195 deletions = 33 fewer lines Before: 53,400 bytes After: 49,777 bytes ------------- 3,623 fewer bytes
That’s a 7% file size reduction, which is nothing to sneeze at, and that’s just with a straight refactoring. There’s plenty of room to improve. At the moment, I’m just super happy to have removed some really bad duplication of functionality and work-arounds.
Next: Figure out how to auto-save script. I know I can do localStorage when
it’s launched from a webserver, but things get complicated/questionable real
fast when the editor is run locally (i.e. file:///hiss.html
).
(Why? The short answer, I believe, is because localStorage works by domain
and there’s no domain when you’re opening the file locally. The boneheads
didn’t consider that in the initial spec, apparently.)
Day 74 (Aug 04)
Looks like localStorage
, indeed, does work locally. At least in
Firefox. Sweet.
I also investigated debouncing the text input event so I can write to localStorage only after typing has been paused for a second or so. Probably not necessary, but it seems like the right thing to do.
None of this in the editor yet, but I did create these two pages: js-localstorage and js-debounce. (Both link to the HTML test files I created to try it out.
By the way, I really like creating these little resources for my future self. They really add up over time.
Next: Add script auto-save to Hiss.
Day 75 (Aug 05)
Auto-saving was surprisingly easy thanks mostly to the preparation yesterday.
I also realized I hadn’t yet made the "Values" (variables) section display correctly after the big editor rendering refactor, so I fixed that as well.
I’ve decided that I will not auto-save the player’s game progress because I want it to be easy to just refresh the browser and be back at the beginning.
At a later time, I could add that feature and maybe put it behind
a setting like set game autosave to true
or something so the game
author controls it. Alternatively, I could put the control entirely
in the hands of the game author with a new save progress
and
clear progress
or something like that. I’ll think about it.
Next: Auto-save the chosen editor color theme!
Day 76 (Aug 06)
Generalized the autosave from and autoload to localStorage
so it’ll
save the chosen editor color scheme as well.
Easy stuff and it worked right away. Sometimes I really need these easy
tasks to get a "win" and feel good about my progress. Today is one of
those days.
Looking at my TODO list, this seems to round out the actual editor features I’d been tracking. I’m sure there are some other quality-of-life things to add, but this thing pretty much works!
The next item on my TODO list is to clean up the source. I am so looking forward to that. This thing has done nothing but grow and mutate since I started on it. I’ve done very little cleanup.
I really want to make the source for this thing readable and compact. While it’s true that compact code can be at odds with readability, I find that it’s generally an aid. Small is usually simpler and simpler is better. So small is usually better.
Next: As mentioned above, clean up the source! Make it compact, clever, readable, organized, and as simple as possible.
Days 77 through 80 (Aug 07-10)
Been re-arranging the source and grouping it into better sections. Also added a "Table of Contents", which makes it really handy to jump to a section. In Vim, you can just type '*' with the cursor over an item in the table to jump to its section.
Here’s what you currently see at the top of the Hiss source:
<!-- Welcome to _ _ ___ ____ ____ | | | |_ _/ ___/ ___| | |_| || |\___ \___ \ +-----| _ || | ___) |__) | -----+ | |_| |_|___|____/____/ | | | | Copyright 2024 David Gauer | | http://ratfactor.com/hiss/ | +---------------------------------+ | | | Table of Contents | | ----------------- | | 0. EditorHtml <--(You are here) | | 1. ExportedPlayerHtml | | 2. SharedPlayerJS | | 3. ExampleScript | | 4. ColorPreviewSVG | | 5. EditorJavaScript | | 6. HissDocumentation | | | +---------------------------------+ -->
With the basic structure in place, now I can do a more granular cleanup. There are only 6 "TODO" notes in the source remaining, but there’s tons of room for improvement.
Next: Deeper dive into the source to clean it up. Also: decide what to do with incrementing/decrementing strings (as opposed to numbers).
Day 81 (Aug 11)
I tackled the second thing from yesterday’s "Next" note this morning first. Way back on Day 15 (see log entry above), I thought it was clever to handle the weird case of incrementing/decrementing string variables by increasing and decreasing the font size of the variable’s text when you print it.
Well, it was clever, but it required bookkeeping in the player, and because I needed to increase/decrease the font size a noticeable amount with each change, I used additional CSS styles to set the exact amount for each step. Even then, you really had to watch it to see what was happening.
So I’ve chosen to do something that should be much clearer and requires no bookkeeping in the player.
If you’ve got this somewhere:
set goose to Honk Honk
And later, you do this:
inc goose print goose
The game will print "Honk Honk plus one", then "Honk Honk plus one plus one", then "Honk Honk plus one plus one plus one", etc.
(And dec goose
will add "minus one" to the end of the string.)
This makes it really clear really fast that something weird is happening to your string. And I think it will be pretty easy to figure out why. At least, that’s the hope.
Next: Deep dive refactor/cleanup.
Days 82-83 (Aug 12-13)
Doing some real "yak-shaving" on the shared player JavaScript. Apparently it had some stuff that I’d missed when I shared the player in the first place, so I was able to fix those while I was in there.
I’m nearly done with the main command-playing loop.
Next: Finish cleaning up the player. Start by moving the color table out of the main loop.
hiss.html ← this link will always have the (working?) live state of the game editor.
Day 84 (Aug 14)
Good refactor today. Finished cleaning up the shared player. Again, the shared player is the code that renders the game while you’re in the editor and the exact same code that runs the whole thing in the stand-alone exported HTML games.
The "deco engine" code was already as compact as I could reasonably make it, so I skipped right over that part.
Finally, I re-arranged the order of the source and was able to remove a
conditional that needed to check if we were in the editor or an exported
game (originally document.is_hiss_editor
).
The new order, as seen in the "table of contents" in the source:
| 1. ExampleScript | | 2. ColorPreviewSVG | | 3. EditorJavaScript | | 4. ExportedPlayerHtml | | 5. SharedPlayerJS | | 6. HissDocumentation |
Gradually, the inelegant (albeit expedient) hacks are going away.
Next: Start cleaning the editor code!
Day 85 (Aug 15)
Let the editor cleaning begin! It’s a bit frustrating at this stage because I’m finding myself adding more TODO comments in the source where I’ve found things that probably have better solutions, but I’m not yet sure until I’ve done my first pass and loaded the whole thing back into my head.
(It’s worth noting, again, that I had a five month pause on this project. So some of this is just a dim memory. It’s almost like trying to remember what I had for breakfast on some particular day.)
Next: Continue the cleaning.
Day 86 (Aug 16)
More cleaning, more reading code (hey, some of this is pretty good!), and more adding TODO comments where I think things can be improved.
Next: Continue the cleaning. (I also added a NEXT comment to the source so I won’t lose my place.)
Day 87 (Aug 17)
I’ve completed the first pass through the source. Plenty of TODOs identify the things to take a look at on a second go.
Next: Second pass on the editor source. (Do the TODOs.)
Day 88 (Aug 22)
Skipped a couple days.
The first TODO I’m tackling is a big block of
document.getElementById()
calls where I’m getting references to stuff
in the HTML source. I’m replacing that with an array of the IDs to get
and a forEach
iterator (loop) to find the elements and set them
with an el_
prefix in the global namespace.
Rant time!
Here el_
is short for "element". The bone-headed misuse of
Hungarian notation
(wikipedia.org)
to prefix the variable’s name with its data type (making them long and
practically unreadable) has rightly made programmers avoid it like the plague.
But, of course, the real intent was always right there for anyone to read it. From
Wikipedia:
Simonyi’s paper on the notation referred to prefixes used to indicate the "type" of information being stored. His proposal was largely concerned with decorating identifier names based upon the semantic information of what they store (in other words, the variable’s purpose).
Having the "el" prefix makes it immediately clear (to me, at least) what this variable is storing (in terms of its purpose). This is extremely useful and I’ve found that good programmers naturally adopt this style when appropriate. It’s a real shame that mis-use by fools has given this common-sense idea a bad name.
Anyway, it’s working great, bringing consistency to the naming while also slightly shortening the source.
Next: More TODOs.
Day 89 (Aug 23)
Finished what I described above. Better consistency!
Days 90-93 (Aug 24 - 29)
I’ve been very slowly completing the above. Also skipped a couple days.
A glacial pace is expected sometimes. No doubt I will have a sudden burst of activity soon. The important thing is to keep coming back to it often so I can capture that burst.
Next: More TODOs.
Day 94 (Aug 30)
New idea, and yes, this is some serious yak shaving, but bear with me.
So I currently have this (in miniature):
<div id="foo"></div> <div id="bar"></div> <script> // Create references to the elements in the global namespace: [ "foo", "bar", ].forEach(function(id){ window["el_"+id] = document.getElementById(id); }); // Use the element references: el_foo.addEventListener... el_bar.addEventListener... </script>
There are two annoying things about this:
-
I have to manually list the elements.
-
The "el_" prefix is great, but I can’t use Vim’s "*" command to search for the other uses of an element because that prefix doesn’t exist on the element itself or in the list.
There are all sorts of ways I could approach this, but I think what I’m going to do is add the "el_" to the actual IDs. And then, I’m going to automate the gathering of the IDs directly from the DOM so I don’t have to list them manually (a task that is both error-prone and annoying).
Next: That.
Day 95 (Aug 31)
Well, that was easy. Now instead of the above, I have:
<div id="el_foo"></div> <div id="el_bar"></div> <script> document.querySelectorAll('[id^=el_]').forEach(function(e){ window[e.id] = e; }); // Use the element references: el_foo.addEventListener... el_bar.addEventListener... </script>
Not only is it less code and less tedious manual labor, I can use '*' in Vim to jump from the HTML element to the reference in JavaScript and vice versa.
I also replaced all HTML inline onclick="…"
attributes by setting them in
the JS. That was always the plan, but I also changed all addEventListener()
calls to the shorter element.onclick = function
assignment. I understand the
difference and I don’t have a need for multiple listeners for the same
events on these elements.
The DOM manipulation stuff is looking pretty decent to me now. There is no end to the clever things I could add, but I don’t think anything’s going to actually reduce the line count or increase clarity at this point.
Next: I’ve got a bunch of places where I set the current script to be edited that seem to do more-or-less the same things. I want to see if I can find the commonalities and extract those into a function.
Day 96 (Sep 1)
I stared at the stuff I mentioned in the Next note above for a while and decided there weren’t as many improvements to be made there as I’d thought. I’m going to leave it alone for now.
Next: More TODOs
Day 97 (Sep 2)
The next TODO I’ve got is to memoize (wikipedia.org) the left-hand panel (script list, game variable list, color preview). The DOM operations to redraw everything over there are extremely expensive compared to an in-memory comparison to check for changes.
Updates happen every time you make a single change (like typing a character) in the current game script. And even though it happens instantly on every computer I’ve tested this with, I still hate wasting processor cycles like that.
While caching is one of the "two hard problems in computer science," memoization is straight-forward: check the current function input against previous function input. I don’t even have to cache previous answers. If there’s no changes since the last time, the function can simply exit.
Well, that was easy. After thinking about it for a moment, I decided to use JSON to stringify the data to be memoized:
function draw_script_list(){ // Memoization var current_memo = JSON.stringify(script_names); if(draw_script_list.memo == current_memo){ // No change since last call. Leave DOM alone. return; } draw_script_list.memo = current_memo; ... }
That operation should be wicked fast since a native function call is doing most of the work.
Next: I noticed the SVG color preview needs to update and redraw when we change color schemes. I guess I’ll fix that next.
Day 98 (Sep 3)
No problem forcing the SVG color preview to redraw when you change the editor color scheme.
Tracked down the bug that led to all scripts getting additional newlines appended at the end every time you viewed them in the editor.
And now there’s just one TODO left in the source: When I added an autosave feature, it’s no longer easy to reset the current game back to the sample script.
Next: Add a menu feature to load the sample game script.
Day 99 (Sep 4)
Oops, my previous fix for the trailing newlines broke the autosave - the scripts were all getting bunched togethere with no newlines to separate them. Fixed that.
Started the "Load Sample" menu item to re-load the initial sample script.
Next: Finish the Load Sample feature. Also: I have scripts, plural. Should "Save Script" and "Load Script" be "Save Game" and "Load Game"? And then the option to reset to the initial sample game should be "Reset Game"??? But that sounds like you’re resetting the game you’re playing. Hmmm… I’ll have to think about this.
Day 100 (Sep 5)
Okay, I’ve re-worded the menu options so they’re clearer (hopefully). And added a confirmation for Load Sample Game so you know it’s going to overwrite the current game.
And that seems to complete all of my functionality TODOs! How appropriate that I’ve hit Day 100 on the project.
This ever-changing TODO list has been at the bottom of this page since the beginning. Now I’m going to plop it here for posterity:
[X] Export whole game as script text [X] Import whole game as script text [X] Write tiny stand-alone player engine [X] Save game as stand-alone player! [X] Thing management! (add/rename/delete) [X] Re-work values display in editor [X] Background/foreground/title settings (with player preview) [X] Game Colors section shouldn't draw until you set some [X] Make custom game colors work in stand-alone player! [X] Maybe editor can share script player code? [X] Factor out RetroV in editor and remove recent hacks to work around it [X] Editor: auto-Save current script, theme [X] Re-arrange basic structure of the editor source [X] Do something different with inc/dec text variables [X] Clean up and compact all source [ ] Documentation
Also: The total size of hiss.html has dropped to 48,994 bytes, which is down nearly 4Kb from where I (re)started this summer, and that’s including new and improved functionality.
Also: A small exported game is 12Kb, so you can definitely share them with your friends via 1.44Mb floppy disks.
Now begins the documentation portion. I’m really excited about starting this phase.
Next: Documentation!
Day 101 (Sep 6)
Documentation has begun! Made some nice progress. Got to test out something I’d been envisioning for a while: explaining the editor interface in an HTML table that visually resembles the interface itself.
Also fleshing out sections headings to be written as I think of them.
These docs are going to take a fair amount of work to get right - I predict a lot of writing and deleting to get it down to (what I hope will be) the maximum clarity.
But it’s joyful work at the moment for two reasons:
-
I’m so pleased to be on the homeward stretch of this project.
-
It’s fun to re-visit Hiss’s functionality. Especially the parts I haven’t played with in a while, like the Decorations.
Day 102 (Sep 7)
It’s funny how trying to explain things in documentation makes poorly-named UI components so obvious. I think it’s because I’m seeing it from the perspective of my audience: someone who is encountering everything for the first time.
Anyway, I’ve realized that an option such as "Reset Game" sounds very much like you’re resetting the game that’s being played. And the same problem with Save Game/Load Game.
Here’s the new top menu names I’ve come up with:
-
Export Game
-
Save File
-
Load File
-
New Game
-
Editor Color Scheme
And under "New Game", I’ve put these options (with explanatory text):
-
Make New Black Game
-
Load Sample Game
-
Cancel
It’s not perfect, but I think the intent is much clearer. And since nothing destructive happens without.
Day 103 (Sep 9)
I’ve written the introduction to the User Guide. I’ve got the Editor Interface section right after that. I’ll probably re-arrange and re-word things when I’ve finished writing the bulk of the Guide, but at least I’m no longer working from a "blank page". :-)
Day 104 (Sep 10)
I knew that writing the documentation would unearth bugs in the editor, and indeed it has. It looks like at least one of the bugs is a regression. When I use backspace to delete a variable’s value, the variable remains with the last character of the value still set.
Code Title ========================= set title to Foo Foo set title to Fo Fo set title to F F set title to F <---- remains
I’m, like, 90% sure I had already fixed that…
Day 105 (Sep 11)
Okay, it’s weirder than that. The only in-game variable that shows the behavior
above is the title
(as shown). All others are removed properly when I delete
the last character of the value. Okay, well, that should help me narrow it
down a bit.
Actually, I was able to fix the problem by swapping the order of these
two statements in my draw()
function:
Before: clean_vars(); play_obj(current_obj); After: play_obj(current_obj); clean_vars();
That makes plenty of sense in an abstract way: clean_vars()
depends on:
-
The existence of the variable being set in any game script, and
-
An actual value having been set in the course of playing the game.
So that fixed it for the variable list in the left panel.
But the problem is still apparent in the actual title that displays in the player’s H1 tag. And…oh dear, what a chicken-and-egg problem.
So here’s what’s happening when you delete the F' from `set title to F
:
-
The
set title to
statement is NOT a valid variable setting statement anymore. -
So the final assignment to nothing never happens when I
play()
the script. -
So the title still has that last value.
The solution, I think, is simple: I need to make set var-name to
a valid
variable-setting statement all on its own even with no value at all. I think
that’s perfectly reasonable and sensible. And I think in that case, the value
should be 'EMPTY' just as it would be if I wasn’t setting it at all.
I’ll try it and see if it makes sense to me. Hopefully it will make sense to Hiss beginners as well.
However, this opens a whole can of worms: I’m creating the regexes to match all Hiss syntax with this list:
var syntax = { 'insert': ['insert', FRIEND, 'here'], 'link' : ['link', TXT, 'to', TO], 'var' : ['set', KEY, 'to', VAL], 'if' : ['if', KEY, 'is', VAL], 'print' : ['print', KEY],
And the regex created comes from this:
var m = RegExp('^\\s*' + syn.map(function(s){ return (typeof s === 'string') ? s : '(\\S.*)'; })...
I know this looks cryptic as heck, but here’s the regex it’s building for the
var
list from the syntax
table:
-
Start with the beginning of the line using the
^
anchor. -
Then allow any amount of whitespace before the matched syntax with
\s*
. -
Then for each item in the syntax list:
-
If it’s a string, match that exact string
-
If it’s not a string (KEY and VAL are hard-coded numbers), match
\S.*
.
-
And \S.*
is "one non-whitespace character followed by anything or nothing".
(You’ll note that it actually has a double backslash (\\S
). That’s to escape
the single backslash in the JavaScript string.)
To allow for an optional captured value in the statement set variable to
value
, I have to either:
-
Make an exception just for a 'var' statement and complicate my already cryptic regex building code, or
-
Allow all captured terms to be optional.
I really don’t want to make the exception, but I’m curious what sort of weird side-effects I’m going to get if I make captured terms in all syntax optional.
It’s easy enough to find out. I just remove the \S
, so the line above becomes:
return (typeof s === 'string') ? s : '(.*)';
To be continued to tomorrow…
Day 106 (Sep 12)
So is it sheer chaos with optional captured items in the language syntax? From my initial try, it does appear to solve the problem.
Funnily enough, I also need to swap the order of these statements in draw()
back to the way they were:
clean_vars(); play_obj(current_obj);
Day 107 (Sep 13)
The results of allowing zero-length strings in the capture portions of the Hiss langauge syntax are interesting. The only thing I’ve needed to do to keep things from getting too weird are to check for '' in a couple instances. I remove variables with '' as a name. And in the game player, I don’t render the "Create 'Foo'" button for scripts that don’t exist when the link or insert is asking for a script named ''.
LOL, and I’m back to…
play_obj(current_obj); clean_vars();
-
again. Feels fragile and I don’t like that, but the order does seem correct this way to me.
For the record, here’s the full language syntax as it’s described in the Hiss source. All quoted strings string ('') on the right hand side are syntax literals. Everything else is a captured user-created value or name (which are now "optional" in the sense that the syntax will still match the pattern without them):
'insert': ['insert', FRIEND, 'here'], 'link' : ['link', TXT, 'to', TO], 'var' : ['set', KEY, 'to', VAL], 'if' : ['if', KEY, 'is', VAL], 'print' : ['print', KEY], 'deco' : ['deco:', TXT], 'inc' : ['inc', KEY], 'dec' : ['dec', KEY], 'endif' : ['endif'], 'stop' : ['STOP!'], 'else' : ['else'], 'break' : [''], 'txt' : [TXT],
So, you can type some silly stuff like…
insert here link to set to if is hello else bye endif print deco: inc dec
…and I haven’t seen anything bad happen so far. (The result is "print insert here link to set to if is hello" in the player.)
But I did uncover a new bug, so:
Next: Fix the bug where a Hiss statement followed immediately by a 'print X' causes "undefined" to appear before the X value. (Edit: Okay, that’s weird, now I can’t get it to happen again. Well, confirm that’s the case, I guess.)
Day 108,109,110 (Sep 14,15,16)
Okay, after a bunch of console.log()
debugging, I’ve got the variables working
right (I hope) in terms of dealing with unset/empty variables in all sorts of situations.
The bug in the "next" note above ended up being specific to trying to increment
and decrement undefined variables. The print
was not to blame. It turns out, I had
accounted for incrementing/decrementing strings, but I’d never tried it with
unset variables. Now I’m checking for any "falsy" value and treating those like a 0.
Next: Back to the documentation!
Day 111 (Sep 17)
Slowly.
Day 112 (Sep 18)
Very slowly.
Next: Why does this not render the link in the paragraph at all?:
This is where the party link ends to The End for you.
Day 113-114 (Sep 19-20)
I was having too much fun making a Space Gerbil game for the tutorial that I put off fixing the above bug. It feels so good to have a working tutorial that produces a (very, very simple) complete game.
Next: The bug mentioned in Day 112.
Day 115-118 (Oct 1-4)
As you can see by the dates, I took a little hiatus again. And after that, I had a little side-quest to create a fun little SVG diagram of the Space Gerbil script. It was super easy to make the diagram in Inkscape - but the resulting file was 11Kb, most of which was redundant or wasteful markup (which is absolutely not a complaint about Inkscape - 11Kb would normally be great!).
So I spent a couple mornings hand-editing the SVG source down to a 1290 bytes (1.2Kb) embedded in the document. It also uses the CSS variables for color, so when you change the color scheme in the editor, the diagram’s colors change as well. I’m really happy with it.
In my excitement to create the diagram, I’d completely forgotten about my "next note" above. So…
Next: The bug mentioned in Day 112.
Day 119 (Oct 7)
Oops, totally forgot to check my Next note again and continued writing the User Guide. No problem, I’ll just keep transferring the note until I get to it.
Next: The bug mentioned in Day 112.
Day 120 (Oct 20)
Okay, I finally buckled down and fixed that bug. I also had a cold bad enough that it kept me out of work and off screens entirely for a bit.
Anyway, the bug wasn’t as bad as it first appeared. In the case of a
non-existent link
or insert
, I needed to handle the insertion of a "Create
…" button if you’re in the editor or the red error message if you’re in an
exported stand-alone game.
Well, it needed to append to the current paragraph element in the case of
a link, but after it in the case of an insert. I thought that was going to
cause a cascade of global state tracking to get stuff appended correctly.
But in reality, I was able to pass the element to function parameters in just
two places. No problemo!
Now I can get back to writing the help docs and…get this thing out the door!
Days 121-130 (Oct 21-30)
Been very slowly grinding away on the documentation. Added a Table of Contents which gives me the feeling of structure and utility, even if the written text is still an evolving mess that I’m making up as I go. :-)