diff --git a/src/displayapp/screens/WatchFaceMaze.cpp b/src/displayapp/screens/WatchFaceMaze.cpp index 5d2605c830..c99807bd38 100644 --- a/src/displayapp/screens/WatchFaceMaze.cpp +++ b/src/displayapp/screens/WatchFaceMaze.cpp @@ -1,227 +1,250 @@ -#include #include "displayapp/screens/WatchFaceMaze.h" -#include "Tile.h" - using namespace Pinetime::Applications::Screens; - // Despite being called Maze, this really is only a relatively simple wrapper for the specialized // (fake) 2d array on which the maze structure is built. It should only have manipulations for -// the structure, generating and printing should be handled elsewhere. +// the structure; generating and printing should be handled elsewhere. Maze::Maze() { - std::fill_n(mazemap, FLATSIZE, 0); + std::fill_n(mazeMap, FLATSIZE, 0); } - -// only returns 4 bits (since that's all that's stored) -// returns walls but unset flags in case of out of bounds access -MazeTile Maze::get(int x, int y) { - if (x<0||x>WIDTH||y<0||y>HEIGHT) {return MazeTile(0b0011);} - return get((y * WIDTH) + x); +// Only returns 4 bits (since that's all that's stored) +// Returns set walls but unset flags in case of out of bounds access +MazeTile Maze::Get(const coord_t x, const coord_t y) const { + if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) { + return MazeTile(MazeTile::UPMASK | MazeTile::LEFTMASK); + } + return Get((y * WIDTH) + x); } -MazeTile Maze::get(int index) { - if (index < 0 || index/2 >= FLATSIZE) {return MazeTile(0b0011);} + +MazeTile Maze::Get(const int32_t index) const { + if (index < 0 || index / 2 >= FLATSIZE) { + return MazeTile(MazeTile::UPMASK | MazeTile::LEFTMASK); + } // odd means right (low) nibble, even means left (high) nibble - if (index & 0b1) return MazeTile(mazemap[index/2] & 0b00001111); - else return MazeTile(mazemap[index/2] >> 4); + if (index % 2 == 1) { + return MazeTile(mazeMap[index / 2] & 0b00001111); + } + return MazeTile(mazeMap[index / 2] >> 4); } - -// only stores the low 4 bits of the value -// if out of bounds, does nothing -void Maze::set(int x, int y, MazeTile tile) { - if (x<0||x>WIDTH||y<0||y>HEIGHT) {return;} - set((y * WIDTH) + x, tile); +// Only stores the low 4 bits of the value +// If out of bounds, does nothing +void Maze::Set(const coord_t x, const coord_t y, const MazeTile tile) { + if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) { + return; + } + Set((y * WIDTH) + x, tile); } -void Maze::set(int index, MazeTile tile) { - if (index < 0 || index/2 >= FLATSIZE) {return;} + +void Maze::Set(const int32_t index, const MazeTile tile) { + if (index < 0 || index / 2 >= FLATSIZE) { + return; + } // odd means right (low) nibble, even means left (high) nibble - if (index & 0b1) {mazemap[index/2] = (mazemap[index/2] & 0b11110000) | tile.map;} - else {mazemap[index/2] = (mazemap[index/2] & 0b00001111) | tile.map << 4;} + if (index % 2 == 1) { + mazeMap[index / 2] = (mazeMap[index / 2] & 0b11110000) | tile.map; + } else { + mazeMap[index / 2] = (mazeMap[index / 2] & 0b00001111) | tile.map << 4; + } } - // For quickly manipulating. Also allows better abstraction by allowing setting of down and right sides. // Silently does nothing if given invalid values. -void Maze::setSide(int index, TileAttr attr, bool value) { - switch(attr) { - case up: set(index, get(index).setUp(value)); break; - case down: set(index+WIDTH, get(index+WIDTH).setUp(value)); break; - case left: set(index, get(index).setLeft(value)); break; - case right: set(index+1, get(index+1).setLeft(value)); break; - case flagempty: set(index, get(index).setFlagEmpty(value)); break; - case flaggen: set(index, get(index).setFlagGen(value)); break; - } -} -void Maze::setSide(int x, int y, TileAttr attr, bool value) { - setSide((y*WIDTH)+x, attr, value); -} -bool Maze::getSide(int index, TileAttr attr) { - switch(attr) { - case up: return get(index).getUp(); - case down: return get(index+WIDTH).getUp(); - case left: return get(index).getLeft(); - case right: return get(index+1).getLeft(); - case flagempty: return get(index).getFlagEmpty(); - case flaggen: return get(index).getFlagGen(); +void Maze::SetAttr(const int32_t index, const TileAttr attr, const bool value) { + switch (attr) { + case Up: + Set(index, Get(index).SetUp(value)); + break; + case Down: + Set(index + WIDTH, Get(index + WIDTH).SetUp(value)); + break; + case Left: + Set(index, Get(index).SetLeft(value)); + break; + case Right: + Set(index + 1, Get(index + 1).SetLeft(value)); + break; + case FlagEmpty: + Set(index, Get(index).SetFlagEmpty(value)); + break; + case FlagGen: + Set(index, Get(index).SetFlagGen(value)); + break; } - return false; } -bool Maze::getSide(int x, int y, TileAttr attr) { - return getSide((y*WIDTH)+x, attr); + +void Maze::SetAttr(const coord_t x, const coord_t y, const TileAttr attr, const bool value) { + SetAttr((y * WIDTH) + x, attr, value); +} + +bool Maze::GetTileAttr(const int32_t index, const TileAttr attr) const { + switch (attr) { + case Up: + return Get(index).GetUp(); + case Down: + return Get(index + WIDTH).GetUp(); + case Left: + return Get(index).GetLeft(); + case Right: + return Get(index + 1).GetLeft(); + case FlagEmpty: + return Get(index).GetFlagEmpty(); + case FlagGen: + return Get(index).GetFlagGen(); + } + return false; } +bool Maze::GetTileAttr(const coord_t x, const coord_t y, const TileAttr attr) const { + return GetTileAttr((y * WIDTH) + x, attr); +} -// only operates on the low 4 bits of the uint8_t. -// only sets the bits from the value that are also on in the mask, rest are left alone +// Only operates on the low 4 bits of the uint8_t. +// Only sets the bits from the value that are also on in the mask, rest are left alone // e.g. existing = 1010, value = 0001, mask = 0011, then result = 1001 // (mask defaults to 0xFF which keeps all bits) -void Maze::fill(uint8_t value, uint8_t mask) { +void Maze::Fill(uint8_t value, uint8_t mask) { value = value & 0b00001111; value |= value << 4; - if (mask == 0xFF) { - // did not include a mask - std::fill_n(mazemap, FLATSIZE, value); + if (mask >= 0x0F) { + // mask includes all bits, simply use fill_n + std::fill_n(mazeMap, FLATSIZE, value); } else { // included a mask mask = mask & 0b00001111; mask |= mask << 4; value = value & mask; // preprocess mask for value - mask = ~mask; // this mask will be applied to the value - for (uint8_t& mapitem : mazemap) { - mapitem = (mapitem & mask) + value; + mask = ~mask; // this inverted mask will be applied to the existing value in mazeMap + for (uint8_t& mapItem : mazeMap) { + mapItem = (mapItem & mask) + value; } } } -inline void Maze::fill(MazeTile tile, uint8_t mask) - {fill(tile.map, mask);} +inline void Maze::Fill(const MazeTile tile, const uint8_t mask) { + Fill(tile.map, mask); +} // Paste a set of tiles into the given coords. -void Maze::pasteMazeSeed(int x1, int y1, int x2, int y2, const uint8_t toPaste[]) { +void Maze::PasteMazeSeed(const coord_t x1, const coord_t y1, const coord_t x2, const coord_t y2, const uint8_t toPaste[]) { // Assumes a maze with empty flags all true, and all walls present - uint16_t flatcoord = 0; // the position in the array (inside the byte, so index 1 would be mask 0b00110000 in the first byte) - for (int y = y1; y <= y2; y++) { - for (int x = x1; x <= x2; x++) { + int32_t flatCoord = 0; // the position in the array (inside the byte, so index 1 would be mask 0b00110000 in the first byte) + for (coord_t y = y1; y <= y2; y++) { + for (coord_t x = x1; x <= x2; x++) { // working holds the target wall (bit 2 for left wall, bit 1 for up wall) - const uint8_t working = (toPaste[flatcoord/4] & (0b11 << ((3-(flatcoord%4))*2))) >> ((3-(flatcoord%4))*2); + const uint8_t working = (toPaste[flatCoord / 4] & (0b11 << ((3 - (flatCoord % 4)) * 2))) >> ((3 - (flatCoord % 4)) * 2); // handle left wall - if (!(working & 0b10)) { - setSide(x, y, TileAttr::left, false); - setSide(x, y, TileAttr::flagempty, false); - if (x > 0) setSide(x-1, y, TileAttr::flagempty, false); + if (!(bool) (working & 0b10)) { + SetAttr(x, y, TileAttr::Left, false); + SetAttr(x, y, TileAttr::FlagEmpty, false); + if (x > 0) { + SetAttr(x - 1, y, TileAttr::FlagEmpty, false); + } } // handle up wall - if (!(working & 0b01)) { - setSide(x, y, TileAttr::up, false); - setSide(x, y, TileAttr::flagempty, false); - if (y > 0) setSide(x, y-1, TileAttr::flagempty, false); + if (!(bool) (working & 0b01)) { + SetAttr(x, y, TileAttr::Up, false); + SetAttr(x, y, TileAttr::FlagEmpty, false); + if (y > 0) { + SetAttr(x, y - 1, TileAttr::FlagEmpty, false); + } } - flatcoord++; + flatCoord++; } } } - - - -bool ConfettiParticle::step() { +bool ConfettiParticle::Step() { // first apply gravity (only to y), then dampening, then apply velocity to position. - xvel *= DAMPING_FACTOR; - xpos += xvel; + xVel *= DAMPING_FACTOR; + xPos += xVel; - yvel += GRAVITY; - yvel *= DAMPING_FACTOR; - ypos += yvel; + yVel += GRAVITY; + yVel *= DAMPING_FACTOR; + yPos += yVel; // return true if particle is finished (went OOB (ignore top; particle can still fall down)) - return (xpos < 0 || xpos > 240 || ypos > 240); + return xPos < 0 || xPos > 240 || yPos > 240; } - -void ConfettiParticle::reset(MazeRNG &prng) { +void ConfettiParticle::Reset(MazeRNG& prng) { // always start at bottom middle - xpos = 120; - ypos = 240; + xPos = 120; + yPos = 240; // velocity in pixels/tick - const float velocity = ((float)prng.rand(MIN_START_VELOCITY*100, MAX_START_VELOCITY*100))/100; + const float velocity = ((float) prng.Rand(MIN_START_VELOCITY * 100, MAX_START_VELOCITY * 100)) / 100; // angle, in radians, for going up at the chosen degree angle - const float angle = ((float)prng.rand(0,MAX_START_ANGLE*2) - MAX_START_ANGLE + 90) * ((float)3.14159265 / 180); + const float angle = ((float) prng.Rand(0, MAX_START_ANGLE * 2) - MAX_START_ANGLE + 90) * (std::numbers::pi_v / 180); - xvel = std::cos(angle) * velocity * START_X_COMPRESS; - yvel = -std::sin(angle) * velocity; + xVel = std::cos(angle) * velocity * START_X_COMPRESS; + yVel = -std::sin(angle) * velocity; // Low 3 bits represent red, green, and blue. Also don't allow all three off or all three on at once. // Effectively choose any max saturation color except black or white. - const uint8_t colorBits = prng.rand(1,6); - color = LV_COLOR_MAKE((colorBits&0b001) * 0xFF, ((colorBits&0b010)>>1) * 0xFF, ((colorBits&0b100)>>2) * 0xFF); + const uint8_t colorBits = prng.Rand(1, 6); + color = LV_COLOR_MAKE((colorBits & 0b001) * 0xFF, ((colorBits & 0b010) >> 1) * 0xFF, ((colorBits & 0b100) >> 2) * 0xFF); } - - - WatchFaceMaze::WatchFaceMaze(Pinetime::Components::LittleVgl& lvgl, Controllers::DateTime& dateTimeController, Controllers::Settings& settingsController, Controllers::MotorController& motor, const Controllers::Battery& batteryController, const Controllers::Ble& bleController) - : dateTimeController {dateTimeController}, - settingsController {settingsController}, - motor {motor}, - batteryController {batteryController}, - bleController {bleController}, - lvgl {lvgl}, - maze {Maze()}, - prng {MazeRNG()} { + : dateTimeController{dateTimeController}, + settingsController{settingsController}, + motor{motor}, + batteryController{batteryController}, + bleController{bleController}, + lvgl{lvgl}, + maze{Maze()}, + prng{MazeRNG()} { + + // set it to be in the past so it always takes two clicks to go to the secret, even if you're fast + lastLongClickTime = xTaskGetTickCount() - doubleDoubleClickDelay; taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); // refreshing here seems to cause issues in infinisim - //Refresh(); + // Refresh(); } - WatchFaceMaze::~WatchFaceMaze() { lv_obj_clean(lv_scr_act()); lv_task_del(taskRefresh); } - void WatchFaceMaze::Refresh() { - // store time for other functions to use. functions called directly from Refresh() can just assume this is accurate. - realTime = dateTimeController.CurrentDateTime(); // handle everything related to refreshing and printing stuff to the screen HandleMazeRefresh(); - + // handle confetti printing // yeah it's not very pretty how this is hanging out in the refresh() function but I don't want to modify anything related - // to printing in the touch interrupts - HandleConfetti(); + // to printing in the touch interrupts + HandleConfettiRefresh(); } - void WatchFaceMaze::HandleMazeRefresh() { // convert time to minutes and update if needed - currentDateTime = std::chrono::time_point_cast(realTime); - - // refresh if generation isn't complete, a minute has passed on the watchface, or the screen refresh timer expired. - // if minute rolls over while screenrefresh is required, ignore it. the refresh timer will handle it. - if (pausedGeneration || // if generation paused, need to complete it - (currentState == Displaying::watchface && !screenRefreshRequired && currentDateTime.IsUpdated()) || // already on watchface, not waiting for a screen refresh, and time updated - (screenRefreshRequired && realTime > screenRefreshTargetTime)) { // waiting on a refresh + currentDateTime = std::chrono::time_point_cast(dateTimeController.CurrentDateTime()); + + // Refresh if it's needed by some other component, a minute has passed on the watchface, or if generation has paused. + if (screenRefreshRequired || (currentState == Displaying::WatchFace && currentDateTime.IsUpdated()) || pausedGeneration) { // if generation wasn't paused (i.e. doing a ground up maze gen), set everything up if (!pausedGeneration) { // only reseed PRNG if got here by the minute rolling over - if (!screenRefreshRequired) prng.seed(currentDateTime.Get().time_since_epoch().count()); + if (!screenRefreshRequired) { + prng.Seed(currentDateTime.Get().time_since_epoch().count()); + } InitializeMaze(); SeedMaze(); } @@ -229,41 +252,43 @@ void WatchFaceMaze::HandleMazeRefresh() { // always need to run GenerateMaze() when refreshing. This is a maze watchface after all. GenerateMaze(); - // only draw once maze is fully generated (not paused) + // only finalize and draw once maze is fully generated (not paused) if (!pausedGeneration) { ForceValidMaze(); - if (currentState != Displaying::watchface) {ClearIndicators();} + if (currentState != Displaying::WatchFace) { + ClearIndicators(); + } DrawMaze(); screenRefreshRequired = false; - // if switched to watchface, also add indicators for BLE and battery - if (currentState == Displaying::watchface) { + // if on watchface, also add indicators for BLE and battery + if (currentState == Displaying::WatchFace) { UpdateBatteryDisplay(true); UpdateBleDisplay(true); } } } - + // update battery and ble displays if on main watchface - if (currentState == Displaying::watchface) { + if (currentState == Displaying::WatchFace) { UpdateBatteryDisplay(); UpdateBleDisplay(); } } - -void WatchFaceMaze::HandleConfetti() { +void WatchFaceMaze::HandleConfettiRefresh() { // initialize confetti if tapped on autism creature if (initConfetti) { ClearConfetti(); - for (ConfettiParticle &particle : confettiArr) - {particle.reset(prng);} + for (ConfettiParticle& particle : confettiArr) { + particle.Reset(prng); + } confettiActive = true; initConfetti = false; } // update confetti if needed if (confettiActive) { - if (currentState != Displaying::autismcreature) { - // nuke confetti if went to a different display + if (currentState != Displaying::AutismCreature) { + // remove confetti if went to a different display ClearConfetti(); confettiActive = false; } else { @@ -273,47 +298,54 @@ void WatchFaceMaze::HandleConfetti() { } } - -void WatchFaceMaze::UpdateBatteryDisplay(bool forceRedraw) { +void WatchFaceMaze::UpdateBatteryDisplay(const bool forceRedraw) { batteryPercent = batteryController.PercentRemaining(); charging = batteryController.IsCharging(); if (forceRedraw || batteryPercent.IsUpdated() || charging.IsUpdated()) { // need to redraw battery stuff - swapActiveBuffer(); + SwapActiveBuffer(); // number of pixels between top of indicator and fill line. rounds up, so 0% is 24px but 1% is 23px - uint8_t fillLevel = 24 - ((uint16_t)(batteryPercent.Get()) * 24) / 100; - lv_area_t area = {223,3,236,26}; + const uint8_t fillLevel = 24 - ((uint16_t) (batteryPercent.Get()) * 24) / 100; + constexpr lv_area_t area = {223, 3, 236, 26}; // battery body color - green >25%, orange >10%, red <=10%. Charging always makes it yellow. lv_color_t batteryBodyColor; - if (charging.Get()) {batteryBodyColor = LV_COLOR_YELLOW;} - else if (batteryPercent.Get() > 25) {batteryBodyColor = LV_COLOR_GREEN;} - else if (batteryPercent.Get() > 10) {batteryBodyColor = LV_COLOR_ORANGE;} - else {batteryBodyColor = LV_COLOR_RED;} + if (charging.Get()) { + batteryBodyColor = LV_COLOR_YELLOW; + } else if (batteryPercent.Get() > 25) { + batteryBodyColor = LV_COLOR_GREEN; + } else if (batteryPercent.Get() > 10) { + batteryBodyColor = LV_COLOR_ORANGE; + } else { + batteryBodyColor = LV_COLOR_RED; + } - // battery top color (upper gray section) - gray normally, light blue when charging, light red at <=10% charge + // battery top color (upper section) - gray normally, light blue when charging, light red at <=10% charge lv_color_t batteryTopColor; - if (charging.Get()) {batteryTopColor = LV_COLOR_MAKE(0x80,0x80,0xC0);} - else if (batteryPercent.Get() <= 10) {batteryTopColor = LV_COLOR_MAKE(0xC0,0x80,0x80);} - else {batteryTopColor = LV_COLOR_GRAY;} + if (charging.Get()) { + batteryTopColor = LV_COLOR_MAKE(0x80, 0x80, 0xC0); + } else if (batteryPercent.Get() <= 10) { + batteryTopColor = LV_COLOR_MAKE(0xC0, 0x80, 0x80); + } else { + batteryTopColor = LV_COLOR_GRAY; + } // actually fill the buffer with the chosen colors and print it - std::fill_n(activeBuffer, fillLevel*14, batteryTopColor); - std::fill_n((activeBuffer+(fillLevel*14)), (24-fillLevel)*14, batteryBodyColor); + std::fill_n(activeBuffer, fillLevel * 14, batteryTopColor); + std::fill_n(activeBuffer + (fillLevel * 14), (24 - fillLevel) * 14, batteryBodyColor); lvgl.SetFullRefresh(Components::LittleVgl::FullRefreshDirections::None); lvgl.FlushDisplay(&area, activeBuffer); } } - -void WatchFaceMaze::UpdateBleDisplay(bool forceRedraw) { +void WatchFaceMaze::UpdateBleDisplay(const bool forceRedraw) { bleConnected = bleController.IsConnected(); if (forceRedraw || bleConnected.IsUpdated()) { // need to redraw BLE indicator - swapActiveBuffer(); + SwapActiveBuffer(); - lv_area_t area = {213,3,216,26}; + constexpr lv_area_t area = {213, 3, 216, 26}; std::fill_n(activeBuffer, 96, (bleConnected.Get() ? LV_COLOR_BLUE : LV_COLOR_GRAY)); lvgl.SetFullRefresh(Components::LittleVgl::FullRefreshDirections::None); @@ -321,204 +353,240 @@ void WatchFaceMaze::UpdateBleDisplay(bool forceRedraw) { } } - void WatchFaceMaze::ClearIndicators() { - swapActiveBuffer(); + SwapActiveBuffer(); lv_area_t area; - std::fill_n(activeBuffer, 24*14, LV_COLOR_BLACK); + std::fill_n(activeBuffer, 24 * 14, LV_COLOR_BLACK); // battery indicator - area.x1=223; area.y1=3; area.x2=236; area.y2=26; + area.x1 = 223; + area.y1 = 3; + area.x2 = 236; + area.y2 = 26; lvgl.SetFullRefresh(Components::LittleVgl::FullRefreshDirections::None); lvgl.FlushDisplay(&area, activeBuffer); // BLE indicator - area.x1=213; area.y1=3; area.x2=216; area.y2=26; + area.x1 = 213; + area.y1 = 3; + area.x2 = 216; + area.y2 = 26; lvgl.SetFullRefresh(Components::LittleVgl::FullRefreshDirections::None); lvgl.FlushDisplay(&area, activeBuffer); } - -bool WatchFaceMaze::OnTouchEvent(TouchEvents event) { +bool WatchFaceMaze::OnTouchEvent(const TouchEvents event) { // if generation is paused, let it continue working on that. This should really never trigger. - if (pausedGeneration) {return false;} - + if (pausedGeneration) { + return false; + } + // Interrupts never seem to overlap (instead they're queued) so I don't have to worry about anything happening while one of - // these handlers are running - + // these handlers are running + switch (event) { - case Pinetime::Applications::TouchEvents::LongTap: return HandleLongTap(); - case Pinetime::Applications::TouchEvents::Tap: return HandleTap(); - case Pinetime::Applications::TouchEvents::SwipeUp: return HandleSwipe(0); - case Pinetime::Applications::TouchEvents::SwipeRight: return HandleSwipe(1); - case Pinetime::Applications::TouchEvents::SwipeDown: return HandleSwipe(2); - case Pinetime::Applications::TouchEvents::SwipeLeft: return HandleSwipe(3); - default: return false; + case Pinetime::Applications::TouchEvents::LongTap: + return HandleLongTap(); + case Pinetime::Applications::TouchEvents::Tap: + return HandleTap(); + case Pinetime::Applications::TouchEvents::SwipeUp: + return HandleSwipe(0); + case Pinetime::Applications::TouchEvents::SwipeRight: + return HandleSwipe(1); + case Pinetime::Applications::TouchEvents::SwipeDown: + return HandleSwipe(2); + case Pinetime::Applications::TouchEvents::SwipeLeft: + return HandleSwipe(3); + default: + return false; } } // allow pushing the button to go back to the watchface bool WatchFaceMaze::OnButtonPushed() { - if (currentState != Displaying::watchface) { + if (currentState != Displaying::WatchFace) { screenRefreshRequired = true; - currentState = Displaying::watchface; - // reset lastInputTime so it always needs two long taps to get back to blank, even if you're fast - lastInputTime = std::chrono::time_point(); + currentState = Displaying::WatchFace; + // set lastLongClickTime to be in the past so it always needs two long taps to get back to blank, even if you're fast + lastLongClickTime = xTaskGetTickCount() - doubleDoubleClickDelay; return true; } return false; } - bool WatchFaceMaze::HandleLongTap() { - if (currentState == Displaying::watchface) { + if (currentState == Displaying::WatchFace) { // On watchface; either refresh maze or go to blank state - if (lastInputTime + std::chrono::milliseconds(2500) > realTime) { + if (xTaskGetTickCount() - lastLongClickTime < doubleDoubleClickDelay) { // long tapped twice in sequence; switch to blank maze - currentState = Displaying::blank; + currentState = Displaying::Blank; screenRefreshRequired = true; - std::fill_n(currentCode, sizeof(currentCode), 255); // clear current code in preparation for code entry + std::fill_n(currentCode, sizeof(currentCode), 255); // clear current code in preparation for code entry } else { // long tapped not in main watchface; go back to previous state screenRefreshRequired = true; } - lastInputTime = realTime; + lastLongClickTime = xTaskGetTickCount(); motor.RunForDuration(20); - return true; } else { // Not on watchface; go back to main watchface screenRefreshRequired = true; - currentState = Displaying::watchface; - // reset lastInputTime so it always needs two long taps to get back to blank, even if you're fast - lastInputTime = std::chrono::time_point(); + currentState = Displaying::WatchFace; + // set lastLongClickTime to be in the past so it always needs two long taps to get back to blank, even if you're fast + lastLongClickTime = xTaskGetTickCount() - doubleDoubleClickDelay; motor.RunForDuration(20); - return true; } -} + // no situation where long tap doesn't get handled + return true; +} bool WatchFaceMaze::HandleTap() { // confetti must only display on autismcreature - if (currentState != Displaying::autismcreature) {return false;} - // only need to set confettiActive, everything else is handled in functions called by refresh() + if (currentState != Displaying::AutismCreature) { + return false; + } + // only need to set initConfetti, everything else is handled in functions called by refresh() initConfetti = true; return true; } - -bool WatchFaceMaze::HandleSwipe(uint8_t direction) { +bool WatchFaceMaze::HandleSwipe(const uint8_t direction) { // Don't handle any swipes on watchface - if (currentState == Displaying::watchface) return false; + if (currentState == Displaying::WatchFace) { + return false; + } // Add the new direction to the swipe list, dropping the last item - for (int i = sizeof(currentCode)-1; i > 0; i--) {currentCode[i] = currentCode[i-1];} + for (unsigned int i = sizeof(currentCode) - 1; i > 0; i--) { + currentCode[i] = currentCode[i - 1]; + } currentCode[0] = direction; // check if valid code has been entered - // Displaying::watchface is used here simply as a dummy value, and it will never transition to that - Displaying newState = Displaying::watchface; - if (std::memcmp(currentCode, lossCode, sizeof(lossCode)) == 0) {newState = Displaying::loss;} // loss - else if (std::memcmp(currentCode, amogusCode, sizeof(amogusCode)) == 0) {newState = Displaying::amogus;} // amogus - else if (std::memcmp(currentCode, autismCode, sizeof(autismCode)) == 0) {newState = Displaying::autismcreature;} // autismcreature/tbh - else if (std::memcmp(currentCode, foxCode, sizeof(foxCode)) == 0) {newState = Displaying::foxgame;} // foxxo game - else if (std::memcmp(currentCode, reminderCode, sizeof(reminderCode)) == 0) {newState = Displaying::reminder;} // reminder - else if (std::memcmp(currentCode, pinetimeCode, sizeof(pinetimeCode)) == 0) {newState = Displaying::pinetime;} // pinetime logo + // Displaying::WatchFace is used here simply as a dummy value, and it will never transition to that + Displaying newState = Displaying::WatchFace; + if (std::memcmp(currentCode, lossCode, sizeof(lossCode)) == 0) { + newState = Displaying::Loss; + } else if (std::memcmp(currentCode, amogusCode, sizeof(amogusCode)) == 0) { + newState = Displaying::Amogus; + } else if (std::memcmp(currentCode, autismCode, sizeof(autismCode)) == 0) { + newState = Displaying::AutismCreature; + } else if (std::memcmp(currentCode, foxCode, sizeof(foxCode)) == 0) { + newState = Displaying::FoxGame; + } else if (std::memcmp(currentCode, reminderCode, sizeof(reminderCode)) == 0) { + newState = Displaying::GameReminder; + } else if (std::memcmp(currentCode, pinetimeCode, sizeof(pinetimeCode)) == 0) { + newState = Displaying::PineTime; + } // only request a screen refresh if state has been updated - if (newState != Displaying::watchface) { + if (newState != Displaying::WatchFace) { currentState = newState; screenRefreshRequired = true; motor.RunForDuration(10); - std::fill_n(currentCode, sizeof(currentCode), 255); // clear code + std::fill_n(currentCode, sizeof(currentCode), 0xFF); // clear code } return true; } - // Clear maze void WatchFaceMaze::InitializeMaze() { - maze.fill(MazeTile().setLeft(true).setUp(true).setFlagEmpty(true)); + maze.Fill(MazeTile().SetLeft(true).SetUp(true).SetFlagEmpty(true)); } - // seeds the maze with whatever the current state needs void WatchFaceMaze::SeedMaze() { switch (currentState) { - case Displaying::watchface: - PutTimeDate(); break; - case Displaying::blank: { // seed maze with 4 tiles - const int randx = prng.rand(0, 20); - const int randy = prng.rand(3, 20); - maze.pasteMazeSeed(randx, randy, randx + 3, randy, blankseed); + case Displaying::WatchFace: + PutTime(); + break; + case Displaying::Blank: { + // seed maze with 4 tiles + const coord_t randX = (coord_t) prng.Rand(0, 20); + const coord_t randY = (coord_t) prng.Rand(3, 20); + maze.PasteMazeSeed(randX, randY, randX + 3, randY, blankseed); break; } - case Displaying::loss: - maze.pasteMazeSeed(2, 2, 22, 21, loss); break; - case Displaying::amogus: - maze.pasteMazeSeed(3, 0, 21, 23, amogus); break; - case Displaying::autismcreature: - maze.pasteMazeSeed(0, 2, 23, 22, autismcreature); break; - case Displaying::foxgame: - maze.pasteMazeSeed(0, 1, 23, 22, foxgame); break; - case Displaying::reminder: - maze.pasteMazeSeed(0, 3, 23, 19, reminder); break; - case Displaying::pinetime: - maze.pasteMazeSeed(2, 0, 21, 23, pinetime); break; + case Displaying::Loss: + maze.PasteMazeSeed(2, 2, 22, 21, loss); + break; + case Displaying::Amogus: + maze.PasteMazeSeed(3, 0, 21, 23, amogus); + break; + case Displaying::AutismCreature: + maze.PasteMazeSeed(0, 2, 23, 22, autismCreature); + break; + case Displaying::FoxGame: + maze.PasteMazeSeed(0, 1, 23, 22, foxGame); + break; + case Displaying::GameReminder: + maze.PasteMazeSeed(0, 3, 23, 19, gameReminder); + break; + case Displaying::PineTime: + maze.PasteMazeSeed(2, 0, 21, 23, pinetime); + break; } } - // Put time and date info on the screen. -void WatchFaceMaze::PutTimeDate() { +void WatchFaceMaze::PutTime() { uint8_t hours = dateTimeController.Hours(); uint8_t minutes = dateTimeController.Minutes(); // modify hours to account for 12 hour format if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) { // if 0am in 12 hour format, it's 12am - if (hours == 0) {hours = 12;} + if (hours == 0) { + hours = 12; + } // if after noon in 12 hour format, shift over by 12 hours if (hours > 12) { - maze.pasteMazeSeed(18, 15, 22, 22, pm); + maze.PasteMazeSeed(18, 15, 22, 22, pm); hours -= 12; } else { - maze.pasteMazeSeed(18, 15, 22, 22, am); + maze.PasteMazeSeed(18, 15, 22, 22, am); } } // put time on screen - maze.pasteMazeSeed(3, 1, 8, 10, numbers[hours / 10]); // top left: hours major digit - maze.pasteMazeSeed(10, 1, 15, 10, numbers[hours % 10]); // top right: hours minor digit - maze.pasteMazeSeed(3, 13, 8, 22, numbers[minutes / 10]); // bottom left: minutes major digit - maze.pasteMazeSeed(10, 13, 15, 22, numbers[minutes % 10]); // bottom right: minutes minor digit + maze.PasteMazeSeed(3, 1, 8, 10, numbers[hours / 10]); // top left: hours major digit + maze.PasteMazeSeed(10, 1, 15, 10, numbers[hours % 10]); // top right: hours minor digit + maze.PasteMazeSeed(3, 13, 8, 22, numbers[minutes / 10]); // bottom left: minutes major digit + maze.PasteMazeSeed(10, 13, 15, 22, numbers[minutes % 10]); // bottom right: minutes minor digit // reserve some space at the top right to put the battery and BLE indicators there - maze.pasteMazeSeed(21, 0, 23, 2, indicatorSpace); + maze.PasteMazeSeed(21, 0, 23, 2, indicatorSpace); } - // Generates the maze around whatever it was seeded with void WatchFaceMaze::GenerateMaze() { - int x, y; + coord_t x = -1; + coord_t y = -1; // task should only run for 3/4 the time it takes for the task to refresh. // Will go over; only checks once it's finished with current line. It won't go too far over though. - auto maxGenTarget = dateTimeController.CurrentDateTime() + std::chrono::milliseconds((taskRefresh->period*3)/4); + uint32_t mazeGenStartTime = xTaskGetTickCount(); while (true) { // find position to start generating a path from for (uint8_t i = 0; i < 30; i++) { - x = (int)prng.rand(0, Maze::WIDTH-1); - y = (int)prng.rand(0, Maze::HEIGHT-1); - if (maze.getSide(x,y, TileAttr::flagempty)) {break;} // found solution tile + x = (coord_t) prng.Rand(0, Maze::WIDTH - 1); + y = (coord_t) prng.Rand(0, Maze::HEIGHT - 1); + if (maze.GetTileAttr(x, y, TileAttr::FlagEmpty)) { + break; + } // found solution tile if (i == 29) { // failed all 30 attempts (this is inside the for loop for 'organization') // find solution tile slowly but guaranteed (scan over entire field and choose random valid tile) int count = 0; // count number of valid tiles - for (int j = 0; j < Maze::WIDTH*Maze::HEIGHT; j++) - {if (maze.getSide(j, TileAttr::flagempty)) {count++;}} + for (int32_t j = 0; j < Maze::WIDTH * Maze::HEIGHT; j++) { + if (maze.GetTileAttr(j, TileAttr::FlagEmpty)) { + count++; + } + } // if no valid tiles are left, maze is done if (count == 0) { @@ -528,9 +596,11 @@ void WatchFaceMaze::GenerateMaze() { // if execution gets here then maze gen is not done. select random index from valid tiles to start from // 'count' is now used as an index - count = prng.rand(1, count); - for (int j = 0; j < Maze::WIDTH*Maze::HEIGHT; j++) { - if (maze.getSide(j, TileAttr::flagempty)) {count--;} + count = (coord_t) prng.Rand(1, count); + for (int32_t j = 0; j < Maze::WIDTH * Maze::HEIGHT; j++) { + if (maze.GetTileAttr(j, TileAttr::FlagEmpty)) { + count--; + } if (count == 0) { y = j / Maze::WIDTH; x = j % Maze::WIDTH; @@ -543,7 +613,7 @@ void WatchFaceMaze::GenerateMaze() { GeneratePath(x, y); // if generating paths took too long, suspend it - if (dateTimeController.CurrentDateTime() > maxGenTarget) { + if (xTaskGetTickCount() - mazeGenStartTime > taskRefresh->period * 3 / 4) { pausedGeneration = true; return; } @@ -551,81 +621,109 @@ void WatchFaceMaze::GenerateMaze() { // execution never gets here! it returns earlier in the function. } - -void WatchFaceMaze::GeneratePath(int x, int y) { - int oldx, oldy; // used in backtracking - uint8_t direction; // which direction the cursor moved in +void WatchFaceMaze::GeneratePath(coord_t x, coord_t y) { + // oldX, oldY are used in backtracking + coord_t oldX; + coord_t oldY; + // which direction the cursor moved in + uint8_t direction; while (true) { // set current tile to reflect that it's been worked on - maze.setSide(x, y, TileAttr::flagempty, false); // no longer empty - maze.setSide(x, y, TileAttr::flaggen, true); // in generation - oldx = x, oldy = y; // used in backtracking + maze.SetAttr(x, y, TileAttr::FlagEmpty, false); // no longer empty + maze.SetAttr(x, y, TileAttr::FlagGen, true); // in generation + oldX = x, oldY = y; // used in backtracking // move to next tile - // the if statements are very scuffed, but they prevent backtracking. + // the if statements are very scuffed, but they prevent turning around. while (true) { - switch (direction = prng.rand(0, 3)) { + switch (direction = prng.Rand(0, 3)) { case 0: // moved up - if (y <= 0 || !maze.getSide(x, y, TileAttr::up)) {continue;} + if (y <= 0 || !maze.GetTileAttr(x, y, TileAttr::Up)) { + continue; + } y -= 1; break; case 1: // moved left - if (x <= 0 || !maze.getSide(x, y, TileAttr::left)) {continue;} + if (x <= 0 || !maze.GetTileAttr(x, y, TileAttr::Left)) { + continue; + } x -= 1; break; case 2: // moved down - if (y >= Maze::HEIGHT - 1 || !maze.getSide(x, y, TileAttr::down)) {continue;} + if (y >= Maze::HEIGHT - 1 || !maze.GetTileAttr(x, y, TileAttr::Down)) { + continue; + } y += 1; break; case 3: // moved right - if (x >= Maze::WIDTH - 1 || !maze.getSide(x, y, TileAttr::right)) {continue;} + if (x >= Maze::WIDTH - 1 || !maze.GetTileAttr(x, y, TileAttr::Right)) { + continue; + } x += 1; break; - default: // invalid + default: // invalid, will never hit in normal operation std::abort(); } break; } // moved to next tile, check if looped in on self - if (!maze.getSide(x, y, TileAttr::flaggen)) { + if (!maze.GetTileAttr(x, y, TileAttr::FlagGen)) { + // did NOT loop in on self, simply remove wall and move on switch (direction) { - case 0: maze.setSide(x, y, TileAttr::down, false); break; // moved up - case 1: maze.setSide(x, y, TileAttr::right, false); break; // moved left - case 2: maze.setSide(x, y, TileAttr::up, false); break; // moved down - case 3: maze.setSide(x, y, TileAttr::left, false); break; // moved right + case 0: + maze.SetAttr(x, y, TileAttr::Down, false); + break; // moved up + case 1: + maze.SetAttr(x, y, TileAttr::Right, false); + break; // moved left + case 2: + maze.SetAttr(x, y, TileAttr::Up, false); + break; // moved down + case 3: + maze.SetAttr(x, y, TileAttr::Left, false); + break; // moved right + default: // invalid, will never hit in normal operation + std::abort(); } + // if attached to main maze, path finished generating - if (!maze.getSide(x, y, TileAttr::flagempty)) { + if (!maze.GetTileAttr(x, y, TileAttr::FlagEmpty)) { break; } } else { + // DID loop in on self, track down and eliminate loop // targets are the coordinates of where it needs to backtrack to - const int targetx = x, targety = y; - x = oldx, y = oldy; - while (x != targetx || y != targety) { - if (y > 0 && (maze.getSide(x, y, TileAttr::up) == false)) { // backtrack up - maze.setSide(x, y, TileAttr::up, true); - maze.setSide(x, y, TileAttr::flaggen, false); - maze.setSide(x, y, TileAttr::flagempty, true); + const coord_t targetX = x; + const coord_t targetY = y; + x = oldX, y = oldY; + while (x != targetX || y != targetY) { + if (y > 0 && (maze.GetTileAttr(x, y, TileAttr::Up) == false)) { + // backtrack up + maze.SetAttr(x, y, TileAttr::Up, true); + maze.SetAttr(x, y, TileAttr::FlagGen, false); + maze.SetAttr(x, y, TileAttr::FlagEmpty, true); y -= 1; - } else if (x > 0 && (maze.getSide(x, y, TileAttr::left) == false)) { // backtrack left - maze.setSide(x, y, TileAttr::left, true); - maze.setSide(x, y, TileAttr::flaggen, false); - maze.setSide(x, y, TileAttr::flagempty, true); + } else if (x > 0 && (maze.GetTileAttr(x, y, TileAttr::Left) == false)) { + // backtrack left + maze.SetAttr(x, y, TileAttr::Left, true); + maze.SetAttr(x, y, TileAttr::FlagGen, false); + maze.SetAttr(x, y, TileAttr::FlagEmpty, true); x -= 1; - } else if (y < Maze::HEIGHT - 1 && (maze.getSide(x, y, TileAttr::down) == false)) { // backtrack down - maze.setSide(x, y, TileAttr::down, true); - maze.setSide(x, y, TileAttr::flaggen, false); - maze.setSide(x, y, TileAttr::flagempty, true); + } else if (y < Maze::HEIGHT - 1 && (maze.GetTileAttr(x, y, TileAttr::Down) == false)) { + // backtrack down + maze.SetAttr(x, y, TileAttr::Down, true); + maze.SetAttr(x, y, TileAttr::FlagGen, false); + maze.SetAttr(x, y, TileAttr::FlagEmpty, true); y += 1; - } else if (x < Maze::WIDTH && (maze.getSide(x, y, TileAttr::right) == false)) { // backtrack right - maze.setSide(x, y, TileAttr::right, true); - maze.setSide(x, y, TileAttr::flaggen, false); - maze.setSide(x, y, TileAttr::flagempty, true); + } else if (x < Maze::WIDTH && (maze.GetTileAttr(x, y, TileAttr::Right) == false)) { + // backtrack right + maze.SetAttr(x, y, TileAttr::Right, true); + maze.SetAttr(x, y, TileAttr::FlagGen, false); + maze.SetAttr(x, y, TileAttr::FlagEmpty, true); x += 1; } else { // bad backtrack; die @@ -637,47 +735,57 @@ void WatchFaceMaze::GeneratePath(int x, int y) { } // finished generating the entire path // mark all tiles as finalized and not in generation by removing ALL flaggen's - maze.fill(0, MazeTile::FLAGGENMASK); + maze.Fill(0, MazeTile::FLAGGENMASK); } - // goes through the maze, finds disconnected segments and connects them void WatchFaceMaze::ForceValidMaze() { - // crude maze-optimized flood fill: follow a path until can't move any more, then find some other location to follow from. repeat. - // once it's traversed all reachable tiles, checks if there are any tiles that have not been traversed. if there are, then find a border - // between the traversed and non-traversed segments. poke a hole at one of these borders randomly. - // once the hole has been poked, more maze is reachable. continue this "fill-search then poke" scheme until the entire maze is accessible. - // this function repurposes flaggen for traversed tiles, so it expects it to be false on all tiles (should be in normal control flow) - + // Crude maze-optimized flood fill: follow a path until can't move any more, then find some other location to follow from. repeat. + // Once it's traversed all reachable tiles, checks if there are any tiles that have not been traversed. if there are, then find a border + // between the traversed and non-traversed segments. poke a hole at one of these borders randomly. + // Once the hole has been poked, more maze is reachable. continue this "fill-search then poke" scheme until the entire maze is accessible. + // This function repurposes flaggen for traversed tiles, so it expects it to be false on all tiles (should be in normal control flow) + // initialize cursor x and y to bottom right - int x = Maze::WIDTH-1, y = Maze::HEIGHT - 1; + coord_t x = Maze::WIDTH - 1; + coord_t y = Maze::HEIGHT - 1; while (true) { // sorry for using goto but this needs to be really nested and the components are too integrated to split out into functions... - ForceValidMazeLoop: - maze.setSide(x, y, TileAttr::flaggen, true); + ForceValidMazeLoop: + maze.SetAttr(x, y, TileAttr::FlagGen, true); // move cursor - if (y > 0 && !maze.getSide(x, y, TileAttr::up) && !maze.getSide(x, y-1, TileAttr::flaggen)) {y--;} - else if (x < Maze::WIDTH-1 && !maze.getSide(x, y, TileAttr::right) && !maze.getSide(x+1, y, TileAttr::flaggen)) {x++;} - else if (y < Maze::HEIGHT-1 && !maze.getSide(x, y, TileAttr::down) && !maze.getSide(x, y+1, TileAttr::flaggen)) {y++;} - else if (x > 0 && !maze.getSide(x, y, TileAttr::left) && !maze.getSide(x-1, y, TileAttr::flaggen)) {x--;} - else { - int pokeLocationCount = 0; + if (y > 0 && !maze.GetTileAttr(x, y, TileAttr::Up) && !maze.GetTileAttr(x, y - 1, TileAttr::FlagGen)) { + y--; + } else if (x < Maze::WIDTH - 1 && !maze.GetTileAttr(x, y, TileAttr::Right) && !maze.GetTileAttr(x + 1, y, TileAttr::FlagGen)) { + x++; + } else if (y < Maze::HEIGHT - 1 && !maze.GetTileAttr(x, y, TileAttr::Down) && !maze.GetTileAttr(x, y + 1, TileAttr::FlagGen)) { + y++; + } else if (x > 0 && !maze.GetTileAttr(x, y, TileAttr::Left) && !maze.GetTileAttr(x - 1, y, TileAttr::FlagGen)) { + x--; + } else { + unsigned int pokeLocationCount = 0; // couldn't find any position to move to, need to set cursor to a different usable location - for (int proposedy = 0; proposedy < Maze::HEIGHT; proposedy++) { - for (int proposedx = 0; proposedx < Maze::WIDTH; proposedx++) { - const bool ownState = maze.getSide(proposedx, proposedy, TileAttr::flaggen); + for (coord_t proposedY = 0; proposedY < Maze::HEIGHT; proposedY++) { + for (coord_t proposedX = 0; proposedX < Maze::WIDTH; proposedX++) { + const bool ownState = maze.GetTileAttr(proposedX, proposedY, TileAttr::FlagGen); // if tile to the left is of a different traversal state (is traversed boundary) - if (proposedx > 0 && (maze.getSide(proposedx-1, proposedy, TileAttr::flaggen) != ownState)) { + if (proposedX > 0 && (maze.GetTileAttr(proposedX - 1, proposedY, TileAttr::FlagGen) != ownState)) { // if found boundary AND can get to it, just continue working from here - if (maze.getSide(proposedx, proposedy, TileAttr::left) == false) {x = proposedx, y = proposedy; goto ForceValidMazeLoop;} + if (maze.GetTileAttr(proposedX, proposedY, TileAttr::Left) == false) { + x = proposedX, y = proposedY; + goto ForceValidMazeLoop; + } pokeLocationCount++; } - // if tile to up is of a different traversal state (is traversed boundary) - if (proposedy > 0 && (maze.getSide(proposedx, proposedy-1, TileAttr::flaggen) != ownState)) { + // if tile up is of a different traversal state (is traversed boundary) + if (proposedY > 0 && (maze.GetTileAttr(proposedX, proposedY - 1, TileAttr::FlagGen) != ownState)) { // if found boundary AND can get to it, just continue working from here - if (maze.getSide(proposedx, proposedy, TileAttr::up) == false) {x = proposedx, y = proposedy; goto ForceValidMazeLoop;} + if (maze.GetTileAttr(proposedX, proposedY, TileAttr::Up) == false) { + x = proposedX, y = proposedY; + goto ForceValidMazeLoop; + } pokeLocationCount++; } } @@ -685,34 +793,37 @@ void WatchFaceMaze::ForceValidMaze() { // finished scanning maze; there are no locations the cursor can be placed for it to continue scanning // if there are no walls that can be poked through to increase reachable area, maze is finished - if (pokeLocationCount == 0) {return;} + if (pokeLocationCount == 0) { + return; + } // if execution gets here, need to poke a hole. // choose a random poke location to poke a hole through. pokeLocationCount is now used as an index - pokeLocationCount = prng.rand(1, pokeLocationCount); - for (int proposedy = 0; proposedy < Maze::HEIGHT; proposedy++) { - for (int proposedx = 0; proposedx < Maze::WIDTH; proposedx++) { + pokeLocationCount = (int) prng.Rand(1, pokeLocationCount); + + for (coord_t proposedY = 0; proposedY < Maze::HEIGHT; proposedY++) { + for (coord_t proposedX = 0; proposedX < Maze::WIDTH; proposedX++) { // pretty much a copy of the previous code which FINDS poke locations, but now with the goal of actually doing the poking - const bool ownState = maze.getSide(proposedx, proposedy, TileAttr::flaggen); + const bool ownState = maze.GetTileAttr(proposedX, proposedY, TileAttr::FlagGen); - if (proposedx > 0 && (maze.getSide(proposedx-1, proposedy, TileAttr::flaggen) != ownState)) { + if (proposedX > 0 && (maze.GetTileAttr(proposedX - 1, proposedY, TileAttr::FlagGen) != ownState)) { pokeLocationCount--; // found the target poke location, poke and loop if (pokeLocationCount == 0) { - maze.setSide(proposedx, proposedy, TileAttr::left, false); - x = proposedx, y = proposedy; + maze.SetAttr(proposedX, proposedY, TileAttr::Left, false); + x = proposedX, y = proposedY; goto ForceValidMazeLoop; // continue OUTSIDE loop } } - // if tile to up is of a different traversal state (is traversed boundary) - if (proposedy > 0 && (maze.getSide(proposedx, proposedy-1, TileAttr::flaggen) != ownState)) { + // if tile up is of a different traversal state (is traversed boundary) + if (proposedY > 0 && (maze.GetTileAttr(proposedX, proposedY - 1, TileAttr::FlagGen) != ownState)) { pokeLocationCount--; // found the target poke location, poke and loop if (pokeLocationCount == 0) { - maze.setSide(proposedx, proposedy, TileAttr::up, false); - x = proposedx, y = proposedy; - goto ForceValidMazeLoop; // continue processing + maze.SetAttr(proposedX, proposedY, TileAttr::Up, false); + x = proposedX, y = proposedY; + goto ForceValidMazeLoop; // continue processing } } } @@ -722,72 +833,99 @@ void WatchFaceMaze::ForceValidMaze() { } } - void WatchFaceMaze::DrawMaze() { // this used to be nice code, but it was retrofitted to print offset by 1 pixel for a fancy border. // I'm not proud of the logic but it works. lv_area_t area; - swapActiveBuffer(); // who knows who used the buffer before this + SwapActiveBuffer(); // who knows who used the buffer before this // Print horizontal lines // This doesn't bother with corners, those just get overwritten by the vertical lines area.x1 = 1; area.x2 = 238; - for (int y = 1; y < Maze::HEIGHT; y++) { - for (int x = 0; x < Maze::WIDTH; x++) { - if (maze.get(x, y).getUp()) {std::fill_n(&activeBuffer[x*Maze::TILESIZE], Maze::TILESIZE, LV_COLOR_WHITE);} - else {std::fill_n(&activeBuffer[x*Maze::TILESIZE], Maze::TILESIZE, LV_COLOR_BLACK);} + for (coord_t y = 1; y < Maze::HEIGHT; y++) { + for (coord_t x = 0; x < Maze::WIDTH; x++) { + if (maze.Get(x, y).GetUp()) { + std::fill_n(&activeBuffer[x * Maze::TILESIZE], Maze::TILESIZE, LV_COLOR_WHITE); + } else { + std::fill_n(&activeBuffer[x * Maze::TILESIZE], Maze::TILESIZE, LV_COLOR_BLACK); + } } std::copy_n(activeBuffer, 238, &activeBuffer[238]); area.y1 = (Maze::TILESIZE * y) - 1; area.y2 = (Maze::TILESIZE * y); lvgl.SetFullRefresh(Components::LittleVgl::FullRefreshDirections::None); lvgl.FlushDisplay(&area, activeBuffer); - swapActiveBuffer(); + SwapActiveBuffer(); } // Print vertical lines area.y1 = 1; area.y2 = 238; - for (int x = 1; x < Maze::WIDTH; x++) { - for (int y = 0; y < Maze::HEIGHT; y++) { - MazeTile curblock = maze.get(x,y); + for (coord_t x = 1; x < Maze::WIDTH; x++) { + for (coord_t y = 0; y < Maze::HEIGHT; y++) { + MazeTile curblock = maze.Get(x, y); // handle corners: if any of the touching lines are present, add corner. else leave it black - if (curblock.getUp() || curblock.getLeft() || maze.get(x-1,y).getUp() || maze.get(x,y-1).getLeft()) - {std::fill_n(&activeBuffer[y*Maze::TILESIZE*2], 4, LV_COLOR_WHITE);} - else {std::fill_n(&activeBuffer[y*Maze::TILESIZE*2], 4, LV_COLOR_BLACK);} + if (curblock.GetUp() || curblock.GetLeft() || maze.Get(x - 1, y).GetUp() || maze.Get(x, y - 1).GetLeft()) { + std::fill_n(&activeBuffer[y * Maze::TILESIZE * 2], 4, LV_COLOR_WHITE); + } else { + std::fill_n(&activeBuffer[y * Maze::TILESIZE * 2], 4, LV_COLOR_BLACK); + } // handle actual wall segments - if (curblock.getLeft()) {std::fill_n(&activeBuffer[(y*Maze::TILESIZE*2)+4], (Maze::TILESIZE*2)-4, LV_COLOR_WHITE);} - else {std::fill_n(&activeBuffer[(y*Maze::TILESIZE*2)+4], (Maze::TILESIZE*2)-4, LV_COLOR_BLACK);} + if (curblock.GetLeft()) { + std::fill_n(&activeBuffer[(y * Maze::TILESIZE * 2) + 4], (Maze::TILESIZE * 2) - 4, LV_COLOR_WHITE); + } else { + std::fill_n(&activeBuffer[(y * Maze::TILESIZE * 2) + 4], (Maze::TILESIZE * 2) - 4, LV_COLOR_BLACK); + } } - area.x1 = Maze::TILESIZE * x - 1; - area.x2 = Maze::TILESIZE * x; + area.x1 = (Maze::TILESIZE * x) - 1; + area.x2 = (Maze::TILESIZE * x); lvgl.SetFullRefresh(Components::LittleVgl::FullRefreshDirections::None); lvgl.FlushDisplay(&area, &activeBuffer[4]); - swapActiveBuffer(); + SwapActiveBuffer(); } // Print borders // don't need to worry about switching buffers here since buffer contents aren't changing std::fill_n(activeBuffer, 240, LV_COLOR_GRAY); for (int i = 0; i < 4; i++) { - if (i==0) {area.x1=0; area.x2=239; area.y1=0; area.y2=0; } // top - else if (i==1) {area.x1=0; area.x2=239; area.y1=239; area.y2=239;} // bottom - else if (i==2) {area.x1=0; area.x2=0; area.y1=0; area.y2=239;} // left - else if (i==3) {area.x1=239; area.x2=239; area.y1=0; area.y2=239;} // right + if (i == 0) { + area.x1 = 0; + area.x2 = 239; + area.y1 = 0; + area.y2 = 0; + } // top + else if (i == 1) { + area.x1 = 0; + area.x2 = 239; + area.y1 = 239; + area.y2 = 239; + } // bottom + else if (i == 2) { + area.x1 = 0; + area.x2 = 0; + area.y1 = 0; + area.y2 = 239; + } // left + else if (i == 3) { + area.x1 = 239; + area.x2 = 239; + area.y1 = 0; + area.y2 = 239; + } // right lvgl.SetFullRefresh(Components::LittleVgl::FullRefreshDirections::None); lvgl.FlushDisplay(&area, activeBuffer); } } - -void WatchFaceMaze::DrawTileInner(int16_t x, int16_t y, lv_color_t color) { +void WatchFaceMaze::DrawTileInner(const coord_t x, const coord_t y, const lv_color_t color) { // early exit if would print OOB - if (x < 0 || y < 0 || x > Maze::WIDTH-1 || y > Maze::HEIGHT-1) - {return;} + if (x < 0 || y < 0 || x > Maze::WIDTH - 1 || y > Maze::HEIGHT - 1) { + return; + } // prepare buffer - swapActiveBuffer(); + SwapActiveBuffer(); std::fill_n(activeBuffer, 64, color); lv_area_t area; @@ -802,38 +940,38 @@ void WatchFaceMaze::DrawTileInner(int16_t x, int16_t y, lv_color_t color) { lvgl.FlushDisplay(&area, activeBuffer); } - void WatchFaceMaze::ClearConfetti() { // prevent superfluous calls - if (!confettiActive) {return;} + if (!confettiActive) { + return; + } // clear all particles and reset state - for (const ConfettiParticle &particle : confettiArr) { - DrawTileInner(particle.tileX(), particle.tileY(), LV_COLOR_BLACK); + for (const ConfettiParticle& particle : confettiArr) { + DrawTileInner(particle.TileX(), particle.TileY(), LV_COLOR_BLACK); } confettiActive = false; } - void WatchFaceMaze::ProcessConfetti() { // and draw all the confetti // flag "done" stays true if all step() calls stated that the particle was done, otherwise it goes false - bool done = true; - for (ConfettiParticle &particle : confettiArr) { - int16_t oldx = particle.tileX(); - int16_t oldy = particle.tileY(); - // if any step() calls return false (i.e. not finished), done gets set to false as well - done = particle.step() && done; + bool isDone = true; + for (ConfettiParticle& particle : confettiArr) { + const coord_t oldX = particle.TileX(); + const coord_t oldY = particle.TileY(); + // if any step() calls return false (i.e. not finished), isDone gets set to false as well + isDone = particle.Step() && isDone; // need to redraw? - if (oldx != particle.tileX() || oldy != particle.tileY()) { - DrawTileInner(oldx, oldy, LV_COLOR_BLACK); - DrawTileInner(particle.tileX(), particle.tileY(), particle.color); + if (oldX != particle.TileX() || oldY != particle.TileY()) { + DrawTileInner(oldX, oldY, LV_COLOR_BLACK); + DrawTileInner(particle.TileX(), particle.TileY(), particle.color); } } // handle done flag // should only set confettiActive to false, since all confetti will have been cleared as it moved out of frame - if (done) { + if (isDone) { confettiActive = false; } } \ No newline at end of file diff --git a/src/displayapp/screens/WatchFaceMaze.h b/src/displayapp/screens/WatchFaceMaze.h index 407354ae30..2c0da8abd6 100644 --- a/src/displayapp/screens/WatchFaceMaze.h +++ b/src/displayapp/screens/WatchFaceMaze.h @@ -1,8 +1,15 @@ #pragma once +#include +#include +#include "FreeRTOS.h" +#include "task.h" // configTICK_RATE_HZ +#include "sys/unistd.h" +#include "displayapp/LittleVgl.h" #include "displayapp/apps/Apps.h" #include "displayapp/screens/Screen.h" #include "displayapp/Controllers.h" +#include "displayapp/screens/Tile.h" #include "components/datetime/DateTimeController.h" #include "utility/DirtyValue.h" #include "displayapp/LittleVgl.h" @@ -11,9 +18,10 @@ namespace Pinetime { namespace Applications { namespace Screens { + using coord_t = int16_t; // Really just an abstraction of a uint8_t but with functions to get the individual bits. - // reading up on bitfields and their potential inconsistencies scared me away from them... + // Not using bitfields because they can't guarantee value positions, and I need everything in the low 4 bits. struct MazeTile { static constexpr uint8_t UPMASK = 0b0001; static constexpr uint8_t LEFTMASK = 0b0010; @@ -22,33 +30,61 @@ namespace Pinetime { uint8_t map = 0; // Set flags on given tile. Returns the object so they can be chained. - // Am I cool for aligning it like this? Or does it just make it hard to read... - MazeTile setUp(bool value) {map = (map&~UPMASK) | (value * UPMASK); return *this;} - MazeTile setLeft(bool value) {map = (map&~LEFTMASK) | (value * LEFTMASK); return *this;} - MazeTile setFlagEmpty(bool value) {map = (map&~FLAGEMPTYMASK) | (value * FLAGEMPTYMASK); return *this;} - MazeTile setFlagGen(bool value) {map = (map&~FLAGGENMASK) | (value * FLAGGENMASK); return *this;} + + MazeTile SetUp(const bool value) { + map = (map & ~UPMASK) | (value * UPMASK); + return *this; + } + + MazeTile SetLeft(const bool value) { + map = (map & ~LEFTMASK) | (value * LEFTMASK); + return *this; + } + + MazeTile SetFlagEmpty(const bool value) { + map = (map & ~FLAGEMPTYMASK) | (value * FLAGEMPTYMASK); + return *this; + } + + MazeTile SetFlagGen(const bool value) { + map = (map & ~FLAGGENMASK) | (value * FLAGGENMASK); + return *this; + } // Get flags on given tile - bool getUp() const {return map & UPMASK;} - bool getLeft() const {return map & LEFTMASK;} - bool getFlagEmpty() const {return map & FLAGEMPTYMASK;} - bool getFlagGen() const {return map & FLAGGENMASK;} - }; + bool GetUp() const { + return map & UPMASK; + } + bool GetLeft() const { + return map & LEFTMASK; + } + bool GetFlagEmpty() const { + return map & FLAGEMPTYMASK; + } + + bool GetFlagGen() const { + return map & FLAGGENMASK; + } + }; // Custom PRNG for the maze to easily allow it to be deterministic for any given minute - // Maybe faster too? Probably not by much if it is. class MazeRNG { public: - MazeRNG(uint64_t start_seed = 64) {seed(start_seed);} + MazeRNG(const uint64_t start_seed = 64) { + Seed(start_seed); + } // Reseed the generator. Handles any input well. If seed is 0, acts as though it was seeded with 1 (prevents breakage). - void seed(uint64_t seed) {state = seed ? seed : 1; rand();} + void Seed(const uint64_t seed) { + state = seed ? seed : 1; + Rand(); + } // RNG lifted straight from https://en.wikipedia.org/wiki/Xorshift#xorshift* (asterisk is part of the link) - uint32_t rand() { + uint32_t Rand() { state ^= state >> 12; state ^= state << 25; state ^= state >> 27; @@ -56,17 +92,17 @@ namespace Pinetime { } // Random in range, inclusive on both ends (don't make max=min); return (rand()%(max-min+1))+min;} + uint32_t Rand(const uint32_t min, const uint32_t max) { + assert(max >= min); + return (Rand() % (max - min + 1)) + min; + } private: uint64_t state; }; - - - - + // Little bit of convenience for working with tile flags - enum TileAttr {up, down, left, right, flagempty, flaggen}; + enum TileAttr { Up, Down, Left, Right, FlagEmpty, FlagGen }; // could also be called Field or something. Does not handle stuff like generation or printing, // ONLY handles interacting with the board. @@ -75,91 +111,89 @@ namespace Pinetime { Maze(); // Get and set can work with either xy or indexes - MazeTile get(int x, int y); - MazeTile get(int index); + MazeTile Get(coord_t x, coord_t y) const; + MazeTile Get(int32_t index) const; - void set(int x, int y, MazeTile tile); - void set(int index, MazeTile tile); + void Set(coord_t x, coord_t y, MazeTile tile); + void Set(int32_t index, MazeTile tile); // Allows abstractly setting a given side on a tile. Supports down and right for convenience. - void setSide(int x, int y, TileAttr attr, bool value); - void setSide(int index, TileAttr attr, bool value); + void SetAttr(coord_t x, coord_t y, TileAttr attr, bool value); + void SetAttr(int32_t index, TileAttr attr, bool value); + + // Same as SetAttr, just getting. + bool GetTileAttr(coord_t x, coord_t y, TileAttr attr) const; + bool GetTileAttr(int32_t index, TileAttr attr) const; - // Same as setSide, just getting. - bool getSide(int x, int y, TileAttr attr); - bool getSide(int index, TileAttr attr); - // fill() fills all tiles in the maze with a given value, optionally with a mask on what bits to change. // Only cares about the low 4 bits in the value and mask. // (use the MazeTile::--MASK values for mask) - void fill(MazeTile tile, uint8_t mask = 0xFF); - void fill(uint8_t value, uint8_t mask = 0xFF); + void Fill(MazeTile tile, uint8_t mask = 0xFF); + void Fill(uint8_t value, uint8_t mask = 0xFF); // Paste onto an empty board. Marks a tile as not empty if any neighboring walls are gone. // toPaste is a 1d array of uint8_t, only containing the two wall bits (left then up). So that's 4 walls in a byte. - // If you have a weird number of tiles (like 17), just pad it out to the nearest byte. The function ignores any extra data. + // If you have a weird number of tiles (like 17), just pad it out to the next byte. The function ignores any extra data. // Always places values left to right, top to bottom. Coords are inclusive. - void pasteMazeSeed(int x1, int y1, int x2, int y2, const uint8_t toPaste[]); + void PasteMazeSeed(coord_t x1, coord_t y1, coord_t x2, coord_t y2, const uint8_t toPaste[]); // 10x10 px tiles on the maze = 24x24 (on 240px screen) // Warning: While these are respected in most functions, changing these could break other features like number displaying and - // indicators. - static constexpr int WIDTH = 24; - static constexpr int HEIGHT = 24; + // indicators. + static constexpr coord_t WIDTH = 24; + static constexpr coord_t HEIGHT = 24; static constexpr int TILESIZE = 10; // The actual size of the entire map. only store 4 bits per block, so 2 blocks per byte - static constexpr int FLATSIZE = WIDTH*HEIGHT/2; + static constexpr int32_t FLATSIZE = WIDTH * HEIGHT / 2; private: // The internal map. Doesn't actually store MazeTiles, but packs their contents to 2 tiles per byte. - uint8_t mazemap[FLATSIZE]; + uint8_t mazeMap[FLATSIZE]; }; - - - // Click on the autismcreature for a colorful surprise class ConfettiParticle { public: - // steps the confetti simulation. Importantly, updates the maze equivalent position. + // Steps the confetti simulation. Importantly, updates the maze equivalent position. // Returns true if the particle has finished processing, else false. // Need to save the particle position elsewhere before stepping to be able to clear the old particle position (if redraw needed). - // I couldn't really figure a better way to do it other than saving the old positions in the class itself and that's just awful. - bool step(); + bool Step(); - // reset position and generate new velocity using the passed prng - void reset(MazeRNG &prng); + // Reset position and generate new direction + velocity using the passed rng object + void Reset(MazeRNG& prng); // x and y positions of the particle. Positions are in pixels, so just divide by tile size to get the tile it's in. - int16_t tileX() const {return xpos > 0 ? xpos/((float)Maze::TILESIZE) : -1;} // need positive check else particles can get stuck to left wall - int16_t tileY() const {return ypos/((float)Maze::TILESIZE);} + coord_t TileX() const { + return xPos > 0 ? (coord_t) ((float) xPos / (float) Maze::TILESIZE) : (coord_t) -1; + } // Need positive check else particles can get stuck to left wall + + coord_t TileY() const { + return (coord_t) ((float) yPos / (float) Maze::TILESIZE); + } - // color of the particle + // Color of the particle lv_color_t color; private: - // positions and velocities of particle, in pixels and pixels/step (~50 steps per second) - float xpos; - float ypos; - float xvel; - float yvel; + // Positions and velocities of particle, in pixels and pixels/step (~50 steps per second) + float xPos; + float yPos; + float xVel; + float yVel; // first apply gravity, then apply damping factor, then add velocity to position - static constexpr float GRAVITY = 0.3; // added to yvel every step (remember up is -y) - static constexpr float DAMPING_FACTOR = 0.99; // keep this much of the velocity every step (applied after gravity) - static constexpr int8_t MAX_START_ANGLE = 45; // degrees off from straight vertical a particle can be going when spawned (must be <90) - static constexpr int16_t MIN_START_VELOCITY = 5; // minimum velocity a particle can spawn with + static constexpr float GRAVITY = 0.3; // added to yvel every step (remember up is -y) + static constexpr float DAMPING_FACTOR = 0.99; // keep this much of the velocity every step (applied after gravity) + static constexpr int8_t MAX_START_ANGLE = 45; // degrees off from straight vertical a particle can be going when spawned (<90) + static constexpr int16_t MIN_START_VELOCITY = 5; // minimum velocity a particle can spawn with static constexpr int16_t MAX_START_VELOCITY = 14; // maximum velocity a particle can spawn with - static constexpr float START_X_COMPRESS = 1./2.; // multiply X velocity by this value. can give a more concentrated confetti blast. + static constexpr float START_X_COMPRESS = 1. / 2.; // multiply X velocity by this value. for a more concentrated confetti blast. }; - - - // What is currently being displayed. // Watchface is normal operation; anything else is an easter egg. Really only used to indicate what - // should be displayed when the screen refreshes. - enum Displaying {watchface, blank, loss, amogus, autismcreature, foxgame, reminder, pinetime}; + // should be displayed when the screen refreshes. + enum Displaying { WatchFace, Blank, Loss, Amogus, AutismCreature, FoxGame, GameReminder, PineTime }; // The watchface itself class WatchFaceMaze : public Screen { @@ -178,54 +212,53 @@ namespace Pinetime { bool OnButtonPushed() override; private: - // Functions related to refreshing, for better separation of processes + // Functions called from Refresh(), for slightly better separation of processes void HandleMazeRefresh(); - void HandleConfetti(); // okay it's not very well separated but at least the confetti part is separate - + void HandleConfettiRefresh(); + // Functions related to drawing the indicators at the top right void UpdateBatteryDisplay(bool forceRedraw = false); void UpdateBleDisplay(bool forceRedraw = false); void ClearIndicators(); - + // Functions related to touching the screen, also for better separation of processes bool HandleLongTap(); bool HandleTap(); bool HandleSwipe(uint8_t direction); // MAZE GENERATION - - // Very simple function which just resets the maze to what is considered blank + + // Resets the maze to what is considered blank void InitializeMaze(); // Seed the maze with whatever the currentState dictates should be shown void SeedMaze(); - void PutTimeDate(); // Puts time onto the watchface. Part of SeedMaze, Do not call directly. + void PutTime(); // Puts time onto the watchface. Part of SeedMaze, Do not call directly. // Generate the maze around whatever the maze was seeded with. // MAZE MUST BE SEEDED ELSE ALL YOU'LL GENERATE IS AN INFINITE LOOP! - // If seed has disconnected components, maze will not be perfect. + // If seed has disconnected components, maze will also have disconnected components. void GenerateMaze(); - void GeneratePath(int x, int y); // Generates a single path starting at the x,y coords. Part of GenerateMaze, do not call directly. + void GeneratePath(coord_t x, coord_t y); // Generates a single path starting at the x,y coords. Part of GenerateMaze, do not call directly. - // If the maze has any disconnected components (such as if seeded with multiple disconnected blocks), - // poke holes to force all components to be connected. + // If the maze has any disconnected components (such as if seed wasn't fully connected), poke holes to force all components to be + // connected. void ForceValidMaze(); - // Draws the maze to the screen. + // Draws the maze to the screen void DrawMaze(); - + // OTHER FUNCTIONS // Fill in the inside of a maze square. Wall states don't affect this; it never draws in the area where walls go. // Generic, but only actually used for confetti. - void DrawTileInner(int16_t x, int16_t y, lv_color_t color); + void DrawTileInner(coord_t x, coord_t y, lv_color_t color); - // Draw and generally deal with confetti + // Functions to update and generally deal with confetti void ProcessConfetti(); void ClearConfetti(); - - // Stuff necessary for interacting with the OS and whatnot + // Stuff necessary for interacting with the OS lv_task_t* taskRefresh; Controllers::DateTime& dateTimeController; Controllers::Settings& settingsController; @@ -238,13 +271,8 @@ namespace Pinetime { Maze maze; MazeRNG prng; - // Time stuff. - // currentDateTime is used for keeping track of minutes. It's what refreshes the screen every minute. - // realTime is present due to what seems to be a race condition if dateTimeController.CurrentDateTime() is called - // by two things at once. It gets updated every refresh, or roughly every 20ms. Do not use it for anything high precision. - // (I really don't understand, but doing this decreased infinisim crashes due to mutexes releasing without being held so whatever) + // Used for keeping track of minutes. It's what refreshes the screen every minute. Utility::DirtyValue> currentDateTime {}; - std::chrono::time_point realTime {}; // Indicator values. Used for refreshing the respective indicators. Utility::DirtyValue batteryPercent; @@ -255,77 +283,119 @@ namespace Pinetime { // Warning: because each confetti moving causes 2 draw calls, this is really slow in Infinisim. Lower if using Infinisim (to ~20). constexpr static uint16_t CONFETTI_COUNT = 50; ConfettiParticle confettiArr[CONFETTI_COUNT]; - bool initConfetti = false; // don't want to modify confettiArr in touch event handler, so use a flag and do it in refresh() + bool initConfetti = false; // don't want to modify confettiArr in touch event handler, so use a flag and do it in Refresh() bool confettiActive = false; - // Buffers for use during printing. There's two it flips between because if there was only one, it would start - // being overwritten before the DMA finishes, and it'd corrupt parts of the display. - // activeBuffer is, well, the currently active one. Switch with swapActiveBuffer(); + // Buffers for use during printing. There's two it flips between because if there was only one, it could start + // being overwritten before the DMA finishes, and it'd corrupt the write. + // activeBuffer is, well, the currently active one. Switch with SwapActiveBuffer(); lv_color_t buf1[480]; lv_color_t buf2[480]; - lv_color_t *activeBuffer = buf1; - void swapActiveBuffer() {activeBuffer = (activeBuffer==buf1) ? buf2 : buf1;} + lv_color_t* activeBuffer = buf1; - // All concerning the printing of the screen. If screenRefreshRequired is set and screenRefreshTargetTime is - // greater than the current time, it waits until that target time before refreshing. Otherwise, it refreshes - // immediately when it sees that screenRefreshRequired is set. + void SwapActiveBuffer() { + activeBuffer = (activeBuffer == buf1) ? buf2 : buf1; + } + + // All concerning the printing of the screen. + // screenRefreshRequired is just a flag that the screen needs redrawing. Used when switching between secrets. // pausedGeneration is used if the maze took too long to generate, so it lets other processes get cpu time. - // It really should never trigger with this small 24x24 maze. + // It really should never trigger with this small 24x24 maze. // pausedGeneration does NOT protect against infinite loops from unseeded mazes! It only checks after each path has been generated! - std::chrono::time_point screenRefreshTargetTime {}; bool screenRefreshRequired = false; bool pausedGeneration = false; // Number data and AM/PM data for displaying time constexpr static uint8_t numbers[10][15] /*6x10*/ = { - {0xF5,0x7C,0x01,0x8F,0x88,0xF8,0x8F,0x88,0xF8,0x8F,0x88,0xF8,0x85,0x0E,0x03}, - {0xF5,0xFC,0x0F,0x80,0xFF,0x8F,0xF8,0xFF,0x8F,0xF8,0xFF,0x8F,0xD0,0x58,0x00}, - {0xF5,0x7C,0x01,0x8F,0x88,0xF8,0xFF,0x0F,0xC0,0xF0,0x3C,0x0F,0x80,0x58,0x00}, - {0xF5,0x7C,0x01,0x8F,0x8F,0xF8,0xFD,0x0F,0x80,0xFF,0x8D,0xF8,0x85,0x0E,0x03}, - {0xDF,0xD8,0xF8,0x8F,0x88,0xF8,0x85,0x08,0x00,0xFF,0x8F,0xF8,0xFF,0x8F,0xF8}, - {0xD5,0x58,0x00,0x8F,0xF8,0xFF,0x85,0x78,0x01,0xFF,0x8F,0xF8,0xD5,0x08,0x03}, - {0xF5,0x7C,0x03,0x8F,0xF8,0xFF,0x85,0x78,0x01,0x8F,0x88,0xF8,0x85,0x0E,0x03}, - {0xD5,0x58,0x00,0xFF,0x8F,0xF8,0xFF,0x0F,0xE3,0xFE,0x3F,0xC3,0xF8,0xFF,0x8F}, - {0xF5,0x7C,0x01,0x8F,0x88,0xF0,0x84,0x3E,0x01,0xC3,0x88,0xF8,0x85,0x0E,0x03}, - {0xF5,0x7C,0x01,0x8F,0x88,0xF8,0x85,0x0E,0x00,0xFF,0x8F,0xF8,0xF5,0x0E,0x03}}; - constexpr static uint8_t am[10] /*5x8*/ = {0xF5,0xF0,0x18,0xE2,0x38,0x84,0x20,0x08,0xE2,0x38}; - constexpr static uint8_t pm[10] /*5x8*/ = {0xD5,0xE0,0x18,0xE2,0x38,0x84,0x20,0x38,0xFE,0x3F}; + {0xF5, 0x7C, 0x01, 0x8F, 0x88, 0xF8, 0x8F, 0x88, 0xF8, 0x8F, 0x88, 0xF8, 0x85, 0x0E, 0x03}, + {0xF5, 0xFC, 0x0F, 0x80, 0xFF, 0x8F, 0xF8, 0xFF, 0x8F, 0xF8, 0xFF, 0x8F, 0xD0, 0x58, 0x00}, + {0xF5, 0x7C, 0x01, 0x8F, 0x88, 0xF8, 0xFF, 0x0F, 0xC0, 0xF0, 0x3C, 0x0F, 0x80, 0x58, 0x00}, + {0xF5, 0x7C, 0x01, 0x8F, 0x8F, 0xF8, 0xFD, 0x0F, 0x80, 0xFF, 0x8D, 0xF8, 0x85, 0x0E, 0x03}, + {0xDF, 0xD8, 0xF8, 0x8F, 0x88, 0xF8, 0x85, 0x08, 0x00, 0xFF, 0x8F, 0xF8, 0xFF, 0x8F, 0xF8}, + {0xD5, 0x58, 0x00, 0x8F, 0xF8, 0xFF, 0x85, 0x78, 0x01, 0xFF, 0x8F, 0xF8, 0xD5, 0x08, 0x03}, + {0xF5, 0x7C, 0x03, 0x8F, 0xF8, 0xFF, 0x85, 0x78, 0x01, 0x8F, 0x88, 0xF8, 0x85, 0x0E, 0x03}, + {0xD5, 0x58, 0x00, 0xFF, 0x8F, 0xF8, 0xFF, 0x0F, 0xE3, 0xFE, 0x3F, 0xC3, 0xF8, 0xFF, 0x8F}, + {0xF5, 0x7C, 0x01, 0x8F, 0x88, 0xF0, 0x84, 0x3E, 0x01, 0xC3, 0x88, 0xF8, 0x85, 0x0E, 0x03}, + {0xF5, 0x7C, 0x01, 0x8F, 0x88, 0xF8, 0x85, 0x0E, 0x00, 0xFF, 0x8F, 0xF8, 0xF5, 0x0E, 0x03}}; + constexpr static uint8_t am[10] /*5x8*/ = {0xF5, 0xF0, 0x18, 0xE2, 0x38, 0x84, 0x20, 0x08, 0xE2, 0x38}; + constexpr static uint8_t pm[10] /*5x8*/ = {0xD5, 0xE0, 0x18, 0xE2, 0x38, 0x84, 0x20, 0x38, 0xFE, 0x3F}; constexpr static uint8_t blankseed[1] /*4x1*/ = {0xD5}; - constexpr static uint8_t indicatorSpace[3] /*3x3*/ = {0xF6,0x8A,0x00}; + constexpr static uint8_t indicatorSpace[3] /*3x3*/ = {0xF6, 0x8A, 0x00}; // Used for swipe sequences for easter eggs. - // currentState is what is being displayed. It's generally categorized into "watchface" and "not watchface". - // lastInputTime is for long clicking on the main watchface. If you long click twice in 2 seconds, it goes to the secret input screen. - // currentCode is the current swipe sequence that's being worked on - Displaying currentState = Displaying::watchface; - std::chrono::time_point lastInputTime {}; + // currentState is what is being displayed. It's generally categorized into "WatchFace" and "not WatchFace". + // lastInputTime is for long clicking on the main watchface. If you long click twice in a certain timespan, it goes to the secret input + // doubleDoubleClickDelay is the aforementioned 'certain timespan' to get to the secret input. In ticks. + // screen. currentCode is the current swipe sequence that's being inputted. + Displaying currentState = Displaying::WatchFace; + uint32_t lastLongClickTime; + constexpr static uint32_t doubleDoubleClickDelay = pdMS_TO_TICKS(2500); uint8_t currentCode[8]; // Input codes for secret swipe gestures - // Note that the codes are effectively backwards; the currentCode is a stack being pushed from the left. Values are 0-3, clockwise from up. - // After a code is inputted the code is cleared, so if making a code smaller than the max size take care that it doesn't overlap - // with any other code. - constexpr static uint8_t lossCode[8] = {0,0,2,2,3,1,3,1}; // RLRLDDUU (konami code backwards) - constexpr static uint8_t amogusCode[8] = {1,3,1,3,2,2,0,0}; // UUDDLRLR (konami code) - constexpr static uint8_t autismCode[8] = {3,1,3,1,3,1,3,1}; // RLRLRLRL (pet pet pet pet) - constexpr static uint8_t foxCode[7] = {0,1,0,3,2,1,2}; // a healthy secret in a curious game :3 - constexpr static uint8_t reminderCode[4] = {3,2,1,0}; // URDL - constexpr static uint8_t pinetimeCode[8] = {1,2,3,0,1,2,3,0}; // ULDRULDR (two counterclockwise rotations) + // Note that the codes are effectively backwards; the currentCode is like a stack being pushed from the left. Values are 0-3, + // clockwise from up. After a code is inputted the currentCode is cleared, so if making a code smaller than the max size take care + // that it doesn't overlap with any other code. + constexpr static uint8_t lossCode[8] = {0, 0, 2, 2, 3, 1, 3, 1}; // RLRLDDUU (konami code backwards) + constexpr static uint8_t amogusCode[8] = {1, 3, 1, 3, 2, 2, 0, 0}; // UUDDLRLR (konami code) + constexpr static uint8_t autismCode[8] = {3, 1, 3, 1, 3, 1, 3, 1}; // RLRLRLRL (pet pet pet pet) + constexpr static uint8_t foxCode[7] = {0, 1, 0, 3, 2, 1, 2}; // a healthy secret in Tunic :3 + constexpr static uint8_t reminderCode[4] = {3, 2, 1, 0}; // URDL (clockwise rotation) + constexpr static uint8_t pinetimeCode[8] = {1, 2, 3, 0, 1, 2, 3, 0}; // ULDRULDR (two counterclockwise rotations) // Maze data for secrets. These are pasted onto the screen when the corresponding code is entered. - constexpr static uint8_t loss[105] /*21x20*/ = {0xFD,0xFF,0xFF,0xF7,0xFF,0xFE,0x3F,0xFF,0xF8,0xFF,0xFF,0x8F,0xFF,0xFE,0x3F,0xDF,0xE3,0xFF,0xFF,0x8F,0xE3,0xF8,0xFF,0xFF,0xE3,0xF8,0xFE,0x3F,0xFF,0xF8,0xFE,0x3F,0x8F,0xFF,0xFE,0x3F,0x8F,0xE3,0xFF,0xFF,0x8F,0xE3,0xF8,0xFF,0xFF,0xE3,0xF8,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x7F,0xDF,0xFF,0x7F,0xFF,0x8F,0xE3,0xFF,0x8F,0xFF,0xE3,0xF8,0xFF,0xE3,0xFF,0xF8,0xFE,0x3F,0xF8,0xFF,0xFE,0x3F,0x8F,0xFE,0x3F,0xFF,0x8F,0xE3,0xFF,0x8F,0xFF,0xE3,0xF8,0xFF,0xE3,0xFF,0xF8,0xFE,0x3F,0xF8,0xF5,0x56,0x3F,0x8F,0xFE,0x38,0x00}; - constexpr static uint8_t amogus[114] /*19x24*/ = {0xFF,0xFF,0x55,0x7F,0xFF,0xFF,0xD0,0x00,0x7F,0xFF,0xFE,0x0F,0xF8,0x7F,0xFF,0xF0,0xFF,0x40,0x7F,0xFF,0x83,0xF0,0x00,0x5F,0xFE,0x3F,0x0F,0xFE,0x1F,0x50,0xF8,0xFF,0xFE,0x30,0x03,0xE3,0xFF,0xF8,0x8F,0x8F,0x87,0xFF,0xC2,0x3E,0x3F,0x85,0x54,0x38,0xF8,0xFF,0xE0,0x03,0xE3,0xE3,0xFF,0xFF,0x8F,0x8F,0x8F,0xFF,0xFE,0x3E,0x3E,0x3F,0xFF,0xF8,0xF8,0xF8,0xFF,0xFF,0xE3,0xE3,0xE3,0xFF,0xFF,0x8F,0x8F,0x8F,0xFF,0xFE,0x3E,0x14,0x3F,0xD7,0xF8,0xFE,0x00,0xFC,0x07,0xE3,0xFF,0x83,0xE3,0x8F,0x8F,0xFF,0x8F,0x8E,0x3E,0x3F,0xFE,0x3E,0x38,0xF8,0xFF,0xF8,0x50,0xE1,0x43,0xFF,0xE0,0x03,0x80,0x3F}; - constexpr static uint8_t autismcreature[126] /*24x21*/ = {0xFD,0x55,0x55,0x7F,0xFF,0xFF,0xF0,0x00,0x00,0x17,0xFF,0xFF,0xC3,0xFF,0xFF,0x81,0xFF,0xFF,0x8F,0xFF,0xFF,0xF8,0x7F,0xFF,0xBF,0x5F,0xF5,0xFE,0x3F,0xFF,0xBF,0x87,0xF8,0x7E,0x3F,0xFF,0xB9,0x03,0x90,0x3E,0x3F,0xFF,0xB8,0x03,0x80,0x3E,0x3F,0xFF,0xBE,0x0F,0xE0,0xFE,0x15,0x57,0x9F,0xFF,0xFF,0xFC,0x00,0x01,0x87,0xFF,0xFF,0xF0,0x3F,0xF8,0xE1,0x5F,0xFF,0x43,0xFF,0xF8,0xF8,0x05,0x54,0x0F,0xFF,0xF8,0xFE,0x00,0x00,0xFF,0xFF,0xF8,0xFE,0x3F,0xFF,0xFF,0xFF,0xF8,0xFE,0x3F,0x7F,0xD7,0xFD,0xF8,0xFE,0x3E,0x3F,0x01,0xF8,0xF8,0xFE,0x3E,0x3E,0x00,0xF8,0xF8,0xFE,0x3E,0x3E,0x38,0xF8,0xF8,0xFE,0x14,0x14,0x38,0x50,0x50,0xFF,0x80,0x00,0xFE,0x00,0x03}; - constexpr static uint8_t foxgame[132] /*24x22*/ = {0xFF,0xD7,0xFF,0xFF,0xF5,0xFF,0xFD,0x01,0x7F,0xFF,0x40,0x5F,0xF0,0x38,0x1F,0xFC,0x0E,0x07,0xC3,0xFF,0x87,0xF0,0xFF,0xE1,0x8F,0xFF,0xE3,0xE3,0xFF,0xF8,0x8F,0xFF,0xFF,0xE1,0xFF,0xF0,0x8F,0xFF,0xFF,0xE0,0x5F,0x43,0x8F,0xFF,0xFF,0xE0,0x04,0x0F,0x8F,0xFF,0xFF,0xE3,0xE0,0xFF,0x8F,0xFF,0xFF,0xE3,0xFF,0xFF,0x85,0x55,0x55,0x41,0x55,0x55,0x80,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0xFF,0xF7,0xFF,0xFF,0x8F,0xFF,0xFF,0xE3,0xFF,0xFF,0x8F,0xFF,0xFF,0xE3,0xFF,0xFF,0x8F,0xFF,0xFF,0xE3,0xFF,0xFF,0x8F,0xFF,0xFF,0xE3,0xFF,0xFF,0x87,0xFF,0xFF,0xE1,0xFF,0xFF,0xE1,0x7F,0xFF,0xF8,0x5F,0xFF,0xF8,0x17,0xFF,0xFE,0x05,0xFF,0xFF,0x83,0xFF,0xFF,0xE0,0xFF}; - constexpr static uint8_t reminder[102] /*24x17*/ = {0xFF,0xD5,0xF7,0xDF,0x57,0xFF,0xFF,0x80,0xE3,0x8E,0x03,0xFF,0xFF,0xE3,0xE3,0x8E,0x3F,0xFF,0xFF,0xE3,0xE1,0x0E,0x17,0xFF,0xFF,0xE3,0xE0,0x0E,0x03,0xFF,0xFF,0xE3,0xE3,0x8E,0x3F,0xFF,0xFF,0xE3,0xE3,0x8E,0x17,0xFF,0xFF,0xE3,0xE3,0x8E,0x03,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF5,0x7F,0x5F,0xF5,0x5F,0xD5,0xC0,0x3C,0x07,0xC0,0x07,0x80,0x8F,0xF8,0xE3,0x88,0xE3,0x8F,0x8F,0xF8,0xE3,0x88,0xE3,0x85,0x8F,0x78,0x43,0x88,0xE3,0x80,0x8E,0x38,0x03,0x8F,0xE3,0x8F,0x84,0x38,0xE3,0x8F,0xE3,0x85,0xE0,0xF8,0xE3,0x8F,0xE3,0x80}; - //constexpr static uint8_t foxface[144] /*24x24*/ = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFD,0x7F,0xFF,0xFF,0xFF,0xFF,0xF8,0x17,0xFF,0xFF,0xFF,0xFF,0xF8,0x01,0x7F,0xFF,0xF5,0x5F,0xF8,0xF8,0x1F,0xFF,0x40,0x07,0xF8,0xFF,0x8F,0xF4,0x0F,0xE3,0xF8,0x7F,0x85,0x40,0xFF,0xC3,0xF8,0x1F,0xE0,0x0F,0xFD,0x03,0xF8,0xE7,0xFF,0xFF,0xF3,0xE3,0xF0,0xFF,0xFF,0xFF,0xFF,0xE3,0xC0,0xFD,0x7F,0xFF,0xFF,0xCF,0x8F,0xFE,0x1F,0xFD,0x7F,0x8F,0xBF,0xE4,0x0F,0xFE,0x1F,0x87,0xFF,0xE0,0x0F,0xE4,0x0F,0xE1,0xFF,0xF8,0x3F,0xE0,0x0F,0xF8,0xDF,0xFF,0xFF,0xF8,0x3F,0xF8,0xE5,0x7F,0xF5,0xFF,0xFF,0xF8,0xFF,0x95,0x40,0x7F,0xFF,0xFE,0xFF,0xFF,0xE0,0x15,0x55,0x54,0xBF,0xFF,0xF8,0xFF,0xFF,0xFF,0x9F,0xFF,0xFF,0xFF,0xFF,0xFD,0x87,0xFF,0xFF,0xFF,0xFF,0xF0,0xE1,0x5F,0xFF,0xFF,0xFF,0x43,0xF8,0x05,0x5F,0xFF,0xD4,0x3F}; - constexpr static uint8_t pinetime[120] /*20x24*/ = {0xFF,0xFF,0xF7,0xFF,0xFF,0xFF,0xFF,0xC1,0xFF,0xFF,0xFF,0xFF,0x00,0x7F,0xFF,0xFF,0xF6,0x00,0x37,0xFF,0xFF,0xC1,0x63,0x41,0xFF,0xFF,0x80,0xD5,0x80,0xFF,0xFF,0xB5,0x00,0x56,0xFF,0xF7,0xC0,0x00,0x01,0xF7,0xE1,0x60,0x00,0x03,0x43,0xE0,0x15,0x80,0xD4,0x03,0xE0,0x00,0x5D,0x00,0x03,0xE0,0x00,0xD5,0x80,0x03,0xE0,0x35,0x00,0x56,0x03,0xE3,0x40,0x00,0x01,0x63,0x96,0x00,0x00,0x00,0x34,0x81,0x60,0x00,0x03,0x40,0xE0,0x15,0x80,0xD4,0x03,0xE0,0x00,0x77,0x00,0x03,0xF8,0x00,0xC1,0x80,0x0F,0xF8,0x0D,0x00,0x58,0x0F,0xFF,0xD0,0x00,0x05,0xFF,0xFF,0xE0,0x00,0x03,0xFF,0xFF,0xFE,0x00,0x3F,0xFF,0xFF,0xFF,0xE3,0xFF,0xFF}; + constexpr static uint8_t loss[105] /*21x20*/ = { + 0xFD, 0xFF, 0xFF, 0xF7, 0xFF, 0xFE, 0x3F, 0xFF, 0xF8, 0xFF, 0xFF, 0x8F, 0xFF, 0xFE, 0x3F, 0xDF, 0xE3, 0xFF, 0xFF, 0x8F, 0xE3, + 0xF8, 0xFF, 0xFF, 0xE3, 0xF8, 0xFE, 0x3F, 0xFF, 0xF8, 0xFE, 0x3F, 0x8F, 0xFF, 0xFE, 0x3F, 0x8F, 0xE3, 0xFF, 0xFF, 0x8F, 0xE3, + 0xF8, 0xFF, 0xFF, 0xE3, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xDF, 0xFF, 0x7F, 0xFF, + 0x8F, 0xE3, 0xFF, 0x8F, 0xFF, 0xE3, 0xF8, 0xFF, 0xE3, 0xFF, 0xF8, 0xFE, 0x3F, 0xF8, 0xFF, 0xFE, 0x3F, 0x8F, 0xFE, 0x3F, 0xFF, + 0x8F, 0xE3, 0xFF, 0x8F, 0xFF, 0xE3, 0xF8, 0xFF, 0xE3, 0xFF, 0xF8, 0xFE, 0x3F, 0xF8, 0xF5, 0x56, 0x3F, 0x8F, 0xFE, 0x38, 0x00}; + constexpr static uint8_t amogus[114] /*19x24*/ = { + 0xFF, 0xFF, 0x55, 0x7F, 0xFF, 0xFF, 0xD0, 0x00, 0x7F, 0xFF, 0xFE, 0x0F, 0xF8, 0x7F, 0xFF, 0xF0, 0xFF, 0x40, 0x7F, + 0xFF, 0x83, 0xF0, 0x00, 0x5F, 0xFE, 0x3F, 0x0F, 0xFE, 0x1F, 0x50, 0xF8, 0xFF, 0xFE, 0x30, 0x03, 0xE3, 0xFF, 0xF8, + 0x8F, 0x8F, 0x87, 0xFF, 0xC2, 0x3E, 0x3F, 0x85, 0x54, 0x38, 0xF8, 0xFF, 0xE0, 0x03, 0xE3, 0xE3, 0xFF, 0xFF, 0x8F, + 0x8F, 0x8F, 0xFF, 0xFE, 0x3E, 0x3E, 0x3F, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0xFF, 0xE3, 0xE3, 0xE3, 0xFF, 0xFF, 0x8F, + 0x8F, 0x8F, 0xFF, 0xFE, 0x3E, 0x14, 0x3F, 0xD7, 0xF8, 0xFE, 0x00, 0xFC, 0x07, 0xE3, 0xFF, 0x83, 0xE3, 0x8F, 0x8F, + 0xFF, 0x8F, 0x8E, 0x3E, 0x3F, 0xFE, 0x3E, 0x38, 0xF8, 0xFF, 0xF8, 0x50, 0xE1, 0x43, 0xFF, 0xE0, 0x03, 0x80, 0x3F}; + constexpr static uint8_t autismCreature[126] /*24x21*/ = { + 0xFD, 0x55, 0x55, 0x7F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x17, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, + 0xF8, 0x7F, 0xFF, 0xBF, 0x5F, 0xF5, 0xFE, 0x3F, 0xFF, 0xBF, 0x87, 0xF8, 0x7E, 0x3F, 0xFF, 0xB9, 0x03, 0x90, 0x3E, 0x3F, 0xFF, + 0xB8, 0x03, 0x80, 0x3E, 0x3F, 0xFF, 0xBE, 0x0F, 0xE0, 0xFE, 0x15, 0x57, 0x9F, 0xFF, 0xFF, 0xFC, 0x00, 0x01, 0x87, 0xFF, 0xFF, + 0xF0, 0x3F, 0xF8, 0xE1, 0x5F, 0xFF, 0x43, 0xFF, 0xF8, 0xF8, 0x05, 0x54, 0x0F, 0xFF, 0xF8, 0xFE, 0x00, 0x00, 0xFF, 0xFF, 0xF8, + 0xFE, 0x3F, 0xFF, 0xFF, 0xFF, 0xF8, 0xFE, 0x3F, 0x7F, 0xD7, 0xFD, 0xF8, 0xFE, 0x3E, 0x3F, 0x01, 0xF8, 0xF8, 0xFE, 0x3E, 0x3E, + 0x00, 0xF8, 0xF8, 0xFE, 0x3E, 0x3E, 0x38, 0xF8, 0xF8, 0xFE, 0x14, 0x14, 0x38, 0x50, 0x50, 0xFF, 0x80, 0x00, 0xFE, 0x00, 0x03}; + constexpr static uint8_t foxGame[132] /*24x22*/ = { + 0xFF, 0xD7, 0xFF, 0xFF, 0xF5, 0xFF, 0xFD, 0x01, 0x7F, 0xFF, 0x40, 0x5F, 0xF0, 0x38, 0x1F, 0xFC, 0x0E, 0x07, 0xC3, + 0xFF, 0x87, 0xF0, 0xFF, 0xE1, 0x8F, 0xFF, 0xE3, 0xE3, 0xFF, 0xF8, 0x8F, 0xFF, 0xFF, 0xE1, 0xFF, 0xF0, 0x8F, 0xFF, + 0xFF, 0xE0, 0x5F, 0x43, 0x8F, 0xFF, 0xFF, 0xE0, 0x04, 0x0F, 0x8F, 0xFF, 0xFF, 0xE3, 0xE0, 0xFF, 0x8F, 0xFF, 0xFF, + 0xE3, 0xFF, 0xFF, 0x85, 0x55, 0x55, 0x41, 0x55, 0x55, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0xE3, 0xFF, + 0xFF, 0x8F, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0x87, 0xFF, 0xFF, 0xE1, 0xFF, 0xFF, + 0xE1, 0x7F, 0xFF, 0xF8, 0x5F, 0xFF, 0xF8, 0x17, 0xFF, 0xFE, 0x05, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xE0, 0xFF}; + constexpr static uint8_t gameReminder[102] /*24x17*/ = { + 0xFF, 0xD5, 0xF7, 0xDF, 0x57, 0xFF, 0xFF, 0x80, 0xE3, 0x8E, 0x03, 0xFF, 0xFF, 0xE3, 0xE3, 0x8E, 0x3F, 0xFF, 0xFF, 0xE3, 0xE1, + 0x0E, 0x17, 0xFF, 0xFF, 0xE3, 0xE0, 0x0E, 0x03, 0xFF, 0xFF, 0xE3, 0xE3, 0x8E, 0x3F, 0xFF, 0xFF, 0xE3, 0xE3, 0x8E, 0x17, 0xFF, + 0xFF, 0xE3, 0xE3, 0x8E, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF5, 0x7F, 0x5F, 0xF5, 0x5F, 0xD5, 0xC0, 0x3C, 0x07, + 0xC0, 0x07, 0x80, 0x8F, 0xF8, 0xE3, 0x88, 0xE3, 0x8F, 0x8F, 0xF8, 0xE3, 0x88, 0xE3, 0x85, 0x8F, 0x78, 0x43, 0x88, 0xE3, 0x80, + 0x8E, 0x38, 0x03, 0x8F, 0xE3, 0x8F, 0x84, 0x38, 0xE3, 0x8F, 0xE3, 0x85, 0xE0, 0xF8, 0xE3, 0x8F, 0xE3, 0x80}; + // constexpr static uint8_t foxFace[144] /*24x24*/ = { + // 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x17, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x01, 0x7F, + // 0xFF, 0xF5, 0x5F, 0xF8, 0xF8, 0x1F, 0xFF, 0x40, 0x07, 0xF8, 0xFF, 0x8F, 0xF4, 0x0F, 0xE3, 0xF8, 0x7F, 0x85, 0x40, 0xFF, 0xC3, + // 0xF8, 0x1F, 0xE0, 0x0F, 0xFD, 0x03, 0xF8, 0xE7, 0xFF, 0xFF, 0xF3, 0xE3, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xC0, 0xFD, 0x7F, + // 0xFF, 0xFF, 0xCF, 0x8F, 0xFE, 0x1F, 0xFD, 0x7F, 0x8F, 0xBF, 0xE4, 0x0F, 0xFE, 0x1F, 0x87, 0xFF, 0xE0, 0x0F, 0xE4, 0x0F, 0xE1, + // 0xFF, 0xF8, 0x3F, 0xE0, 0x0F, 0xF8, 0xDF, 0xFF, 0xFF, 0xF8, 0x3F, 0xF8, 0xE5, 0x7F, 0xF5, 0xFF, 0xFF, 0xF8, 0xFF, 0x95, 0x40, + // 0x7F, 0xFF, 0xFE, 0xFF, 0xFF, 0xE0, 0x15, 0x55, 0x54, 0xBF, 0xFF, 0xF8, 0xFF, 0xFF, 0xFF, 0x9F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, + // 0x87, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xE1, 0x5F, 0xFF, 0xFF, 0xFF, 0x43, 0xF8, 0x05, 0x5F, 0xFF, 0xD4, 0x3F}; + constexpr static uint8_t pinetime[120] /*20x24*/ = { + 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xFF, 0xF6, 0x00, 0x37, 0xFF, + 0xFF, 0xC1, 0x63, 0x41, 0xFF, 0xFF, 0x80, 0xD5, 0x80, 0xFF, 0xFF, 0xB5, 0x00, 0x56, 0xFF, 0xF7, 0xC0, 0x00, 0x01, 0xF7, + 0xE1, 0x60, 0x00, 0x03, 0x43, 0xE0, 0x15, 0x80, 0xD4, 0x03, 0xE0, 0x00, 0x5D, 0x00, 0x03, 0xE0, 0x00, 0xD5, 0x80, 0x03, + 0xE0, 0x35, 0x00, 0x56, 0x03, 0xE3, 0x40, 0x00, 0x01, 0x63, 0x96, 0x00, 0x00, 0x00, 0x34, 0x81, 0x60, 0x00, 0x03, 0x40, + 0xE0, 0x15, 0x80, 0xD4, 0x03, 0xE0, 0x00, 0x77, 0x00, 0x03, 0xF8, 0x00, 0xC1, 0x80, 0x0F, 0xF8, 0x0D, 0x00, 0x58, 0x0F, + 0xFF, 0xD0, 0x00, 0x05, 0xFF, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF}; }; } - - - template <> struct WatchFaceTraits { static constexpr WatchFace watchFace = WatchFace::Maze; @@ -345,4 +415,4 @@ namespace Pinetime { } }; } -} \ No newline at end of file +}