Game of Life page element and built-in page

A new page element runs Conway's Game of Life in an area (the whole display
by default), one per page. Start: random dots, one pattern, or the icons
whose names start with a text in turn or in random order. Edges: wrap
around, dead, or a hidden margin where gliders fly out of view (leftovers
that stay put in the margin are cleared, so a glider gun runs cleanly).
It restarts when the board is still or repeats within 16 generations
and/or after a number of generations, after a hold time (an empty board
at once).

logic/life holds the board and timing (tested), display/life_player runs
the board of the shown page, ticked before each frame; frames are paced
like scrolling text. The element reuses existing fields, so the page
format stays the same. The Icons tab has a library of 17 LifeWiki
patterns (checked with a simulation), and the built-in page "Game of
Life" shuffles through the life_ icons. About 12 KB flash, 8 KB RAM.

Version 2.17.0, tested on the display over OTA.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-20 17:52:25 +02:00
1 parent 948087077d
commit a91d2550d7
29 files changed
+1402 -31

No files matched your search

+58
View File
@@ -156,6 +156,62 @@ static void drawBar(const PageElement &e, PageResources &resources, uint8_t *bit
drawBarPercent(e, resources, bitmap, left + inner, e.y + inner, w - 2 * inner, h - 2 * inner);
}
// Life without a running board (a preview of a page that is not shown): the start, the
// pattern in the middle or a random fill that is always the same, in area coordinates
static void lifeStart(const PageElement &e, int w, int h, PageResources &resources, uint8_t *start)
{
memset(start, 0, BITMAP_BYTES);
uint32_t random = 1;
uint8_t pattern[BITMAP_BYTES];
int dx, dy;
if (e.input_unit == LIFE_START_PATTERN)
{
if (!resources.background(e.text, pattern) || !lifePlacement(pattern, w, h, false, random, dx, dy))
return;
for (int y = 0; y < BITMAP_HEIGHT; y++)
for (int x = 0; x < BITMAP_WIDTH; x++)
if (bitmapPixel(pattern, x, y))
setBitmapPixel(start, x + dx, y + dy, true);
return;
}
int percent = e.input_unit == LIFE_START_RANDOM ? (int)e.factor : 30;
for (int y = 0; y < h; y++)
for (int x = 0; x < w; x++)
setBitmapPixel(start, x, y, (int)(lifeRandom(random) % 100) < percent);
}
// Life: the running board of the shown page; returns the ms until its next generation
static uint32_t drawLife(const PageElement &e, uint32_t now_ms, PageResources &resources, uint8_t *bitmap,
ElementBox &box)
{
int w = e.bar_width, h = e.bar_height;
int left = e.x;
if (e.align == Align::Center)
left -= w / 2;
else if (e.align == Align::Right)
left -= w;
box.x = left;
box.y = e.y;
box.width = w;
box.height = h;
if (e.overlay == Overlay::Box)
clearBox(bitmap, left - 1, e.y - 1, w + 2, h + 2);
const LifeRunner *runner = resources.life(e);
uint8_t start[BITMAP_BYTES];
if (!runner)
lifeStart(e, w, h, resources, start);
for (int y = 0; y < h; y++)
for (int x = 0; x < w; x++)
{
if (!(runner ? runner->board.cell(x, y) : bitmapPixel(start, x, y)))
continue;
int bx = left + x, by = e.y + y;
setBitmapPixel(bitmap, bx, by, e.overlay == Overlay::Invert ? !bitmapPixel(bitmap, bx, by) : true);
}
return runner ? runner->msUntilNext(now_ms) : 0;
}
// A text element; returns the ms until it scrolls on (0: it doesn't scroll)
static uint32_t drawTextElement(const PageElement &e, const char *text, uint32_t now_ms, PageResources &resources,
uint8_t *bitmap, ElementBox &box)
@@ -223,6 +279,8 @@ uint32_t renderPage(const DynamicPage &page, const LocalTime &time, uint32_t now
drawImage(e, resources, bitmap, box);
else if (e.type == ElementType::Bar)
drawBar(e, resources, bitmap, box);
else if (e.type == ElementType::Life)
next = sooner(next, drawLife(e, now_ms, resources, bitmap, box));
else
{
ElementValues values;