Fussy Baby Checklist

Fall Asleep

Shape Your Destiny

Tim's Vermeer

Day and Night

Lights Off

Batman Curve

FormulaGraph

Bouncing Ball

Catalog of Math Shapes

Draw Shapes Using Math

Similar Songs

Building a Reddit app

Fall Asleep

Shape Your Destiny

Tim's Vermeer

Day and Night

Lights Off

Batman Curve

FormulaGraph

Bouncing Ball

Catalog of Math Shapes

Draw Shapes Using Math

Similar Songs

Building a Reddit app

Posted on 11/14/2021

Tags: Parenting

Do you have a fussy infant?Tags: Parenting

Here's my checklist for trying to resolve fussiness:

- Does the baby need a diaper change? Check for diaper rash - make sure to slather on extra diaper paste if you see redness/bumps. I saw one website say you should spread it on as if "icing a cake". Our first few diaper rashes were caused by the diaper being too small - check whether the next size up is a better fit!

- Does the baby want a pacifier?

- Is the baby hungry? Try offering a bottle even if they recently ate. They might be going through a growth spurt.

- Does the baby need to burp? Try holding them upright and patting their back.

- They might be uncomfortable. Try holding the baby in different positions.

- The baby might be overstimulated after a long and interesting day. Try darkening the room and make it quiet. Try putting them down for sleep. Try putting them in a bouncer. Try putting them on your chest for a rest.

- If you're feeling overwhelmed or desperate, tag your partner in for help!

- Echoing advice from our pediatrician: If your baby won't stop crying no matter what you try and you're at your wit's end, call your pediatrician. Your baby could be telling you something more serious. WebMD has some additional advice.

Posted on 11/13/2021

Tags: Brain Hacking

I've always had a hard time falling asleep. Closing my eyes invites a whirlwind of thoughts. I can't stop thinking so I can't fall asleep.Tags: Brain Hacking

Then I found a way to clear my head and fall asleep quickly. It's been working for months now!

Here's what I do:

As I shower and brush my teeth before bed, I spend that ~30 minutes clearing my head. When I notice a thought, I think "ah" and let the thought go, let it dissipate and fade. This isn't the time for thinking and problem solving, it's the time to empty my mind so I can fall asleep.

I repeat this for every thought that creeps in. I've found it also helps to focus on my immediate senses to keep my head emptier: the sound of the toothbrush, the soap/water/friction on my hands. I'm grounded in the present. I'm not thinking about these sounds and feelings; I'm just experiencing them without commentary.

Sometimes this helps me notice a feeling of muscle tightness. Maybe I'm furrowing my brow or frowning. Maybe I have tightness in my shoulders. I release the tension and phsyically relax.

I continue this meditation as I slide into bed until I fall asleep. And I continue it if I wake up in the middle of the night.

I don't know if this technique is real "meditation" in the Zen Buddhism sense but I was inspired to try it after, months ago, stumbling upon 101 Zen Stories (Wikipedia). (Warning: these are old and may contain antiquated or even racist terms)

I don't understand them even though they are simple. I tried to search for answers about one of the more famous stories: The Sound of One Hand (mirror).

I found a Quora thread with some discussion. No answer was perfectly satisfying but two similar posts stood out:

Khiêm Bảo Thiện:

However, when one asks you about one hand clapping - which is extraordinary, uncommon, unseen, not understandable - your mind starts to set on a quest for answer, which raises a lot of noises, like you throw a big rock into a tranquil lake. ... So, using this koan and similar, for certain situations, helps the learner realize his/her mind's activities.Chris Peters:

The idea behind this phrase is not literal, but is meant to "blend" your mind Matrix-style and hopefully trick you into observing your mind as it kinda freaks out.Is the key to enlightenment learning to notice your thoughts and then clear your head? Who knows. I certainly feel lighter when my head is clear, my muscles are relaxed, and I float off to sleep.

A friend of ours shared the eulogy he gave for his dad. In it, he quoted a lesson his father taught him as a kid:

Watch your thoughts, they become words;(This quote and ones with a similar sentiment are often attributed to many different sources. Ultimately, the original is by Frank Outlaw based on Quote Investigator's research.)

watch your words, they become actions;

watch your actions, they become habits;

watch your habits, they become character;

watch your character, for it becomes your destiny.

I don't think I was taught to watch my thoughts as a kid. At least not in a way that was effective for me.

I was taught that particular actions are bad and why they are bad.

I was taught that jealousy, intolerance, hate, etc are bad.

But I didn't realize that the way I choose to think about things can have a big impact on long-term happiness and fulfillment in life. On friendships and relationships too.

I realized in adulthood that being happy is a choice much of the time. A choice made every day. Focusing on the positive (and even helping to amplify it) can do a lot to diminish the negative: disappointment, annoyance, frustration, misfortune.

Of course, really bad things happen sometimes and you can't just ignore them. It wouldn't be healthy to try to force yourself to be happy when you're truly hurting. But for all the small bad things, with practice it's possible to decide how much energy to spend thinking about them -- and whether to think about them at all. Sometimes negativity can be self-fulfilling: expecting to have a bad time can lead to having a bad time.

This is similar to what I'm trying to say in Day and Night. Thinking envious thoughts leads to envious actions. Comparing yourself to others leads to unempathetic behaviors. And envious actions, unempathetic behaviors lead to a lonely life.

On the other hand, looking for the good and trying to be truly empathetic can lead to happiness, more positivity, and supportive friendships.

I recently saw news that a painting from the 1650s had been restored to unhide a painting-within-a-painting in the background.

Here's what the "after" and "before" look like:

It's fascinating that modern technology can both find the hidden painted-over image and confirm that it was painted over by someone other than the artist.

I showed my wife who asked "Is that a Vermeer?" which then sent me down another rabbit hole.

I asked "Whoah how could you tell?" and she said that all Vermeer paintings look exactly like this due to similar composition and photo-realistic lighting.

I found that I was already familiar with one of Vermeer's paintings, Girl with a Pearl Earring, possibly due to its use as an example image in computer vision.

Then I stumbled upon Tim's Vermeer, a documentary made by Penn and Teller about an inventor (and successful company founder) named Tim Jenison who had a side-project (bordering on obsession) to understand how Vermeer captured such realistic lighting in paintings and then do so himself.

He ultimately figured out a combination of two mirrors that enable you to color match what's painted exactly to the real scene. It turns the process of painting a brilliant masterpiece into something mechanical, essentially a manual photograph machine with a person holding a paintbrush in the middle.

He starts out copying an old black-and-white photograph this way:

The results are phenomenal! Tim says this was the first painting he's ever made and it was a wholly mechanical process of copying the color he saw in the mirror.

After that successful proof of concept, Tim sets out to completely recreate one of Vermeer's most detailed paintings.

Check out the full documentary (it's only 80 minutes long and you can watch it faster than 1x) to see how he first replicates the physical space and then reproduces the painting. The painting itself takes 130 days.

Here are some snapshots of the mirror setup and the final painting:

By the end of the documentary, I'm convinced that Vermeer used a setup like Tim's. Here's why:

- At one point, Tim realizes that the mirrors cause slight visual distortion in the form of curving of straight lines. He looks at the original Vermeer and discovers the same distortion!

- The lighting in Vermeer paintings goes beyond what the human visual system can detect across the size of the rooms in the paintings

- The fine detail in the rug in the painting is not perceivable from the artist's distance without the magnifying help of the mirrors

- And several more visual quirks of Vermeer's paintings shown in the documentary

But don't take my word for it - check it out yourself!

Saul made a friend or two

A friend was successful, Saul celebrated

A friend had misfortune, Saul felt their pain and supported them with kindness

Saul had success, the friends celebrated

Saul had misfortune, they came to the rescue

Sometimes they didn't see each other but could always pick up where they left off

Friendships were sometimes superficial and sometimes deep

They looked past the worst in each other and focused on the best

Consciously managing impulses, starving envy, amplifying empathy

Their warmth welcomed others and their circle grew

Luna had some friends

A friend had professional success, but Luna could have done that job better

A friend had a relationship, but their partner wasn't very intelligent or funny or interesting or attractive or...

A friend tried to stay in touch, but it was just superficial. Why are they even bothering?

A friend was happy to catch up and chat, but they were never the one to initiate. Forget them.

Luna and a friend had an argument. Luna was still upset later and said some hurtful things, as hurtful as possible. Later, with regret, Luna asked, "Why can't we pick back up where we left off? Can't you see I need you? Can't you see I need compassion?"

But no one was left to respond

Tap or click and take a trip down memory lane with the historic Lights Off. Exactly fourteen years ago today Wired got the scoop: Lights Off: First iPhone Native Game Arrives.

And today I'm sharing a new implementation that everyone can play in their web browser:

Play here!

Lights Off's story begins with Tiger Electronics who launched their game Lights Out in 1995. Then in 2007 Lucas Newman and Adam Betts cloned the game for the iPhone's touch screen. Notably, they built the game before there was an official iPhone SDK or App Store. It was only available to jailbreakers!

A year later, Craig Hockenberry wrote a post to commemorate the milestone and share the source code (my backup).

Since then the app was rebuilt and modernized by Steve Troughton-Smith as possibly the oldest still-actively-maintained app on the App Store. You can find his version here.

For more history on the App Store and its first killer apps, check out this article (also written by Craig Hockenberry - mirror on the iconfactory website).

In the process of building the web version, I wrote tests (source code) to ensure every level is solvable. To my surprise almost half of the original levels cannot be solved! How cruel! This was probably a mistake in how the levels were generated - maybe by flipping certain lights on without checking that the overall puzzle was valid.

To replace the bad levels, I generated my own levels of increasing difficulty by ramping up the minimum number of taps required to solve each puzzle. My algorithm can be found in the unit tests code in the "test_generateLevels" method. This could even be hooked into the game itself for an "endless" mode.

If ever you can't figure out how to solve a level in the fewest taps, there's a guaranteed method to win called "light chasing" or "chase the lights":

- Start on the top row

- For every lit button in the row, turn it off by tapping the button in the row right below it

- Repeat until you get to the bottom row

- At this point, the bottom row will have one of seven patterns. Based on the pattern, tap the specific circled cells on the top row and repeat these steps. By the time you reach the bottom row again, every light will be off!

⬛⬛⬛⬛⬛ ⬛⭕️⬛⬛⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ => ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

🟡🟡🟡⬛⬛ 🟡🟡🟡⬛⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⭕️⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ => ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

⬛⬛🟡🟡🟡 ⬛⬛🟡🟡🟡

⬛⬛⬛⬛⬛ ⬛⬛⭕️⬛⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ => ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

🟡🟡⬛🟡🟡 🟡🟡⬛🟡🟡

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⭕️

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ => ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

🟡⬛🟡🟡⬛ 🟡⬛🟡🟡⬛

⬛⬛⬛⬛⬛ ⭕️⬛⬛⬛⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ => ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

⬛🟡🟡⬛🟡 ⬛🟡🟡⬛🟡

⬛⬛⬛⬛⬛ ⭕️⭕️⬛⬛⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ => ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

🟡⬛⬛⬛🟡 🟡⬛⬛⬛🟡

⬛⬛⬛⬛⬛ ⭕️⬛⬛⭕️⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ => ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

⬛🟡⬛🟡⬛ ⬛🟡⬛🟡⬛

While every level can be beaten mechanically like this, it's still more fun (and often faster) to find the fewest taps needed! That's the real challenge.⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ => ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

🟡🟡🟡⬛⬛ 🟡🟡🟡⬛⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⭕️⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ => ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

⬛⬛🟡🟡🟡 ⬛⬛🟡🟡🟡

⬛⬛⬛⬛⬛ ⬛⬛⭕️⬛⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ => ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

🟡🟡⬛🟡🟡 🟡🟡⬛🟡🟡

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⭕️

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ => ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

🟡⬛🟡🟡⬛ 🟡⬛🟡🟡⬛

⬛⬛⬛⬛⬛ ⭕️⬛⬛⬛⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ => ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

⬛🟡🟡⬛🟡 ⬛🟡🟡⬛🟡

⬛⬛⬛⬛⬛ ⭕️⭕️⬛⬛⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ => ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

🟡⬛⬛⬛🟡 🟡⬛⬛⬛🟡

⬛⬛⬛⬛⬛ ⭕️⬛⬛⭕️⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ => ⬛⬛⬛⬛⬛

⬛⬛⬛⬛⬛ ⬛⬛⬛⬛⬛

⬛🟡⬛🟡⬛ ⬛🟡⬛🟡⬛

Have fun! :)

Posted on 8/7/2021

Tags: Math

With FormulaGraph now available, it's easier than ever to explore more math shapes.Tags: Math

An interesting one is the "Batman Curve".

A professor named J. Matthew Register created this formula to make math fun. Here's what he says about the history in a post on Quora:

"I wrote it many, many years ago. I was teaching at a few art schools throughout the greater Sacramento area, and I used it to engage my students in the topic of graphing. One of my coolest students (Mr. Wilkinson aka i_luv_ur_mom) posted it to Reddit back in 2011 and it went viral. I'm a full time professor over at American River College, doing every thing I can to make math as enjoyable as possible."

There's a nice breakdown of how the formula's separate pieces come together to make the full shape on Math StackExchange.

While the 6-formula version graphs ok (see here or on Desmos), I noticed that the original single formula version does not work in FormulaGraph or Desmos (it forms a blank graph showing no solutions). User copper.hat on Math StackExchange notes why: there are several terms which introduce an imaginary number (by taking sqrt of a negative number). For this reason, the combined formula does not have the solutions required for the necessary range of x and y values. (Wolfram also has a writeup that notes that the Batman Curve is defined piecewise.)

The original formula uses the trick of undefined/imaginary values to truncate shapes to a specific range of x values. Since this trick breaks the formula, I wondered if it's possible to find a different gadget that could constrain x values without introducing undefined/imaginary values.

Here's what I came up with:

For a formula F, constrain its x values to the interval [L, U] with:

((F)^2 + min(1, ceil(abs(min(0, U-x)))) + min(1, ceil(abs(max(0, L-x))))) = 0

To fix the Batman Curve, I went segment-by-segment applying the gadget. First, I needed to resolve any undefined/imaginary results. Second, I needed to apply my gadget for the relevant x (and sometimes y) ranges.((F)^2 + min(1, ceil(abs(min(0, U-x)))) + min(1, ceil(abs(max(0, L-x))))) = 0

Here's the crazy formula I ended up with:

(((2.25-y)^2+min(1, ceil(abs(min(0, 0.5-x))))+min(1, ceil(abs(max(0, -0.5-x))))) * ((abs(x/2)-((3*sqrt(33)-7)/112)*(x^2)-3+sqrt(abs(1-(abs(abs(x)-2)-1)^2))-y)^2 + min(1, ceil(abs(min(0, 4-x))))+min(1, ceil(abs(max(0, -4-x)))))) * (((min(1, ceil(abs(max(0, 3-x))))+min(1, ceil(abs(min(0, 7-x))))) + min(1, ceil(abs(max(0, 0-y))))+ (((x/7)^2)+((y/3)^2)-1)) * ((min(1, ceil(abs(max(0, 4-x))))+min(1, ceil(abs(min(0, 7-x))))) + min(1, ceil(abs(min(0, 0-y))))+ (((x/7)^2)+((y/3)^2)-1)) * ((min(1, ceil(abs(max(0, 3-(-x)))))+min(1, ceil(abs(min(0, 7-(-x)))))) + min(1, ceil(abs(max(0, 0-y))))+ (((-x/7)^2)+((y/3)^2)-1)) * ((min(1, ceil(abs(max(0, 4-(-x)))))+min(1, ceil(abs(min(0, 7-(-x)))))) + min(1, ceil(abs(min(0, 0-y))))+ (((-x/7)^2)+((y/3)^2)-1)) + min(1, ceil(abs(max(0, 1-abs(x)))))) * ((min(1, ceil(abs(min(0, 3-x))))+min(1, ceil(abs(max(0, 1-x))))) + ((6*sqrt(10)/7+(1.5-0.5*abs(x)) -6*sqrt(10)/14*sqrt(abs(4-(abs(x)-1)^2))-y))^2) * ((min(1, ceil(abs(min(0, 3+x))))+min(1, ceil(abs(max(0, 1+x))))) + ((6*sqrt(10)/7+(1.5-0.5*abs(-x)) -6*sqrt(10)/14*sqrt(abs(4-(abs(-x)-1)^2))-y))^2) * ((min(1, ceil(abs(max(0, 1-y))))+min(1, ceil(abs(min(0, 3-y))))) + (9-8*abs(x)-y)^2) * ((min(1, ceil(abs(max(0, 2.25-y))))+min(1, ceil(abs(min(0, 3-y))))) + (3*abs(x)+0.75-y)^2) = 0

FormulaGraph linkThis curve is pictured at the top of this post!

(See my raw stream of consciousness notes as I went step-by-step)

There are a few more Batman Curves that I found on the pages linked above:

Here's a different and simpler formula (though I don't think it looks as good) from copper.hat's post on Math StackExchange:

0=-1+(1/6)*x^2+(1/3)*y^2+max(0,2-(1.5*(x+3)^2+(y+2.7)^2))+max(0,2-(1.5*(x-3)^2+(y+2.7)^2))+max(0,2-(1.9*((5*(x+1)+(y+3.5))/sqrt(26))^2+1/1.7*((-(x+1)+5*(y+3.5))/sqrt(26))^2))+max(0,2-(1.9*((5*(x-1)-(y+3.5))/sqrt(26))^2+1/1.7*(((x-1)+5*(y+3.5))/sqrt(26))^2))+max(0,2-((1.1*(x-2))^4-(y-2.1)))+max(0,2-((1.1*(x+2))^4-(y-2.1)))+max(0,2-((1.5*x)^8-(y-3.5)))

FormulaGraph linkJ. Matthew Register shared a newer formula on his Quora post:

(3*sqrt(4-(abs(x)-2)^2)+abs(x)-20-4*y)

*

(3*sqrt(4-(abs(x)-6)^2)+abs(x)-20-4*y)

*

(x^2+4*(y^2)-100*sqrt(abs((7-abs(2*y-1)))/(7-abs(2*y-1))))

*

(2*((abs(x)-3)^2)-9*y+18*sqrt(abs((2-abs((abs(x)-4))))/(2-abs((abs(x)-4)))))

*

(-68*abs((abs(x)-3/2))-9*y+54*sqrt(abs((43-abs((136*abs(x)-229))))/(43-abs((136*abs(x)-229)))))

*

(y-5*sqrt(abs((1-abs(x)))/(1-abs(x)))) = 0

FormulaGraph link*

(3*sqrt(4-(abs(x)-6)^2)+abs(x)-20-4*y)

*

(x^2+4*(y^2)-100*sqrt(abs((7-abs(2*y-1)))/(7-abs(2*y-1))))

*

(2*((abs(x)-3)^2)-9*y+18*sqrt(abs((2-abs((abs(x)-4))))/(2-abs((abs(x)-4)))))

*

(-68*abs((abs(x)-3/2))-9*y+54*sqrt(abs((43-abs((136*abs(x)-229))))/(43-abs((136*abs(x)-229)))))

*

(y-5*sqrt(abs((1-abs(x)))/(1-abs(x)))) = 0

(I made one substantive change as I re-transcribed the formula so the top of head line would be long enough to touch the ears)

Compare his original and new Batman Curves here.

I'm excited to introduce FormulaGraph, a graphing calculator for general formulas (not just functions of x)!

Whereas many graphing calculators require an equation like y = sqrt(1 - x^2), FormulaGraph can handle formulas like y^2 + x^2 = 1. You don't have to solve for y to use FormulaGraph.

Here's what else it can do:

- Polar formulas in terms of r and theta, such as r = 2*sin(2*theta)

- Animate based on time t: y = x^(t % 4)

- Inequalities: x^100 + y^100 <= 2

- Handle panning and zooming using mouse and touch/pinch

- Share links

While FormulaGraph lacks features other graphing calculators have (such as precisely calculating y- and x-intercepts), it has some unique benefits:

(I introduced this list of calculators previously)

- TI-83 Plus

- Features only in FormulaGraph: interactive graphs, animate with time, available in any web browser, graph formulas (not just functions)

- WolframAlpha

- Features only in FormulaGraph: interactive graphs, animate with time

- WolframAlpha won't graph some formulas, e.g. y^2+x^x=1 and x^y=x^x^x^x^x^x

- Meta-Calculator

- Features only in FormulaGraph: interactive graphs, animate with time, graph formulas (not just functions), modulo operator

- FooPlot

- Features only in FormulaGraph: animate with time, graph formulas (not just functions), graph inequalities

- GeoGebra Graphing Calculator

- Features only in FormulaGraph: available in any web browser, graph polar formulas, modulo operator

- GeoGebra Graphing Calculator won't graph some formulas, e.g. x*y*(x-y)*(x+y) < 1

- Desmos

- Desmos is really great. It's the best graphing calculator in this list. It can handle formulas, animations, polar formulas, and more!

- One thing FormulaGraph handles better: Desmos doesn't support polar equations which aren't linear in r. Example: r^2 = cos(theta)

- Desmos has another quirky inconvenience: it's not possible to copy/paste formulas or easily edit their text. FormulaGraph's formula input boxes are super convenient in comparison. This feature is a must-have to make complex shapes like the Batman Curve.

- NumWorks Graphing Calculator

- Features only in FormulaGraph: interactive graphs, convenient UI, animate with time, available in any web browser, graph formulas (not just functions), graph polar formulas, graph inequalities

- Relplot

- Features only in FormulaGraph: interactive graphs, animate with time

- Relplot has some graphing correctness bugs. Examples: y = x mod 2, y = x^x, y^y = x^x, r = 2*sin(4*theta), r = theta covers a small range of theta values

- I'm reporting these bugs and I hope they get fixed!

- Graphtoy

- Features only in FormulaGraph: graph formulas (not just functions), graph polar formulas, graph inequalities

- Graphtoy has visual glitches (missing segments) when drawing circles and other similar shapes (e.g. y = sqrt(1-x^2) and y = -sqrt(1-x^2))

- Bonus: FormulaGraph has tons of test coverage, including checks that graphs render accurately. You can run the tests here. Beware: they take ~1 minute to run!

FormulaGraph's origins:

This is a hobby project I made by combining code and ideas from other peoples' cool projects.

It all started with Relplot, a project by Cornell's Andrew Myers. In Professor Myers' functional programming class, we implemented formula plotters using interval arithmetic.

Over a decade later, I stumbled upon Inigo Quilez's Graphtoy. I had fun making shapes and I thought it'd be even more fun to bring Relplot's general formula capabilities to Graphtoy's interactive UI.

So that's what I did: I ported Relplot's implementation of interval arithmetic from SML to JavaScript and spliced it into Graphtoy. I really appreciate all the work Professor Myers and Inigo did, especially publishing the source code for these projects.

There was one remaining difficulty: While Graphtoy uses JavaScript's built-in parser for inputted expressions, I needed to completely replace the implementations of operators like +, -, *, /, %. JavaScript doesn't have operator overloading so I needed my own expression parser.

That's where Lawrence Kesteloot's awesome web implementation of Turbo Pascal (my backup) comes in (see his interesting writeup about the project). I simply copied and reused his expression parser. Thank you Lawrence for building and sharing that passion project! I particularly like that this project has very few dependencies. And I was able to remove the few 3rd party dependencies it had in the portion of code I reused.

Key integration points where these projects are glued together:

- relplot.js - look for "MAIntervalMath" for a reimplementation of Relplot's interval arithmetic

- graphtoy.js - look for "Draw Relplot graphs" to see where Relplot was added to Graphtoy

- utilities.js - look for "_convertFormulaToFnSyntax" to see where the parser comes into play

Here are some miscellaneous things I've learned during this project:

- Some equations have solutions that are not clearly visible on most graphing calculators. For example, y = x^x has valid solutions for negative values of x which are single points (e.g. (-1, -1)).

- New shapes:

- Square

- Heart

- Crazy lotus

- Modulo

- Create concentric shapes using modulo

- Create repeating animations using modulo

- There are different definitions of modulo for negative numbers

- Many programming languages implement modulo as "truncated division"

- Desmos, Relplot, and Graphtoy use "floored division" so I use that in FormulaPlot too

- How to convert Cartesian coordinates to polar

- Based on Relplot's implementation, I thought I just had to replace "r" with "sqrt(x^2+y^2)" and "theta" with "atan2(y, x)".

- This turned out to be much harder than I expected in part because Relplot's polar coodinate support is incomplete (noted above in my comparison of FormulaGraph and Relplot).

- After what I thought would be a 20 minute feature consumed several weekends, I'm reminded of the tongue-in-cheek Programmers' Credo: "We do these things not because they are easy, but because we thought they were going to be easy"

- I solved this with 3 techniques: graph positive r and negative r (with theta shifted by pi), translate atan2's result range to [0, 2pi] (instead of [-pi, pi]), and iteratively render with theta += 2pi.

- Debugging an incorrect graph is similar to general debugging. Try to find the simplest case that reproduces the issue. I was relieved when a glitch in 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))) reproduced in the far simpler y = ceil(x-floor(x)).

- When using interval arithmetic to graph formulas, it's ok to return wide intervals when the input is wide as long as the result intervals converge as the inputs get smaller. I use this technique to avoid the incorrect vertical line segments some graphing calculators (including Relplot and Graphtoy) show for y = floor(x).

To conclude, here's an improved version of the bouncing ball animation (original here).

Happy graphing :)

Posted on 3/28/2021

Tags: Math

I mentioned here that TI calculators can make some simple animations:Tags: Math

"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

Tags: Math

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.Tags: Math

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.=============================

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

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.A flipped sawtooth wave looks like this: |\|\|\|\

A triangle wave looks like this: /\/\/\/\

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)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)

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 linky = mod(floor(x), 2) * (x-floor(x)) + (1-mod(floor(x), 2)) * (-x-floor(-x)))

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.============================

0 0 1

1 0 0

2 0 1

3 0 0

4 0 1

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: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

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

Inigo Quilez's Useful Little Functions (my backup)

Posted on 3/21/2021

Tags: Math

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.Tags: Math

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:

- FormulaGraph

- ** Added to this list in August 2021 **

- After originally publishing this list, I built my own formula grapher by combining Relplot and Graphtoy (both mentioned below)

- Features: interactive graphs, convenient UI, animate with time, available in any web browser, graph formulas (not just functions), graph polar formulas, graph inequalities

- See the full details here

- 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

- FooPlot

- Source code

- Fairly full-featured!

- 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.

- Desmos Graphing Calculator

- This app might be even better. It also supports polar equations (e.g. r = cos(4*theta)).

- Also available as a website

- 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)"| 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))))

- 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

Tags: Leisure

I like listening to music. I like it even more when I discover connections between songs.Tags: Leisure

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

Tags: Programming

Visiting Reddit can be a nice way to relax. Reddit can be many things: entertaining, mindless, funny, eye-opening, and more.Tags: Programming

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: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

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: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")

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")

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")

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

Tags: Interactive Fiction

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.Tags: 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.

More posts:

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