diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7e486070..fde1f586 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -259,6 +259,7 @@ set(LVGL_SRC libs/lvgl/src/lv_objx/lv_cont.c libs/lvgl/src/lv_objx/lv_label.h libs/lvgl/src/lv_objx/lv_label.c + libs/lvgl/src/lv_objx/lv_table.c libs/lvgl/src/lv_themes/lv_theme.c libs/lvgl/src/lv_themes/lv_theme.h libs/lvgl/src/lv_themes/lv_theme_night.h @@ -347,6 +348,7 @@ list(APPEND SOURCE_FILES displayapp/screens/FirmwareValidation.cpp displayapp/screens/ApplicationList.cpp displayapp/screens/Notifications.cpp + displayapp/screens/Twos.cpp main.cpp drivers/St7789.cpp drivers/SpiNorFlash.cpp diff --git a/src/displayapp/Apps.h b/src/displayapp/Apps.h index 7e4afa2e..f5fb24d0 100644 --- a/src/displayapp/Apps.h +++ b/src/displayapp/Apps.h @@ -2,6 +2,6 @@ namespace Pinetime { namespace Applications { - enum class Apps {None, Launcher, Clock, SysInfo, Meter, Gauge, Brightness, Music, FirmwareValidation, Paint, Paddle, Notifications}; + enum class Apps {None, Launcher, Clock, SysInfo, Meter, Gauge, Brightness, Music, FirmwareValidation, Paint, Paddle, Notifications, Twos}; } } diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 72823193..162d0226 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -17,6 +17,7 @@ #include "displayapp/screens/Notifications.h" #include "displayapp/screens/SystemInfo.h" #include "displayapp/screens/Tile.h" +#include "displayapp/screens/Twos.h" #include "drivers/Cst816s.h" #include "drivers/St7789.h" #include "drivers/Watchdog.h" @@ -202,6 +203,7 @@ void DisplayApp::RunningState() { // case Apps::Test: currentScreen.reset(new Screens::Message(this)); break; case Apps::SysInfo: currentScreen.reset(new Screens::SystemInfo(this, dateTimeController, batteryController, brightnessController, bleController, watchdog)); break; case Apps::Meter: currentScreen.reset(new Screens::Meter(this)); break; + case Apps::Twos: currentScreen.reset(new Screens::Twos(this)); break; case Apps::Gauge: currentScreen.reset(new Screens::Gauge(this)); break; case Apps::Paint: currentScreen.reset(new Screens::InfiniPaint(this, lvgl)); break; case Apps::Paddle: currentScreen.reset(new Screens::Paddle(this, lvgl)); break; diff --git a/src/displayapp/screens/ApplicationList.cpp b/src/displayapp/screens/ApplicationList.cpp index ddb98e5d..0b8face3 100644 --- a/src/displayapp/screens/ApplicationList.cpp +++ b/src/displayapp/screens/ApplicationList.cpp @@ -61,7 +61,7 @@ std::unique_ptr ApplicationList::CreateScreen2() { {Symbols::paintbrush, Apps::Paint}, {Symbols::info, Apps::Notifications}, {Symbols::paddle, Apps::Paddle}, - {Symbols::none, Apps::None} + {"2", Apps::Twos} } }; diff --git a/src/displayapp/screens/Twos.cpp b/src/displayapp/screens/Twos.cpp new file mode 100644 index 00000000..f36e35d4 --- /dev/null +++ b/src/displayapp/screens/Twos.cpp @@ -0,0 +1,271 @@ +#include "Twos.h" +#include +#include +#include +#include +#include +#include + +using namespace Pinetime::Applications::Screens; + +extern lv_font_t jetbrains_mono_bold_20; + +Twos::Twos(Pinetime::Applications::DisplayApp *app) : Screen(app) { + + // create styles to apply to different valued tiles + static lv_style_t style_cell1; + lv_style_copy(&style_cell1, &lv_style_plain); + style_cell1.body.border.width = 1; + style_cell1.text.font = &jetbrains_mono_bold_20; + style_cell1.body.padding.top = 16; + style_cell1.body.padding.bottom = 16; + style_cell1.body.main_color = LV_COLOR_MAKE(214, 197, 165); + style_cell1.body.grad_color = LV_COLOR_MAKE(214, 197, 165); + style_cell1.text.color = LV_COLOR_BLACK; + + static lv_style_t style_cell2; + lv_style_copy(&style_cell2, &style_cell1); + style_cell2.body.main_color = LV_COLOR_MAKE(209, 146, 92); + style_cell2.body.grad_color = LV_COLOR_MAKE(209, 146, 92); + style_cell2.text.color = LV_COLOR_WHITE; + + static lv_style_t style_cell3; + lv_style_copy(&style_cell3, &style_cell2); + style_cell3.body.main_color = LV_COLOR_MAKE(246, 94, 59); + style_cell3.body.grad_color = LV_COLOR_MAKE(246, 94, 59); + + static lv_style_t style_cell4; + lv_style_copy(&style_cell4, &style_cell3); + style_cell4.body.main_color = LV_COLOR_MAKE(212, 170, 28); + style_cell4.body.grad_color = LV_COLOR_MAKE(212, 170, 28); + + // format grid display + gridDisplay = lv_table_create(lv_scr_act(), nullptr); + lv_table_set_style(gridDisplay, LV_TABLE_STYLE_CELL1, &style_cell1); + lv_table_set_style(gridDisplay, LV_TABLE_STYLE_CELL2, &style_cell2); + lv_table_set_style(gridDisplay, LV_TABLE_STYLE_CELL3, &style_cell3); + lv_table_set_style(gridDisplay, LV_TABLE_STYLE_CELL4, &style_cell4); + lv_table_set_col_cnt(gridDisplay, 4); + lv_table_set_row_cnt(gridDisplay, 4); + lv_table_set_col_width(gridDisplay, 0, LV_HOR_RES/4); + lv_table_set_col_width(gridDisplay, 1, LV_HOR_RES/4); + lv_table_set_col_width(gridDisplay, 2, LV_HOR_RES/4); + lv_table_set_col_width(gridDisplay, 3, LV_HOR_RES/4); + lv_obj_align(gridDisplay, NULL, LV_ALIGN_IN_BOTTOM_MID, 0, 0); + + // initialize grid + for(int row = 0; row < 4; row++) { + for(int col = 0; col < 4; col++) { + grid[row][col].value = 0; + lv_table_set_cell_type(gridDisplay, row, col, 2); + lv_table_set_cell_align(gridDisplay, row, col, LV_LABEL_ALIGN_CENTER); + } + } + placeNewTile(); + placeNewTile(); + + // format score text + scoreText = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_width(scoreText, LV_HOR_RES); + lv_label_set_align(scoreText, LV_ALIGN_IN_LEFT_MID); + lv_obj_align(scoreText, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0); + lv_label_set_text(scoreText, ("Score: " + std::to_string(score)).c_str()); +} + +Twos::~Twos() { + lv_obj_clean(lv_scr_act()); +} + +bool Twos::Refresh() { + return running; +} + +bool Twos::OnButtonPushed() { + running = false; + return true; +} + +bool Twos::placeNewTile() { + std::vector< std::pair > availableCells; + for(int row = 0; row < 4; row++) { + for(int col = 0; col < 4; col++) { + if(!grid[row][col].value) { + availableCells.push_back(std::make_pair(row, col)); + } + } + } + + if (availableCells.size() == 0) { + return false; // game lost + } + + auto it = availableCells.cbegin(); + int random = rand() % availableCells.size(); + std::advance(it, random); + std::pair newCell = *it; + + if ((rand() % 100) < 90) grid[newCell.first][newCell.second].value = 2; + else grid[newCell.first][newCell.second].value = 4; + updateGridDisplay(grid); + return true; +} + +bool Twos::tryMerge(Tile grid[][4], int &newRow, int &newCol, int oldRow, int oldCol) { + if((grid[newRow][newCol].value == grid[oldRow][oldCol].value)) { + if((newCol != oldCol) || (newRow != oldRow)) { + if(!grid[newRow][newCol].merged) { + unsigned int newVal = grid[oldRow][oldCol].value *= 2; + grid[newRow][newCol].value = newVal; + score += newVal; + lv_label_set_text(scoreText, ("Score: " + std::to_string(score)).c_str()); + grid[oldRow][oldCol].value = 0; + grid[newRow][newCol].merged = true; + return true; + } + } + } + return false; +} + +bool Twos::tryMove(Tile grid[][4], int newRow, int newCol, int oldRow, int oldCol) { + if(((newCol >= 0) && (newCol != oldCol)) || ((newRow >= 0) && (newRow != oldRow))) { + grid[newRow][newCol].value = grid[oldRow][oldCol].value; + grid[oldRow][oldCol].value = 0; + return true; + } + return false; +} + +bool Twos::OnTouchEvent(Pinetime::Applications::TouchEvents event) { + bool validMove; + validMove = false; + for(int row = 0; row < 4; row++) { + for(int col = 0; col < 4; col++) { + grid[row][col].merged = false; // reinitialize merge state + } + } + switch(event) { + case TouchEvents::SwipeLeft: + for(int col = 1; col < 4; col++) { // ignore tiles already on far left + for(int row = 0; row < 4; row++) { + if(grid[row][col].value) { + int newCol = -1; + for(int potentialNewCol = col - 1; potentialNewCol >= 0; potentialNewCol--) { + if(!grid[row][potentialNewCol].value) { + newCol = potentialNewCol; + } + else { // blocked by another tile + if(tryMerge(grid, row, potentialNewCol, row, col)) validMove = true; + break; + } + } + if(tryMove(grid, row, newCol, row, col)) validMove = true; + } + } + } + if (validMove) { + placeNewTile(); + } + return true; + case TouchEvents::SwipeRight: + for(int col = 2; col >= 0; col--) { // ignore tiles already on far right + for(int row = 0; row < 4; row++) { + if(grid[row][col].value) { + int newCol = -1; + for(int potentialNewCol = col + 1; potentialNewCol < 4; potentialNewCol++) { + if(!grid[row][potentialNewCol].value) { + newCol = potentialNewCol; + } + else { // blocked by another tile + if(tryMerge(grid, row, potentialNewCol, row, col)) validMove = true; + break; + } + } + if(tryMove(grid, row, newCol, row, col)) validMove = true; + } + } + } + if (validMove) { + placeNewTile(); + } + return true; + case TouchEvents::SwipeUp: + for(int row = 1; row < 4; row++) { // ignore tiles already on top + for(int col = 0; col < 4; col++) { + if(grid[row][col].value) { + int newRow = -1; + for(int potentialNewRow = row - 1; potentialNewRow >= 0; potentialNewRow--) { + if(!grid[potentialNewRow][col].value) { + newRow = potentialNewRow; + } + else { // blocked by another tile + if(tryMerge(grid, potentialNewRow, col, row, col)) validMove = true; + break; + } + } + if(tryMove(grid, newRow, col, row, col)) validMove = true; + } + } + } + if (validMove) { + placeNewTile(); + } + return true; + case TouchEvents::SwipeDown: + for(int row = 2; row >=0; row--) { // ignore tiles already on bottom + for(int col = 0; col < 4; col++) { + if(grid[row][col].value) { + int newRow = -1; + for(int potentialNewRow = row + 1; potentialNewRow < 4; potentialNewRow++) { + if(!grid[potentialNewRow][col].value) { + newRow = potentialNewRow; + } + else { // blocked by another tile + if(tryMerge(grid, potentialNewRow, col, row, col)) validMove = true; + break; + } + } + if(tryMove(grid, newRow, col, row, col)) validMove = true; + } + } + } + if (validMove) { + placeNewTile(); + } + return true; + default: + return false; + } + return false; +} + +void Twos::updateGridDisplay(Tile grid[][4]) { + for(int row = 0; row < 4; row++) { + for(int col = 0; col < 4; col++) { + if (grid[row][col].value) { + lv_table_set_cell_value(gridDisplay, row, col, (std::to_string(grid[row][col].value)).c_str()); + } + else { + lv_table_set_cell_value(gridDisplay, row, col, ""); + } + switch (grid[row][col].value) { + case 0: + case 2: + case 4: + lv_table_set_cell_type(gridDisplay, row, col, 1); + break; + case 8: + case 16: + lv_table_set_cell_type(gridDisplay, row, col, 2); + break; + case 32: + case 64: + lv_table_set_cell_type(gridDisplay, row, col, 3); + break; + default: + lv_table_set_cell_type(gridDisplay, row, col, 4); + break; + } + } + } +} \ No newline at end of file diff --git a/src/displayapp/screens/Twos.h b/src/displayapp/screens/Twos.h new file mode 100644 index 00000000..ad80ca15 --- /dev/null +++ b/src/displayapp/screens/Twos.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include "Screen.h" + +namespace Pinetime { + namespace Applications { + struct Tile { + bool merged = false; + unsigned int value = 0; + }; + namespace Screens { + class Twos : public Screen { + public: + Twos(DisplayApp* app); + ~Twos() override; + bool Refresh() override; + bool OnButtonPushed() override; + bool OnTouchEvent(TouchEvents event) override; + + private: + bool running = true; + lv_obj_t *scoreText; + lv_obj_t *gridDisplay; + Tile grid[4][4]; + unsigned int score = 0; + void updateGridDisplay(Tile grid[][4]); + bool tryMerge(Tile grid[][4], int &newRow, int &newCol, int oldRow, int oldCol); + bool tryMove(Tile grid[][4], int newRow, int newCol, int oldRow, int oldCol); + bool placeNewTile(); + }; + } + } +} \ No newline at end of file