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.
This commit is contained in:
bdring
2018-03-22 11:16:47 -05:00
parent 23df267c9f
commit 97341671b9
5 changed files with 600 additions and 115 deletions

View File

@@ -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;
}

View File

@@ -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.

View File

@@ -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 <FastLED.h>
#include<Wire.h>
#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,27 +157,35 @@ 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();
settings_init();
FastLED.addLeds<LED_TYPE, DATA_PIN, CLOCK_PIN, LED_COLOR_ORDER>(leds, NUM_LEDS);
FastLED.setBrightness(BRIGHTNESS);
FastLED.setDither(1);
Wire.begin();
accelgyro.initialize();
FastLED.addLeds<LED_TYPE, DATA_PIN, CLOCK_PIN, LED_COLOR_ORDER>(leds, NUM_LEDS);
FastLED.setBrightness(user_settings.led_brightness);
FastLED.setDither(1);
sound_init(DAC_AUDIO_PIN);
stage = STARTUP;
stageStartTime = millis();
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){
SFXattacking();
@@ -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;
}
@@ -250,6 +279,7 @@ void loop() {
}else if(stage == DEAD){
// DEAD
FastLED.clear();
tickDie(mm);
if(!tickParticles()){
loadLevel();
}
@@ -267,9 +297,10 @@ void loop() {
else
{
FastLED.clear();
score = 0; // reset the score
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
{
@@ -537,12 +567,12 @@ void levelComplete(){
}
void nextLevel(){
levelNumber ++;
//if(levelNumber > LEVEL_COUNT)
if(lastLevel)
levelNumber = 0;
lives = LIVES_PER_LEVEL;
lives = user_settings.lives_per_level;
loadLevel();
}
@@ -556,7 +586,7 @@ void die(){
playerAlive = 0;
if(levelNumber > 0)
lives --;
//updateLives();
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;
}
@@ -759,27 +789,34 @@ bool tickParticles(){
}
void tickConveyors(){
int b, dir, n, i, ss, ee, led;
//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<CONVEYOR_COUNT; i++){
if(conveyorPool[i]._alive){
dir = conveyorPool[i]._dir;
speed = constrain(conveyorPool[i]._speed, -MAX_PLAYER_SPEED+1, MAX_PLAYER_SPEED-1);
ss = getLED(conveyorPool[i]._startPoint);
ee = getLED(conveyorPool[i]._endPoint);
for(led = ss; led<ee; led++){
b = 5;
n = (-led + (m/100)) % 5;
if(dir == -1) n = (led + (m/100)) % 5;
b = (5-n)/2.0;
if(b > 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;
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));
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);
// 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;
}
#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);

View File

@@ -1,6 +1,19 @@
#ifndef SETTINGS_H
#define SETTINGS_H
#include <EEPROM.h>
// 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
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<sizeof(user_settings); i++)
{
temp[i] = EEPROM.read(i);
}
memcpy((char*)&user_settings, temp, sizeof(user_settings));
EEPROM.end();
}
void settings_eeprom_write() {
EEPROM.begin(EEPROM_SIZE);
uint8_t temp[sizeof(user_settings)];
memcpy(temp, (uint8_t*)&user_settings, sizeof(user_settings));
Serial.println("Writing settings...");
for (int i=0; i<sizeof(user_settings); i++)
{
EEPROM.write(i, temp[i]);
}
EEPROM.commit();
EEPROM.end();
}
void printError(int reason) {
switch(reason) {
case ERR_SETTING_NUM:
Serial.print("Error: Invalid setting number");
break;
case ERR_SETTING_RANGE:
Serial.print("Error: Setting out of range");
break;
default:
Serial.print("Error:");Serial.println(reason);
break;
}
}
#endif

65
wifi_ap.h Normal file
View File

@@ -0,0 +1,65 @@
#include <WiFi.h>
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("<html>");
client.println("<body>");
client.println("<h1>TWANG32 Play Stats</h1>");
client.println("<ul>");
client.print("<li>Games played: "); client.print(user_settings.games_played); client.println("</li>");
if (user_settings.games_played > 0) { // prevent divide by 0
client.print("<li>Average score: "); client.print(user_settings.total_points / user_settings.games_played); client.println("</li>");
}
client.print("<li>High score: "); client.print(user_settings.high_score); client.println("</li>");
client.print("<li>Boss kills: "); client.print(user_settings.boss_kills); client.println("</li>");
client.println("</ul>");
client.println("</body>");
client.println("</html>");
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");
}
}