├── README.md ├── FastLED_RGBW.h └── 1D-Pong.ino /README.md: -------------------------------------------------------------------------------- 1 | # 1D-Pong 2 | Firmware for [vspase.one 1D-Pong](https://wiki.vspace.one/doku.php?id=projekte:2023:1d-pong), based on [1D-Pong of flyingangel](https://www.hackster.io/flyingangel/1d-pong-85e965) 3 | -------------------------------------------------------------------------------- /FastLED_RGBW.h: -------------------------------------------------------------------------------- 1 | /* FastLED_RGBW 2 | * 3 | * Hack to enable SK6812 RGBW strips to work with FastLED. 4 | * 5 | * Original code by Jim Bumgardner (http://krazydad.com). 6 | * Modified by David Madison (http://partsnotincluded.com). 7 | * Extended by Christoph Wempe 8 | * 9 | */ 10 | 11 | #ifndef FastLED_RGBW_h 12 | #define FastLED_RGBW_h 13 | 14 | 15 | /// scale four one byte values by a fith one, which is treated as 16 | /// the numerator of a fraction whose demominator is 256 17 | /// In other words, it computes r,g,b,w * (scale / 256) 18 | /// 19 | /// THIS FUNCTION ALWAYS MODIFIES ITS ARGUMENTS IN PLACE 20 | 21 | LIB8STATIC void nscale8x4( uint8_t& r, uint8_t& g, uint8_t& b, uint8_t& w, fract8 scale) 22 | { 23 | #if SCALE8_C == 1 24 | #if (FASTLED_SCALE8_FIXED == 1) 25 | uint16_t scale_fixed = scale + 1; 26 | r = (((uint16_t)r) * scale_fixed) >> 8; 27 | g = (((uint16_t)g) * scale_fixed) >> 8; 28 | b = (((uint16_t)b) * scale_fixed) >> 8; 29 | w = (((uint16_t)w) * scale_fixed) >> 8; 30 | #else 31 | r = ((int)r * (int)(scale) ) >> 8; 32 | g = ((int)g * (int)(scale) ) >> 8; 33 | b = ((int)b * (int)(scale) ) >> 8; 34 | w = ((int)w * (int)(scale) ) >> 8; 35 | #endif 36 | #elif SCALE8_AVRASM == 1 37 | r = scale8_LEAVING_R1_DIRTY(r, scale); 38 | g = scale8_LEAVING_R1_DIRTY(g, scale); 39 | b = scale8_LEAVING_R1_DIRTY(b, scale); 40 | w = scale8_LEAVING_R1_DIRTY(w, scale); 41 | cleanup_R1(); 42 | #else 43 | #error "No implementation for nscale8x3 available." 44 | #endif 45 | } 46 | 47 | 48 | struct CRGBW { 49 | union { 50 | struct { 51 | union { 52 | uint8_t g; 53 | uint8_t green; 54 | }; 55 | union { 56 | uint8_t r; 57 | uint8_t red; 58 | }; 59 | union { 60 | uint8_t b; 61 | uint8_t blue; 62 | }; 63 | union { 64 | uint8_t w; 65 | uint8_t white; 66 | }; 67 | }; 68 | uint8_t raw[4]; 69 | }; 70 | 71 | CRGBW(){} 72 | 73 | CRGBW(uint8_t rd, uint8_t grn, uint8_t blu, uint8_t wht){ 74 | r = rd; 75 | g = grn; 76 | b = blu; 77 | w = wht; 78 | } 79 | 80 | inline void operator = (const CRGB c) __attribute__((always_inline)){ 81 | this->r = c.r; 82 | this->g = c.g; 83 | this->b = c.b; 84 | this->white = 0; 85 | } 86 | 87 | /// add one RGBW to another, saturating at 0xFF for each channel 88 | inline CRGBW& operator+= (const CRGB& rhs ) 89 | { 90 | r = qadd8( r, rhs.r); 91 | g = qadd8( g, rhs.g); 92 | b = qadd8( b, rhs.b); 93 | w = 0; 94 | return *this; 95 | } 96 | 97 | /// scale down a RGBW to N 256ths of it's current brightness, using 98 | /// 'plain math' dimming rules, which means that if the low light levels 99 | /// may dim all the way to 100% black. 100 | inline CRGBW& nscale8 (uint8_t scaledown ) 101 | { 102 | nscale8x4( r, g, b, w, scaledown); 103 | return *this; 104 | } 105 | 106 | /// scale down a RGBW to N 256ths of it's current brightness, using 107 | /// 'plain math' dimming rules, which means that if the low light levels 108 | /// may dim all the way to 100% black. 109 | inline CRGBW& nscale8 (const CRGBW & scaledown ) 110 | { 111 | r = ::scale8(r, scaledown.r); 112 | g = ::scale8(g, scaledown.g); 113 | b = ::scale8(b, scaledown.b); 114 | w = 0; 115 | return *this; 116 | } 117 | }; 118 | 119 | 120 | inline uint16_t getRGBWsize(uint16_t nleds){ 121 | uint16_t nbytes = nleds * 4; 122 | if(nbytes % 3 > 0) return nbytes / 3 + 1; 123 | else return nbytes / 3; 124 | } 125 | 126 | void fill_solid( struct CRGBW * leds, int numToFill, CRGB color) 127 | { 128 | for( int i = 0; i < numToFill; i++) { 129 | leds[i] = color; 130 | } 131 | } 132 | 133 | void nscale8( CRGBW* leds, uint16_t num_leds, uint8_t scale) 134 | { 135 | for( uint16_t i = 0; i < num_leds; i++) { 136 | leds[i].nscale8( scale); 137 | } 138 | } 139 | 140 | void fadeToBlackBy( CRGBW* leds, uint16_t num_leds, uint8_t fadeBy) 141 | { 142 | nscale8( leds, num_leds, 255 - fadeBy); 143 | } 144 | 145 | void fill_rainbow( struct CRGBW * pFirstLED, int numToFill, 146 | uint8_t initialhue, 147 | uint8_t deltahue ) 148 | { 149 | CHSV hsv; 150 | hsv.hue = initialhue; 151 | hsv.val = 255; 152 | hsv.sat = 240; 153 | for( int i = 0; i < numToFill; i++) { 154 | pFirstLED[i] = hsv; 155 | hsv.hue += deltahue; 156 | } 157 | } 158 | 159 | #endif -------------------------------------------------------------------------------- /1D-Pong.ino: -------------------------------------------------------------------------------- 1 | /* 2 | "1D Pong" 3 | 4 | Its a 1D Pong Game on 44 WERMA MC35 Beacons 5 | 6 | based on 1D Pong by FlyingAngel - 18.4.2020 7 | Pixtxa - 31.03.2024 8 | */ 9 | 10 | /* 11 | Arduino ESP32-S2 D1mini settings: 12 | Board: "ESP32S2 Dev Module" 13 | USB CDC On Boot: "Enabled" 14 | CPU Frequency: "240MHz (WiFi)' 15 | Core Debug Level: "None" 16 | USB DFU On Boot: "Disabled" 17 | Erase All Flash Before Sketch Upload: "Disabled" 18 | Flash Frequency: "80MHz" 19 | Flash Mode: "QIO" 20 | Flash Size: "4MB (32Mb)" 21 | JTAG Adapter: "Disabled" 22 | USB Firmware MSC On Boot: "Disabled" 23 | Partition Scheme: "Default 4MB with spiffs (1.2MB APP/1.5MB SPIFFS)" 24 | PSRAM: "Disabled" 25 | Upload Mode: "Internal USB" 26 | Upload Speed: "921600" 27 | */ 28 | 29 | //FOTA 30 | const char *wifi_ssid = "vspace.one"; 31 | const char *wifi_password = "12345678"; 32 | const char *hostname = "1D-Pong"; 33 | const char *soft_ap_password = "letmeinletmeinletmein"; 34 | const char *update_pw = "GetThisUpdateDoneRightNow1"; 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | // LEDs 42 | #define FASTLED_INTERNAL // Disable version number message in FastLED library (looks like an error) 43 | #include 44 | #include "FastLED_RGBW.h" //https://www.partsnotincluded.com/fastled-rgbw-neopixels-sk6812/ 45 | 46 | #include "SPI.h" 47 | 48 | enum { 49 | COLOR_BLACK = 0, 50 | COLOR_RED, 51 | COLOR_GREEN, 52 | COLOR_ORANGE, 53 | COLOR_BLUE, 54 | COLOR_MAGENTA, 55 | COLOR_CYAN, 56 | COLOR_WHITE 57 | }; 58 | 59 | byte rainbow_color[] = { 60 | COLOR_RED, 61 | COLOR_ORANGE, 62 | COLOR_GREEN, 63 | COLOR_CYAN, 64 | COLOR_BLUE, 65 | COLOR_MAGENTA, 66 | COLOR_WHITE, 67 | COLOR_BLACK 68 | }; 69 | 70 | // ********************************* 71 | // Hardware settings 72 | // ********************************* 73 | // Buttons 74 | byte player_btn_pin[] = { 33, 15 }; // pins for buttons 75 | 76 | // Shift registers 77 | #define NUM_PIXELS 44 // number of werma mc35 leds 78 | #define D_IN 0 79 | #define CLK 35 80 | #define D_OUT 37 81 | #define LATCH 39 82 | #define OE 40 83 | 84 | // LEDs 85 | #define DATA_PIN 18 86 | #define NUM_LEDS ((1 * 4 + NUM_PIXELS * 3 + 1 * 4) / 4) //1x RGBW, 44x GRB, 1x RGBW in RGBW space 87 | byte player_btn_led[] = { 0, (1 + NUM_PIXELS * 3 / 4) }; // LEDs for buttons 88 | 89 | 90 | // ********************************* 91 | // default game settings 92 | // ********************************* 93 | #define CONFIG_VERSION 1 // Change to delete locally saved settings 94 | byte bg_color = COLOR_BLACK; // background color 95 | byte ball_color = COLOR_WHITE; // ball color 96 | byte goal_color = COLOR_GREEN; // color of the hitting goal 97 | byte player_color[] = { COLOR_BLUE, COLOR_RED }; // colors of the players 98 | byte beep_time = 2; // time to beep on each hit 99 | byte pcb_brightness = 10; // brightness of debug leds 100 | int game_speed_min = 50; // min game-speed 101 | int game_speed_max = 15; // max game-speed 102 | int game_speed_step = 2; // fasten up when change direction 103 | int ball_speed_max = 9; // max ball-speed 104 | int ball_boost_0 = 25; // superboost on last position 105 | int ball_boost_1 = 15; // boost on forelast position 106 | int win_rounds = 5; // x winning rounds for winning game 107 | int end_zone_size = 6; // size endzone 108 | 109 | // ********************************* 110 | // Definition System-Variablen 111 | // ********************************* 112 | 113 | enum { 114 | PLAYER_1 = 0, 115 | PLAYER_2 116 | }; 117 | 118 | unsigned long previous_button_millis = 0; // time of last button-press 119 | unsigned long last_hit_millis[2] = { 0 }; // time of last button-press 120 | int player_button_pressed[2]; // ball-position where button was pressed; „-1“ button not pressed 121 | int previous_button_pos = -1; // position of last button-press 122 | byte previous_button; // last Button-press 123 | byte player_score[2] = { 0 }; // actual Score 124 | byte player_start; // who starts game 125 | int game_speed; // actual game-speed 126 | int ball_dir = 1; // direction, ball is moving (+/- 1) 127 | int ball_pos; // actual ball-position 128 | int ball_speed = 50; // actual ball-speed (higher = slower) 129 | 130 | CRGBW leds[NUM_LEDS]; 131 | CRGB *leds_rgb = (CRGB *)&leds[0]; 132 | CRGB *leds_pcb = (CRGB *)&leds[1]; 133 | 134 | byte werma[NUM_PIXELS] = { 0 }; 135 | 136 | // Set Werma MC35 and neopixels 137 | void SetLeds() { 138 | digitalWrite(LATCH, LOW); 139 | for (uint8_t i = 0; i < NUM_PIXELS; i++) { 140 | if (i % 2 == 0) { 141 | SPI.transfer((werma[NUM_PIXELS - 1 - i] & 0x0F) << 4 | werma[NUM_PIXELS - 2 - i] & 0x0F); 142 | } 143 | leds_pcb[i] = CRGB(((werma[i] & COLOR_GREEN) > 0) * pcb_brightness, ((werma[i] & COLOR_RED) > 0) * pcb_brightness, ((werma[i] & COLOR_BLUE) > 0) * pcb_brightness); 144 | } 145 | digitalWrite(LATCH, HIGH); 146 | FastLED.show(); 147 | } 148 | 149 | // Set RGBW leds in button 150 | void SetButton(byte button, byte color, byte brightness) { 151 | // Use white for glow after hit 152 | byte white = 0; 153 | if (last_hit_millis[button] + 511 > millis()) { 154 | white = ((last_hit_millis[button] + 511) - millis()) / 2; 155 | } 156 | 157 | // Set RGB to color 158 | switch (color) { 159 | case COLOR_RED: 160 | leds[player_btn_led[button]] = CRGBW(255 * brightness / 255, 0, 0, white); 161 | break; 162 | case COLOR_GREEN: 163 | leds[player_btn_led[button]] = CRGBW(0, 255 * brightness / 255, 0, white); 164 | break; 165 | case COLOR_BLUE: 166 | leds[player_btn_led[button]] = CRGBW(0, 0, 255 * brightness / 255, white); 167 | break; 168 | case COLOR_ORANGE: 169 | leds[player_btn_led[button]] = CRGBW(207 * brightness / 255, 47 * brightness / 255, 0, white); 170 | break; 171 | case COLOR_CYAN: 172 | leds[player_btn_led[button]] = CRGBW(0, 127 * brightness / 255, 127 * brightness / 255, white); 173 | break; 174 | case COLOR_MAGENTA: 175 | leds[player_btn_led[button]] = CRGBW(127 * brightness / 255, 0, 127 * brightness / 255, white); 176 | break; 177 | case COLOR_WHITE: 178 | leds[player_btn_led[button]] = CRGBW(85 * brightness / 255, 85 * brightness / 255, 85 * brightness / 255, white); 179 | break; 180 | case COLOR_BLACK: 181 | default: 182 | leds[player_btn_led[button]] = CRGBW(0, 0, 0, white); 183 | break; 184 | } 185 | } 186 | 187 | // ********************************* 188 | // Setup 189 | // ********************************* 190 | void setup() { 191 | // disable outputs 192 | digitalWrite(OE, HIGH); 193 | pinMode(OE, OUTPUT); 194 | 195 | // init Buttons 196 | pinMode(player_btn_pin[PLAYER_1], INPUT_PULLUP); 197 | pinMode(player_btn_pin[PLAYER_2], INPUT_PULLUP); 198 | 199 | // init WS2812B 200 | FastLED.addLeds(leds_rgb, getRGBWsize(NUM_LEDS)); 201 | FastLED.setBrightness(255); 202 | 203 | // init SPI 204 | pinMode(LATCH, OUTPUT); 205 | SPI.begin(CLK, D_IN, D_OUT, LATCH); 206 | // SPI.setClockDivider(SPI_CLOCK_DIV2);//divide the clock by 2 => 8 MHz 207 | SPI.setClockDivider(SPI_CLOCK_DIV128); //divide the clock by 128 => 125 kHz 208 | //SPI.setDataMode(SPI_MODE0); 209 | 210 | // FOTA 211 | WiFi.mode(WIFI_MODE_APSTA); 212 | WiFi.softAP(hostname, soft_ap_password); 213 | WiFi.setHostname(hostname); 214 | WiFi.begin(wifi_ssid, wifi_password); 215 | 216 | ArduinoOTA.setHostname(hostname); 217 | ArduinoOTA.setPassword(update_pw); 218 | ArduinoOTA 219 | .onStart([]() { 220 | String type; 221 | if (ArduinoOTA.getCommand() == U_FLASH) 222 | type = "sketch"; 223 | else // U_SPIFFS 224 | type = "filesystem"; 225 | 226 | // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() 227 | memset(werma, COLOR_BLACK, NUM_PIXELS); 228 | SetButton(PLAYER_1, COLOR_BLACK, 0); 229 | SetButton(PLAYER_2, COLOR_BLACK, 0); 230 | SetLeds(); 231 | }) 232 | .onEnd([]() { 233 | memset(werma, COLOR_GREEN, NUM_PIXELS); 234 | SetLeds(); 235 | delay(1000); 236 | }) 237 | .onProgress([](unsigned int progress, unsigned int total) { 238 | memset(werma, COLOR_BLACK, NUM_PIXELS); 239 | memset(werma, COLOR_WHITE, (progress / (total / NUM_PIXELS))); 240 | SetLeds(); 241 | }) 242 | .onError([](ota_error_t error) { 243 | if (error == OTA_AUTH_ERROR) memset(werma, COLOR_MAGENTA, NUM_PIXELS); 244 | else if (error == OTA_BEGIN_ERROR)memset(werma, COLOR_RED, NUM_PIXELS); 245 | else if (error == OTA_CONNECT_ERROR) memset(werma, COLOR_BLUE, NUM_PIXELS); 246 | else if (error == OTA_RECEIVE_ERROR) memset(werma, COLOR_CYAN, NUM_PIXELS); 247 | else if (error == OTA_END_ERROR) memset(werma, COLOR_ORANGE, NUM_PIXELS); 248 | SetLeds(); 249 | }); 250 | ArduinoOTA.begin(); 251 | 252 | // deactivate beacons 253 | memset(werma, 0, NUM_PIXELS); 254 | SetLeds(); 255 | digitalWrite(OE, LOW); // enable outputs 256 | } 257 | 258 | enum { 259 | S_START = 0, 260 | S_SET_WIN_ROUNDS, 261 | S_IDLE, 262 | S_END_TRANSITION, 263 | S_START_GAME, 264 | S_INIT_PLAYERS, 265 | S_GAME_LOOP, 266 | S_CHECK_SCORE, 267 | S_BLINK_SCORE, 268 | S_CHECK_WINNER, 269 | S_WON 270 | }; 271 | 272 | int state = S_START; 273 | uint32_t sub_state = 0; 274 | int old_state = -1; 275 | int pos = 0; 276 | unsigned long previous_millis = 0; 277 | unsigned long reset_millis = 0; 278 | unsigned long beep_millis = 0; 279 | int beep_pos = -1; 280 | int lost = 0; 281 | 282 | // ********************************* 283 | // Loop 284 | // ********************************* 285 | void loop() { 286 | ArduinoOTA.handle(); 287 | if (old_state != state) { 288 | old_state = state; 289 | sub_state = 0; 290 | previous_millis = millis(); 291 | } 292 | 293 | switch (state) { 294 | case S_START: 295 | memset(player_score, 0, sizeof(player_score)); // reset all scores 296 | lost = 0; 297 | // wait for both buttons to be released 298 | if (digitalRead(player_btn_pin[PLAYER_1]) == HIGH && digitalRead(player_btn_pin[PLAYER_2]) == HIGH) { 299 | state = S_IDLE; 300 | } else { 301 | memset(werma, COLOR_WHITE, NUM_PIXELS); 302 | SetButton(PLAYER_1, COLOR_WHITE, 255); 303 | SetButton(PLAYER_2, COLOR_WHITE, 255); 304 | } 305 | if (digitalRead(player_btn_pin[PLAYER_1]) == LOW && digitalRead(player_btn_pin[PLAYER_2]) == LOW) { 306 | if ((millis() - previous_millis) > 2000) { 307 | state = S_SET_WIN_ROUNDS; 308 | } 309 | } else { 310 | previous_millis = millis(); 311 | } 312 | break; 313 | case S_SET_WIN_ROUNDS: 314 | win_rounds = 1 + (((millis() - previous_millis) / 300) % (NUM_PIXELS / 2 - end_zone_size)); 315 | if (sub_state != win_rounds) { 316 | if (sub_state > win_rounds) { 317 | if (beep_time != 0) { 318 | beep_time = 0; 319 | } else { 320 | beep_time = 2; 321 | } 322 | } 323 | sub_state = win_rounds; 324 | memset(player_score, win_rounds, sizeof(player_score)); 325 | GeneratePlayField(); 326 | if (beep_time) { 327 | werma[NUM_PIXELS / 2 + 1 - win_rounds] |= 0b1000; 328 | werma[NUM_PIXELS / 2 + win_rounds] |= 0b1000; 329 | SetLeds(); 330 | werma[NUM_PIXELS / 2 + 1 - win_rounds] &= 0b111; 331 | werma[NUM_PIXELS / 2 + win_rounds] &= 0b111; 332 | SetLeds(); 333 | } 334 | } 335 | if (digitalRead(player_btn_pin[PLAYER_1]) == HIGH || digitalRead(player_btn_pin[PLAYER_2]) == HIGH) { 336 | delay(200); 337 | state = S_START; 338 | } 339 | break; 340 | case S_IDLE: 341 | // idle animation + Reset when all LEDs set to white 342 | if (previous_millis + 20 < millis()) { 343 | previous_millis = millis(); 344 | sub_state = sub_state % (7 * (NUM_PIXELS + 2) * 2); 345 | byte color = rainbow_color[(sub_state / (NUM_PIXELS + 2)) % 7]; 346 | switch (sub_state % ((NUM_PIXELS + 2) * 2)) { 347 | case 0: 348 | //beep_pos = 0; 349 | //beep_millis = millis(); 350 | case (NUM_PIXELS * 2 + 3): 351 | SetButton(0, color, 255); 352 | break; 353 | 354 | case 1 ... NUM_PIXELS: 355 | werma[(sub_state % ((NUM_PIXELS + 2) * 2)) - 1] = color; 356 | break; 357 | 358 | case (NUM_PIXELS + 2): 359 | //beep_pos = NUM_PIXELS - 1; 360 | //beep_millis = millis(); 361 | case (NUM_PIXELS + 1): 362 | SetButton(1, color, 255); 363 | // reset scores if idle animation runs too long 364 | if (color == COLOR_WHITE) { 365 | memset(player_score, 0, sizeof(player_score)); 366 | } 367 | break; 368 | 369 | default: 370 | werma[90 - (sub_state % ((NUM_PIXELS + 2) * 2))] = color; 371 | break; 372 | } 373 | sub_state++; 374 | } 375 | 376 | // Transition to game view on button press. If no game is running, first move is given to the player who pesses first, except the other player presses the button while the transition is running 377 | uint8_t transition_leds[2]; 378 | for (int player = PLAYER_1; player <= PLAYER_2; player++) { 379 | if (((millis() - last_hit_millis[player]) / 5) <= NUM_PIXELS + 2) { 380 | transition_leds[player] = (millis() - last_hit_millis[player]) / 5; 381 | SetButton(player, player_color[player], 255); 382 | } else { 383 | transition_leds[player] = 0; 384 | if (digitalRead(player_btn_pin[player]) == LOW) { 385 | last_hit_millis[player] = millis(); 386 | previous_button = player; 387 | if ((player_score[PLAYER_1] + player_score[PLAYER_2]) == 0) { 388 | player_start = player; 389 | } 390 | } 391 | } 392 | } 393 | 394 | if (transition_leds[PLAYER_1] || transition_leds[PLAYER_2]) { 395 | if (transition_leds[PLAYER_1] + transition_leds[PLAYER_2] > NUM_PIXELS) { 396 | state = S_END_TRANSITION; 397 | break; 398 | } 399 | uint8_t backup_werma[NUM_PIXELS]; 400 | memcpy(backup_werma, werma, NUM_PIXELS); 401 | GeneratePlayField(); 402 | memcpy(&werma[transition_leds[PLAYER_1]], &backup_werma[transition_leds[PLAYER_1]], NUM_PIXELS - transition_leds[PLAYER_1] - transition_leds[PLAYER_2]); 403 | } 404 | break; 405 | case S_END_TRANSITION: 406 | // Transition between idle animation and game start 407 | GeneratePlayField(); 408 | SetButton(PLAYER_1, player_color[PLAYER_1], 255); 409 | SetButton(PLAYER_2, player_color[PLAYER_2], 255); 410 | 411 | //wait for both buttons to be released 412 | if (digitalRead(player_btn_pin[PLAYER_1]) == HIGH && digitalRead(player_btn_pin[PLAYER_2]) == HIGH) { 413 | state = S_START_GAME; 414 | } 415 | break; 416 | case S_START_GAME: 417 | game_speed = game_speed_min; // set starting game speed 418 | ball_speed = game_speed; // set starting ball speed 419 | memset(player_button_pressed, -1, sizeof(player_button_pressed)); // clear keypress 420 | previous_button_pos = -1; 421 | beep_pos = -1; 422 | lost = 0; 423 | state = S_INIT_PLAYERS; 424 | break; 425 | case S_INIT_PLAYERS: 426 | GeneratePlayField(); 427 | if (player_start == PLAYER_1) { 428 | ball_dir = 1; // set ball direction 429 | ball_pos = 0; // set startposition of ball 430 | 431 | if (digitalRead(player_btn_pin[PLAYER_1]) == LOW) { 432 | state = S_GAME_LOOP; 433 | last_hit_millis[PLAYER_1] = millis(); 434 | } 435 | 436 | SetButton(PLAYER_1, player_color[PLAYER_1], 255); 437 | SetButton(PLAYER_2, player_color[PLAYER_2], 0); 438 | } else { 439 | ball_dir = -1; // set ball direction 440 | ball_pos = NUM_PIXELS - 1; // set startposition of ball 441 | 442 | if (digitalRead(player_btn_pin[PLAYER_2]) == LOW) { 443 | state = S_GAME_LOOP; 444 | last_hit_millis[PLAYER_2] = millis(); 445 | } 446 | 447 | SetButton(PLAYER_1, player_color[PLAYER_1], 0); 448 | SetButton(PLAYER_2, player_color[PLAYER_2], 255); 449 | } 450 | if (previous_millis + 10000 < millis()) { 451 | state = S_IDLE; 452 | } 453 | break; 454 | case S_GAME_LOOP: 455 | if ((millis() - previous_millis > ball_speed) || (sub_state == 0)) { 456 | previous_millis = millis(); 457 | 458 | GeneratePlayField(); 459 | 460 | if (sub_state == 0) { 461 | sub_state++; 462 | beep_pos = ball_pos; 463 | beep_millis = millis(); 464 | } else { 465 | ball_pos += ball_dir; 466 | if (ball_pos < 0 || ball_pos >= NUM_PIXELS) // ball left endzone? 467 | { 468 | state = S_CHECK_SCORE; 469 | break; 470 | } 471 | } 472 | werma[ball_pos] = ball_color; // generate ball 473 | } 474 | 475 | if (ball_dir == 1) { 476 | SetButton(PLAYER_1, player_color[PLAYER_1], 64); 477 | if (player_button_pressed[PLAYER_2] == -1) { 478 | SetButton(PLAYER_2, player_color[PLAYER_2], 255); 479 | } else { 480 | SetButton(PLAYER_2, player_color[PLAYER_2], 64); 481 | lost = 1; 482 | } 483 | } else { 484 | SetButton(PLAYER_2, player_color[PLAYER_2], 64); 485 | if (player_button_pressed[PLAYER_1] == -1) { 486 | SetButton(PLAYER_1, player_color[PLAYER_1], 255); 487 | } else { 488 | SetButton(PLAYER_1, player_color[PLAYER_1], 64); 489 | lost = 1; 490 | } 491 | } 492 | 493 | for (int i = PLAYER_1; i <= PLAYER_2; i++) { 494 | // player pressed button? 495 | if (player_button_pressed[i] == -1 && digitalRead(player_btn_pin[i]) == LOW && (ball_dir + 1) / 2 == i) 496 | // (ball_dir + 1) / 2 == i --> TRUE, when: 497 | // ball_dir == -1 AND i = 0 --> player 0 is active player 498 | // ball_dir == +1 AND i = 1 --> player 1 is active player 499 | // only the button-press of the active player is stored 500 | { 501 | if ((i==PLAYER_1 && ball_pos < (NUM_PIXELS/2+2)) || (i==PLAYER_2 && ball_pos >= (NUM_PIXELS/2-2))) //limit players range to half the field + 2 pixels 502 | { 503 | player_button_pressed[i] = ball_pos; //store position of pressed button 504 | previous_button_pos = ball_pos; 505 | previous_button = i; 506 | previous_button_millis = millis(); // store time when button was pressed 507 | last_hit_millis[i] = millis(); 508 | beep_pos = ball_pos; 509 | beep_millis = millis(); 510 | } 511 | } 512 | } 513 | 514 | // fix positions of keypress for testing 515 | // if (ball_pos == 3) player_button_pressed[PLAYER_1] = 3; 516 | // if (ball_pos == 59) player_button_pressed[PLAYER_2] = 59; 517 | 518 | if (ball_dir == -1 && player_button_pressed[PLAYER_1] <= end_zone_size - 1 && player_button_pressed[PLAYER_1] != -1) { 519 | ChangeDirection(); 520 | } 521 | 522 | if (ball_dir == +1 && player_button_pressed[PLAYER_2] >= NUM_PIXELS - end_zone_size) { 523 | ChangeDirection(); 524 | } 525 | break; 526 | case S_CHECK_SCORE: 527 | SetButton(PLAYER_1, player_color[PLAYER_1], 64); 528 | SetButton(PLAYER_2, player_color[PLAYER_2], 64); 529 | previous_button_pos = -1; // clear last ball-position at button-press 530 | 531 | // check who made score 532 | if (ball_pos >= NUM_PIXELS) { 533 | player_score[PLAYER_1] += 1; // new score for player 1 534 | 535 | GeneratePlayField(); // show new score full bright 536 | pos = NUM_PIXELS / 2 - player_score[PLAYER_1]; 537 | 538 | player_start = PLAYER_1; // define next player to start (player, who made the point) 539 | } else { 540 | player_score[PLAYER_2] += 1; // new score for player 2 541 | 542 | GeneratePlayField(); // show new score full bright 543 | pos = NUM_PIXELS / 2 - 1 + player_score[PLAYER_2]; 544 | 545 | player_start = PLAYER_2; // define next player to start (player, who made the point) 546 | } 547 | 548 | GeneratePlayField(); // show new score full bright 549 | 550 | state = S_BLINK_SCORE; 551 | break; 552 | case S_BLINK_SCORE: 553 | switch (sub_state) { 554 | case 0: 555 | case 2: 556 | case 4: 557 | werma[pos] = player_color[player_start]; 558 | SetButton(player_start, player_color[player_start], 255); 559 | previous_button_pos = pos; 560 | static int ch = -1; 561 | if (ch != sub_state) { 562 | ch = sub_state; 563 | beep_pos = pos; 564 | beep_millis = millis(); 565 | } 566 | break; 567 | case 1: 568 | case 3: 569 | werma[pos] = bg_color; 570 | case 5 ... 7: 571 | SetButton(PLAYER_1, player_color[PLAYER_1], 64); 572 | SetButton(PLAYER_2, player_color[PLAYER_2], 64); 573 | break; 574 | default: 575 | state = S_CHECK_WINNER; 576 | break; 577 | } 578 | 579 | if (previous_millis + 250 < millis()) { 580 | previous_millis = millis(); 581 | sub_state++; 582 | } 583 | 584 | break; 585 | case S_CHECK_WINNER: 586 | // check if we have a winner 587 | if (player_score[PLAYER_1] >= win_rounds || player_score[PLAYER_2] >= win_rounds) { // we have a winner! 588 | memset(player_score, 0, sizeof(player_score)); // reset all scores 589 | player_start = abs(player_start - 1); // next game starts looser 590 | state = S_WON; 591 | } else { 592 | state = S_START_GAME; 593 | } 594 | break; 595 | case S_WON: 596 | if (player_start == PLAYER_1) { 597 | werma[NUM_PIXELS / 2] = bg_color; 598 | } else { 599 | werma[NUM_PIXELS / 2 - 1] = bg_color; 600 | } 601 | if ((previous_millis + 100 < millis()) || (sub_state == 0)) { 602 | previous_millis = millis(); 603 | for (int i = 0; i < NUM_PIXELS / 2 - 1; i++) { 604 | if (player_start == PLAYER_2) { 605 | werma[i] = rainbow_color[(i + sub_state) / 3 % 7]; 606 | } else { 607 | werma[NUM_PIXELS - i - 1] = rainbow_color[(i + sub_state) / 3 % 7]; 608 | } 609 | } 610 | sub_state++; 611 | } 612 | if (sub_state == 100 || (digitalRead(player_btn_pin[1-player_start]) == LOW) && sub_state > 5) { 613 | state = S_START_GAME; 614 | } 615 | break; 616 | } 617 | 618 | if (digitalRead(player_btn_pin[PLAYER_1]) == LOW && digitalRead(player_btn_pin[PLAYER_2]) == LOW && (state != S_SET_WIN_ROUNDS)) { 619 | if ((reset_millis + 1000) < millis()) { 620 | unsigned long end = (millis() - (reset_millis + 1000)) / 45; 621 | if (end < NUM_PIXELS / 2) { 622 | for (int i = 0; i < end; i++) { 623 | werma[i] = COLOR_WHITE; 624 | werma[NUM_PIXELS - i] = COLOR_WHITE; 625 | } 626 | SetButton(PLAYER_1, COLOR_WHITE, 255); 627 | SetButton(PLAYER_2, COLOR_WHITE, 255); 628 | } else { 629 | state = S_START; 630 | } 631 | } 632 | } else { 633 | reset_millis = millis(); 634 | } 635 | 636 | if (beep_pos != -1 && (beep_millis + beep_time) > millis()) { 637 | werma[beep_pos] |= 0b1000; 638 | SetLeds(); 639 | werma[beep_pos] &= 0b111; 640 | } else { 641 | SetLeds(); 642 | } 643 | } 644 | 645 | void ChangeDirection() { 646 | ball_dir *= -1; 647 | game_speed -= game_speed_step; 648 | ball_speed = game_speed; 649 | if (ball_pos == 0 || ball_pos == NUM_PIXELS - 1) // triggered on first or last segment 650 | { 651 | ball_speed -= ball_boost_0; // Super-Boost 652 | } 653 | 654 | if (ball_pos == 1 || ball_pos == NUM_PIXELS - 2) // triggered on second or forelast segment 655 | { 656 | ball_speed -= ball_boost_1; // Boost 657 | } 658 | 659 | ball_speed = max(ball_speed, ball_speed_max); // limit the maximum ball_speed 660 | memset(player_button_pressed, -1, sizeof(player_button_pressed)); // clear keypress 661 | } 662 | 663 | void GeneratePlayField() { 664 | for (int i = 0; i < NUM_PIXELS; i++) { 665 | werma[i] = bg_color; 666 | } 667 | GenerateEndZone(); // generate endzone 668 | GenerateScore(); // generate actual score 669 | GenerateLastHit(); // generate mark of position of last button-press 670 | } 671 | 672 | 673 | void GenerateEndZone() { 674 | for (int i = 0; i < end_zone_size; i++) { 675 | werma[i] = goal_color; 676 | werma[NUM_PIXELS - 1 - i] = goal_color; 677 | } 678 | } 679 | 680 | void GenerateScore() { 681 | int i; 682 | 683 | // Player 0 684 | for (i = 0; i < player_score[PLAYER_1]; i++) { 685 | werma[NUM_PIXELS / 2 - 1 - i] = player_color[PLAYER_1]; 686 | } 687 | 688 | // Player 1 689 | for (i = 0; i < player_score[PLAYER_2]; i++) { 690 | werma[NUM_PIXELS / 2 + i] = player_color[PLAYER_2]; 691 | } 692 | } 693 | 694 | void GenerateLastHit() { 695 | if (previous_button_pos != -1 && ((previous_button_millis + 500 > millis()) || lost)) { 696 | werma[previous_button_pos] = player_color[previous_button]; 697 | } 698 | } 699 | --------------------------------------------------------------------------------