From 97341671b90ca5b4ab8d96222fba96feac3aef5b Mon Sep 17 00:00:00 2001 From: bdring Date: Thu, 22 Mar 2018 11:16:47 -0500 Subject: [PATCH] Added web server and serial port setup - Access game stats via wifi access point and web server - Setup game preferences via serial port (EEPROM issue causes crashes...help me fix). Does not affect game normal play. - Updated to recent stuff on Arduino port. --- Conveyor.h | 8 +- README.md | 16 ++- TWANG32.ino | 250 ++++++++++++++++++++-------------- settings.h | 376 +++++++++++++++++++++++++++++++++++++++++++++++++++- wifi_ap.h | 65 +++++++++ 5 files changed, 600 insertions(+), 115 deletions(-) create mode 100644 wifi_ap.h diff --git a/Conveyor.h b/Conveyor.h index 1060715..185217a 100644 --- a/Conveyor.h +++ b/Conveyor.h @@ -4,18 +4,18 @@ class Conveyor { public: - void Spawn(int startPoint, int endPoint, int dir); + void Spawn(int startPoint, int endPoint, int speed); void Kill(); int _startPoint; int _endPoint; - int _dir; + int _speed; bool _alive; }; -void Conveyor::Spawn(int startPoint, int endPoint, int dir){ +void Conveyor::Spawn(int startPoint, int endPoint, int speed){ _startPoint = startPoint; _endPoint = endPoint; - _dir = constrain(dir, -MAX_PLAYER_SPEED+1, MAX_PLAYER_SPEED - 1); // must allow some player speed + _speed = constrain(speed, -MAX_PLAYER_SPEED+1, MAX_PLAYER_SPEED - 1); // must allow some player speed _alive = true; } diff --git a/README.md b/README.md index 0f165e4..0f40818 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,19 @@ This was ported from the [TWANG fork](https://github.com/bdring/TWANG) by bdring - DAC pins for better sound capabilities. - Wifi and Bluetooth. -The current state of the code is a basic port of the Arduino version. Several of the libraries did not work, so ESP32 versions are included in the code. +**Current State** + +- All of the Arduino version game features are functional. +- There is a serial port based setup feature, but the **EEPROM saving crashes randomly**. It does not affect game play, so I am leaving it in. **Help me fix this!** +- The game now has a wifi access port to get game stats. Connect a smartphone or computer to see them. Due to the EEPROM saving bug, thee stats reset often :-( + - **SSID:** TWANG_AP + - **Password:** esp32rocks + - **URL:** 192.168.4.1 **Coming Soon:** - Wireless features - - A wireless version of the serial port features of TWANG. I think the easiest way is to make it a Wifi access point with a simple web page interface. This allows control by any smartphone or computer with no client side work required. + - ~~A wireless version of the serial port features of TWANG. I think the easiest way is to make it a Wifi access point with a simple web page interface. This allows control by any smartphone or computer with no client side work required.~~ - 2 Player features by linking controllers. TBD - Digitized Audio - Currently the port uses the same square wave tones of the the Arduino version. @@ -96,11 +103,10 @@ They all call different functions and variables to setup the level. Each one is * offtime: How long the lava is ON for * offset: How long (ms) after the level starts before the lava turns on, use this to create patterns with multiple lavas -**spawnConveyor(startPoint, endPoint, direction);** (2 conveyors max) +**spawnConveyor(startPoint, endPoint, speed);** (2 conveyors max) * startPoint, endPoint: Same as lava -* direction: The direction and speed of the travel. Negative moves to base and positive moves towards exit. Must be less than +/- max player speed. +* speed: The direction and speed of the travel. Negative moves to base and positive moves towards exit. Must be less than +/- max player speed. **spawnBoss(); ** (only one, don't edit boss level) * There are no parameters for a boss, they always spawn in the same place and have 3 lives. Tweak the values of Boss.h to modify -Feel free to edit, comment on the YouTube video (link at top) if you have any questions. \ No newline at end of file diff --git a/TWANG32.ino b/TWANG32.ino index 668412a..3d26208 100644 --- a/TWANG32.ino +++ b/TWANG32.ino @@ -6,13 +6,15 @@ TWANG was originally created by Critters https://github.com/Critters/TWANG - It was inspired by Robin Baumgartens Line Wobbler Game_Audio + It was inspired by Robin Baumgarten's Line Wobbler Game_Audio */ +#define VERSION "2018-03-21" #include #include +#include "Arduino.h" #include "RunningMedian.h" // twang files @@ -26,6 +28,7 @@ #include "iSin.h" #include "sound.h" #include "settings.h" +#include "wifi_ap.h" //#include "SoundData.h"; //#include "Game_Audio.h" @@ -33,10 +36,28 @@ #define DATA_PIN 16 #define CLOCK_PIN 17 #define LED_TYPE APA102 -#define LED_COLOR_ORDER BGR +//#define LED_COLOR_ORDER BGR #define NUM_LEDS 288 -#define BRIGHTNESS 150 +// what type of LED Strip....pick one +#define USE_APA102 +//#define USE_NEOPIXEL + +#ifdef USE_APA102 + #define LED_TYPE APA102 + #define LED_COLOR_ORDER BGR // typically this will be the order, but switch it if not + #define CONVEYOR_BRIGHTNES 8 + #define LAVA_OFF_BRIGHTNESS 4 +#endif + +#ifdef USE_NEOPIXEL + #define CONVEYOR_BRIGHTNES 40 // low neopixel values are nearly off, they need a higher value + #define LAVA_OFF_BRIGHTNESS 15 +#endif + + + +//#define BRIGHTNESS 150 #define DIRECTION 1 #define MIN_REDRAW_INTERVAL 16 // Min redraw interval (ms) 33 = 30fps / 16 = 63fps #define USE_GRAVITY 0 // 0/1 use gravity (LED strip going up wall) @@ -136,26 +157,34 @@ int score = 0; void setup() { - #ifdef DEBUG - Serial.begin(115200); - #endif + Serial.begin(115200); + Serial.print("\r\nTWANG32 VERSION: "); Serial.println(VERSION); - Wire.begin(); - accelgyro.initialize(); - - FastLED.addLeds(leds, NUM_LEDS); - FastLED.setBrightness(BRIGHTNESS); - FastLED.setDither(1); + settings_init(); - sound_init(DAC_AUDIO_PIN); + Wire.begin(); + accelgyro.initialize(); - stage = STARTUP; - stageStartTime = millis(); + FastLED.addLeds(leds, NUM_LEDS); + FastLED.setBrightness(user_settings.led_brightness); + FastLED.setDither(1); + + sound_init(DAC_AUDIO_PIN); + + ap_setup(); + + stage = STARTUP; + stageStartTime = millis(); + lives = user_settings.lives_per_level; } void loop() { long mm = millis(); int brightness = 0; + + + ap_client_check(); // check for web client + checkSerialInput(); if(stage == PLAY){ if(attacking){ @@ -175,7 +204,7 @@ void loop() { long frameTimer = mm; previousMillis = mm; - if(abs(joystickTilt) > JOYSTICK_DEADZONE){ + if(abs(joystickTilt) > user_settings.joystick_deadzone){ lastInputTime = mm; if(stage == SCREENSAVER){ levelNumber = -1; @@ -205,7 +234,7 @@ void loop() { if(attacking && attackMillis+ATTACK_DURATION < mm) attacking = 0; // If not attacking, check if they should be - if(!attacking && joystickWobble > ATTACK_THRESHOLD){ + if(!attacking && joystickWobble > user_settings.attack_threshold){ attackMillis = mm; attacking = 1; } @@ -248,8 +277,9 @@ void loop() { drawAttack(); drawExit(); }else if(stage == DEAD){ - // DEAD + // DEAD FastLED.clear(); + tickDie(mm); if(!tickParticles()){ loadLevel(); } @@ -261,15 +291,16 @@ void loop() { //tickComplete(mm); } else if (stage == GAMEOVER) { if (stageStartTime+GAMEOVER_FADE_DURATION > mm) - { + { tickGameover(mm); } else { - FastLED.clear(); - score = 0; // reset the score + FastLED.clear(); + save_game_stats(false); + //score = 0; // reset the score levelNumber = 0; - lives = LIVES_PER_LEVEL; + lives = user_settings.lives_per_level; loadLevel(); } } @@ -525,10 +556,9 @@ void cleanupLevel(){ void levelComplete(){ stageStartTime = millis(); stage = WIN; - //if(levelNumber == LEVEL_COUNT){ + if (lastLevel) { stage = BOSS_KILLED; - //save_game_stats(true); } if (levelNumber != 0) // no points for the first level { @@ -536,13 +566,13 @@ void levelComplete(){ } } -void nextLevel(){ - +void nextLevel(){ levelNumber ++; - //if(levelNumber > LEVEL_COUNT) + if(lastLevel) levelNumber = 0; - lives = LIVES_PER_LEVEL; + + lives = user_settings.lives_per_level; loadLevel(); } @@ -555,8 +585,8 @@ void gameOver(){ void die(){ playerAlive = 0; if(levelNumber > 0) - lives --; - //updateLives(); + lives --; + if(lives == 0){ stage = GAMEOVER; stageStartTime = millis(); @@ -580,7 +610,7 @@ void tickStartup(long mm) FastLED.clear(); if(stageStartTime+STARTUP_WIPEUP_DUR > mm) // fill to the top with green { - int n = min(map(((mm-stageStartTime)), 0, STARTUP_WIPEUP_DUR, 0, NUM_LEDS), NUM_LEDS); // fill from top to bottom + int n = _min(map(((mm-stageStartTime)), 0, STARTUP_WIPEUP_DUR, 0, NUM_LEDS), NUM_LEDS); // fill from top to bottom for(int i = 0; i<= n; i++){ leds[i] = CRGB(0, 255, 0); } @@ -599,8 +629,8 @@ void tickStartup(long mm) } else if (stageStartTime+STARTUP_FADE_DUR > mm) // fade it out to bottom { - int n = max(map(((mm-stageStartTime)), STARTUP_SPARKLE_DUR, STARTUP_FADE_DUR, 0, NUM_LEDS), 0); // fill from top to bottom - int brightness = max(map(((mm-stageStartTime)), STARTUP_SPARKLE_DUR, STARTUP_FADE_DUR, 255, 0), 0); + int n = _max(map(((mm-stageStartTime)), STARTUP_SPARKLE_DUR, STARTUP_FADE_DUR, 0, NUM_LEDS), 0); // fill from top to bottom + int brightness = _max(map(((mm-stageStartTime)), STARTUP_SPARKLE_DUR, STARTUP_FADE_DUR, 255, 0), 0); // for(int i = 0; i<= n; i++){ // leds[i] = CRGB(0, brightness, 0); @@ -750,7 +780,7 @@ bool tickParticles(){ leds[getLED(particlePool[p]._pos)] += CRGB(brightness, brightness/2, brightness/2);\ } else - leds[getLED(particlePool[p]._pos)] += CRGB(particlePool[p]._power, 0, 0);\ + leds[getLED(particlePool[p]._pos)] += CRGB(particlePool[p]._power, 0, 0); stillActive = true; } @@ -758,28 +788,35 @@ bool tickParticles(){ return stillActive; } -void tickConveyors(){ - int b, dir, n, i, ss, ee, led; +void tickConveyors(){ + + //TODO should the visual speed be proportional to the conveyor speed? + + int b, speed, n, i, ss, ee, led; long m = 10000+millis(); playerPositionModifier = 0; - // TODO should the visual speed be proportional to the conveyor speed? + int levels = 5; // brightness levels in conveyor + for(i = 0; i 0) leds[led] = CRGB(0, 0, b); + + n = (-led + (m/100)) % levels; + if(speed < 0) + n = (led + (m/100)) % levels; + + b = map(n, 5, 0, 0, CONVEYOR_BRIGHTNES); + if(b > 0) + leds[led] = CRGB(0, 0, b); } - if(playerPosition > conveyorPool[i]._startPoint && playerPosition < conveyorPool[i]._endPoint){ - playerPositionModifier = dir; + if(playerPosition > conveyorPool[i]._startPoint && playerPosition < conveyorPool[i]._endPoint){ + playerPositionModifier = speed; } } } @@ -791,7 +828,7 @@ void tickComplete(long mm) // the boss is dead FastLED.clear(); SFXcomplete(); if(stageStartTime+500 > mm){ - int n = max(map(((mm-stageStartTime)), 0, 500, NUM_LEDS, 0), 0); + int n = _max(map(((mm-stageStartTime)), 0, 500, NUM_LEDS, 0), 0); for(int i = NUM_LEDS; i>= n; i--){ brightness = (sin(((i*10)+mm)/500.0)+1)*255; leds[i].setHSV(brightness, 255, 50); @@ -802,7 +839,7 @@ void tickComplete(long mm) // the boss is dead leds[i].setHSV(brightness, 255, 50); } }else if(stageStartTime+5500 > mm){ - int n = max(map(((mm-stageStartTime)), 5000, 5500, NUM_LEDS, 0), 0); + int n = _max(map(((mm-stageStartTime)), 5000, 5500, NUM_LEDS, 0), 0); for(int i = 0; i< n; i++){ brightness = (sin(((i*10)+mm)/500.0)+1)*255; leds[i].setHSV(brightness, 255, 50); @@ -817,7 +854,7 @@ void tickBossKilled(long mm) // boss funeral int brightness = 0; FastLED.clear(); if(stageStartTime+500 > mm){ - int n = max(map(((mm-stageStartTime)), 0, 500, NUM_LEDS, 0), 0); + int n = _max(map(((mm-stageStartTime)), 0, 500, NUM_LEDS, 0), 0); for(int i = NUM_LEDS; i>= n; i--){ brightness = (sin(((i*10)+mm)/500.0)+1)*255; leds[i].setHSV(brightness, 255, 50); @@ -830,7 +867,7 @@ void tickBossKilled(long mm) // boss funeral } SFXbosskilled(); }else if(stageStartTime+7000 > mm){ - int n = max(map(((mm-stageStartTime)), 5000, 5500, NUM_LEDS, 0), 0); + int n = _max(map(((mm-stageStartTime)), 5000, 5500, NUM_LEDS, 0), 0); for(int i = 0; i< n; i++){ brightness = (sin(((i*10)+mm)/500.0)+1)*255; leds[i].setHSV(brightness, 255, 50); @@ -841,6 +878,28 @@ void tickBossKilled(long mm) // boss funeral } } +void tickDie(long mm) { // a short bright explosion...particles persist after it. + const int duration = 100; // milliseconds + const int width = 10; // half width of the explosion + + if(stageStartTime+duration > mm) {// Spread red from player position up and down the width + + int brightness = map((mm-stageStartTime), 0, duration, 255, 50); // this allows a fade from white to red + + // fill up + int n = _max(map(((mm-stageStartTime)), 0, duration, getLED(playerPosition), getLED(playerPosition)+width), 0); + for(int i = getLED(playerPosition); i<= n; i++){ + leds[i] = CRGB(255, brightness, brightness); + } + + // fill to down + n = _max(map(((mm-stageStartTime)), 0, duration, getLED(playerPosition), getLED(playerPosition)-width), 0); + for(int i = getLED(playerPosition); i>= n; i--){ + leds[i] = CRGB(255, brightness, brightness); + } + } +} + void tickGameover(long mm) { int brightness = 0; @@ -848,12 +907,12 @@ void tickGameover(long mm) { if(stageStartTime+GAMEOVER_SPREAD_DURATION > mm) // Spread red from player position to top and bottom { // fill to top - int n = max(map(((mm-stageStartTime)), 0, GAMEOVER_SPREAD_DURATION, getLED(playerPosition), NUM_LEDS), 0); + int n = _max(map(((mm-stageStartTime)), 0, GAMEOVER_SPREAD_DURATION, getLED(playerPosition), NUM_LEDS), 0); for(int i = getLED(playerPosition); i<= n; i++){ leds[i] = CRGB(255, 0, 0); } // fill to bottom - n = max(map(((mm-stageStartTime)), 0, GAMEOVER_SPREAD_DURATION, getLED(playerPosition), 0), 0); + n = _max(map(((mm-stageStartTime)), 0, GAMEOVER_SPREAD_DURATION, getLED(playerPosition), 0), 0); for(int i = getLED(playerPosition); i>= n; i--){ leds[i] = CRGB(255, 0, 0); } @@ -861,7 +920,7 @@ void tickGameover(long mm) { } else if(stageStartTime+GAMEOVER_FADE_DURATION > mm) // fade down to bottom and fade brightness { - int n = max(map(((mm-stageStartTime)), GAMEOVER_FADE_DURATION, GAMEOVER_SPREAD_DURATION, NUM_LEDS, 0), 0); + int n = _max(map(((mm-stageStartTime)), GAMEOVER_FADE_DURATION, GAMEOVER_SPREAD_DURATION, NUM_LEDS, 0), 0); brightness = map(((mm-stageStartTime)), GAMEOVER_SPREAD_DURATION, GAMEOVER_FADE_DURATION, 200, 0); for(int i = 0; i<= n; i++){ @@ -878,21 +937,21 @@ void tickWin(long mm) { int brightness = 0; FastLED.clear(); if(stageStartTime+WIN_FILL_DURATION > mm){ - int n = max(map(((mm-stageStartTime)), 0, WIN_FILL_DURATION, NUM_LEDS, 0), 0); // fill from top to bottom + int n = _max(map(((mm-stageStartTime)), 0, WIN_FILL_DURATION, NUM_LEDS, 0), 0); // fill from top to bottom for(int i = NUM_LEDS; i>= n; i--){ - brightness = BRIGHTNESS; + brightness = user_settings.led_brightness; leds[i] = CRGB(0, brightness, 0); } SFXwin(); }else if(stageStartTime+WIN_CLEAR_DURATION > mm){ - int n = max(map(((mm-stageStartTime)), WIN_FILL_DURATION, WIN_CLEAR_DURATION, NUM_LEDS, 0), 0); // clear from top to bottom + int n = _max(map(((mm-stageStartTime)), WIN_FILL_DURATION, WIN_CLEAR_DURATION, NUM_LEDS, 0), 0); // clear from top to bottom for(int i = 0; i< n; i++){ - brightness = BRIGHTNESS; + brightness = user_settings.led_brightness; leds[i] = CRGB(0, brightness, 0); } SFXwin(); }else if(stageStartTime+WIN_OFF_DURATION > mm){ // wait a while with leds off - leds[0] = CRGB(0, BRIGHTNESS, 0); + leds[0] = CRGB(0, user_settings.led_brightness, 0); }else{ nextLevel(); } @@ -967,6 +1026,19 @@ void updateLives(){ drawLives(); } +void save_game_stats(bool bossKill) +{ + user_settings.games_played += 1; + user_settings.total_points += score; + if (score > user_settings.high_score) + user_settings.high_score += score; + if (bossKill) + user_settings.boss_kills += 1; + + show_game_stats(); + settings_eeprom_write(); +} + // --------------------------------- // --------- SCREENSAVER ----------- // --------------------------------- @@ -1012,50 +1084,24 @@ void getInput(){ // if(digitalRead(leftButtonPinNumber) == HIGH) joystickTilt = -90; // if(digitalRead(rightButtonPinNumber) == HIGH) joystickTilt = 90; // if(digitalRead(attackButtonPinNumber) == HIGH) joystickWobble = ATTACK_THRESHOLD; - - int16_t ax, ay, az, gx, gy, gz, accel, gyro; + int16_t ax, ay, az; + int16_t gx, gy, gz; accelgyro.getMotion6(&ax, &ay, &az, &gx, &gy, &gz); + int a = (JOYSTICK_ORIENTATION == 0?ax:(JOYSTICK_ORIENTATION == 1?ay:az))/166; + int g = (JOYSTICK_ORIENTATION == 0?gx:(JOYSTICK_ORIENTATION == 1?gy:gz)); - - // what orientation is used? - if (JOYSTICK_ORIENTATION == 0) { - accel = ax; - gyro = gx; - } - else if (JOYSTICK_ORIENTATION == 1) { - accel = ay; - gyro = gy; - } - else { - accel = ay; - gyro = gy; - } - - accel = accel / 166; // adjust it to degrees. - - // apply the deadzone - if(abs(accel) < JOYSTICK_DEADZONE) accel = 0; - if(accel > 0) accel -= JOYSTICK_DEADZONE; - if(accel < 0) accel += JOYSTICK_DEADZONE; - - MPUAngleSamples.add(accel); - MPUWobbleSamples.add(gyro); - - joystickTilt = MPUAngleSamples.getMedian(); - joystickWobble = abs(MPUWobbleSamples.getHighest()); - - if(JOYSTICK_DIRECTION == 1) { // what direction is MPU facing - joystickTilt = 0-joystickTilt; - } + if(abs(a) < user_settings.joystick_deadzone) a = 0; + if(a > 0) a -= user_settings.joystick_deadzone; + if(a < 0) a += user_settings.joystick_deadzone; + MPUAngleSamples.add(a); + MPUWobbleSamples.add(g); - - - #ifdef DEBUG // show input Data - Serial.print("Tilt: "); Serial.println(joystickTilt); - //Serial.print("Wobble: "); Serial.println(joystickWobble); - #endif - + joystickTilt = MPUAngleSamples.getMedian(); + if(JOYSTICK_DIRECTION == 1) { + joystickTilt = 0-joystickTilt; + } + joystickWobble = abs(MPUWobbleSamples.getHighest()); } // --------------------------------- @@ -1082,7 +1128,7 @@ void SFXFreqSweepWarble(int duration, int elapsedTime, int freqStart, int freqEn if (warble) warble = map(sin(millis()/20.0)*1000.0, -1000, 1000, 0, warble); - sound(freq + warble, MAX_VOLUME); + sound(freq + warble, user_settings.audio_volume); } /* @@ -1111,7 +1157,7 @@ void SFXFreqSweepNoise(int duration, int elapsedTime, int freqStart, int freqEnd if (noiseFactor) noiseFactor = noiseFactor - random8(noiseFactor / 2); - sound(freq + noiseFactor, MAX_VOLUME); + sound(freq + noiseFactor, user_settings.audio_volume); } @@ -1124,7 +1170,7 @@ void SFXtilt(int amount){ int f = map(abs(amount), 0, 90, 80, 900)+random8(100); if(playerPositionModifier < 0) f -= 500; if(playerPositionModifier > 0) f += 200; - int vol = map(abs(amount), 0, 90, MAX_VOLUME / 2, MAX_VOLUME * 3/4); + int vol = map(abs(amount), 0, 90, user_settings.audio_volume / 2, user_settings.audio_volume * 3/4); sound(f,vol); } void SFXattacking(){ @@ -1132,7 +1178,7 @@ void SFXattacking(){ if(random8(5)== 0){ freq *= 3; } - sound(freq, MAX_VOLUME); + sound(freq, user_settings.audio_volume); } void SFXdead(){ SFXFreqSweepNoise(1000, millis()-killTime, 1000, 10, 200); @@ -1143,7 +1189,7 @@ void SFXgameover(){ } void SFXkill(){ - sound(2000, MAX_VOLUME); + sound(2000, user_settings.audio_volume); } void SFXwin(){ SFXFreqSweepWarble(WIN_OFF_DURATION, millis()-stageStartTime, 40, 400, 20); diff --git a/settings.h b/settings.h index dcb89a7..fd5b536 100644 --- a/settings.h +++ b/settings.h @@ -1,6 +1,19 @@ +#ifndef SETTINGS_H + #define SETTINGS_H + +#include + +// change this whenever the saved settings are not compatible with a change +// It forces a reset from defaults. +#define SETTINGS_VERSION 1 +#define EEPROM_SIZE 256 + +// LEDS +#define BRIGHTNESS 150 + // PLAYER -#define MAX_PLAYER_SPEED 10 // Max move speed of the player -#define LIVES_PER_LEVEL 3 // default lives per level +const uint8_t MAX_PLAYER_SPEED = 10; // Max move speed of the player +const uint8_t LIVES_PER_LEVEL = 3; // default lives per level // JOYSTICK #define JOYSTICK_ORIENTATION 1 // 0, 1 or 2 to set the axis of the joystick @@ -9,7 +22,362 @@ #define JOYSTICK_DEADZONE 8 // Angle to ignore // AUDIO -#define MAX_VOLUME 20 // 0 to 255 +#define MAX_VOLUME 5 // 0 to 255 #define DAC_AUDIO_PIN 25 // should be 25 or 26 only -//TODO ... move all the settings to this file. \ No newline at end of file +enum ErrorNums{ + ERR_SETTING_NUM, + ERR_SETTING_RANGE +}; + +//EEPROMClass SETTINGS("eeprom", 0x100); + +//TODO ... move all the settings to this file. + +// Function prototypes +//void reset_settings(); +void settings_init(); +void show_game_stats(); +void settings_eeprom_write(); +void settings_eeprom_read(); +void change_setting(char *line); +void processSerial(char inChar); +void printError(int reason); +void show_settings_menu(); +void reset_settings(); + +SemaphoreHandle_t xMutex; + + +typedef struct { + uint8_t settings_version; // stores the settings format version + + //due to the fastLED classes there is not much we can change dynamically + uint8_t led_brightness; + + uint8_t joystick_deadzone; + uint16_t attack_threshold; + + uint8_t audio_volume; + + uint8_t lives_per_level; + + // saved statistics + uint16_t games_played; + uint32_t total_points; + uint16_t high_score; + uint16_t boss_kills; + +}settings_t; + +settings_t user_settings; + +#define READ_BUFFER_LEN 10 +#define CARRIAGE_RETURN 13 +char readBuffer[READ_BUFFER_LEN]; +uint8_t readIndex = 0; + +void settings_init() { + + //if (!EEPROM.begin(EEPROM_SIZE)) + //{ + // Serial.println("failed to initialize EEPROM"); + //} + + settings_eeprom_read(); + show_settings_menu(); + show_game_stats(); +} + +void checkSerialInput() { + if (Serial.available()) { + processSerial(Serial.read()); + } +} + +void processSerial(char inChar) +{ + readBuffer[readIndex] = inChar; + switch(readBuffer[readIndex]){ + case '?': + readIndex = 0; + show_settings_menu(); + return; + break; + + case 'R': + readIndex = 0; + reset_settings(); + return; + break; + + case 'P': + user_settings.games_played = 0; + user_settings.total_points = 0; + user_settings.high_score = 0; + user_settings.boss_kills = 0; + return; + break; + + case '!': + ESP.restart(); + break; + + case 'E': + settings_eeprom_write(); + delay(1000); + return; + break; + + default: + + break; + } + + if (readBuffer[readIndex] == CARRIAGE_RETURN) { + if (readIndex < 3) { + // not enough characters + readIndex = 0; + } + else { + readBuffer[readIndex] = 0; // mark it as the end of the string + change_setting(readBuffer); + readIndex = 0; + } + } + else if (readIndex >= READ_BUFFER_LEN) { + readIndex = 0; // too many characters. Reset and try again + } + else + readIndex++; +} + +void change_setting(char *line) { + // line formate should be ss=nn + // ss is always a 2 character integer + // nn starts at index 3 and can be up to a 5 character unsigned integer + + //12=12345 + //01234567 + + char setting_val[6]; + char param; + uint16_t newValue; + Serial.print("Read Buffer: "); Serial.println(readBuffer); + + if (readBuffer[1] != '='){ // check if the equals sign is there + Serial.print("Missing '=' in command"); + readIndex = 0; + return; + } + + // move the value characters into a char array while verifying they are digits + for(int i=0; i<5; i++) { + if (i+2 < readIndex) { + if (isDigit(readBuffer[i+2])) + setting_val[i] = readBuffer[i+2]; + else { + Serial.println("Invalid setting value"); + return; + } + } + else + setting_val[i] = 0; + } + + param = readBuffer[0]; + newValue = atoi(setting_val); // convert the val section to an integer + + memset(readBuffer,0,sizeof(readBuffer)); + + switch (param) { + case 'B': // brightness + if(newValue >=5 && newValue <=255) + user_settings.led_brightness = (uint8_t)newValue; + else { + printError(ERR_SETTING_RANGE); + return; + } + break; + + case 'S': // sound + if (newValue >=0 && newValue < 255) + user_settings.audio_volume = (uint8_t)newValue; + else { + printError(ERR_SETTING_RANGE); + return; + } + break; + + case 'D': // deadzone, joystick + if(newValue >=3 && newValue <=12) + user_settings.joystick_deadzone = (uint8_t)newValue; + else { + printError(ERR_SETTING_RANGE); + return; + } + break; + + case 'A': // attack threshold, joystick + if(newValue >=20000 && newValue <=35000) + user_settings.attack_threshold = (uint16_t)newValue; + else { + printError(ERR_SETTING_RANGE); + return; + } + break; + + case 'L': // lives per level + if (newValue >= 3 && newValue <= 9) + user_settings.lives_per_level = (uint8_t)newValue; + else { + printError(ERR_SETTING_RANGE); + return; + } + break; + + default: + Serial.print("Command Error: "); + Serial.println(readBuffer[0]); + return; + break; + + } + + show_settings_menu(); +} + +void reset_settings() { + user_settings.settings_version = SETTINGS_VERSION; + + user_settings.led_brightness = BRIGHTNESS; + + user_settings.joystick_deadzone = JOYSTICK_DEADZONE; + user_settings.attack_threshold = ATTACK_THRESHOLD; + + user_settings.audio_volume = MAX_VOLUME; + + user_settings.lives_per_level = LIVES_PER_LEVEL; + + user_settings.games_played = 0; + user_settings.total_points = 0; + user_settings.high_score = 0; + user_settings.boss_kills = 0; + + settings_eeprom_write(); +} + +void show_settings_menu() { + Serial.println("\r\n====== TWANG Settings Menu ========"); + Serial.println("= Current values are shown ="); + Serial.println("= Send new values like B=150 ="); + Serial.println("= with a carriage return ="); + Serial.println("==================================="); + + Serial.print("\r\nB="); + Serial.print(user_settings.led_brightness); + Serial.println(" (LED Brightness 5-255)"); + + Serial.print("S="); + Serial.print(user_settings.audio_volume); + Serial.println(" (Sound Volume 0-10)"); + + Serial.print("D="); + Serial.print(user_settings.joystick_deadzone); + Serial.println(" (Joystick Deadzone 3-12)"); + + Serial.print("A="); + Serial.print(user_settings.attack_threshold); + Serial.println(" (Attack Sensitivity 20000-35000)"); + + Serial.print("L="); + Serial.print(user_settings.lives_per_level); + Serial.println(" (Lives per Level (3-9))"); + + Serial.println("\r\n(Send...)"); + Serial.println(" ? to show current settings"); + Serial.println(" R to reset everything to defaults)"); + Serial.println(" P to reset play statistics)"); + Serial.println(" E to write changes to eeprom)"); + Serial.println(" ! to restart with current settings"); + + show_game_stats(); +} + +void show_game_stats() +{ + Serial.println("\r\n ===== Play statistics ======"); + Serial.print("Games played: ");Serial.println(user_settings.games_played); + if (user_settings.games_played > 0) { + Serial.print("Average Score: ");Serial.println(user_settings.total_points / user_settings.games_played); + } + Serial.print("High Score: ");Serial.println(user_settings.high_score); + Serial.print("Boss kills: ");Serial.println(user_settings.boss_kills); +} + +void settings_eeprom_read() +{ + Serial.println("Begin EEPROM Read"); + + EEPROM.begin(EEPROM_SIZE); + + uint8_t ver = EEPROM.read(0); + uint8_t temp[sizeof(user_settings)]; + + if (ver != SETTINGS_VERSION) { + Serial.print("Error: EEPROM settings read failed:"); Serial.println(ver); + Serial.println("Loading defaults..."); + reset_settings(); + return; + } + else { + Serial.print("Settings version: "); Serial.println(ver); + } + + for (int i=0; i + +const char* ssid = "TWANG_AP"; +const char* passphrase = "esp32rocks"; + +WiFiServer server(80); + +void ap_setup() { + bool ret; + + + + /* + * Set up an access point + * @param ssid Pointer to the SSID (max 63 char). + * @param passphrase (for WPA2 min 8 char, for open use NULL) + * @param channel WiFi channel number, 1 - 13. + * @param ssid_hidden Network cloaking (0 = broadcast SSID, 1 = hide SSID) + */ + ret = WiFi.softAP(ssid, passphrase, 2, 0); + + Serial.println("\r\nWiFi AP online ..."); + server.begin(); + + +} + +void sendStatsPage(WiFiClient client) { + Serial.println("printUploadForm"); + // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK) + // and a content-type so the client knows what's coming, then a blank line: + client.println("HTTP/1.1 200 OK"); + client.println("Content-type:text/html"); + client.println(); + client.println(""); + client.println(""); + client.println("

TWANG32 Play Stats

"); + client.println("
    "); + + client.print("
  • Games played: "); client.print(user_settings.games_played); client.println("
  • "); + if (user_settings.games_played > 0) { // prevent divide by 0 + client.print("
  • Average score: "); client.print(user_settings.total_points / user_settings.games_played); client.println("
  • "); + } + client.print("
  • High score: "); client.print(user_settings.high_score); client.println("
  • "); + client.print("
  • Boss kills: "); client.print(user_settings.boss_kills); client.println("
  • "); + + client.println("
"); + client.println(""); + client.println(""); + client.println(); + +} + +void ap_client_check(){ + int cnt; + bool newconn=false; + int stat; + WiFiClient client = server.available(); // listen for incoming clients + + if (client) { // if you get a client, + sendStatsPage(client); + Serial.println("printUploadForm"); + } + +}