Nyan Cat
The classic nyan cat - pop-tart cat with a rainbow trail and twinkling stars.
By maxswinkels · ▲ 0 · Rate and Discuss
How It Works
The classic nyan cat - pop-tart cat with a rainbow trail and twinkling stars.
Download and extract the complete app folder. Keep its files together; some apps include additional modules, assets, or configuration examples.
Review app.py and any configuration examples for this app’s options, then run it from the extracted folder:
python3 app.pyConnect BUSY Bar over USB, or use the connection options supported by this app. See the official HTTP API documentation for device setup.
Source Files
These are the mirrored source bytes. The complete package also includes every asset and supporting file.
app.py
#!/usr/bin/env python3
"""Nyan cat: pop-tart cat with a rainbow trail and twinkling stars.
python app.py # BUSY Bar over USB (always 10.0.4.20)
python app.py --host 127.0.0.1:8080 # emulator or a Wi-Fi bar
Each frame is rendered to a single 72x16 image and pushed as one image element.
The cat is ~65 rects; drawing them individually costs ~3.6 ms each (~4 fps on
real hardware), while one full-frame image is a flat ~50 ms, so it runs smooth.
"""
import json
import random
import struct
import sys
import time
import urllib.error
import urllib.request
import zlib
APP = "nyan-cat"
W, H = 72, 16
# ---------------------------------------------------------------------------
# BUSY Bar HTTP API — self-contained, stdlib only.
# Over USB the bar is always at 10.0.4.20; --host targets a Wi-Fi bar or the
# emulator. Full API docs are served by the device: http://10.0.4.20/docs
# ---------------------------------------------------------------------------
def _host(default="10.0.4.20"):
if "--host" in sys.argv:
i = sys.argv.index("--host")
if i + 1 < len(sys.argv):
return sys.argv[i + 1]
return default
BASE = "http://" + _host().replace("http://", "").rstrip("/")
def _png(pixels):
"""72x16 flat list of (r, g, b) -> minimal RGBA PNG bytes (stdlib only)."""
raw = bytearray()
for y in range(H):
raw.append(0)
base = y * W
for x in range(W):
r, g, b = pixels[base + x]
raw += bytes((r, g, b, 255))
def _chunk(tag, data):
c = tag + data
return struct.pack(">I", len(data)) + c + struct.pack(">I", zlib.crc32(c) & 0xffffffff)
return (b"\x89PNG\r\n\x1a\n"
+ _chunk(b"IHDR", struct.pack(">IIBBBBB", W, H, 8, 6, 0, 0, 0))
+ _chunk(b"IDAT", zlib.compress(bytes(raw), 6))
+ _chunk(b"IEND", b""))
# The device briefly locks an asset while a draw reads it; re-uploading the same
# name too soon returns HTTP 508, so rotate through a few filenames.
_RING = 4
_frame_no = 0
def _post(path, data, content_type):
req = urllib.request.Request(BASE + path, data=data, method="POST",
headers={"Content-Type": content_type})
with urllib.request.urlopen(req, timeout=5):
pass
def show(pixels):
"""Upload the frame PNG and draw it as one image."""
global _frame_no
fn = "frame%d.png" % (_frame_no % _RING)
_frame_no += 1
_post("/api/assets/upload?application_name=%s&file=%s" % (APP, fn),
_png(pixels), "application/octet-stream")
body = {"application_name": APP,
"elements": [{"id": "frame", "type": "image", "path": fn, "x": 0, "y": 0}]}
try:
_post("/api/display/draw", json.dumps(body).encode(), "application/json")
except urllib.error.HTTPError as e:
if e.code != 409: # 409 = a higher-priority app owns the display
raise
def _clear():
req = urllib.request.Request(
BASE + "/api/display/draw?application_name=" + APP, method="DELETE")
try:
with urllib.request.urlopen(req, timeout=5):
pass
except urllib.error.URLError:
pass
# ---------------------------------------------------------------------------
# Palette and layout (colours as (r, g, b))
# ---------------------------------------------------------------------------
CRUST = (0xFF, 0xCC, 0x99)
FROSTING = (0xFF, 0x99, 0xFF)
SPRINKLE = (0xDD, 0x33, 0x88)
GRAY = (0x99, 0x99, 0x99)
BLACK = (0x00, 0x00, 0x00)
CHEEK = (0xFF, 0x99, 0x99)
STAR = (0xFF, 0xFF, 0xFF)
RAINBOW = [(0xFF, 0x00, 0x00), (0xFF, 0x99, 0x00), (0xFF, 0xFF, 0x00),
(0x33, 0xFF, 0x00), (0x00, 0x99, 0xFF), (0x66, 0x33, 0xFF)]
CX, BY = 44, 3 # pop-tart body top-left
HX, HY = CX + 9, 5 # head top-left (overlaps the body's right side)
TRAIL_END = CX - 5 # rainbow stops before the tail so the gray tail reads
def _blank():
return [(0, 0, 0)] * (W * H)
def _rect(buf, x, y, w, h, rgb):
"""Fill a solid 1-color rect into the pixel buffer, clipped to the display."""
x2, y2 = min(W, x + w), min(H, y + h)
x, y = max(0, x), max(0, y)
for yy in range(y, y2):
base = yy * W
for xx in range(x, x2):
buf[base + xx] = rgb
# ---------------------------------------------------------------------------
# Stars (drawn first: they twinkle behind the rainbow and the cat)
# ---------------------------------------------------------------------------
_stars = [{"x": 8, "y": 3, "p": 0}, {"x": 26, "y": 13, "p": 2},
{"x": 46, "y": 1, "p": 1}, {"x": 66, "y": 11, "p": 3}]
def _tick_stars(buf):
for s in _stars:
s["x"] -= 3
s["p"] = (s["p"] + 1) % 4
if s["x"] < -2:
s["x"] = W + random.randint(0, 10)
s["y"] = random.randint(1, H - 2)
x, y, p = s["x"], s["y"], s["p"]
if p == 0:
_rect(buf, x, y, 1, 1, STAR)
elif p == 1:
_rect(buf, x - 1, y, 3, 1, STAR)
_rect(buf, x, y - 1, 1, 3, STAR)
elif p == 2:
_rect(buf, x - 2, y, 5, 1, STAR)
_rect(buf, x, y - 2, 1, 5, STAR)
else:
for dx, dy in ((-2, 0), (2, 0), (0, -2), (0, 2)):
_rect(buf, x + dx, y + dy, 1, 1, STAR)
# ---------------------------------------------------------------------------
# Rainbow trail: 6 bands of 2px, wiggling in 8px blocks that alternate offset
# ---------------------------------------------------------------------------
def _rainbow(buf, phase):
for band, color in enumerate(RAINBOW):
y = 2 + band * 2
x = 0
while x < TRAIL_END:
w = min(8, TRAIL_END - x)
off = (x // 8 + phase) % 2
_rect(buf, x, y + off, w, 2, color)
x += w
# ---------------------------------------------------------------------------
# The cat: layered rects, later writes draw on top of earlier ones
# ---------------------------------------------------------------------------
def _cat(buf, phase):
bob = phase # body bobs 1px down every other beat
by, hy = BY + bob, HY + bob
# tail (flips up/down against the bob)
_rect(buf, CX - 2, by + 5, 2, 2, GRAY)
if phase == 0:
_rect(buf, CX - 4, by + 3, 2, 2, GRAY)
else:
_rect(buf, CX - 4, by + 7, 2, 2, GRAY)
# legs stay planted; a 1px x-shuffle suggests the gallop
for lx in (CX + 1, CX + 5, CX + 10, CX + 14):
_rect(buf, lx + bob, 13, 2, 2, GRAY)
# pop-tart body (inset top/bottom rows fake the rounded corners)
_rect(buf, CX + 1, by, 12, 1, CRUST)
_rect(buf, CX, by + 1, 14, 8, CRUST)
_rect(buf, CX + 1, by + 9, 12, 1, CRUST)
_rect(buf, CX + 1, by + 1, 12, 8, FROSTING)
for sx, sy in ((2, 2), (6, 3), (3, 5), (7, 6), (5, 7)):
_rect(buf, CX + sx, by + sy, 1, 1, SPRINKLE)
# head + ears
_rect(buf, HX + 1, hy, 8, 1, GRAY)
_rect(buf, HX, hy + 1, 10, 6, GRAY)
_rect(buf, HX + 1, hy + 7, 8, 1, GRAY)
_rect(buf, HX + 1, hy - 2, 1, 1, GRAY)
_rect(buf, HX + 1, hy - 1, 2, 1, GRAY)
_rect(buf, HX + 8, hy - 2, 1, 1, GRAY)
_rect(buf, HX + 7, hy - 1, 2, 1, GRAY)
# face: eyes, cheeks, smile
_rect(buf, HX + 2, hy + 2, 1, 1, BLACK)
_rect(buf, HX + 7, hy + 2, 1, 1, BLACK)
_rect(buf, HX + 1, hy + 4, 1, 1, CHEEK)
_rect(buf, HX + 8, hy + 4, 1, 1, CHEEK)
_rect(buf, HX + 2, hy + 4, 1, 1, BLACK)
_rect(buf, HX + 6, hy + 4, 1, 1, BLACK)
_rect(buf, HX + 3, hy + 5, 3, 1, BLACK)
# ---------------------------------------------------------------------------
# Main loop
# ---------------------------------------------------------------------------
_t = [0]
FRAME_T = 0.08 # ~12.5 fps; the image push itself takes ~50 ms
def tick():
_t[0] += 1
phase = (_t[0] // 3) % 2
buf = _blank()
_tick_stars(buf)
_rainbow(buf, phase)
_cat(buf, phase)
show(buf)
if __name__ == "__main__":
print(f"nyan_cat → {BASE} (Ctrl-C to stop)")
try:
while True:
t0 = time.monotonic()
tick()
dt = time.monotonic() - t0
if dt < FRAME_T:
time.sleep(FRAME_T - dt)
except KeyboardInterrupt:
print("\nstopped.")
except urllib.error.HTTPError as e:
sys.exit(f"error: HTTP {e.code} — {e.read().decode('utf-8', 'ignore')}")
except urllib.error.URLError as e:
sys.exit(f"error: cannot reach {BASE} — {e.reason}")
finally:
_clear()manifest.yaml
name: Nyan Cat
author: maxswinkels
description: The classic nyan cat - pop-tart cat with a rainbow trail and twinkling stars.
tags:
- animation
- effect
preview: ./preview.gifSource and Provenance
Source commit: 26b19ba9ce2ba192c45b4c0eadcfad53df42b153
Content SHA-256: 979efbf16ecbd9570cc5307d43a8b4bcec89265b866d2ed8d58a8d45f78bbed4
Rate and Discuss
Comments and reactions live in this site’s GitHub repository. Sign in with GitHub to participate.





