Public Access
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:
1 parent
948087077d
commit
a91d2550d7
29 files changed
+1402
-31
No files matched your search
@@ -19,6 +19,7 @@
|
||||
#include "logic/builtin_pages.h"
|
||||
#include "logic/font.h"
|
||||
#include "logic/icon_format.h"
|
||||
#include "logic/life.h"
|
||||
#include "logic/boot_policy.h"
|
||||
#include "logic/display_controller.h"
|
||||
#include "logic/page.h"
|
||||
@@ -1325,6 +1326,22 @@ void test_render_unknown_font_uses_normal()
|
||||
TEST_ASSERT_EQUAL(29, box.width);
|
||||
}
|
||||
|
||||
void test_builtin_life_page()
|
||||
{
|
||||
DynamicPage page;
|
||||
TEST_ASSERT_TRUE(builtinPage("game of life", page));
|
||||
TEST_ASSERT_NULL(validatePage(page));
|
||||
TEST_ASSERT_EQUAL_STRING(BUILTIN_PAGE_LIFE, builtinPageName(1));
|
||||
TEST_ASSERT_EQUAL_STRING(BUILTIN_PAGE_LIFE, builtinPageName("GAME OF LIFE"));
|
||||
TEST_ASSERT_EQUAL(2, builtinPageCount());
|
||||
const PageElement &e = page.elements[0];
|
||||
TEST_ASSERT_EQUAL(ElementType::Life, e.type);
|
||||
TEST_ASSERT_EQUAL(BITMAP_WIDTH, e.bar_width);
|
||||
TEST_ASSERT_EQUAL(BITMAP_HEIGHT, e.bar_height);
|
||||
TEST_ASSERT_EQUAL(LIFE_START_SHUFFLE, e.input_unit);
|
||||
TEST_ASSERT_FALSE(pageUsesImage(page, "life_glider")); // patterns can be deleted
|
||||
}
|
||||
|
||||
void test_builtin_clock_page_is_the_old_clock()
|
||||
{
|
||||
DynamicPage page;
|
||||
@@ -2661,6 +2678,466 @@ void test_plex_user_placeholder()
|
||||
TEST_ASSERT_EQUAL_STRING("ab_c", small);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Game of Life (logic/life)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void lifeGlider(LifeBoard &board, int x, int y)
|
||||
{
|
||||
// Flies down and to the right, one dot every 4 generations
|
||||
board.setCell(x + 1, y, true);
|
||||
board.setCell(x + 2, y + 1, true);
|
||||
board.setCell(x, y + 2, true);
|
||||
board.setCell(x + 1, y + 2, true);
|
||||
board.setCell(x + 2, y + 2, true);
|
||||
}
|
||||
|
||||
void test_life_blinker_repeats()
|
||||
{
|
||||
LifeBoard board;
|
||||
board.begin(5, 5, LIFE_EDGES_DEAD);
|
||||
for (int x = 1; x <= 3; x++)
|
||||
board.setCell(x, 2, true);
|
||||
board.step();
|
||||
TEST_ASSERT_TRUE(board.cell(2, 1));
|
||||
TEST_ASSERT_TRUE(board.cell(2, 2));
|
||||
TEST_ASSERT_TRUE(board.cell(2, 3));
|
||||
TEST_ASSERT_FALSE(board.cell(1, 2));
|
||||
TEST_ASSERT_EQUAL(3, board.population());
|
||||
TEST_ASSERT_FALSE(board.repeating());
|
||||
board.step();
|
||||
TEST_ASSERT_TRUE(board.cell(1, 2));
|
||||
TEST_ASSERT_TRUE(board.repeating()); // the same as generation 0
|
||||
TEST_ASSERT_EQUAL(2, board.generation());
|
||||
}
|
||||
|
||||
void test_life_still_and_empty_repeat()
|
||||
{
|
||||
LifeBoard board;
|
||||
board.begin(6, 6, LIFE_EDGES_DEAD);
|
||||
board.setCell(2, 2, true); // block
|
||||
board.setCell(3, 2, true);
|
||||
board.setCell(2, 3, true);
|
||||
board.setCell(3, 3, true);
|
||||
board.step();
|
||||
TEST_ASSERT_TRUE(board.repeating());
|
||||
TEST_ASSERT_EQUAL(4, board.population());
|
||||
|
||||
board.clear();
|
||||
board.setCell(0, 0, true); // dies at once
|
||||
board.step();
|
||||
TEST_ASSERT_EQUAL(0, board.population());
|
||||
TEST_ASSERT_FALSE(board.repeating());
|
||||
board.step();
|
||||
TEST_ASSERT_TRUE(board.repeating());
|
||||
}
|
||||
|
||||
void test_life_edges_wrap_or_dead()
|
||||
{
|
||||
// A blinker on the top row: its vertical phase reaches above the area
|
||||
LifeBoard wrap;
|
||||
wrap.begin(5, 5, LIFE_EDGES_WRAP);
|
||||
LifeBoard dead;
|
||||
dead.begin(5, 5, LIFE_EDGES_DEAD);
|
||||
for (int x = 1; x <= 3; x++)
|
||||
{
|
||||
wrap.setCell(x, 0, true);
|
||||
dead.setCell(x, 0, true);
|
||||
}
|
||||
wrap.step();
|
||||
dead.step();
|
||||
TEST_ASSERT_TRUE(wrap.cell(2, 4)); // came round at the bottom
|
||||
TEST_ASSERT_EQUAL(3, wrap.population());
|
||||
TEST_ASSERT_FALSE(dead.cell(2, 4));
|
||||
TEST_ASSERT_EQUAL(2, dead.population());
|
||||
TEST_ASSERT_FALSE(dead.cell(-1, 0)); // outside the area
|
||||
}
|
||||
|
||||
void test_life_glider_wraps_round_the_screen()
|
||||
{
|
||||
LifeBoard board;
|
||||
board.begin(BITMAP_WIDTH, BITMAP_HEIGHT, LIFE_EDGES_WRAP);
|
||||
lifeGlider(board, 10, 5);
|
||||
// Back at its start after 4 x 84 x 19 generations (84 and 19 have no common divisor)
|
||||
for (int i = 0; i < 4 * BITMAP_WIDTH * BITMAP_HEIGHT; i++)
|
||||
{
|
||||
board.step();
|
||||
TEST_ASSERT_EQUAL(5, board.population());
|
||||
}
|
||||
TEST_ASSERT_TRUE(board.cell(11, 5));
|
||||
TEST_ASSERT_TRUE(board.cell(12, 6));
|
||||
TEST_ASSERT_TRUE(board.cell(10, 7));
|
||||
TEST_ASSERT_TRUE(board.cell(11, 7));
|
||||
TEST_ASSERT_TRUE(board.cell(12, 7));
|
||||
TEST_ASSERT_FALSE(board.repeating()); // the period is longer than LIFE_MAX_PERIOD
|
||||
}
|
||||
|
||||
void test_life_hidden_margin_lets_gliders_go()
|
||||
{
|
||||
LifeBoard board;
|
||||
board.begin(20, 10, LIFE_EDGES_MARGIN);
|
||||
lifeGlider(board, 2, 2);
|
||||
bool left_the_area = false;
|
||||
for (int i = 0; i < 400; i++)
|
||||
{
|
||||
board.step();
|
||||
bool visible = false;
|
||||
for (int y = 0; y < 10; y++)
|
||||
for (int x = 0; x < 20; x++)
|
||||
visible = visible || board.cell(x, y);
|
||||
if (!visible)
|
||||
left_the_area = true;
|
||||
else
|
||||
TEST_ASSERT_FALSE(left_the_area); // never comes back
|
||||
}
|
||||
TEST_ASSERT_TRUE(left_the_area);
|
||||
TEST_ASSERT_EQUAL(0, board.population()); // gone, not a block at the edge
|
||||
TEST_ASSERT_TRUE(board.repeating());
|
||||
|
||||
// Dead edges: it gets stuck in the corner instead
|
||||
LifeBoard dead;
|
||||
dead.begin(20, 10, LIFE_EDGES_DEAD);
|
||||
lifeGlider(dead, 2, 2);
|
||||
for (int i = 0; i < 400; i++)
|
||||
dead.step();
|
||||
TEST_ASSERT_TRUE(dead.population() > 0);
|
||||
TEST_ASSERT_TRUE(dead.repeating());
|
||||
}
|
||||
|
||||
void test_life_glider_gun_runs_clean_with_a_hidden_margin()
|
||||
{
|
||||
static const char *const gun[] = {
|
||||
"........................O...........",
|
||||
"......................O.O...........",
|
||||
"............OO......OO............OO",
|
||||
"...........O...O....OO............OO",
|
||||
"OO........O.....O...OO..............",
|
||||
"OO........O...O.OO....O.O...........",
|
||||
"..........O.....O.......O...........",
|
||||
"...........O...O....................",
|
||||
"............OO......................",
|
||||
};
|
||||
uint8_t pattern[BITMAP_BYTES] = {};
|
||||
for (int y = 0; y < 9; y++)
|
||||
for (int x = 0; gun[y][x]; x++)
|
||||
setBitmapPixel(pattern, x, y, gun[y][x] == 'O');
|
||||
uint32_t random = 1;
|
||||
LifeBoard board;
|
||||
board.begin(BITMAP_WIDTH, BITMAP_HEIGHT, LIFE_EDGES_MARGIN);
|
||||
TEST_ASSERT_TRUE(board.place(pattern, false, random));
|
||||
for (int i = 0; i < 1500; i++)
|
||||
{
|
||||
board.step();
|
||||
TEST_ASSERT_TRUE(board.population() < 200); // the gliders don't pile up
|
||||
}
|
||||
bool before[BITMAP_HEIGHT][BITMAP_WIDTH];
|
||||
for (int y = 0; y < BITMAP_HEIGHT; y++)
|
||||
for (int x = 0; x < BITMAP_WIDTH; x++)
|
||||
before[y][x] = board.cell(x, y);
|
||||
for (int i = 0; i < 30; i++)
|
||||
board.step();
|
||||
for (int y = 0; y < BITMAP_HEIGHT; y++)
|
||||
for (int x = 0; x < BITMAP_WIDTH; x++)
|
||||
TEST_ASSERT_EQUAL(before[y][x], board.cell(x, y)); // still the same gun
|
||||
}
|
||||
|
||||
void test_life_random_and_place()
|
||||
{
|
||||
uint32_t random = 12345;
|
||||
LifeBoard board;
|
||||
board.begin(BITMAP_WIDTH, BITMAP_HEIGHT, LIFE_EDGES_WRAP);
|
||||
board.randomize(0, random);
|
||||
TEST_ASSERT_EQUAL(0, board.population());
|
||||
board.randomize(100, random);
|
||||
TEST_ASSERT_EQUAL(BITMAP_WIDTH * BITMAP_HEIGHT, board.population());
|
||||
board.randomize(30, random);
|
||||
int population = board.population();
|
||||
TEST_ASSERT_TRUE(population > 350 && population < 600); // about 479
|
||||
|
||||
// A 3 x 1 pattern anywhere in the bitmap, placed in the middle of an 11 x 5 area
|
||||
uint8_t pattern[BITMAP_BYTES] = {};
|
||||
for (int x = 5; x <= 7; x++)
|
||||
setBitmapPixel(pattern, x, 9, true);
|
||||
board.begin(11, 5, LIFE_EDGES_MARGIN);
|
||||
TEST_ASSERT_TRUE(board.place(pattern, false, random));
|
||||
TEST_ASSERT_TRUE(board.cell(4, 2));
|
||||
TEST_ASSERT_TRUE(board.cell(5, 2));
|
||||
TEST_ASSERT_TRUE(board.cell(6, 2));
|
||||
TEST_ASSERT_EQUAL(3, board.population());
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
TEST_ASSERT_TRUE(board.place(pattern, true, random));
|
||||
TEST_ASSERT_EQUAL(3, board.population()); // always fully inside the area
|
||||
}
|
||||
// Too big: the middle part is kept
|
||||
board.begin(2, 2, LIFE_EDGES_DEAD);
|
||||
TEST_ASSERT_TRUE(board.place(pattern, true, random));
|
||||
TEST_ASSERT_EQUAL(2, board.population());
|
||||
uint8_t empty[BITMAP_BYTES] = {};
|
||||
TEST_ASSERT_FALSE(board.place(empty, false, random));
|
||||
}
|
||||
|
||||
void test_life_runner_holds_then_restarts()
|
||||
{
|
||||
LifeRunner runner;
|
||||
LifeTiming timing;
|
||||
timing.speed = 10; // every 100 ms
|
||||
timing.hold_ms = 1000;
|
||||
runner.setTiming(timing);
|
||||
runner.board.begin(5, 5, LIFE_EDGES_DEAD);
|
||||
for (int x = 1; x <= 3; x++)
|
||||
runner.board.setCell(x, 2, true);
|
||||
runner.started(0);
|
||||
TEST_ASSERT_EQUAL(100, runner.msUntilNext(0));
|
||||
TEST_ASSERT_EQUAL(LifeTick::Wait, runner.tick(50));
|
||||
TEST_ASSERT_EQUAL(50, runner.msUntilNext(50));
|
||||
TEST_ASSERT_EQUAL(LifeTick::Stepped, runner.tick(100));
|
||||
TEST_ASSERT_FALSE(runner.ending());
|
||||
TEST_ASSERT_EQUAL(LifeTick::Stepped, runner.tick(200)); // back to generation 0
|
||||
TEST_ASSERT_TRUE(runner.ending());
|
||||
// It keeps blinking during the hold time
|
||||
TEST_ASSERT_EQUAL(LifeTick::Stepped, runner.tick(300));
|
||||
TEST_ASSERT_EQUAL(LifeTick::Stepped, runner.tick(1100));
|
||||
TEST_ASSERT_EQUAL(50, runner.msUntilNext(1150));
|
||||
TEST_ASSERT_EQUAL(LifeTick::Restart, runner.tick(1200));
|
||||
runner.started(1200);
|
||||
TEST_ASSERT_FALSE(runner.ending());
|
||||
|
||||
// Not on repeating, but after 3 generations, then at once
|
||||
timing.restart_when_repeating = false;
|
||||
timing.max_generations = 3;
|
||||
timing.hold_ms = 0;
|
||||
runner.setTiming(timing);
|
||||
runner.board.clear();
|
||||
runner.started(0);
|
||||
TEST_ASSERT_EQUAL(LifeTick::Stepped, runner.tick(100));
|
||||
TEST_ASSERT_EQUAL(LifeTick::Stepped, runner.tick(200));
|
||||
TEST_ASSERT_FALSE(runner.ending()); // empty and repeating, but that is off
|
||||
TEST_ASSERT_EQUAL(LifeTick::Stepped, runner.tick(300));
|
||||
TEST_ASSERT_TRUE(runner.ending());
|
||||
TEST_ASSERT_EQUAL(1, runner.msUntilNext(300));
|
||||
TEST_ASSERT_EQUAL(LifeTick::Restart, runner.tick(300));
|
||||
|
||||
// Empty: no hold time
|
||||
timing.restart_when_repeating = true;
|
||||
timing.max_generations = 0;
|
||||
timing.hold_ms = 5000;
|
||||
runner.setTiming(timing);
|
||||
runner.board.clear();
|
||||
runner.board.setCell(2, 2, true); // dies at once
|
||||
runner.started(0);
|
||||
TEST_ASSERT_EQUAL(LifeTick::Stepped, runner.tick(100));
|
||||
TEST_ASSERT_EQUAL(LifeTick::Stepped, runner.tick(200)); // empty twice: repeating
|
||||
TEST_ASSERT_TRUE(runner.ending());
|
||||
TEST_ASSERT_EQUAL(LifeTick::Restart, runner.tick(201));
|
||||
|
||||
// Never ends
|
||||
timing.restart_when_repeating = false;
|
||||
timing.max_generations = 0;
|
||||
timing.hold_ms = 0;
|
||||
runner.setTiming(timing);
|
||||
runner.started(0);
|
||||
for (uint32_t t = 100; t < 10000; t += 100)
|
||||
TEST_ASSERT_EQUAL(LifeTick::Stepped, runner.tick(t));
|
||||
TEST_ASSERT_FALSE(runner.ending());
|
||||
}
|
||||
|
||||
void test_life_next_pattern()
|
||||
{
|
||||
uint32_t random = 1;
|
||||
TEST_ASSERT_EQUAL(-1, lifeNextPattern(LIFE_START_ROTATE, 0, -1, random));
|
||||
TEST_ASSERT_EQUAL(0, lifeNextPattern(LIFE_START_ROTATE, 3, -1, random));
|
||||
TEST_ASSERT_EQUAL(1, lifeNextPattern(LIFE_START_ROTATE, 3, 0, random));
|
||||
TEST_ASSERT_EQUAL(0, lifeNextPattern(LIFE_START_ROTATE, 3, 2, random));
|
||||
TEST_ASSERT_EQUAL(0, lifeNextPattern(LIFE_START_ROTATE, 3, 7, random)); // list got shorter
|
||||
TEST_ASSERT_EQUAL(0, lifeNextPattern(LIFE_START_SHUFFLE, 1, 0, random));
|
||||
int seen[4] = {};
|
||||
int current = 2;
|
||||
for (int i = 0; i < 200; i++)
|
||||
{
|
||||
int next = lifeNextPattern(LIFE_START_SHUFFLE, 4, current, random);
|
||||
TEST_ASSERT_TRUE(next >= 0 && next < 4);
|
||||
TEST_ASSERT_NOT_EQUAL(current, next); // never the same one twice
|
||||
seen[next]++;
|
||||
current = next;
|
||||
}
|
||||
for (int i = 0; i < 4; i++)
|
||||
TEST_ASSERT_TRUE(seen[i] > 20);
|
||||
}
|
||||
|
||||
static DynamicPage lifePage()
|
||||
{
|
||||
DynamicPage page;
|
||||
page.element_count = 1;
|
||||
PageElement &e = page.elements[0];
|
||||
e.type = ElementType::Life;
|
||||
e.x = 10;
|
||||
e.y = 2;
|
||||
e.bar_width = 20;
|
||||
e.bar_height = 10;
|
||||
e.format = LIFE_EDGES_MARGIN;
|
||||
e.input_unit = LIFE_START_PATTERN;
|
||||
strcpy(e.text, "Day"); // one dot at 0, 0
|
||||
e.factor = 30;
|
||||
e.scroll_speed = 4;
|
||||
e.auto_unit = true;
|
||||
e.bar_max = 5;
|
||||
return page;
|
||||
}
|
||||
|
||||
class LifeResources : public FakeResources
|
||||
{
|
||||
public:
|
||||
const LifeRunner *runner = nullptr;
|
||||
const LifeRunner *life(const PageElement &) override { return runner; }
|
||||
};
|
||||
|
||||
void test_life_element_validation()
|
||||
{
|
||||
TEST_ASSERT_NULL(validatePage(lifePage()));
|
||||
DynamicPage page = lifePage();
|
||||
page.elements[1] = page.elements[0];
|
||||
page.element_count = 2;
|
||||
TEST_ASSERT_NOT_NULL(validatePage(page)); // one per page
|
||||
|
||||
page = lifePage();
|
||||
page.elements[0].bar_width = 0;
|
||||
TEST_ASSERT_NOT_NULL(validatePage(page));
|
||||
page = lifePage();
|
||||
page.elements[0].bar_height = 20;
|
||||
TEST_ASSERT_NOT_NULL(validatePage(page));
|
||||
page = lifePage();
|
||||
page.elements[0].format = LIFE_EDGES_COUNT;
|
||||
TEST_ASSERT_NOT_NULL(validatePage(page));
|
||||
page = lifePage();
|
||||
page.elements[0].input_unit = LIFE_START_COUNT;
|
||||
TEST_ASSERT_NOT_NULL(validatePage(page));
|
||||
page = lifePage();
|
||||
page.elements[0].text[0] = '\0';
|
||||
TEST_ASSERT_NOT_NULL(validatePage(page)); // a pattern is needed
|
||||
page.elements[0].input_unit = LIFE_START_RANDOM;
|
||||
TEST_ASSERT_NULL(validatePage(page));
|
||||
page.elements[0].factor = 0;
|
||||
TEST_ASSERT_NOT_NULL(validatePage(page));
|
||||
page.elements[0].factor = 101;
|
||||
TEST_ASSERT_NOT_NULL(validatePage(page));
|
||||
page = lifePage();
|
||||
page.elements[0].input_unit = LIFE_START_SHUFFLE; // shuffle needs input_unit 3
|
||||
strcpy(page.elements[0].text, "life_");
|
||||
TEST_ASSERT_NULL(validatePage(page));
|
||||
page.elements[0].text[0] = '\0'; // all patterns
|
||||
TEST_ASSERT_NULL(validatePage(page));
|
||||
strcpy(page.elements[0].text, "life/");
|
||||
TEST_ASSERT_NOT_NULL(validatePage(page));
|
||||
page = lifePage();
|
||||
page.elements[0].scroll_speed = 0;
|
||||
TEST_ASSERT_NOT_NULL(validatePage(page));
|
||||
page.elements[0].scroll_speed = 21;
|
||||
TEST_ASSERT_NOT_NULL(validatePage(page));
|
||||
page = lifePage();
|
||||
page.elements[0].bar_max = -1;
|
||||
TEST_ASSERT_NOT_NULL(validatePage(page));
|
||||
page.elements[0].bar_max = 0; // restart at once
|
||||
TEST_ASSERT_NULL(validatePage(page));
|
||||
page.elements[0].target = LIFE_GENERATIONS_MAX + 1;
|
||||
TEST_ASSERT_NOT_NULL(validatePage(page));
|
||||
|
||||
// Other elements still can't use input_unit 3
|
||||
page = barPage();
|
||||
page.elements[0].input_unit = 3;
|
||||
TEST_ASSERT_NOT_NULL(validatePage(page));
|
||||
}
|
||||
|
||||
void test_life_element_render()
|
||||
{
|
||||
LifeResources res;
|
||||
DynamicPage page = lifePage();
|
||||
uint8_t bitmap[BITMAP_BYTES];
|
||||
ElementBox boxes[PAGE_ELEMENTS];
|
||||
|
||||
// Not the shown page: the pattern in the middle of the area, nothing moves
|
||||
TEST_ASSERT_EQUAL(0, renderPage(page, at(12, 0), 0, res, bitmap, boxes));
|
||||
TEST_ASSERT_EQUAL(1, countOn(bitmap));
|
||||
TEST_ASSERT_TRUE(bitmapPixel(bitmap, 10 + 9, 2 + 4));
|
||||
TEST_ASSERT_EQUAL(10, boxes[0].x);
|
||||
TEST_ASSERT_EQUAL(2, boxes[0].y);
|
||||
TEST_ASSERT_EQUAL(20, boxes[0].width);
|
||||
TEST_ASSERT_EQUAL(10, boxes[0].height);
|
||||
// Random: the same fill every time
|
||||
page.elements[0].input_unit = LIFE_START_RANDOM;
|
||||
renderPage(page, at(12, 0), 0, res, bitmap);
|
||||
int dots = countOn(bitmap);
|
||||
TEST_ASSERT_TRUE(dots > 30 && dots < 90); // about 60 of 200
|
||||
uint8_t again[BITMAP_BYTES];
|
||||
renderPage(page, at(12, 0), 0, res, again);
|
||||
TEST_ASSERT_EQUAL_MEMORY(bitmap, again, BITMAP_BYTES);
|
||||
|
||||
// The shown page: the running board
|
||||
static LifeRunner runner;
|
||||
LifeTiming timing;
|
||||
timing.speed = 4;
|
||||
runner.setTiming(timing);
|
||||
runner.board.begin(20, 10, LIFE_EDGES_MARGIN);
|
||||
runner.board.setCell(0, 0, true);
|
||||
runner.board.setCell(19, 9, true);
|
||||
runner.started(0);
|
||||
res.runner = &runner;
|
||||
TEST_ASSERT_EQUAL(150, renderPage(page, at(12, 0), 100, res, bitmap));
|
||||
TEST_ASSERT_EQUAL(2, countOn(bitmap));
|
||||
TEST_ASSERT_TRUE(bitmapPixel(bitmap, 10, 2));
|
||||
TEST_ASSERT_TRUE(bitmapPixel(bitmap, 29, 11));
|
||||
|
||||
// Centred on x, flipping a full background
|
||||
page.elements[0].align = Align::Center;
|
||||
page.elements[0].x = 42;
|
||||
page.elements[0].overlay = Overlay::Invert;
|
||||
page.slot_count = 1;
|
||||
strcpy(page.slots[0].bitmap, "Full");
|
||||
renderPage(page, at(12, 0), 100, res, bitmap);
|
||||
TEST_ASSERT_EQUAL(BITMAP_WIDTH * BITMAP_HEIGHT - 2, countOn(bitmap));
|
||||
TEST_ASSERT_FALSE(bitmapPixel(bitmap, 32, 2));
|
||||
TEST_ASSERT_FALSE(bitmapPixel(bitmap, 51, 11));
|
||||
}
|
||||
|
||||
void test_life_pattern_is_in_use()
|
||||
{
|
||||
DynamicPage page = lifePage();
|
||||
TEST_ASSERT_TRUE(pageUsesImage(page, "day"));
|
||||
TEST_ASSERT_FALSE(pageUsesImage(page, "Evening"));
|
||||
page.elements[0].input_unit = LIFE_START_ROTATE; // can be deleted from a rotation
|
||||
TEST_ASSERT_FALSE(pageUsesImage(page, "Day"));
|
||||
}
|
||||
|
||||
void test_life_same_start()
|
||||
{
|
||||
PageElement a = lifePage().elements[0];
|
||||
PageElement b = a;
|
||||
TEST_ASSERT_TRUE(sameLifeStart(a, b));
|
||||
b.scroll_speed = 10; // timing and position don't matter
|
||||
b.bar_max = 0;
|
||||
b.target = 100;
|
||||
b.auto_unit = false;
|
||||
b.x = 0;
|
||||
b.overlay = Overlay::Invert;
|
||||
strcpy(b.text, "DAY");
|
||||
TEST_ASSERT_TRUE(sameLifeStart(a, b));
|
||||
b = a;
|
||||
b.bar_width = 21;
|
||||
TEST_ASSERT_FALSE(sameLifeStart(a, b));
|
||||
b = a;
|
||||
b.format = LIFE_EDGES_WRAP;
|
||||
TEST_ASSERT_FALSE(sameLifeStart(a, b));
|
||||
b = a;
|
||||
strcpy(b.text, "Evening");
|
||||
TEST_ASSERT_FALSE(sameLifeStart(a, b));
|
||||
b = a;
|
||||
b.absolute = true;
|
||||
TEST_ASSERT_FALSE(sameLifeStart(a, b));
|
||||
b = a;
|
||||
b.type = ElementType::Bar;
|
||||
TEST_ASSERT_FALSE(sameLifeStart(a, b));
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
@@ -2766,6 +3243,7 @@ int main()
|
||||
RUN_TEST(test_render_alignment_and_boxes);
|
||||
RUN_TEST(test_render_unknown_font_uses_normal);
|
||||
RUN_TEST(test_builtin_clock_page_is_the_old_clock);
|
||||
RUN_TEST(test_builtin_life_page);
|
||||
RUN_TEST(test_default_page_is_the_builtin_clock);
|
||||
RUN_TEST(test_clock_with_seconds);
|
||||
RUN_TEST(test_countdown_target_parse_and_format);
|
||||
@@ -2813,6 +3291,19 @@ int main()
|
||||
RUN_TEST(test_attribute_paths_and_16_elements);
|
||||
RUN_TEST(test_plex_user_placeholder);
|
||||
RUN_TEST(test_slow_scrolling_below_one_dot_per_second);
|
||||
RUN_TEST(test_life_blinker_repeats);
|
||||
RUN_TEST(test_life_still_and_empty_repeat);
|
||||
RUN_TEST(test_life_edges_wrap_or_dead);
|
||||
RUN_TEST(test_life_glider_wraps_round_the_screen);
|
||||
RUN_TEST(test_life_hidden_margin_lets_gliders_go);
|
||||
RUN_TEST(test_life_glider_gun_runs_clean_with_a_hidden_margin);
|
||||
RUN_TEST(test_life_random_and_place);
|
||||
RUN_TEST(test_life_runner_holds_then_restarts);
|
||||
RUN_TEST(test_life_next_pattern);
|
||||
RUN_TEST(test_life_element_validation);
|
||||
RUN_TEST(test_life_element_render);
|
||||
RUN_TEST(test_life_pattern_is_in_use);
|
||||
RUN_TEST(test_life_same_start);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
Reference in new issue
Block a user