RSS | JSON Feed
All posts:
Bouncing Ball
Catalog of Math Shapes
Draw Shapes Using Math
Similar Songs
Building a Reddit app
Counterfeit Monkey
Listed Action Interactive Fiction
Andrew Plotkin's Zarfhome
Product Review Writing Prompts
Dungeon Memalign
Pac-Man Dungeon
The Queen's Gambit
Rebecca Meyer
Coding Machines
Clamps
Emoji Simulator
Universal Paperclips
Fall Down the Rabbit Hole
Live COVID-19 Dashboard
Create COVID-19 Graphs Using JavaScript
Books
notes.txt
Download Video Into Your Brain
Download Text Into Your Brain
Source Control
Personal Projects
How to Program a Text Adventure
RSS and Feedly
Welcome
Posted on 3/28/2021
I mentioned here that TI calculators can make some simple animations:
"An upperclassman graphed some functions together which made it look like this circle was a ball bouncing down stairs."

I also mentioned something special about Graphtoy:
"Graphtoy has a variable for elapsed time (t) which can be used to create animations"

In this post, I'll recreate the bouncing ball animation using mathematical formulas in Graphtoy.

Browsing our shape catalog gives us a few useful components:

Steps: y = floor(x)

Diagonal sine wave: y = x + 3 + sin(3*x)
  - This looks similar to the path of a ball as it bounces down steps

Graphtoy link

Here's what those look like together:



One aesthetic tweak: have the stairs go down to the right.
Adjust the sine wave to match the direction and slope of the stairs.

Steps: y = -floor(x)+5
Ball's path: y = -x + 5 + sin(3*-x)
Graphtoy link

With a normal (non-diagonal) sine wave, every other hump is negative and below the x-axis. In our diagonal sine wave, every other hump is going under a step instead of bouncing off it.

Let's figure out how to have a normal sine wave bounce off the x-axis and then bring that same modification to our more complex diagonal sine wave.

To simply make all negative values positive, we can take the absolute value with the abs() function:

y = abs(sin(x))

Here's what that looks like:



Looks promising! Let's try using abs on the diagonal sine wave:

y = -x + 5 + abs(sin(3*-x))
Graphtoy link

Here's what that looks like:



There are a few problems still:

- The bounce should happen in the middle of the step (not in the corner)
- It looks like the width of each period of the sine wave is not the same as the length of each step so there's visible drift

We can fix both of these using techniques noted in the shape catalog.

Shift the bounce to happen farther right:

y = -(x-0.5) + 5 + abs(sin(3*-(x-0.5)))
Graphtoy link

Fix the sine wave's period:

y = -(x-0.5) + 5 + abs(sin(PI*-(x-0.5)))
Graphtoy link

Here's what that looks like:



Now let's add our bouncing ball. We want to draw a circle that's centered on the diagonal bouncing sine wave.

From the shape catalog, here is the equation for a circle centered at (-3, 2):

y = sqrt(1 - (x+3)^2) + 2
y = -sqrt(1 - (x+3)^2) + 2

To have the ball move to the right with time, we'll replace the -3 with t. t is the special parameter Graphtoy provides; its value is the number of seconds that have passed since the webpage refreshed.

y = sqrt(1 - (x-t)^2) + 2
y = -sqrt(1 - (x-t)^2) + 2
Graphtoy link

The y-value at time t is the value of our bouncing sine wave when x=t.

Bouncing sine wave: y = -(x-0.5) + 5 + abs(sin(PI*-(x-0.5)))

Value when x=t:
y = -(t-0.5) + 5 + abs(sin(PI*-(t-0.5)))

Now, we'll replace the 2 in our circle equations with this y expression:

y = sqrt(1 - (x-t)^2) + (-(t-0.5) + 5 + abs(sin(PI*-(t-0.5))))
y = -sqrt(1 - (x-t)^2) + (-(t-0.5) + 5 + abs(sin(PI*-(t-0.5))))
Graphtoy link

Wow! This is getting really close!

The ball is way too big. Let's shrink its radius:

y = sqrt(0.1 - (x-t)^2) + (-(t-0.5) + 5 + abs(sin(PI*-(t-0.5))))
y = -sqrt(0.1 - (x-t)^2) + (-(t-0.5) + 5 + abs(sin(PI*-(t-0.5))))
Graphtoy link

(Note: once the circle moves off-screen, don't forget to reset t (or refresh the page) to make it reappear)

More tweaks needed:

- The ball bounces through the step. Need to shift the bouncing curve up by the ball's radius.
- The ball is too close to the edge of the step. Need to shift the bouncing curve farther to the right.

Shifting up and to the right:

Sine wave:
y = -(x-0.7) + 5 + sqrt(.1) + abs(sin(PI*-(x-0.7)))

Circle:
y = sqrt(0.1 - (x-t)^2) + (-(t-0.7) + 5 + sqrt(.1) + abs(sin(PI*-(t-0.7))))
y = -sqrt(0.1 - (x-t)^2) + (-(t-0.7) + 5 + sqrt(.1) + abs(sin(PI*-(t-0.7))))

Graphtoy link

Looks perfect!



Bonus just for fun, here's the ball bouncing off a square wave, losing height with every bounce:

Graphtoy link


Posted on 3/27/2021
I enjoy drawing shapes using math. Much of the fun is exploring what formulas look like and figuring out how to warp them to look different.

To be able to build more complex shapes and animations, in this post I'll explore some formulas and create a catalog of shapes.


Circles:

x^2 + y^2 = 1

Graphtoy requires that we solve for y to create a function of x: f(x)

y^2 = 1 - x^2
y = ± sqrt(1 - x^2)
f(x) = y = ± sqrt(1 - x^2)


Graphtoy doesn't understand ± so we need to write two separate formulas:
y = sqrt(1 - x^2)
y = -sqrt(1 - x^2)
Graphtoy link

Here's what that looks like:



In general, we can shift formulas up/down by adding/subtracting to/from the function. Here, we shift the circle up the y-axis by 2:
y = sqrt(1 - x^2) + 2
y = -sqrt(1 - x^2) + 2
Graphtoy link

And we can shift left/right similarly by adding/subtracting from every instance of "x". Here, we shift the circle left on the x-axis by 3:

y = sqrt(1 - (x+3)^2)
y = -sqrt(1 - (x+3)^2)
Graphtoy link

We can combine these two techniques to shift left and up at the same time:

y = sqrt(1 - (x+3)^2) + 2
y = -sqrt(1 - (x+3)^2) + 2
Graphtoy link


Lines:

y = mx + b
m is the slope (change in y divided by change in x)
b is the y-intercept (notice that adding b is the same technique we used to shift the circle up)

Here's a steep line that goes up 3 in the y direction for every increase of 1 in the x direction. I shifted it to intersect with the y-axis at y=1

y = 3*x + 1
Graphtoy link

Here's what that looks like:



In general, we can flip the shape over the x-axis by making it negative (notice this is similar to the way we draw the bottom half of a circle, by flipping the formula to the top half):

y = - (3*x + 1)
Graphtoy link

Similarly, we can flip a shape over the y-axis by replacing every instance of "x" with "-x":

y = 3*(-x) + 1
Graphtoy link


Sine wave:

y = sin(x)
Graphtoy link

Here's what that looks like:



Something special about the shape of sin(x): it's periodic, repeating infinitely.


Parabola:

y = x^2
Graphtoy link

Here's what that looks like:



If we want to turn a shape sideways (by swapping the axes), we can swap x and y.

Sideways parabola:

x = y^2

Solve for y to be able to graph using Graphtoy:

y = ± sqrt(x)

y = sqrt(x)
y = -sqrt(x)
Graphtoy link


Combining shapes:

We can combine formulas to combine their shapes. For example, we can make a sine wave follow the shape of a line by adding them together:

Diagonal line:
y = x + 3

Sine wave:
y = sin(3*x)

Diagonal sine wave:
y = x + 3 + sin(3*x)
Graphtoy link


Steps (floor):

We can do some neat things using functions like floor and ceil. Floor rounds down to the nearest integer. Ceil rounds up to the nearest integer.

y = floor(x)
Graphtoy link

This looks like steps!



We can make a spiky sawtooth wave by only giving y the decimal portion of x like this:
y = x - floor(x)
Graphtoy link

To get an intuitive understanding of a shape, sometimes it helps to write out some of the values in a table:
x floor(x) x-floor(x)
=============================
0 0 0
0.2 0 0.2
0.5 0 0.5
1 1 0
1.2 1 0.2
1.5 1 0.5
2 2 0
2.2 2 0.2
2.5 2 0.5
This hopefully makes it easier to see how this ends up looking like a sawtooth.


Triangle wave:

A sawtooth wave looks like this: /|/|/|/|
A flipped sawtooth wave looks like this: |\|\|\|\
A triangle wave looks like this: /\/\/\/\
I noticed that I might be able to construct a triangle wave by combining a sawtooth wave with a flipped sawtooth wave.

The naive approach of simply adding them together gives us this:
Sawtooth wave:
y = x - floor(x)
Flipped (across the y-axis) sawtooth wave:
y = -x - floor(-x)
(Note: when writing this, I accidentally flipped the formula over the x-axis: y = -(x-floor(x)). When I graphed it, I realized my mistake and played around with the -s to get it right. It's normal to play around and make mistakes! Make some terms negative and see what happens to the shape!)

Added together:
y = x - floor(x) + -x - floor(-x)
Graphtoy link

Ah woops! That looks like a straight line. Graphing the sawtooth waves on top of each other reveals why: the diagonal portions cross each other, summing up to a constant value for most values of x.
Graphtoy link

To get a triangle wave, I need to alternate taking a tooth from each sawtooth wave. I can take a tooth from one wave when floor(x) is even; a tooth from the other wave when floor(x) is odd.

That sounds pretty crazy! Fortunately, modular division is an easy way to test whether a number is even or odd.

floor(x) % 2
      also written as:
x mod(floor(x), 2)
==============================
1 1 (odd)
1.9 1 (odd)
2 0 (even)
2.9 0 (even)
3 1 (odd)
4 0 (even)
Our gadget to determine when floor(x) is odd: mod(floor(x), 2)
We can use that to make a complementary gadget to determine when floor(x) is even: 1-mod(floor(x), 2)

Sawtooth wave:
y = x - floor(x)

Flipped (across the y-axis) sawtooth wave:
y = -x - floor(-x)

Using our gadgets to zero out every other tooth:

zero out even teeth zero out odd teeth
y = mod(floor(x), 2) * (x-floor(x)) + (1-mod(floor(x), 2)) * (-x-floor(-x)))
Graphtoy link

Here's what that looks like:



Notice that there are some visible glitches. Let's check the math:

y = mod(floor(x), 2) * (x-floor(x)) + (1-mod(floor(x), 2)) * (-x-floor(-x)))

Plug in x = 0:

y = mod(floor(0), 2) * (0-floor(0)) + (1-mod(floor(0), 2)) * (-0-floor(-0)))
y = 0 * (0-floor(0)) + (1-0) * (-0-floor(-0)))
y = 0

Ah but y is supposed to be 1. It looks like the equation has a bug.

Checking a few more x, y values to build intuition:
x y y is supposed to be
============================
 0 0 1
 1 0 0
 2 0 1
 3 0 0
 4 0 1
In its current form, our formula is taking neither sawtooth's value when x is a perfect integer (looking at the formula, you can see that it's because (x-floor(x)) and (-x-floor(-x)) are always 0 when x is an integer.

One fix is to add a term to our formula that has the value 1 for even integers and 0 otherwise.

I played around and came up with this:
(1-mod(floor(x),2))*(1-ceil(x-floor(x)))

The first half of this is our even/odd gadget. The second half is an expression that is 1 for integers, 0 otherwise.

Checking its values:
(1-mod(floor(x),2)) *
 x 1-mod(floor(x),2) 1-ceil(x-floor(x)) (1-ceil(x-floor(x)))
=================================================================
0 1 1 1*1 = 1
0.5 1 0 1*0 = 0
1 0 1 0*1 = 0
1.5 0 0 0*0 = 0
2 1 1 1*1 = 1
Fixed equation by adding this gadget:

y = (mod(floor(x),2))*(x-floor(x))+(1-mod(floor(x),2))*(-x-floor(-x)) + (1-mod(floor(x),2))*(1-ceil(x-floor(x)))
Graphtoy link

Here's what the fixed equation looks like:



Looks perfect! Our formula is pretty complex, though. It's possible we could come up with a simpler formula by reading this Wikipedia page.


Square wave:

Here's one way to make a square wave: take an already periodic formula and make it blocky.

An easy periodic formula is sin(x).

y = sin(x)

Instead of having the period of sin be 2*PI, I can change it to be 2:

y = sin(x*PI)

I can use the "sign" function to turn positive values into 1 and negative values into -1:

y = sign(sin(x*PI))

If I want it to be truly square (1 tall and 1 wide), I'll need to adjust the height and shift it up:

y = sign(sin(x*PI))/2 + 0.5
Graphtoy link

Here's what that looks like:




How did I come up with these formulas and techniques? I remember many of these from school and others I (re)discovered by playing around. You can expand your own catalog by trying out new formulas to see what they look like and taking notes!

Here's a list of interesting things to explore:
log(x)
exp(x)
abs(x)
pow(x, n)
This animation that is built into Graphtoy
Growing concentric circles
Bouncing ball animation

Posted on 3/21/2021
Computer graphics is a wonderful real-world use case for a lot of the math we learn in school. For example, take a look at Inigo Quilez's video tutorials. This one is particularly cool.

I don't have skills like Inigo but I still have fun making 2D shapes using math.

Here are a few fun tools for drawing 2D shapes using math:

- TI calculators
  - You might have had one of these in high school
  - I had a TI-83 Plus which turned out to be a fun device. Here are some of its neat features:
    - BASIC programming
    - Play games in MirageOS (back in my day the most popular game was Phoenix and it looks like people kept innovating since then! They even made a version of Geometry Wars!)
    - Share programs and games via a cable (they spread through the whole high school this way)
    - Graph interesting equations and shapes
      - There's a graphing mode which traces a function's shape with an animating circle. An upperclassman graphed some functions together which made it look like this circle was a ball bouncing down stairs. This stuck with me and is part of the inspiration for this post!
  - Looking back, I'm glad we got to play with these! Thank you, TI-83 Plus, for my earliest programming and engineering experiences!

- Graphing calculator websites
  - WolframAlpha
    - Very powerful. For example: graphing x^y = y^x, including symbolically refactoring the equation to solve for y.
  - Meta-Calculator
    - OK in a pinch

- Graphing calculator apps
  - GeoGebra Graphing Calculator
    - This app is really good! Much like WolframAlpha and Relplot, this app can graph arbitrary equations and inequations like x^y = y^x, 1 = x^2 + y^2, y > x, etc.
  - NumWorks Graphing Calculator (simulates a physical calculator; hard to use)

- Relplot
  - Powerful graphing capabilities: arbitrary equations and inequations
  - Source code (my backup)
  - Relplot is written by one of my favorite CS professors, Andrew Myers!
  - In fact, it's a more sophisticated version of a programming assignment we completed in his Functional Programming course
  - Interesting implementation details:
    - Lexes and parses inputted formulas so they can be evaluated
    - Slightly refactors the formulas to be of the form "0 = formula" or "0 > formula":
Eqn : Expr EQUALS Expr (Plus(Expr1, neg(Expr2)))
      | Expr LT Expr (Ltz(Plus(Expr1, neg(Expr2))))
      | Expr LE Expr (Ltz(Plus(Expr1, neg(Expr2))))
      | Expr GE Expr (Ltz(Plus(Expr2, neg(Expr1))))
      | Expr GT Expr (Ltz(Plus(Expr2, neg(Expr1))))
      - For example, converts "1 = x^2 + y^2" into "0 = x^2 + y^2 + -(1)"
      - In this form, all solutions to the formula are "zeros"
    - Uses interval arithmetic (implemented here) to identify subsections of the graph that contain x, y values for which the formula evaluates to zero
    - Like a binary search, recursively searches smaller and smaller subsections of the graph for zeros until the interval size is too small to be visible. At this threshold, it can just draw a tiny line between the corners of the interval.

- Graphtoy (my backup)
  - This is another cool piece of work by Inigo Quilez
  - Something special: Graphtoy has a variable for elapsed time (t) which can be used to create animations
  - Source code
  - Interesting implementation details:
    - Instead of implementing its own parser, Graphtoy turns the formulas into JavaScript snippets
    - See the "iCompile" function which tweaks the inputted formula strings to be valid JavaScript and constructs Formula objects which can later be invoked

Posted on 3/14/2021
I like listening to music. I like it even more when I discover connections between songs.

It's cool to find out that a song is a cover of another song or that it contains samples of other songs.

WhoSampled.com is a useful resource to find these connections. Rap songs in particular often contain a bunch of samples! Try searching for your favorites.


Here are some of my favorite connections:

- My Name Is by Eminem
  - Samples I Got The... by Labi Siffre

- Old Town Road by Lil Nas X
  - Samples Ghosts IV - 34 by NIN

- Toxic by Britney Spears
  - Samples Tere Mere Beech Mein by Lata Mangeshkar & S. P. Balasubrahmanyam

- Sugar by Robin Schulz feat. Francesco Yates
  - Samples Suga Suga by Baby Bash feat. Frankie J

- Seth Everman combining Moonlight Sonata and Still D.R.E.


Separate from direct samples, I started noting down songs that remind me of each other. Maybe the songs share a similar palette, a similar bassline, or similar instruments.

- Shared characteristic: eerie robotic beeps and boops
  - Pure Imagination by Fiona Apple
    - See also Chipotle's Scarecrow video that features the same song
    - Note that this song is a cover of Gene Wilder's original rendition in Willy Wonka & the Chocolate Factory
  - Terry Time from Pixar's Soul by Trent Reznor and Atticus Ross
  - Dr. Ford from Westworld Season 1 Soundtrack by Ramin Djawadi

- Shared characteristic: late 90s alt rock
  - Sunday Morning by No Doubt
  - Having An Average Weekend (aka the theme song to Kids in the Hall) by Shadowy Men on a Shadowy Planet

Posted on 2/7/2021
Visiting Reddit can be a nice way to relax. Reddit can be many things: entertaining, mindless, funny, eye-opening, and more.

After years of using reddit.com and various apps, I wondered whether I could improve my own Reddit browsing experience.

It turned out I could! I was able to throw together a Reddit reader app by adopting the official Reddit API and reusing some components from my Feedly RSS reader.

Here are some unique user experiences I built in:

- Automatically hide posts I've already seen
  - I treat Reddit more like a news feed. Any post I scroll past is marked as "read" so I won't have to see it again. Even when I refresh later, read posts remain hidden. This differs from most Reddit reader apps, where if you browse and refresh over the course of an hour, you'll end up seeing a long list of posts you've already encountered.

- Combine Best and Top lists
  - Reddit's website and most apps let you pick whether you want to see the "best" posts or the "top" posts. You can even choose whether to see the "top" posts from the past hour, day, week, month, year, or all time. You can manually pick which list you want to browse.
  - Instead of viewing one batch at a time, I combine several of these lists automatically
  - This helps me avoid running out of content on the days I spend a lot of time on Reddit. It also helps me see the best things I missed on days when I didn't visit Reddit.
  - Here's the heuristic I use as I load subsequent pages:
    - When loading from the start, I load the 20 "best" posts and the 20 "top" posts for the day
    - When I finish that first page, I load the next page of those requests
    - When I finish reading the second page, I load the next page of those requests plus the 20 "top" posts for the week. This is where I start to mix in older posts that I might've missed.
    - When I finish the third page, I load the next page of those requests plus the 20 "top" posts for the month
    - When I finish the fourth page, I load the next page of those requests plus the 20 "top" posts for the year and all time
    - For all subsequent pages, I load the next page of all those requests
    - After 20 minutes of browsing, the next refresh automatically starts over at the first page

- No infinite scrolling
  - With infinite scrolling, it's too hard to find a stopping point. I prefer to manually "pull to refresh" to load the next page. I can set boundaries for myself such as "I'll just read one more page".



Here's some quick info to get started adopting the Reddit API yourself:

- Take a look at the official Reddit API documentation

- The requests return JSON payloads which can be parsed easily in most programming languages

- To get familiar with the specific payload formats, I like to make a request, copy its response, and paste it into a JSON reformatter to make it easier to read

- Here's an example simple request you can perform on the command line:
# Response will be outputted
curl https://api.reddit.com/r/aww+funny+jokes/top?limit=20

# Response will be copied to the clipboard (macOS only)
curl https://api.reddit.com/r/aww+funny+jokes/top?limit=20 | pbcopy
- Here's more detail about the "top" request:
https://api.reddit.com/r/SUBREDDITS/top?limit=LIMIT&t=TIMEFRAME&after=AFTER&count=COUNT

SUBREDDITS = subreddit names separated by "+"
LIMIT = integer number of posts to receive
TIMEFRAME = "hour", "day", "week", "month", "year", or "all"
AFTER = include this argument when requesting the next page. Use the "after" value from the response for the previous page.
COUNT = number of posts seen on all previous pages (when using a limit of 20: count will be omitted on page 1, count=20 on page 2, count=40 on page 3, etc)

Example:

https://api.reddit.com/r/aww+funny+jokes/top?limit=20&t=hour
(when requesting the first page, omit "after" and "count")

https://api.reddit.com/r/aww+funny+jokes/top?limit=20&t=hour&after=t3_ldwt5j&count=20
(when requesting the second page, include "after" and "count")
- Here's more detail about the "best" request:
https://api.reddit.com/r/SUBREDDITS/best?limit=LIMIT&after=AFTER&count=COUNT

SUBREDDITS = subreddit names separated by "+"
LIMIT = integer number of posts to receive
AFTER = include this argument when requesting the next page. Use the "after" value from the response for the previous page.
COUNT = number of posts seen on all previous pages (when using a limit of 20: count will be omitted on page 1, count=20 on page 2, count=40 on page 3, etc)

Example:

https://api.reddit.com/r/aww+funny+jokes/best?limit=20
(when requesting the first page, omit "after" and "count")

https://api.reddit.com/r/aww+funny+jokes/best?limit=20&after=t3_ldwt5j&count=20
(when requesting the second page, include "after" and "count")

Posted on 1/9/2021
Counterfeit Monkey is my favorite interactive fiction game. I love its rich world, its delightful mechanics, and its clever puzzles.

You can play it here (my backup).

But before you dive in there, I'm hoping that you'll give this a try. I've written a small demo of the first section of the game as mobile-friendly Listed Action IF:

Counterfeit Monkey demo


To learn more about the original game and my demo, please read on!


Counterfeit Monkey was written by Emily Short. She's a brilliant author and has made huge contributions to the interactive fiction community. If you're interested in staying up-to-date on IF, I recommend checking out her website and following her blog.

Counterfeit Monkey is a piece of traditional parser-based IF. Parser-based games create rich and immersive worlds through text. Unfortunately, the user needs to type commands to play which makes them unergonomic for touch-based devices like smartphones and tablets.

I've written recently about bringing text-based games to touch-based devices and those posts even include some small games (playable in your web browser!).

I've found that I'm not alone in exploring more touch-friendly IF games that retain the rich world model of traditional parser-based IF. I think a sub-genre of IF may be forming, one I called Listed Action IF in a recent post.

I was delighted when I saw that Emily licensed the game's source code using the Creative Commons Attribution-ShareAlike 4.0 license, opening up opportunities to extend and transform it.

The games I've built so far are small and designed from the beginning for the limitations of short lists of actions. Adapting the first section of Counterfeit Monkey to the listed action model is an experiment to see how well a complex and rich game works.

I think it's still really fun!


Here are some things I've learned and observed during this exploration:

- This demo is built on the same little game engine I wrote and described in the second half of this post about Dungeon Memalign

- When I shared Dungeon Memalign on HackerNews, I got helpful feedback that I've now fixed:
  - The emoji map did not render well on every platform. Not every platform supports every emoji and not every platform has uniform-width emoji. To check platform support of an emoji, I search for it on emojipedia.org. To handle non-uniform-width emoji, I put them in a transparent table.
  - The Go actions would be in different positions depending on which directions were valid moves. That made the interface fiddly. To handle this, I now list every direction even when invalid.

- I also noticed that search engines did not get useful text to index because they saw only the beginning of the game. To address this, I added a crawler mode that detects crawlers based on their user agent and gives them the text of a full play-through.

- As Emily herself noted in an interesting blog post from 2010, it's easy to list too many actions in this style of game. I've done a few things to try to optimize and shorten the list:
  - Some actions are only relevant once so I remove them from the list, especially "look" actions
  - Some actions can hide behind another, such as actions specific to some items being inside Inventory (used in Dungeon Memalign for healing potions)
  - You'll see that I could still have done better in some parts of the demo where the lists of actions get quite long.

- Some puzzles become easier or obvious when a new action appears. I think that's a pro and a con depending on the situation. Fortunately, Counterfeit Monkey's word-manipulation puzzles don't lose much of their challenge.
  - Pro: parser-based games can be needlessly frustrating when trying to guess the right verb to use
  - Con: command exploration is sometimes part of the puzzle, immersiveness, and fun of a parser-based game

- I have new appreciation for how challenging it must have been to create the original game. Just porting a small portion of Emily's existing content was hard work. To have created its novel ideas and puzzles from scratch while also doing all of the implementation is just amazing. I'm reminded of Hunter S. Thompson typing out pages of The Great Gatsby to know what it felt like to write a classic.


You can find the demo's source code here. Since GameSegments.js, NounRepository.js, Memories.js, and Map.js are based on the original game's content and source code, those files are licensed under the Creative Commons Attribution-ShareAlike 4.0 license just like their source material.


Posted on 1/9/2021
As I so love to do, I've fallen down the rabbit hole on a new topic over winter break 2020: the different kinds of interactive fiction.

After building my first text adventure game (play here) with the explicit goal of being mobile-friendly, I did some research and prototyping to understand:

- What have people already figured out about touchscreen-friendly interactive fiction?

- How well does the "listed action" approach I took in Dungeon Memalign work for a traditional IF game?


Excitingly, there's been some deep discussion and successful exploration in the community!


For context, here's a quick recap of the main types of interactive fiction you'll find today on IFDB:

- Parser-based IF
  "Parser-based IF, also known as the 'text adventure' genre, represents one of the oldest and best-known forms of interactive fiction. Some early examples are digital games from the 70s and 80s like Zork and Enchanter. In a parser game, players type natural-language commands into a simulated world, and the game interprets them as actions for the story’s main character to carry out. Parser games often involve puzzles that the player must solve to move forward in the story."

- Choice-based IF
  "In choice-based interactive fiction, players choose among a number of options to advance the story. These options are presented either as an explicit multiple-choice menu, or as hyperlinks within the story text. Compared to parser IF, choice-based games tend to focus more on navigating branching narratives and multiple endings than on puzzles to solve or secrets to find."
  - These games are similar to "Choose Your Own Adventure" books

(These definitions are quoted from IFTF's FAQ)


Games that have a rich world model without requiring typed commands don't fit neatly into these two categories. Despite the lack of neat label, there are a bunch of cool games that can be played entirely (or almost entirely) by clicking/tapping instead of typing. Note: though I have tried all of these games, I have not played them to completion.

- The Impossible Bottle by Linus Åkesson (play here)
  - This game tied for first place in IFComp 2020!
  - Linus writes about the game in depth here, including its hybrid interface:
  "The Impossible Bottle is a hybrid parser/choice game. There's a prompt where you can type commands, but there are also hyperlinks for inserting text at the prompt, and it's possible to complete nearly all of the game using those links. Typing is generally faster, except on touch-screen devices where tapping on links is faster."
  - Note: I almost missed the unique interface of this game because the big "Play On-line" button on IFDB takes you to the traditional parser-based way to play. The "play here" link above (which is less-prominently featured on the IFDB page) takes you to this special way to play.


- Bigger Than You Think by Andrew Plotkin (play here)
  - At first this game may seem purely choice-based but there is rich world state. Andrew has shared the source code which is an interesting read!


- Shadow Operative by Michael Lauenstein (play here)
  - This game placed 20th in IFComp 2020
  - Shadow Operative uses a tool called Vorple to bridge Inform code to tightly integrate with JavaScript in the web browser


- En Garde by Jack Welch (play here)
  - This game tied for 14th place in IFComp 2018
  - You play this game by pushing a few buttons with different colors that correspond to commands


- Hugo Labrande has a few games like this, one of which he describes as a "parser game with hyperlinks to help beginners":
  - Le kébab hanté (play here)
  - Secrets de pêcheurs (play here)


- Swords is a web-based world editor and game engine for Listed Action IF games
  - Play an existing map here
  - Create your own game here
  - In addition to web-based play, Swords games can be played via Amazon Alexa or Discord chat. I haven't tried this out though.


With this growing list that includes the tied-for-first place 2020 IFComp entry, I'd like to try finding a name for this category.

Emily Short wrote about options for leaving the parser behind a decade ago in an insightful post: So, Do We Need This Parser Thing Anyway?

Here, she describes "enumerated-choice games with world modeling" with meaningful discussion of the pros and cons.

That's a good starting point for a name! Tweaking this label to set it apart from "choice-based" IF and using a word more ordinary than "enumerated," I've started calling this sub-genre "Listed Action Interactive Fiction."

The key characteristics of the Listed Action IF sub-genre seem to be:
- The game has a rich world model
- The user interacts with lists of actions and objects
- The game can be played without typing

These properties make these games easier for beginners to pick up and more ergonomic to play on your phone or iPad.


To test out how well this UI works in practice for a rich, more traditional IF game, I took a look at the source code of my favorite IF game (which also happens to be written by Emily Short!), Counterfeit Monkey, and I built a Listed Action demo of the first few puzzles! Emily's game is super fun and brilliant.

Play the Listed Action Counterfeit Monkey demo here.
Read my dedicated post about this demo here.

(You can also play the full original parser-based game here / my backup)


I hope you enjoy it! If you do, I hope you'll consider taking the Listed Action approach the next time you write a game!





Notes:

Here's the chain of tangents I followed to research this topic. This list includes some links to tools that may help you build your own Listed Action IF game.

This is all just light research I did as a hobby. I'm by no means an expert and there are many people more qualified to write on this subject. I'm just a person diving in for fun. I'm sharing this in the hopes it'll inspire and interest other people too.


- I played Counterfeit Monkey years ago and it renewed my interest in IF

- I made my own game and shared it on HackerNews
  - One comment asked whether there are other "click and log" games like this. That made me wonder too.

- I learned more about Andrew Plotkin and explored his website. Some cool things he made:
  - Quixe (used above to link to the original parser-based game, playable in a web browser)
  - His game Bigger Than You Think
  - His iOS games, such as Hadean Lands, that have been tuned to provide useful commands on touchscreen devices
  - His framework for IF on iOS

- I worked on the Counterfeit Monkey demo to further explore Listed Action IF
  - Take a look at the source code and feel free to fork it to make your own game. See also Dungeon Memalign & PacMan Dungeon.

- I looked at IFComp 2020 to see what the current state of the art is

- That listed The Impossible Bottle - I actually missed that this was Listed Action IF because I clicked the wrong link and initially only played the pure parser-based version

- It also listed Shadow Operative

- Shadow Operative led me to Vorple

- That led me to audit Vorple games on IFDB to see if any others are Listed Action IF

- The audit led me to Hugo Labrande's website

- Vorple's website led me to Borogove, an online IDE for making IF games

- Borogove supports a few programming languages for developing IF. I was familiar with most but I saw a new one: Dialog

- Dialog led me to Linus Åkesson's website

- That led me to Linus's games Pas De Deux and The Impossible Bottle - wait a sec! This is when I noticed that The Impossible Bottle is not standard parser-based IF.

- Then I did some general web searches for:
  - mobile interactive fiction
  - hypertext hybrid interactive fiction
  - etc

- I saw that Emily Short's blog covered these topics
  - This took me to her 2010 article
  - Also this other post

- About a month after originally publishing this post, I saw a reddit post about Swords.

Posted on 12/30/2020
My interest in interactive fiction and simple-yet-vast personal websites intersected this week when I stumbled upon Andrew Plotkin's homepage (aka Zarfhome).

(My previous adventures have led me to Drew's clamps, Lawrence's coding machines, and Eric Meyer's heartbreaking posts about his daughter Rebecca.)

Andrew has done some really cool work over the years. His body of work includes foundational tools for building/packaging interactive fiction games and an impressive library of interactive fiction games.

Zarfhome's design aesthetic of black text, blue links, and a white background may not be enticing to everybody browsing the web these days. When I see a site like this, I'm drawn in. This is where the special gems on the internet still exist.

Here are some notable pages to check out:

- The homepage

- The sitemap where he lists decades of projects
  - He even made an interactive-fiction-style caves version of the sitemap. What a clever idea!

- A years-out-of-date changes page. When I see this, I think about how much more durable this is than posts on Facebook or Twitter. There's something special about this kind of website that we don't get in today's social media.

- Andrew's blog
  - This is what led me to the homepage in the first place


As I browsed, I had a few neat surprises:

- Over a decade ago, I downloaded a Python program called Boodler to make soundscapes. It turns out Andrew made Boodler!

- I had stumbled upon "Crazy Uncle Zarf"'s handy interactive fiction reference card when I was getting started playing IF. It turns out Andrew wrote this too!

- Up until now, I hadn't seen other examples of blending richer interactive fiction with a web/mobile-friendly hyperlink interface like I recently experimented with in Dungeon Memalign. I found that Andrew did some work like this back in 2012! His game, Bigger Than You Think, is played by clicking hyperlinks and picking up items.


Andrew's decades of work, shared through his site with the world, are truly inspiring. It's so cool that he's shared complete projects, half-baked prototypes, random ideas, and more. Thank you Andrew!

Posted on 12/27/2020
Circa 2015, I met my friend for dinner at a Vietnamese restaurant in the Mission. We caught up on all things work and life. One small update he shared is that he'd been running an email list for years where he sends out product recommendations with Amazon affiliate links. He made a modest sum on referral fees paid by Amazon when someone made a purchase.

Back then, I wasn't aware of "Instagram Influencers" and it's possible that it wasn't a thing yet. Though I have no aspirations of influencing, every once in a while I'm surprised by a product we're using and I think that it'd be neat to share this with the world. And while I'm sharing, why not tack a referral reference onto the link and see if it goes somewhere?

And then, about a month ago, I felt an itch to do some writing.

Quick aside:
It's weird: all through school I gravitated toward math, science, and engineering. I dreaded writing assignments and classes heavy on reading. Now that I'm an adult who's been out of school for over a decade, I spend much of my free time reading (not necessarily books; mostly articles, blog posts, and discussion about all kinds of topics). And after working as an engineer, discovering that written communication is a vital skill, and then getting years and years of practice, I'm finding joy in expressing my thoughts in writing. This is probably worth a post of its own.

Anyway, I felt an itch to do some writing. I had done some light creative writing as part of my recent text adventure games (Dungeon Memalign and Pac-Man Dungeon) and I wanted more.

Having never engaged well with writing in school, I wasn't sure where to start. Instead of writing a story, a journal entry, or a new interactive fiction game, I latched onto the product recommendations idea.

What resulted are some product reviews I had fun making. Not everyone (anyone?) will enjoy them. Though my wife was supportive, read them, and said they were good, she also pointed out, "Why would anyone want these? People looking for product reviews want specific information presented concisely."

By the way, I did not sign up for the Amazon affiliate program and these are just vanilla product links. I don't get any monetary benefit if you follow these links. I'm simply getting the satisfaction of sharing some things that I liked using with words that I liked writing.

Here they are:


Ants erupt from a .5mm space between the wood floor and the white painted trim. They skitter, weaving single-file, looking for any source of nourishment. They discover a bowl of delicious dog food and, by the tang on their mandibles, it's the premium stuff: imported hypoallergenic kibble made with venison and sweet potatoes.

They obey the queen and the queen commanded, "FEED ME!" She will be most pleased.

This is the scene I stepped onto one Monday morning. I literally stepped onto it two or three times before I even noticed. The uncrushed ants were undeterred despite 10-20 of their flattened peers littering the scent trail.

And then I did notice. "ANTS!" I exclaimed to my wife as I scrambled to wipe them up with a damp papertowel. And yet they kept coming and coming. I dumped the dog's food in the trash and rinsed ants off the bowl. I traced them back to the source, that tiny sliver of a space where they were coming out of the wall. They can come out of the wall?? Apparently this is how ants work.

Last time we had ants I was less surprised because they were simply coming in under a door. Fortunately, we still had some supplies left over from the last ant-pocalypse we orchestrated.

Specifically, we had these liquid ant traps:

TERRO T300 Liquid Ant Bait Ant Killer, 6 Bait Stations

These traps worked great: they attract the ants, the ants slurp up the sweet borax-poisoned bait, and the majority of the ants make it back to the nest to share the dose. Within a week, the swarm of ants had dwindled and the nest had succumbed to the borax.

In the meantime, we ordered outside traps:

TERRO T1812 Outdoor Liquid Ant Killer Bait Stakes - 8 Traps

I placed these around the house to re-establish the perimeter of our ant-free zone. Hopefully we'll be good for another 6 months.




Close your eyes and imagine with me:
- It's winter
- You and your spouse disagree about an appropriate indoor temperature
- You want to keep the house habitable by humans
- They just want to prevent the water pipes from freezing and bursting
- You compromise on a balmy 67ºF

That's just too chilly.

The good news is that you can create a tiny little sauna for yourself in any enclosed space with this tiny little space heater made by Toyuugo:

Portable Electric Heater, Space Heater 500W Ceramic Personal Desk Heater, 2s Quick Heat-up, Tip-Over, Overheating Protection & Auto Shut Off for Office Home

It's small but it packs a big punch!

I like to turn this on in my bathroom a minute before I go in and leave it running while I'm in there. I've used it off-and-on for about a year now. It's great :)




Have you ever wanted to hang a coat or towel?

And you don't want to install a rack of some kind (because you don't have space, a good spot, you don't want to hammer something into the wall, etc)?

And you have a glossy surface (possibly glass) nearby which would be a good spot for a hook?

If you, like me, find yourself in this strangely specific circumstance, I know exactly what will help.

HOME SO Suction Cup Hooks for Shower, Bathroom, Kitchen, Glass Door, Mirror, Tile – Loofah, Towel, Coat, Bath Robe Hook Holder for Hanging up to 15 lbs – Waterproof & Rustproof, Chrome

Note that, while these have a shiny chrome finish, they are plastic.

Normally, I don't trust suction cups. I've been burned too many times! You stick them on only to have them detach days later. Often at inconvenient times. I still haven't found a trustworthy car-phone-mount that uses suction cups.

This product is great. You stick it on the glass and you twist the knob to increase suction. A few days after sticking it on, you twist again in case it unwound at all. I like to give it an extra little twist every few months now. We have four, used every day, and they have never detached after months of use.




So those are my reviews. I have more products I like but I'm shifting my hobby time to something else for now!

Here's what I would write about next:

For the home:
- Electric blankets: Berkshire Blanket Heated Throw
- Heated mattress pad by SoftHeat/Serta
- Air purifiers by Levoit. This big one is great for a large open space.
- Ring Alarm and cameras

Working from home:
- Lululemon Discipline Pant
  - Off-screen comfort
- Herman Miller Aeron chair

Health:
- Lactaid - I should've started using this far earlier than I did!
- Sonicare toothbrush
  - Switching from a classic electric toothbrush to this Sonicare felt like finally arriving in the future. I feel this cleans a lot better than older technology.

Dog:
- Zignature Limited Ingredient Venison dog food
  - Around 1 year old, our Shiba Inu became allergic to normal dog food. He started rubbing his face raw and biting at his fur to scratch his itchy skin. We learned that many dogs become allergic to common proteins. The solution is to switch to a limited-ingredient food and be careful they don't eat random things. It was a relief when we found this food that is safe and he likes to eat.
- Dog Comfy Cone
  - Important to use any time he's having an allergic reaction and trying to scratch/bite himself

Other:
- My favorite car - switching to a nicer car as an adult transformed driving from a routine bore to something fun!
- Stickerless mini speed Rubik's Cube knockoff keychains - there are several brands, make sure to pick one with good reviews



Posted on 11/30/2020
"You open your eyes and find yourself in dimly lit surroundings. The air is damp and cool. The smell of mildew reminds you of an old basement. In your head, you label this location 'room 👀👃'.

Nearby, you see a small potion.

You can head east."

  - Dungeon Memalign


I hinted at a new dungeon game in my post about Pac-Man. Well this game is now complete!

This game, "Dungeon Memalign," puts the player in a maze and a daze. The player solves puzzles and battles their way through ever-stronger monsters. Are you clever and strong enough to win? Play here!

The game is beatable in ~15 minutes and should work well on phones, tablets, and computers. It'll save your progress if you get interrupted.


In the rest of this post, I'm going to talk about how I wrote this game, the structure of the source code, and ideas for other games that can be built on the same foundations.


How I approached writing this game:

- I talked about my long-time desire to built my own text-based game in a previous post.

- I brainstormed about potential stories and even came up with a fairly rich idea

- I created a local git repository and I used a notes.txt engineering journal to capture my thoughts and pending task list

- I worked on the foundations: the game engine, the map, and how to structure game-specific logic. More details on this below.

- As I started scoping out the story-specific work, I realized that writing such a sophisticated story and supporting the game mechanics would take more time than I have for a side project. I didn't want this project to drag on for more than a handful of months. If it took longer, I'd have a bigger and more interesting game but I might get bored of it and never finish. Or it would prevent me from developing other ideas I haven't thought of yet.

- I found a way to scope the story down so I could keep it interesting, fun, and achieve my goal of writing a text-based game in a timeframe I was willing to devote to it.

- With the scoped-down story, I worked on designing the game map next

- I used a pixel art app to draw a grid and then I filled in a maze-like set of inter-connected rooms, light puzzles, and enemies of increasing difficulty. At this phase of design, I knew I wanted to have battles but I wasn't sure how they would work or what rewards to give to celebrate victory. Here's the map I drew (don't look until you've played the game!).

- Next, I worked on implementing the map itself. I wrote the code to name each room, add items to the rooms, describe each room's contents, and hook the rooms up to each other. Some of this work was tedious/mechanical which turned out to be an easy way to unwind in the evening and build momentum. I could even do some of this work from a mobile device while using an exercise bike (e.g. naming all of the rooms).

- Next, I implemented the game mechanics one-by-one:
  - Locked doors and keys to unlock them
  - Simple battle mechanics where the user one-hit killed every enemy
  - Gear: armor to reduce damage during battle, weapons
  - Complex battle mechanics with attacks, variable damage, and enemies fighting back
  - Rewards for battles
  - Healing items and a healing room

- At every step of the way, the game was playable and it got iteratively richer. I like to work my way up from a simpler system to a more complex system iteratively.
  - I'm reminded of Gall's Law: "A complex system that works is invariably found to have evolved from a simple system that worked. A complex system designed from scratch never works and cannot be patched up to make it work. You have to start over with a working simple system."

- The whole time, I kept a detailed task list in my notes.txt file: enemies I needed to flesh out, details I skipped over, mechanics I still needed to implement, and test coverage I needed to write

- I played through the game a few times, tweaking rewards. I added a weapon upgrade and added more explanation of game items and attacks as a reward.

- I added saving/loading/and starting over. I had a strategy in mind for how I would save game progress from the beginning of the project (because the tools available could change how I maintained game state). Knowing this would be played in a web browser meant I only had some tools available.

- Then I worked through the whole test backlog. Though I often wrote tests as I added new logic, I also accumulated a big list of missing coverage for edge cases and some aspects of gameplay. I love projects where most testing can be automated as unit tests and multi-layer "unit tests" (which are actually integration tests written using the unit test harness). More on testing below.

- I played it a bunch and my wife played it too! I fixed any bugs we found.

- And then the game was ready to share :)


How the code is structured:

- I use "MA" as a prefix on class names to create a simple namespace to avoid conflicts with global JavaScript APIs

- Map.js
  - Most of the code in this file is game-specific. When writing a new game, most of this code would be deleted and replaced.
  - Defines the enemies (MAEnemy instances)
  - Defines the rooms (MALocation instances)
  - Defines the graph of room-to-room connections
  - Enemies and rooms have properties and methods that the game engine calls to get the appearance, attacks, etc
  - Has logic to generate an emoji representation of the map

- GameEngine.js
  - Most of the code in this file is foundational. This should require only small tweaks when it's used in a new game.
  - Many foundational classes are in this file. They could be factored out into their own files.
  - Notable classes:
    - MAGameState: an instance of this class stores all of the current game's state. This instance is passed into game engine methods and modified as the game progresses. This object has a reference to the Map instance, the current location, the user's inventory, and storage used by MAGameSegment subclasses.
    - MAGameEngine: this class is the entry point to all of the game logic. It contains some common methods that most games would need (such as a method to calculate the possible actions the user can take given the current game state). There are only two places in this class that contain game-specific logic: constructor and setupNewGame. The constructor instantiates a hard-coded list of MAGameSegment subclasses (more on these below). The setupNewGame method simply has the name of the game hard-coded.
    - MANoun, MAScenery: classes used to represent items in the game map. These include some methods to guess the right indefinite article and in-sentence representation of the objects.
    - MADirection, MALocation: used to represent map locations and the connections between them

- Utilities.js
  - Methods used in many different parts of the codebase
  - One example: naturalLanguageStringForArray, accepts an array of MANoun objects and constructs a string like "a dog and a cat" or "a dog, a mouse, and some ants"
  - Another useful method: fakeRandomInt. All of the "randomness" in the game is actually deterministic so I can replay every action taken since the beginning of the game and always get the same result. I use this trick for easy saving/loading of game progress. It also impacts unit testing.

- GameSegments.js
  - MAGameSegment is an abstract class that is subclassed to create game-specific GameSegments
  - A GameSegment is a way to organize game logic, such as locked doors & keys to unlock them, using potions to heal, etc
  - The GameEngine asks every GameSegment for actions the user can perform (e.g. "Use potion"). The GameSegment is then asked to perform the action that's chosen.
  - GameSegments also get callbacks when other parts of the game will perform or did perform an action in case they have reason to block it or react to it. For example, a GameSegment may want to block "Go north" if the door is locked.
  - Game mechanics and room-specific behaviors are implemented as GameSegments.

- GameSegmentBattle.js
  - The battle mechanics of the game are complex enough that I put this game segment in its own file

- UnitTests.js
  - This is a simple unit test harness and collection of tests of both foundational and game-specific pieces
  - Run the unit tests by visiting unitTests.html
  - Some of the tests are real "unit" tests
  - A lot of the test coverage comes from multi-layer tests which are more accurately called "integration" tests. I chose to get a lot of test coverage this way because it was less effort for me to write these instead of granular unit tests for everything.
  - These integration tests essentially play the game and assert that the game ended up in the right state along the way. See "test_MAGameEngine_beatGame" for a full play-through (and spoilers!).


I'll close this post with some ideas to extend or reuse these pieces:

- Create a game with a linear dungeon where enemies get stronger as you move deeper into it. If you die, you start over at the beginning but you get to keep your experience/attacks/equipment.
- Create an "Emojimon" text-based RPG to collect and battle emoji (similar to Pokémon)
- And one idea I executed on as soon as I had it: Pac-Man Dungeon


Posted on 11/24/2020
"You open your eyes and find yourself in dimly lit surroundings. You are shocked to find that your body has somehow become a smooth yellow sphere. You panic. Your giant wedge-shaped mouth gapes open as you gasp for air."
  - Pac-Man Dungeon

Text adventure games have a special place in my heart. My first memorable run-in was with MUDs. A friend in my summer school World History class showed me how to telnet into a MUD server one day when we were goofing around in the computer lab (instead of working on our assignment).

Many years passed and I eventually played through a game that was super fun and blew my mind, Emily Short's Counterfeit Monkey. (Her blog is here.) The game's word-manipulation puzzles opened my eyes to capabilities I didn't realize these games could have.

On iPhones, iPads, and other devices that spend most of their time without a hardware keyboard, text-based games can be hard to play. In the back of my mind, I've been noodling on how to make these games more touch-friendly.

One way to build a touch-friendly interactive story is to use a tool like Twine, which lets users perform actions by choosing from a set of choices/hyperlinks.

Text-based games can be fun programming projects (check out this previous post on How to Program a Text Adventure). Instead of using Twine, I decided to make a tiny game engine in JavaScript that I could then use to accomplish my long-held goal of writing my own text-based game.

I've been working on a dungeon game for a few months and it's coming along nicely. And then, a few days ago, I got one of my favorite things as a hobbyist programmer: a sudden and powerful urge to take a quick detour on a tangentially related programming project. Following these impulses and bursts of passion have led to some of my most productive coding sessions over the years.

Here's what kicked it off this time (another instance of falling down the rabbit hole):

- I was already working on this game engine and its first game
  - To be touch-friendly, all actions are listed as tappable-links
  - To have some simple graphics, I use emoji. An idea I noted previously.

- I saw an article about PacTxt ("Pac-Man meets Zork")
  - (In case that ever disappears, here's my backup)
  - Use the "debug" command to see a map

- I thought it was a really funny idea and it in turn reminded me of a comic (backup) I saw many years ago that frames Pac-Man as hyper-detailed horror

- I find the comic really creative, funny, and interesting. And while the core idea of PacTxt is amusing, the implementation is not fun to play (it's long, tedious, repetitive). I wondered if my own spin on this could combine the two into something more fun.

- I opened a pixel art app (one I wrote for fun earlier in the year on another programming spurt) and I sketched out a dungeon map for a Pac-Man level. The first maze I drew would be too big to be fun so I scoped it down to a mini version.

- Then I created a new private git repo for this project (I always try to save my code in source control -- who knows when it'll come in handy in the future!)

- I copied over the relevant game engine files, started a notes.txt journal for the project, and hacked it together

- Even though this is a tiny tangential project, I ended up solving some problems I'll ultimately need to solve for the bigger dungeon game I'm working on: emoji weren't displaying correctly in Chrome, tweaks to the page layout, better sorting for game actions


You can play my Pac-Man Dungeon game here.


It's silly, it was fun to make, maybe somebody else will find it amusing, and maybe this will help somebody else to fall down the rabbit hole and come up with more creative ideas.

Here are some quick ideas to extend this:
- Support a full set of levels (and maybe even have the game glitch out on level 256, just like the original Pac-Man)
- Support all the different fruit
- Support larger levels
- Let the user input an ASCII map that they can then play
- Support all the different ghosts with their specific chase and scatter behaviors
- Let the user write code to inspect the game and control Pac-Man to create their own AI to play and win


Other notes:

- The game source source code can be found here
- I wrote a simple unit test harness. Run the tests here.

Posted on 11/21/2020
I recently watched The Queen's Gambit, a 7-episode miniseries on Netflix. It's based on a novel that I haven't read.

The story drew me in and, though I originally planned to watch one episode per day, I ended up needing more and more. I watched one on Sunday, one on Monday, two on Tuesday, and the last three on Wednesday.

As often happens when I finish a story, I was excited to see how it ended but I also felt a pensive sadness at the loss of characters I had grown to care for.

I have some thoughts on the story below. If you haven't watched it yet, I recommend that you stop here and go experience it yourself! I hope you like it.

.
.
.


.
.
.

I kept thinking about the plot and characters, especially the main character, Beth Harmon. At first, I was a little disappointed that Beth had so many people helping her yet she had not been good or kind to them. They supported her because she was so brilliant that they wanted her to succeed. Part of their support and her success also came from being physically attractive and stylish. This happens in the real world, too, but it's not inspiring.

I eventually found more inspiring takeaways:

- Beth overcame her struggle with addiction thanks to people caring about her
- Beth found success by following her passions and developing her talents
- Beth's success required more than raw talent or learning on her own. Friendship and mentors made a huge difference.
- Beth was able to break free from the trouble in her past when she finally let herself face it directly. She had a breakthrough by realizing her troubles weren't the same as her biological mother's. She also needed to see that other people had cared for her and followed her story from the beginning.

In the end, the story resonates with me and the series is great entertainment.

Posted on 11/4/2020
Browsing interesting personal websites is often fun (as in clamps and Coding Machines). But sometimes it can break your heart.

That's the case with Eric Meyer's open grieving after losing a child. I found his website when I learned about rebeccapurple, a special color added to the CSS specification as a tribute to his daughter. As I read his family's experiences, I found myself crying and feeling an aching pain for these people I've never met.

Eric shared his experiences as they happened. You can find them here. His family's story is tragic and emotional and wonderfully written.

(I normally try to keep a backup of external sites but I'm not grabbing one here. I want to be respectful of Eric and his family's potential future choices to remove their story.)

Posted on 11/3/2020
Back when I discovered the website about clamps, I pored over that and similar personal websites (which sometimes linked me to even more interesting personal websites of their friends). I stumbled on a cool story about debugging, compilers, and viruses written by Lawrence Kesteloot.

You can check it out here.
(My backup of the story is here.)

Posted on 11/2/2020
Many years ago, I stumbled upon some cool personal websites made by software engineers back in the 1990s/early-2000s.

I liked the websites because they shared interesting projects, essays, stories, and pictures. Some of them peppered in personality and humor unique to the author.

In a way, they inspired me to eventually make this website.

A simple yet intruiging post was about clamps. You can find my backup of the page here.

It's got some humor (including that poor Pillsbury doughboy) and it opened my eyes to how handy clamps can be around the home and office. (By the way, I also find binder clips and zip ties to be useful fasteners for quick projects/fixes!)

This is a glimpse into a more fun era of the web.

The author eventually took the website down and replaced it with a much more professional one. Normally I would give full attribution but since he removed all the content, I decided to scrub his full name and web address from this post and my backup of the page. I'm assuming he intentionally wants to have a less personal presence on the web. (Drew, if you want attribution or want me to remove this post, please let me know via GitHub.)

More posts:
Emoji Simulator
Universal Paperclips
Fall Down the Rabbit Hole
Live COVID-19 Dashboard
Create COVID-19 Graphs Using JavaScript
Books
notes.txt
Download Video Into Your Brain
Download Text Into Your Brain
Source Control
Personal Projects
How to Program a Text Adventure
RSS and Feedly
Welcome