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

+18 -2
View File
@@ -3,14 +3,17 @@
#include <Arduino.h>
#include <string.h>
#include "logic/life.h"
#include "logic/settings.h"
static const char *const TYPES[] = {"clock", "date", "ha", "text", "countdown", "stopwatch", "image", "bar"};
static const char *const TYPES[] = {"clock", "date", "ha", "text", "countdown", "stopwatch", "image", "bar", "life"};
static const char *const ALIGNS[] = {"left", "center", "right"};
static const char *const OVERLAYS[] = {"top", "box", "invert"};
static const char *const LANGUAGES[] = {"nl", "en"};
static const char *const SHOWS[] = {"number", "duration", "time", "map"};
static const char *const UNITS[] = {"s", "min", "h"};
// Game of Life (in input_unit, see logic/page.h)
static const char *const LIFE_STARTS[] = {"random", "pattern", "rotate", "shuffle"};
template <size_t N> static int indexOf(const char *const (&names)[N], const char *name)
{
@@ -66,7 +69,12 @@ void pageToJson(const DynamicPage &page, JsonObject out)
formatTarget(e.target, target);
o["target"] = String(target);
o["show"] = SHOWS[(int)e.show];
o["input_unit"] = UNITS[e.input_unit];
o["input_unit"] = UNITS[e.input_unit < UNIT_COUNT ? e.input_unit : 0];
if (e.type == ElementType::Life)
{
o["start"] = LIFE_STARTS[e.input_unit < LIFE_START_COUNT ? e.input_unit : 0];
o["generations"] = e.target;
}
o["auto_unit"] = e.auto_unit;
o["absolute"] = e.absolute;
o["map"] = withDegrees(e.map);
@@ -148,6 +156,14 @@ const char *pageFromJson(JsonVariantConst in, DynamicPage &page)
const char *target = o["target"] | "";
if (e.type == ElementType::Countdown && !parseTarget(target, e.target))
return "Countdown: enter a date and time";
if (e.type == ElementType::Life)
{
int start = indexOf(LIFE_STARTS, o["start"] | "random");
if (start < 0)
return "Game of Life: unknown start";
e.input_unit = start;
e.target = constrain(o["generations"] | 0.0, 0.0, (double)LIFE_GENERATIONS_MAX + 1);
}
if (!copyText(o["font"], e.font, sizeof(e.font)) || !copyText(o["text"], e.text, sizeof(e.text)) ||
!copyText(o["suffix"], e.suffix, sizeof(e.suffix)) || !copyText(o["entity"], e.entity, sizeof(e.entity)) ||
!copyText(o["attribute"], e.attribute, sizeof(e.attribute)))