Desk Pal fortune 8-ball

A magic 8-ball you print yourself. Ask a yes-or-no question, shake or tap, and a blue triangle swirls up with your answer. Print two parts, click in an ESP32-S3 touch screen, and learn how states, random numbers and motion sensors work.

DifficultyBeginner
Build time45 min
Est. cost$30
Parts4
Files9
Steps14

Things you'll need

QtyPartType
1
Waveshare ESP32-S3-Touch-LCD-1.28 board
Waveshare ESP32-S3-Touch-LCD-1.28 board The brain, a round 1.28-inch touch screen and a motion sensor in one board. No soldering.
electronics
2
Printed parts
Printed parts Back cup and front ring. Both are in the 3MF below on one plate, no supports.
printed
1
USB-C data cable Powers the board and loads the program. It must be a data cable, not a charge-only one.
electronics
11 g
PLA filament Black makes it look like a real 8-ball. Any PLA works.
consumable

Tools & software

1
Chrome or Edge browser Loads the program onto the board straight from this page. Nothing to install.
1
VS Code + PlatformIO (optional) Free. Only needed if you want to change the code later. See 'Level up' at the end.

Files & downloads

Download all

Flash writes a precompiled image over USB-C in Chrome or Edge. The .ino below is the source — you do not need Arduino IDE unless you want to change the sketch.

What you're building

A magic 8-ball that fits in your hand. Ask it a yes-or-no question, then shake it or tap the glass. The big 8 fades away, a window opens, and a blue triangle floats up with your answer. Twenty answers, including a few that only a 3D-printing fan would give.

No soldering. No wires. You print two parts, drop in one board, push the halves together, plug in one cable, and click one button on this page to load the program.

Note

It isn't magic, it's maths. The answer is picked by a random number generator inside the chip, the same way a video game rolls dice. The swirl is only there to build suspense. (So maybe don't use it for big decisions.)

What you'll learn

  • How a state machine keeps track of what the toy is doing: waiting, swirling or answering
  • How a motion sensor feels a shake, and ignores a bump
  • How random numbers pick an answer, and how to stop the same one coming twice
  • How an animation is just a number going from 0 to 1
  • Why drawing into memory first stops the screen flickering

Meet the parts

The black printed front ring above the black back cup
Two printed parts: the front ring (top) and the back cup (bottom). Add one board and you're done.
  1. Back cup: holds everything. It has a window in the side for the USB-C plug and a slot in the back, so you can push the board out again later.
  2. Front ring: the bezel around the screen. It has a small notch where the USB-C port goes.
  3. The board: a Waveshare ESP32-S3 with a round 1.28-inch touch screen and a motion sensor built in. Brain, screen and sensor in one.
Tip

Same case, different toy. This round case is shared with the touch doodle. Black looks like a real 8-ball, but any colour works.

Note

Before you start. The printer works on its own for about 25 minutes. You do about 5 minutes of clicking parts together and 2 minutes of loading the program. No tools needed.


Part 1: Print

One plate, two parts, one colour. Start the printer, then read Part 2 while it works.

1

Download the 3MF

Grab Print-ready 3MF from Files & downloads above and open it in Bambu Studio. Using another slicer? Open the STL instead; both parts are in it, already lying flat.

Download the 3MF
2

Pick a colour and print

Settings are already in the file: 0.2 mm layers, 2 walls, 15% infill, no supports. The cup prints with its back on the plate, open side up, and the ring prints flat. About 25 minutes on a Bambu P1S.

Pick a colour and print
3

Check the parts

Run a fingertip around the inside of the cup and the ring. Pick off any strings or blobs, especially around the USB-C window in the side of the cup and the notch in the ring. The board has to slide in without catching.


Part 2: Put it together

No glue, no screws. Three moves: board into the ring, ring into the cup, check the port. If a part feels tight, wiggle it; never force it.

1

Drop the board into the ring

Lay the front ring on the table. Put the board in screen-down, so you are looking at the back of the board with all the little chips. Turn it until the USB-C port sits in the notch of the ring.

Drop the board into the ring
2

Slide it into the cup, USB-C first

Pick up the ring with the board in it. Tilt it and slide the USB-C end in first, so the port pokes into the window in the side of the cup. Then lower the other side down.

Slide it into the cup, USB-C first
3

Press it flat and centre the port

Press around the ring until it sits flush with the cup all the way round. Look at the side window: the USB-C port should sit right in the middle, so the cable plugs in straight and doesn't pull the board sideways.

Press it flat and centre the port

Part 3: Load the program

New boards come with a factory demo. You are going to replace it with the 8-ball program, straight from this web page. That takes about a minute.

Note

Word to know: flash. Flashing means copying a program onto a chip. The chip keeps it even when unplugged, like saving a game.

1

Plug in

Connect the USB-C cable from the board to a computer (Windows, Mac, Chromebook or Linux). It must be a data cable, not a charge-only one.

2

Click Flash to board

Scroll up to Files & downloads and click Flash to board. Use Chrome or Edge. Pick the port that appears, click Install, and wait for the green tick.

3

Watch it wake up

A black ball with a big white 8 appears. It's waiting for your first question.

Watch it wake up
Tip

No port in the list? The BOOT button is hidden inside the case. Push the board out through the slot in the back, hold BOOT while you plug the cable in, let go, then click Flash again. Phones and Safari can't flash; use a computer.


Play it

Ask it anything with a yes or no answer. Then:

1

Shake = ask

Hold the puck and its cable together and give it a firm shake. The 8 fades, the blue window swirls, and after about a second and a half the triangle rises with your answer.

Shake = ask
2

Tap = ask, too

Don't feel like shaking? Tap the glass once. Same thing.

3

Tap during the swirl = skip

Too much suspense? Tap while it swirls and the answer appears straight away.

4

Tap the answer = back to the 8

When you've read it, tap once and the 8 comes back, ready for the next question.

5

Shake really hard = big swirl

A hard shake makes a longer, wilder swirl: 2.4 seconds instead of 1.6, with extra particles. The answer is just as random either way.


How it works: four tiny lessons

You don't need to read the whole program (it's in Files & downloads if you're curious). Each lesson below shows the one small piece of code that does the job, and gives you something to try.

Lesson 1: Three states

At any moment the 8-ball is doing exactly one of three things: idle (showing the 8), swirl (building suspense) or reveal (showing the answer). Programmers call this a state machine.

The same tap means something different in each state. That's why one finger is enough to ask, skip and go back.

fortune-8ball.ino
enum State { ST_IDLE, ST_SWIRL, ST_REVEAL };   // the only three things it can be doing

static void onTap() {
  if (state == ST_SWIRL)  { startReveal(now); return; }  // impatient? jump to the answer
  if (state == ST_REVEAL) { goIdle();         return; }  // done reading: back to the 8
  startSwirl(now, false);                                // waiting: a tap asks the question
}

Simplified from onTap().

Tip

Try it: draw the three states as bubbles on paper and add an arrow for every way to get from one to the next (tap, shake, time's up). How many arrows do you need? (Hint: the swirl ends by itself after 1.6 seconds, so that's an arrow too.)

Lesson 2: Feeling a shake

The board has a motion sensor that measures pushes in three directions (an accelerometer) and spinning (a gyroscope). Lying still, it feels exactly 1 g: gravity. A shake pushes it far above or below 1 g.

One bump shouldn't ask a question, so the program uses a leaky bucket. Every reading, the bucket loses 16% of what's in it and each jolt pours a little in. A single bump leaks away. A real shake fills it past the line. After that, it ignores motion for 0.9 seconds, so one shake asks one question.

fortune-8ball.ino
const float dev = fabsf(mag - 1.0f);                             // how far from plain gravity (1 g)?
const float accTerm = dev > 0.28f ? (dev - 0.28f) * 0.48f : 0;   // ignore tiny wobbles
const float gyroTerm = gyro > 130 ? (gyro - 130) / 1300.0f : 0;  // spinning counts too
shakeEnergy = shakeEnergy * 0.84f + accTerm + gyroTerm;          // the leaky bucket
if (shakeEnergy > 2.35f) shaking = true;                         // full enough: that's a shake

if (state == ST_IDLE && shaking) startSwirl(now, shakeEnergy > 3.2f);  // over 3.2 = big swirl

Simplified from updateMotion().

Tip

Try it: put the puck on the table and tap the table hard. Does it ask a question? Usually not: one bump leaks out of the bucket before it fills. Now pick it up and shake. Type status in the Serial Monitor straight after to see the energy= number.

Lesson 3: Random, but never twice in a row

esp_random() asks the chip's built-in random number generator for a huge random number. The % sign means remainder after dividing: any number % 20 gives something from 0 to 19, one for each answer. Like rolling a 20-sided dice.

Real randomness can give the same answer twice in a row, which feels broken. So if the new answer matches the last one, it rolls again.

fortune-8ball.ino
static void pickFortune() {
  int next = esp_random() % FORTUNE_COUNT;    // roll a 20-sided dice: 0 to 19
  while (next == lastFortune) {               // same as last time?
    next = esp_random() % FORTUNE_COUNT;      // roll again
  }
  fortuneIndex = next;
  lastFortune = next;
}

Simplified from pickFortune().

Tip

Try it: ask 20 questions and tick off each answer you get. Do you see all 20? Almost certainly not! On average you need about 70 shakes to collect every answer. Mathematicians call this the coupon collector's problem, and it's why sticker albums take forever to fill.

Lesson 4: An animation is a number from 0 to 1

When the swirl starts, the program sets a stopwatch. Every frame it works out how far through the swirl it is, as a number u from 0 (just started) to 1 (finished). Every moving part is worked out from that one number: the 8 fades in the first 22%, the window opens by 38%, the triangle rises after halfway, and the words appear at the very end.

easeOut makes things start fast and slow down gently, like a ball rolling to a stop. That's why it feels smooth instead of robotic.

fortune-8ball.ino
float u = (now - swirlAt) / (float)swirlMs;     // 0 at the start, 1 at the end
eight    = 1 - easeOut(u / 0.22f);              // the 8 fades in the first 22%
winR     = WIN_R * easeOut(u / 0.38f);          // the window opens by 38%
triRise  = easeOut((u - 0.48f) / 0.40f);        // the triangle rises after halfway
textFade = easeInOut((u - 0.82f) / 0.16f);      // the words appear at the very end

Simplified from drawFrame(). The real code also clamps each value so it stays between 0 and 1.

Tip

Try it: watch a swirl and spot the four moments: 8 fades, window opens, triangle rises, words appear. Then do a hard shake: same four moments, stretched over 2.4 seconds.

Note

Why doesn't it flicker? Every frame is drawn into a sprite: a 240 × 240 picture kept in memory (about 115 KB). Only when it's finished does the program copy it to the screen in one go. Like finishing a drawing before you hold it up.


Secret commands

Your computer can chat with the board through the same USB-C cable. That chat is called serial. Open the Serial Monitor in VS Code (the plug icon in the PlatformIO bar) or any serial app at 115200, type a word, press Enter.

Type this What happens
help lists the commands
tap same as tapping the glass
shake starts a swirl without shaking
fortune shows a new answer instantly, no swirl
status tells you the state (idle, swirl or reveal), the answer number and the shake energy
Tip

Spy on the answers. Every time an answer appears, the board also types it into the Serial Monitor, like fortune: THE SPAGHETTI KNOWS. Leave the monitor open and you get a list of every answer it has given.


Level up: change the code

Ready to make it yours? The easiest way to change the code is PlatformIO, a free add-on for VS Code. It downloads the right libraries and settings for this board by itself.

  1. Install VS Code, then open Extensions and install PlatformIO IDE.
  2. Download platformio.zip from Files & downloads, unzip it, and open the fortune-8ball-platformio folder in VS Code (File → Open Folder).
  3. Open fortune-8ball/fortune-8ball.ino. Find the FORTUNES list near the top.
  4. Plug in the board and click the → (Upload) arrow in the blue bar at the bottom of VS Code. The first build takes a few minutes while it downloads everything; after that it takes seconds.

Ideas, easiest first:

  • Write your own answers: each one is two short lines. Use CAPITALS and keep each line to about 12 letters so it fits inside the triangle.
  • More suspense: change SWIRL_MS = 1600 to 3000.
  • Easier to shake: lower SHAKE_ENTER from 2.35f to 1.8f.
fortune-8ball.ino
static const Fortune FORTUNES[] = {
    {"IT IS", "CERTAIN"},
    ...
    {"ASK YOUR", "PRINTER"},
    {"THE SPAGHETTI", "KNOWS"},
    {"PIZZA FOR", "DINNER"},     // your own answer: top line, bottom line
};

One answer is one line. The program counts the answers by itself.


If something goes wrong

  • Screen stays black after flashing. Unplug and plug back in. Still black? Try a different USB-C cable; many are charge-only.
  • No port shows up. Push the board out of the case, hold BOOT while you plug it in, let go, and click Flash again.
  • Shaking does nothing. Take your finger off the glass first: while you touch it, the shake sensor is paused. Short, sharp shakes work best. Or just tap.
  • It answered when I put it down. A hard thump can count as a shake. Tap the answer to go back.
  • My own answer is cut off. One of its lines is too long. Split it differently or use shorter words.
  • The cable won't plug in. The board is a bit crooked. Press the ring down flat and check that the port sits in the middle of the side window.

Taking it apart

The back of the black cup with its slot, held in a hand
**Need to take it apart?** Unplug the cable. Push through the slot in the back of the cup with a fingertip or the blunt end of a pencil. The ring and the board pop out of the front.

Build photos

Licensed under CC BY-SA 4.0. Check with the author before commercial use.

Community makes

Built this? Share a photo of your print so others can see how it turned out.

No makes yet. Be the first.