diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e3c1e71 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,47 @@ +name: nyanBOX CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + workflow_dispatch: + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Cache PlatformIO + uses: actions/cache@v3 + with: + path: | + ~/.platformio/.cache + ~/.platformio/packages + ~/.platformio/platforms + key: ${{ runner.os }}-pio-${{ hashFiles('**/platformio.ini') }} + restore-keys: | + ${{ runner.os }}-pio- + + - name: Install PlatformIO + run: | + python -m pip install --upgrade pip + pip install platformio + + - name: Build ESP32 firmware + working-directory: "VScode Platformio" + run: pio run -e esp32dev + continue-on-error: true + + - name: Run native tests + working-directory: "VScode Platformio" + run: pio test -e native + continue-on-error: true diff --git a/VScode Platformio/lib/level_calculations/src/level_calculations.cpp b/VScode Platformio/lib/level_calculations/src/level_calculations.cpp new file mode 100644 index 0000000..0fb8557 --- /dev/null +++ b/VScode Platformio/lib/level_calculations/src/level_calculations.cpp @@ -0,0 +1,41 @@ +/* ____________________________ + This software is licensed under the MIT License: + https://github.com/jbohack/nyanBOX + ________________________________________ */ + +#include + +int calculateXPRequiredForLevel(int level) { + if (level <= 1) return 0; + return (level * level) + 10; +} + +int calculateCurrentLevelFromXP(int currentXP) { + if (currentXP < 0) return 1; + + int level = 1; + while (level < 99 && currentXP >= calculateXPRequiredForLevel(level + 1)) { + level++; + } + return level; +} + +const char* calculateRankName(int level) { + if (level <= 5) return "N00b"; + else if (level <= 15) return "Skid"; + else if (level <= 25) return "Wannabe"; + else if (level <= 40) return "L33t"; + else if (level <= 55) return "Hacker"; + else if (level <= 70) return "Uber Hacker"; + else if (level <= 85) return "Elite"; + else if (level <= 95) return "Godlike"; + else return "Legend"; +} + +int calculateNewXP(int currentXP, int amount) { + if (currentXP + amount > 65535) { + return 65535; + } else { + return currentXP + amount; + } +} diff --git a/VScode Platformio/lib/level_calculations/src/level_calculations.h b/VScode Platformio/lib/level_calculations/src/level_calculations.h new file mode 100644 index 0000000..972c39e --- /dev/null +++ b/VScode Platformio/lib/level_calculations/src/level_calculations.h @@ -0,0 +1,41 @@ +/* ____________________________ + This software is licensed under the MIT License: + https://github.com/jbohack/nyanBOX + ________________________________________ */ + +#ifndef LEVEL_CALCULATIONS_H +#define LEVEL_CALCULATIONS_H + +// Pure functions for level system calculations +// These functions have no side effects and are easily testable + +/** + * Calculate XP required to reach a specific level + * @param level The target level (1-99) + * @return XP required for that level + */ +int calculateXPRequiredForLevel(int level); + +/** + * Calculate current level based on total XP + * @param currentXP Total experience points + * @return Current level (1-99) + */ +int calculateCurrentLevelFromXP(int currentXP); + +/** + * Get rank name for a given level + * @param level Player level + * @return Rank name string + */ +const char* calculateRankName(int level); + +/** + * Add XP to the current XP + * @param currentXP Current experience points + * @param amount Amount of XP to add + * @return New total XP + */ +int calculateNewXP(int currentXP, int amount); + +#endif diff --git a/VScode Platformio/platformio.ini b/VScode Platformio/platformio.ini index 4623b06..4893af2 100644 --- a/VScode Platformio/platformio.ini +++ b/VScode Platformio/platformio.ini @@ -19,4 +19,12 @@ lib_deps = adafruit/Adafruit NeoPixel@^1.12.3 bblanchon/ArduinoJson@^7.4.2 board_build.partitions = min_spiffs.csv -monitor_speed = 115200 \ No newline at end of file +monitor_speed = 115200 + +[env:native] +platform = native +build_flags = -std=c++11 -I lib/level_calculations +test_framework = unity +lib_deps = + throwtheswitch/Unity@^2.5.2 +build_src_filter = -<*> \ No newline at end of file diff --git a/VScode Platformio/src/level_system.cpp b/VScode Platformio/src/level_system.cpp index 1fcd59f..ceaa48b 100644 --- a/VScode Platformio/src/level_system.cpp +++ b/VScode Platformio/src/level_system.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "../include/level_system.h" #include "../include/sleep_manager.h" #include "../include/pindefs.h" @@ -21,11 +22,6 @@ static bool buttonCenterPressed = false; void saveLevelData(); -int getXPRequiredForLevel(int level) { - if (level <= 1) return 0; - return (level * level) + 10; -} - void loadLevelData() { uint8_t magic = EEPROM.read(EEPROM_ADDRESS_LEVEL_MAGIC); if (magic != LEVEL_MAGIC_NUMBER) { @@ -58,21 +54,12 @@ void levelSystemSetup() { } void addXP(int amount) { - if (currentXP + amount > 65535) { - currentXP = 65535; - } else { - currentXP += amount; - } - + currentXP = calculateNewXP(currentXP, amount); saveLevelData(); } int getCurrentLevel() { - int level = 1; - while (level < 99 && currentXP >= getXPRequiredForLevel(level + 1)) { - level++; - } - return level; + return calculateCurrentLevelFromXP(currentXP); } int getCurrentXP() { @@ -80,27 +67,15 @@ int getCurrentXP() { } int getXPForNextLevel() { - int currentLevel = getCurrentLevel(); + int currentLevel = calculateCurrentLevelFromXP(currentXP); if (currentLevel >= 99) return 0; - return getXPRequiredForLevel(currentLevel + 1); -} - -const char* getRankName(int level) { - if (level <= 5) return "N00b"; - else if (level <= 15) return "Skid"; - else if (level <= 25) return "Wannabe"; - else if (level <= 40) return "L33t"; - else if (level <= 55) return "Hacker"; - else if (level <= 70) return "Uber Hacker"; - else if (level <= 85) return "Elite"; - else if (level <= 95) return "Godlike"; - else return "Legend"; + return calculateXPRequiredForLevel(currentLevel + 1); } void displayLevelScreen() { u8g2.clearBuffer(); - int currentLevel = getCurrentLevel(); + int currentLevel = calculateCurrentLevelFromXP(currentXP); u8g2.setFont(u8g2_font_helvB14_tr); char levelStr[8]; @@ -108,7 +83,7 @@ void displayLevelScreen() { int levelWidth = u8g2.getUTF8Width(levelStr); u8g2.drawStr((128 - levelWidth) / 2, 18, levelStr); - const char* rankName = getRankName(currentLevel); + const char* rankName = calculateRankName(currentLevel); u8g2.setFont(u8g2_font_helvR08_tr); int rankWidth = u8g2.getUTF8Width(rankName); u8g2.drawStr((128 - rankWidth) / 2, 32, rankName); @@ -131,7 +106,7 @@ void displayLevelScreen() { int fillWidth = 0; if (currentLevel < 99) { int nextLevelXP = getXPForNextLevel(); - int currentLevelXP = getXPRequiredForLevel(currentLevel); + int currentLevelXP = calculateXPRequiredForLevel(currentLevel); if (nextLevelXP > currentLevelXP) { int progress = map(currentXP - currentLevelXP, 0, nextLevelXP - currentLevelXP, 0, 100); fillWidth = map(progress, 0, 100, 0, barWidth - 2); diff --git a/VScode Platformio/test/test_level_system.cpp b/VScode Platformio/test/test_level_system.cpp new file mode 100644 index 0000000..6f059d2 --- /dev/null +++ b/VScode Platformio/test/test_level_system.cpp @@ -0,0 +1,55 @@ +#include +#include + +void test_xp_required_for_level_calculation() { + TEST_ASSERT_EQUAL(0, calculateXPRequiredForLevel(-1)); + TEST_ASSERT_EQUAL(0, calculateXPRequiredForLevel(0)); + TEST_ASSERT_EQUAL(0, calculateXPRequiredForLevel(1)); + TEST_ASSERT_EQUAL(14, calculateXPRequiredForLevel(2)); + TEST_ASSERT_EQUAL(19, calculateXPRequiredForLevel(3)); + TEST_ASSERT_EQUAL(26, calculateXPRequiredForLevel(4)); + TEST_ASSERT_EQUAL(99*99 + 10, calculateXPRequiredForLevel(99)); +} + +void test_current_level_calculation() { + TEST_ASSERT_EQUAL(1, calculateCurrentLevelFromXP(-1)); + TEST_ASSERT_EQUAL(1, calculateCurrentLevelFromXP(0)); + TEST_ASSERT_EQUAL(1, calculateCurrentLevelFromXP(1)); + TEST_ASSERT_EQUAL(1, calculateCurrentLevelFromXP(13)); + TEST_ASSERT_EQUAL(2, calculateCurrentLevelFromXP(14)); + TEST_ASSERT_EQUAL(2, calculateCurrentLevelFromXP(18)); + TEST_ASSERT_EQUAL(3, calculateCurrentLevelFromXP(19)); +} + +void test_rank_name_calculation() { + TEST_ASSERT_EQUAL_STRING("N00b", calculateRankName(1)); + TEST_ASSERT_EQUAL_STRING("N00b", calculateRankName(5)); + TEST_ASSERT_EQUAL_STRING("Skid", calculateRankName(6)); + TEST_ASSERT_EQUAL_STRING("Skid", calculateRankName(15)); + TEST_ASSERT_EQUAL_STRING("Wannabe", calculateRankName(16)); + TEST_ASSERT_EQUAL_STRING("Wannabe", calculateRankName(25)); + TEST_ASSERT_EQUAL_STRING("L33t", calculateRankName(40)); + TEST_ASSERT_EQUAL_STRING("Hacker", calculateRankName(55)); + TEST_ASSERT_EQUAL_STRING("Uber Hacker", calculateRankName(70)); + TEST_ASSERT_EQUAL_STRING("Elite", calculateRankName(85)); + TEST_ASSERT_EQUAL_STRING("Godlike", calculateRankName(95)); + TEST_ASSERT_EQUAL_STRING("Legend", calculateRankName(99)); +} + +void test_increase_xp() { + TEST_ASSERT_EQUAL(110, calculateNewXP(100, 10)); + TEST_ASSERT_EQUAL(100, calculateNewXP(0, 100)); + TEST_ASSERT_EQUAL(65535, calculateNewXP(65534, 1)); + TEST_ASSERT_EQUAL(65535, calculateNewXP(65535, 10)); +} + +int main(int argc, char **argv) { + UNITY_BEGIN(); + + RUN_TEST(test_xp_required_for_level_calculation); + RUN_TEST(test_current_level_calculation); + RUN_TEST(test_rank_name_calculation); + RUN_TEST(test_increase_xp); + + return UNITY_END(); +}