├── .gitattributes ├── TWANG32 ├── Conveyor.h ├── Boss.h ├── Spawner.h ├── iSin.h ├── Enemy.h ├── Particle.h ├── twang_mpu.h ├── Lava.h ├── config.h ├── sound.h ├── wifi_ap.h ├── settings.h ├── TWANG32.ino └── SoundData.h ├── .gitignore ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /TWANG32/Conveyor.h: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | #include "settings.h" 3 | 4 | class Conveyor 5 | { 6 | public: 7 | void Spawn(int startPoint, int endPoint, int speed); 8 | void Kill(); 9 | int _startPoint; 10 | int _endPoint; 11 | int _speed; 12 | bool _alive; 13 | }; 14 | 15 | void Conveyor::Spawn(int startPoint, int endPoint, int speed){ 16 | _startPoint = startPoint; 17 | _endPoint = endPoint; 18 | _speed = constrain(speed, -MAX_PLAYER_SPEED+1, MAX_PLAYER_SPEED - 1); // must allow some player speed 19 | _alive = true; 20 | } 21 | 22 | void Conveyor::Kill(){ 23 | _alive = false; 24 | } 25 | -------------------------------------------------------------------------------- /TWANG32/Boss.h: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | 3 | class Boss 4 | { 5 | public: 6 | void Spawn(); 7 | void Hit(); 8 | void Kill(); 9 | bool Alive(); 10 | int _pos; 11 | int _lives; 12 | int _ticks; 13 | private: 14 | bool _alive; 15 | }; 16 | 17 | void Boss::Spawn(){ 18 | _pos = 800; 19 | _lives = 3; 20 | _alive = 1; 21 | } 22 | 23 | void Boss::Hit(){ 24 | _lives --; 25 | if(_lives == 0) { 26 | Kill(); 27 | return; 28 | } 29 | if(_lives == 2){ 30 | _pos = 200; 31 | }else if(_lives == 1){ 32 | _pos = 600; 33 | } 34 | } 35 | 36 | bool Boss::Alive(){ 37 | return _alive; 38 | } 39 | 40 | void Boss::Kill(){ 41 | _alive = 0; 42 | } 43 | -------------------------------------------------------------------------------- /TWANG32/Spawner.h: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | 3 | class Spawner 4 | { 5 | public: 6 | void Spawn(int pos, int rate, int sp, int dir, long activate); 7 | void Kill(); 8 | int Alive(); 9 | int _pos; 10 | int _rate; 11 | int _sp; 12 | int _dir; 13 | long _lastSpawned; 14 | long _activate; 15 | private: 16 | int _alive; 17 | }; 18 | 19 | void Spawner::Spawn(int pos, int rate, int sp, int dir, long activate){ 20 | _pos = pos; 21 | _rate = rate; 22 | _sp = sp; 23 | _dir = dir; 24 | _activate = millis()+activate; 25 | _alive = 1; 26 | } 27 | 28 | void Spawner::Kill(){ 29 | _alive = 0; 30 | _lastSpawned = 0; 31 | } 32 | 33 | int Spawner::Alive(){ 34 | return _alive; 35 | } 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 bdring 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /TWANG32/iSin.h: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | 3 | class iSin 4 | { 5 | public: 6 | int convert(long x); 7 | private: 8 | uint8_t isinTable8[91] = { 9 | 0, 4, 9, 13, 18, 22, 27, 31, 35, 40, 44, 10 | 49, 53, 57, 62, 66, 70, 75, 79, 83, 87, 11 | 91, 96, 100, 104, 108, 112, 116, 120, 124, 128, 12 | 131, 135, 139, 143, 146, 150, 153, 157, 160, 164, 13 | 167, 171, 174, 177, 180, 183, 186, 190, 192, 195, 14 | 198, 201, 204, 206, 209, 211, 214, 216, 219, 221, 15 | 223, 225, 227, 229, 231, 233, 235, 236, 238, 240, 16 | 241, 243, 244, 245, 246, 247, 248, 249, 250, 251, 17 | 252, 253, 253, 254, 254, 254, 255, 255, 255, 255 18 | }; 19 | }; 20 | 21 | int iSin::convert(long x) 22 | { 23 | boolean pos = true; // positive - keeps an eye on the sign. 24 | if (x < 0) 25 | { 26 | x = -x; 27 | pos = !pos; 28 | } 29 | if (x >= 360) x %= 360; 30 | if (x > 180) 31 | { 32 | x -= 180; 33 | pos = !pos; 34 | } 35 | if (x > 90) x = 180 - x; 36 | if (pos) return isinTable8[x]/2 ; 37 | return -isinTable8[x]/2 ; 38 | } 39 | -------------------------------------------------------------------------------- /TWANG32/Enemy.h: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | 3 | class Enemy 4 | { 5 | public: 6 | void Spawn(int pos, int dir, int speed, int wobble); 7 | void Tick(); 8 | void Kill(); 9 | bool Alive(); 10 | int _pos; 11 | int _wobble; 12 | int playerSide; 13 | private: 14 | int _dir; 15 | int _speed; 16 | int _alive; 17 | int _origin; 18 | }; 19 | 20 | void Enemy::Spawn(int pos, int dir, int speed, int wobble){ 21 | _pos = pos; 22 | _dir = dir; // 0 = left, 1 = right 23 | _wobble = wobble; // 0 = no, >0 = yes, value is width of wobble 24 | _origin = pos; 25 | _speed = speed; 26 | _alive = 1; 27 | } 28 | 29 | void Enemy::Tick(){ 30 | if(_alive){ 31 | if(_wobble > 0){ 32 | _pos = _origin + (sin((millis()/3000.0)*_speed)*_wobble); 33 | }else{ 34 | if(_dir == 0){ 35 | _pos -= _speed; 36 | }else{ 37 | _pos += _speed; 38 | } 39 | if(_pos > 1000) { 40 | Kill(); 41 | } 42 | if(_pos <= 0) { 43 | Kill(); 44 | } 45 | } 46 | } 47 | } 48 | 49 | bool Enemy::Alive(){ 50 | return _alive; 51 | } 52 | 53 | void Enemy::Kill(){ 54 | _alive = 0; 55 | } 56 | -------------------------------------------------------------------------------- /TWANG32/Particle.h: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | #define FRICTION 1 3 | 4 | class Particle 5 | { 6 | public: 7 | void Spawn(int pos); 8 | void Tick(int USE_GRAVITY); 9 | void Kill(); 10 | bool Alive(); 11 | int _pos; 12 | int _power; 13 | private: 14 | int _life; 15 | int _alive; 16 | int _sp; 17 | }; 18 | 19 | void Particle::Spawn(int pos){ 20 | _pos = pos; 21 | _sp = random(-200, 200); 22 | _power = 255; 23 | _alive = 1; 24 | _life = 220 - abs(_sp); 25 | } 26 | 27 | void Particle::Tick(int USE_GRAVITY){ 28 | if(_alive){ 29 | _life ++; 30 | if(_sp > 0){ 31 | _sp -= _life/10; 32 | }else{ 33 | _sp += _life/10; 34 | } 35 | if(USE_GRAVITY && _pos > 500) _sp -= 10; 36 | _power = 100 - _life; 37 | if(_power <= 0){ 38 | Kill(); 39 | }else{ 40 | _pos += _sp/7.0; 41 | if(_pos > 1000){ 42 | _pos = 1000; 43 | _sp = 0-(_sp/2); 44 | } 45 | else if(_pos < 0){ 46 | _pos = 0; 47 | _sp = 0-(_sp/2); 48 | } 49 | } 50 | } 51 | } 52 | 53 | bool Particle::Alive(){ 54 | return _alive; 55 | } 56 | 57 | void Particle::Kill(){ 58 | _alive = 0; 59 | } 60 | -------------------------------------------------------------------------------- /TWANG32/twang_mpu.h: -------------------------------------------------------------------------------- 1 | // A very simple implementation of the MPU-6050 limited to the 2 | // TWANG requirements 3 | // I reused the function names to make it compatible 4 | // B. Dring 2/2018 5 | 6 | class Twang_MPU 7 | { 8 | 9 | public: 10 | void initialize(); 11 | void getMotion6(int16_t* xAccel, int16_t* yAccel, int16_t* zAccel, int16_t* xGyro, int16_t* yGyro, int16_t* zGyro); 12 | bool verify(); 13 | 14 | private: 15 | static const uint8_t MPU_ADDR = 0x68; 16 | static const uint8_t PWR_MGMT_1 = 0x6B; 17 | static const uint8_t MPU_DATA_REG_START = 0x3B; 18 | static const uint8_t MPU_DATA_LEN = 14; 19 | static const uint8_t MPU_DATA_WHO_AM_I = 0x75; 20 | 21 | }; 22 | 23 | void Twang_MPU::initialize() 24 | { 25 | Wire.beginTransmission(MPU_ADDR); 26 | Wire.write(PWR_MGMT_1); // PWR_MGMT_1 register 27 | Wire.write(0); // set to zero (wakes up the MPU-6050) 28 | Wire.endTransmission(true); 29 | } 30 | 31 | bool Twang_MPU::verify() 32 | { 33 | Wire.beginTransmission(MPU_ADDR); 34 | Wire.write(MPU_DATA_WHO_AM_I); 35 | Wire.endTransmission(false); 36 | Wire.requestFrom(MPU_ADDR,1,true); // read the whole MPU data section 37 | return (Wire.read() == MPU_ADDR); 38 | } 39 | 40 | void Twang_MPU::getMotion6(int16_t* xAccel, int16_t* yAccel, int16_t* zAccel, int16_t* xGyro, int16_t* yGyro, int16_t* zGyro) 41 | { 42 | 43 | Wire.beginTransmission(MPU_ADDR); 44 | Wire.write(MPU_DATA_REG_START); // starting with register 0x3B (ACCEL_XOUT_H) 45 | Wire.endTransmission(false); 46 | Wire.requestFrom(MPU_ADDR,MPU_DATA_LEN,true); // read the whole MPU data section 47 | *yAccel=Wire.read()<<8|Wire.read(); // x Accel 48 | *zAccel=Wire.read()<<8|Wire.read(); // y Accel 49 | *xAccel=Wire.read()<<8|Wire.read(); // z Accel 50 | Wire.read(); Wire.read(); // Temperature..not used, but need to read it 51 | *yGyro=Wire.read()<<8|Wire.read(); // x Gyro 52 | *zGyro=Wire.read()<<8|Wire.read(); // y Gyro 53 | *xGyro=Wire.read()<<8|Wire.read(); // z Gyro 54 | } 55 | 56 | -------------------------------------------------------------------------------- /TWANG32/Lava.h: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | 3 | class Lava 4 | { 5 | public: 6 | void Spawn(int left, int right, int ontime, int offtime, int offset, int state, float grow_rate, float flow_vector); 7 | void Kill(); 8 | int Alive(); 9 | void Update(); 10 | int _left; 11 | int _right; 12 | int _ontime; 13 | int _offtime; 14 | int _offset; 15 | long _lastOn; 16 | int _state; 17 | float _grow_rate = 0.0; // size grows by this much each tick 18 | float _flow_vector = 0.0; // endpoints move in the direction each tick. 19 | static const int OFF = 0; 20 | static const int ON = 1; 21 | private: 22 | int _alive; 23 | float _growth = 0; 24 | float _flow = 0; 25 | int _width; 26 | }; 27 | 28 | void Lava::Spawn(int left, int right, int ontime, int offtime, int offset, int state, float grow_rate, float flow_vector){ 29 | _left = left; 30 | _right = right; 31 | _ontime = ontime; 32 | _offtime = offtime; 33 | _offset = offset; 34 | _alive = 1; 35 | _lastOn = millis()-offset; 36 | _state = state; 37 | 38 | _width = _right - _left; 39 | 40 | _grow_rate = fabs(grow_rate); // only allow positive growth 41 | _flow_vector = flow_vector; 42 | 43 | 44 | } 45 | 46 | void Lava::Kill(){ 47 | _alive = 0; 48 | } 49 | 50 | int Lava::Alive(){ 51 | return _alive; 52 | } 53 | 54 | // this gets called on every frame. 55 | void Lava::Update() { 56 | // update how much it has changed 57 | if (_grow_rate != 0) { 58 | _growth += _grow_rate; 59 | if (_growth >= 1.0) { 60 | if (_left > 0) 61 | _left -= 1; 62 | 63 | if (_right < VIRTUAL_LED_COUNT) 64 | _right += 1; 65 | 66 | _growth = 0.0; 67 | } 68 | } 69 | 70 | if (_flow_vector != 0) { 71 | _flow += _flow_vector; 72 | if (fabs(_flow) >=1) { 73 | if (_left > 1 && _left < VIRTUAL_LED_COUNT - _width) { 74 | _left += (int)_flow; 75 | } 76 | if (_right > _width && _right < VIRTUAL_LED_COUNT) 77 | _right += (int)_flow; 78 | 79 | 80 | _flow = 0.0; 81 | } 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /TWANG32/config.h: -------------------------------------------------------------------------------- 1 | /* 2 | TWANG32 - An ESP32 port of TWANG 3 | (c) B. Dring 3/2018 4 | License: Creative Commons 4.0 Attribution - Share Alike 5 | 6 | TWANG was originally created by Critters 7 | https://github.com/Critters/TWANG 8 | 9 | Basic hardware definitions 10 | 11 | Light Strip Notes: 12 | 13 | Noepixel / WS2812 14 | - Low Cost 15 | - You might already have a strip. 16 | - No Clock Line - This means a fixed and relatively slow data rate and only shorter strips can be used 17 | - Poor Dynamic Range - The low end of brightness is basically not visible 18 | Dotstar (Highly recommended) 19 | - Higher Cost 20 | - Higher speed - Longer strips can be used. 21 | - Great dynamic range, so lower levels and more colors can be used. 22 | 23 | 24 | */ 25 | 26 | #ifndef CONFIG_H 27 | #define CONFIG_H 28 | 29 | #define DATA_PIN 26 30 | #define CLOCK_PIN 27 31 | 32 | /* Game is rendered to this and scaled down to your strip. 33 | This allows level definitions to work on all strip lengths */ 34 | #define VIRTUAL_LED_COUNT 1000 35 | 36 | // what type of LED Strip....uncomment to define only one of these 37 | //#define USE_APA102 38 | 39 | #define USE_NEOPIXEL 40 | 41 | // Check to make sure LED choice was done right 42 | #if !defined(USE_NEOPIXEL) && !defined(USE_APA102) 43 | #error "You must have USE_APA102 or USE_NEOPIXEL defined in config.h" 44 | #endif 45 | 46 | #if defined(USE_NEOPIXEL) && defined(USE_APA102) 47 | #error "Both USE_APA102 and USE_NEOPIXEL are defined in config.h. Only one can be used" 48 | #endif 49 | 50 | #ifdef USE_APA102 51 | #define LED_TYPE APA102 52 | #define LED_COLOR_ORDER BGR // typically this will be the order, but switch it if not 53 | #define CONVEYOR_BRIGHTNESS 8 54 | #define LAVA_OFF_BRIGHTNESS 4 55 | #define MAX_LEDS VIRTUAL_LED_COUNT // these LEDS can handle the max 56 | #define MIN_REDRAW_INTERVAL 1000.0 / 60.0 // divide by frames per second..if you tweak, adjust player speed 57 | #endif 58 | 59 | #ifdef USE_NEOPIXEL 60 | #define CONVEYOR_BRIGHTNESS 40 // low neopixel values are nearly off, Neopixels need a higher value 61 | #define LAVA_OFF_BRIGHTNESS 15 // low neopixel values are nearly off, Neopixels need a higher value 62 | #define MAX_LEDS 300 // Neopixels cannot handle the framerate 63 | #define MIN_REDRAW_INTERVAL 1000.0 / 60.0 // divide by frames per second..if you tweak adjust player speed 64 | #endif 65 | 66 | // Comment or remove the next #define to disable the /metrics endpoint on the HTTP server. 67 | // This endpoint provides the Twang32 stats for ingestion via Prometheus. 68 | #define ENABLE_PROMETHEUS_METRICS_ENDPOINT 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /TWANG32/sound.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This creates sound tones by outputting a square wave on a DAC pin. The 3 | * volume of the tone is the level of the DAC pin. 4 | * 5 | * The square wave is created by a timer. The timer runs at 2x the freq, because 6 | * it needs to transition high and then low. 7 | * 8 | * 9 | */ 10 | #ifndef SOUND_H 11 | #define SOUND_H 12 | 13 | #include "esp32-hal-timer.h"; 14 | 15 | #define ESP32_F_CPU 80000000 // the speed of the processor 16 | #define AUDIO_INTERRUPT_PRESCALER 80 17 | #define SOUND_TIMER_NO 0 18 | //#define AUDIO_PIN 25 19 | #define MIN_FREQ 20 20 | #define MAX_FREQ 16000 21 | 22 | hw_timer_t * sndTimer = NULL; 23 | 24 | bool sound_on = true; 25 | bool sound_wave_high = true; // this toggles to create the high/low transitions of the wave 26 | uint8_t sound_volume = 0; 27 | 28 | void sound_init(int pin); 29 | bool sound(uint16_t freq, uint8_t volume); 30 | void soundOff(); 31 | 32 | int dac_pin; 33 | 34 | 35 | 36 | void IRAM_ATTR onSoundTimer() 37 | { 38 | if (sound_on) { 39 | dacWrite(dac_pin, (sound_wave_high?sound_volume:0)); 40 | sound_wave_high = ! sound_wave_high; 41 | 42 | } 43 | else 44 | dacWrite(dac_pin, 0); 45 | } 46 | 47 | void sound_init(int pin){ // pin must be a DAC pin number !! (typically 25 or 26) 48 | dac_pin = pin; 49 | sound_on = false; 50 | pinMode(dac_pin, OUTPUT); 51 | sound_volume = 0; 52 | 53 | sndTimer = timerBegin(SOUND_TIMER_NO, AUDIO_INTERRUPT_PRESCALER, true); 54 | timerAttachInterrupt(sndTimer, &onSoundTimer, true); 55 | timerAlarmWrite(sndTimer, ESP32_F_CPU/AUDIO_INTERRUPT_PRESCALER/MIN_FREQ, true); // lower timer freq 56 | timerAlarmEnable(sndTimer); 57 | } 58 | 59 | void sound_pause() // this prevents the interrupt from firing ... use during eeprom write 60 | { 61 | if (sndTimer != NULL) 62 | timerStop(sndTimer); 63 | } 64 | 65 | void sound_resume() // resume from pause ... after eeprom write 66 | { 67 | if (sndTimer != NULL) 68 | timerRestart(sndTimer); 69 | } 70 | 71 | bool sound(uint16_t freq, uint8_t volume){ 72 | if (volume == 0) { 73 | soundOff(); 74 | return false; 75 | } 76 | if (freq < MIN_FREQ || freq > MAX_FREQ) { 77 | return false; 78 | } 79 | sound_on = true; 80 | sound_volume = volume; 81 | timerAlarmWrite(sndTimer, ESP32_F_CPU/AUDIO_INTERRUPT_PRESCALER/(freq * 2), true); 82 | return true; 83 | } 84 | 85 | void soundOff(){ 86 | sound_on = false; 87 | sound_volume = 0; 88 | timerAlarmWrite(sndTimer, ESP32_F_CPU/AUDIO_INTERRUPT_PRESCALER/(MIN_FREQ), true); // lower timer freq 89 | } 90 | 91 | 92 | 93 | #endif 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TWANG32 Hack 2 | An ESP32 based, 1D, LED strip, dungeon crawler. inspired by Line Wobbler by Robin B 3 | 4 | The [ESP32 version](https://github.com/bdring/TWANG32) was ported from the [TWANG fork](https://github.com/bdring/TWANG) by bdring of [Buildlog.net Blog](http://www.buildlog.net/blog?s=twang) 5 | Pixtxa forked it again, added some extra leds to the controller and added some other features and bugfixes. 6 | 7 | https://github.com/Pixtxa/TWANG32-Hack/assets/30337073/99b64ab3-129b-4cfb-838a-5d0afe2453d9 8 | 9 | **Current State** 10 | 11 | - All of the Arduino version game features are functional. 12 | - The game now has a WiFi access port to get game stats. Connect a smartphone or computer to see them. 13 | - **SSID:** TWANG_AP 14 | - **Password:** 12345666 15 | - **URL:** 192.168.4.1 16 | - You can update these settings over WiFi 17 | - WS2812B LED type 18 | - LED Count 19 | - LED Brightness 20 | - Audio Volume 21 | - Joystick direction 22 | - Joystick Deadzone (removes drift) 23 | - Attack Threshold (twang sensitivity) 24 | - Lives Per Level 25 | 26 | ## TO DO List: 27 | - Wireless features 28 | - level/score/life count/... live output 29 | - stream to another stripe/make controller wireless 30 | - support a brightness sensor for automatic dimming at night 31 | - reset highscore 32 | - Add an extra animation if new highscore is set 33 | - Add a level display 34 | - Add a score display 35 | - Add a scoreboard/highscore list? 36 | - Config for adding a little glow to the LEDs, so people don't hit unlit parts of the LED chain at night or oversee it during build up/teardown 37 | - Save energy on idle for longer powerbank run time 38 | 39 | ## Required libraries: 40 | * [FastLED](http://fastled.io/) 41 | * [RunningMedian](http://playground.arduino.cc/Main/RunningMedian) 42 | 43 | ## Hardware used: 44 | * ESP32, I use the D1-mini module 45 | * I use cheap WS2812B ones, the maximum I used is 300 LEDs, which is a 5 m LED stripe with 60 LED/m, more might be bad because of the latency 46 | * If you want it longer, I reccomend using more spacing between the LEDs to keep it cheap. I've combined two LED chains with 10 led/m, where the LEDs are visible from all sides, playing the game on 20 meters is really fun 47 | * The game supports 1000 LEDs maximum, but then APA102C should be used. HD107S might also work, but I haven't tested this. Anything compatible with the FastLED library should work. 48 | * MPU6050 accelerometer 49 | * Originally a spring doorstop, is used, I used a random spring from the local hardware store 50 | * Speaker and amplifier: A PAM8403 module is recomended, because the ESP32 cannot drive a speaker as loudly as an Arduino. But I've connected a piezoelectric speaker which is good enough for me 51 | * Some level converter because the LEDs want 5 V on the data lines. Somehow putting an 1 kOhm resistor in series works, found this hack somewhere 52 | 53 | See description of [ESP32 version](https://github.com/bdring/TWANG32) for more details. 54 | 55 | ## Enclosure 56 | [ESP32 version](https://github.com/bdring/TWANG32) has STL files, but I've used some e-waste parts. 57 | 58 | ## Overview 59 | The following is a quick overview of the code to help you understand and tweak the game to your needs. 60 | 61 | The game is played on a 1000 unit line, the position of enemies, the player, lava etc range from 0 to 1000 and the LEDs that represent them are derived using the `getLED()` function. You don't need to worry about this but it's good to know for things like the width of the attack and player max move speed. Regardless of the number of LEDs, everything takes place in this 1000 unit wide line. 62 | 63 | **LED SETUP** Defines the quantity of LEDs as well as the data and clock pins used. I've tested several WS2812B strips and the color order sometimes changes. In my setup the player is green, the exit blue and the enemies are red. Brightness should range from 50 to 255, use a lower number if playing at night or wanting to use a smaller power supply. the joystick direction can be set to 0 or 1 to flip the game orientation. In `setup()` there is a `FastLED.addLeds()` line, in there you could change it to another brand of LED strip. 64 | 65 | I've added 13 other neopixels: A circle of 12 for life-display (that also output the level number by counting flashes) and one RGBW on top of the controller that lights up when wobble or on game start/end. They're set in `drawLifebar()` function. 66 | 67 | **JOYSTICK SETUP** All parameters are commented in the code, you can set it to work in both forward/backward as well as side-to-side mode by changing `JOYSTICK_ORIENTATION`. Adjust the `ATTACK_THRESHOLD` if the "Twanging" is overly sensitive and the `JOYSTICK_DEADZONE` if the player slowly drifts when there is no input (because it's hard to get the MPU6050 dead level). 68 | 69 | **WOBBLE ATTACK** Sets the width, duration (ms) of the attack. 70 | 71 | **POOLS** These are the object pools for enemies, particles, lava, conveyors etc. You can modify the quantity of any of them if your levels use more or if you want to save some memory, just remember to update the respective counts to avoid errors. 72 | 73 | **USE_GRAVITY** 0/1 to set if particles created by the player getting killed should fall towards the start point, the `BEND_POINT` variable can be set to mark the point at which the strip of LEDs goes from being horizontal to vertical. The game is 1000 units wide (regardless of number of LED's) so 500 would be the mid point. If this is confusing just set `USE_GRAVITY` to 0. 74 | 75 | ## Modifying / Creating levels 76 | Find the `loadLevel()` function, in there you can see a switch statement with the 10 levels I created. 77 | They all call different functions and variables to setup the level. Each one is described below: 78 | 79 | **playerPosition;** Where the player starts on the 0 to 1000 line. If not set it defaults to 0. I set it to 200 in the first level so the player can see movement even if the first action they take is to push the joystick left 80 | 81 | **spawnEnemy(position, direction, speed, wobble);** (10 enemies max) 82 | * position: 0 to 1000 83 | * direction: 0/1, initial direction of travel 84 | * speed: >=0, speed of the enemy, remember the game is 1000 wide and runs at 60fps. I recommend between 1 and 4 85 | * wobble: 0=regular moving enemy, 1=sine wave enemy, in this case speed sets the width of the wave 86 | 87 | **spawnPool[poolNumber].Spawn(position, rate, speed, direction);** (2 spawners max) 88 | * A spawn pool is a point which spawns enemies forever 89 | * position: 0 to 1000 90 | * rate: milliseconds between spawns, 1000 = 1 second 91 | * speed: speed of the enemis it spawns 92 | * direction: 0=towards start, 1=away from start 93 | 94 | **spawnLava(startPoint, endPoint, ontime, offtime, offset);** (4 lava pools max) 95 | * startPoint: 0 to 1000 96 | * endPoint: 0 to 1000, combined with startPoint this sets the location and size of the lava 97 | * ontime: How long (ms) the lava is ON for 98 | * offtime: How long the lava is ON for 99 | * offset: How long (ms) after the level starts before the lava turns on, use this to create patterns with multiple lavas 100 | * grow: This specifies the rate of growth. Use 0 for no growth. Reasonable growth is 0.1 to 0.5 101 | * flow: This specifies the rate/direction of flow. Reasonable numbers are 0.2 to 0.8 102 | 103 | **spawnConveyor(startPoint, endPoint, speed);** (2 conveyors max) 104 | * startPoint, endPoint: Same as lava 105 | * speed: The direction and speed of the travel. Negative moves to base and positive moves towards exit. Must be less than +/- max player speed. 106 | 107 | **spawnBoss();** (only one, don't edit boss level) 108 | * 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 109 | -------------------------------------------------------------------------------- /TWANG32/wifi_ap.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include "settings.h" 3 | 4 | const char* ssid = "TWANG_AP"; 5 | const char* passphrase = "12345678"; 6 | 7 | WiFiServer server(80); 8 | 9 | char linebuf[80]; 10 | int charcount=0; 11 | 12 | enum PAGE_TO_SEND 13 | { 14 | Stats, 15 | Metrics 16 | }; 17 | 18 | void ap_setup() { 19 | 20 | bool ret; 21 | 22 | 23 | 24 | /* 25 | * Set up an access point 26 | * @param ssid Pointer to the SSID (max 63 char). 27 | * @param passphrase (for WPA2 min 8 char, for open use NULL) 28 | * @param channel WiFi channel number, 1 - 13. 29 | * @param ssid_hidden Network cloaking (0 = broadcast SSID, 1 = hide SSID) 30 | */ 31 | ret = WiFi.softAP(ssid, passphrase, 2, 0); 32 | server.begin(); 33 | 34 | Serial.print("\r\nWiFi SSID: "); 35 | Serial.println(ssid); 36 | Serial.print("WiFi Password: "); 37 | Serial.println(passphrase); 38 | Serial.println("Web Server Address: http://192.168.4.1"); 39 | 40 | } 41 | 42 | void sendStatsPage(WiFiClient client) { 43 | // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK) 44 | // and a content-type so the client knows what's coming, then a blank line: 45 | client.println("HTTP/1.1 200 OK"); 46 | client.println("Content-type:text/html"); 47 | client.println(); 48 | client.println(""); 49 | client.println(""); 50 | client.println("

TWANG32 Play Stats

"); 51 | client.println("