From 249b50f1c20c870093ba5f6f88cfd0dce3cf8b3c Mon Sep 17 00:00:00 2001 From: gustebeast Date: Fri, 23 Jan 2026 20:24:57 +0000 Subject: [PATCH 1/3] Add flashing effect on line clear in TetrisAI_v2. See https://imgur.com/1dsNCVd for a demo. --- usermods/TetrisAI_v2/TetrisAI_v2.cpp | 33 +++++++++++++++++--- usermods/TetrisAI_v2/gridbw.h | 46 ++++++++++++++++++++++++++-- usermods/TetrisAI_v2/gridcolor.h | 2 +- usermods/TetrisAI_v2/tetrisai.h | 2 +- 4 files changed, 73 insertions(+), 10 deletions(-) diff --git a/usermods/TetrisAI_v2/TetrisAI_v2.cpp b/usermods/TetrisAI_v2/TetrisAI_v2.cpp index b51250b262..edcbd0d3d1 100644 --- a/usermods/TetrisAI_v2/TetrisAI_v2.cpp +++ b/usermods/TetrisAI_v2/TetrisAI_v2.cpp @@ -8,6 +8,7 @@ typedef struct TetrisAI_data { unsigned long lastTime = 0; + unsigned long clearingStartTime = 0; TetrisAIGame tetris; uint8_t intelligence; uint8_t rotate; @@ -31,16 +32,23 @@ void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data) //GRID for (auto index_y = 4; index_y < tetris->grid.height; index_y++) { + bool isRowClearing = tetris->grid.gridBW.clearingRows[index_y]; for (auto index_x = 0; index_x < tetris->grid.width; index_x++) { CRGB color; - if (*tetris->grid.getPixel(index_x, index_y) == 0) - { + uint8_t gridPixel = *tetris->grid.getPixel(index_x, index_y); + if (isRowClearing) { + //flash color white and black every 200ms + color = (strip.now % 200) < 150 + ? CRGB::Gray + : CRGB::Black; + } + else if (gridPixel == 0) { //BG color color = SEGCOLOR(1); } //game over animation - else if(*tetris->grid.getPixel(index_x, index_y) == 254) + else if (gridPixel == 254) { //use fg color = SEGCOLOR(0); @@ -48,7 +56,7 @@ void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data) else { //spread the color over the whole palette - uint8_t colorIndex = *tetris->grid.getPixel(index_x, index_y) * 32; + uint8_t colorIndex = gridPixel * 32; colorIndex += tetrisai_data->colorOffset; color = ColorFromPalette(SEGPALETTE, colorIndex, 255, NOBLEND); } @@ -170,6 +178,7 @@ uint16_t mode_2DTetrisAI() tetrisai_data->tetris = TetrisAIGame(gridWidth, gridHeight, nLookAhead, piecesData, numPieces); tetrisai_data->tetris.state = TetrisAIGame::States::INIT; + tetrisai_data->clearingStartTime = 0; SEGMENT.fill(SEGCOLOR(1)); } @@ -184,7 +193,21 @@ uint16_t mode_2DTetrisAI() tetrisai_data->tetris.ai.bumpiness = -0.184483f + dui; } - if (tetrisai_data->tetris.state == TetrisAIGame::ANIMATE_MOVE) + //end line clearing flashing effect if needed + if (tetrisai_data->tetris.grid.gridBW.hasClearingRows()) + { + if (tetrisai_data->clearingStartTime == 0) { + tetrisai_data->clearingStartTime = strip.now; + } + if (strip.now - tetrisai_data->clearingStartTime > 750) + { + tetrisai_data->tetris.grid.gridBW.clearedLinesReadyForRemoval = true; + tetrisai_data->tetris.grid.cleanupFullLines(); + tetrisai_data->clearingStartTime = 0; + } + drawGrid(&tetrisai_data->tetris, tetrisai_data); + } + else if (tetrisai_data->tetris.state == TetrisAIGame::ANIMATE_MOVE) { if (strip.now - tetrisai_data->lastTime > msDelayMove) diff --git a/usermods/TetrisAI_v2/gridbw.h b/usermods/TetrisAI_v2/gridbw.h index a96351749a..99fc9c3782 100644 --- a/usermods/TetrisAI_v2/gridbw.h +++ b/usermods/TetrisAI_v2/gridbw.h @@ -25,11 +25,18 @@ class GridBW uint8_t width; uint8_t height; std::vector pixels; + // When a row fills, we mark it here first so it can flash before being + // fully removed. + std::vector clearingRows; + // True when a line clearing flashing effect is over and we're ready to + // fully clean up the lines + bool clearedLinesReadyForRemoval = false; GridBW(uint8_t width, uint8_t height): width(width), height(height), - pixels(height) + pixels(height), + clearingRows(height) { if (width > 32) { @@ -84,9 +91,26 @@ class GridBW piece->landingY = piece->landingY > 0 ? piece->landingY - 1 : 0; } + bool hasClearingRows() + { + for (bool rowClearing : clearingRows) + { + if (rowClearing) + { + return true; + } + } + return false; + } + void cleanupFullLines() { + // Skip cleanup if there are rows clearing + if (hasClearingRows() && !clearedLinesReadyForRemoval) { + return; + } uint8_t offset = 0; + bool doneRemovingClearedLines = false; //from "height - 1" to "0", so from bottom row to top for (uint8_t row = height; row-- > 0; ) @@ -94,8 +118,13 @@ class GridBW //full line? if (isLineFull(row)) { - offset++; - pixels[row] = 0x0; + if (clearedLinesReadyForRemoval) { + offset++; + pixels[row] = 0x0; + doneRemovingClearedLines = true; + } else { + clearingRows[row] = true; + } continue; } @@ -105,6 +134,10 @@ class GridBW pixels[row] = 0x0; } } + if (doneRemovingClearedLines) { + clearingRows.assign(height, false); + clearedLinesReadyForRemoval = false; + } } bool isLineFull(uint8_t y) @@ -112,6 +145,11 @@ class GridBW return pixels[y] == (uint32_t)((1 << width) - 1); } + bool isLineReadyForRemoval(uint8_t y) + { + return clearedLinesReadyForRemoval && isLineFull(y); + } + void reset() { if (width > 32) @@ -121,6 +159,8 @@ class GridBW pixels.clear(); pixels.resize(height); + clearingRows.assign(height, false); + clearedLinesReadyForRemoval = false; } }; diff --git a/usermods/TetrisAI_v2/gridcolor.h b/usermods/TetrisAI_v2/gridcolor.h index 815c2a5603..5f69027968 100644 --- a/usermods/TetrisAI_v2/gridcolor.h +++ b/usermods/TetrisAI_v2/gridcolor.h @@ -82,7 +82,7 @@ class GridColor //from "height - 1" to "0", so from bottom row to top for (uint8_t y = height; y-- > 0; ) { - if (gridBW.isLineFull(y)) + if (gridBW.isLineReadyForRemoval(y)) { offset++; for (uint8_t x = 0; x < width; x++) diff --git a/usermods/TetrisAI_v2/tetrisai.h b/usermods/TetrisAI_v2/tetrisai.h index ba4fe60e43..7bb1d9321a 100644 --- a/usermods/TetrisAI_v2/tetrisai.h +++ b/usermods/TetrisAI_v2/tetrisai.h @@ -68,7 +68,7 @@ class TetrisAI } //line full if all ones in mask :-) - if (grid.isLineFull(row)) + if (grid.isLineReadyForRemoval(row)) { rating->fullLines++; } From fa45a65d7428d01e147f3a3f54f7bc8d1cf49303 Mon Sep 17 00:00:00 2001 From: gustebeast Date: Wed, 28 Jan 2026 23:00:19 +0000 Subject: [PATCH 2/3] Address feedback for tetris flashing effect. --- usermods/TetrisAI_v2/TetrisAI_v2.cpp | 30 ++++++++++++++++++++++++---- usermods/TetrisAI_v2/readme.md | 9 ++++++--- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/usermods/TetrisAI_v2/TetrisAI_v2.cpp b/usermods/TetrisAI_v2/TetrisAI_v2.cpp index edcbd0d3d1..5f2f944b68 100644 --- a/usermods/TetrisAI_v2/TetrisAI_v2.cpp +++ b/usermods/TetrisAI_v2/TetrisAI_v2.cpp @@ -5,6 +5,8 @@ #include "tetrisaigame.h" // By: muebau +bool noFlashOnClear = false; + typedef struct TetrisAI_data { unsigned long lastTime = 0; @@ -38,10 +40,14 @@ void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data) CRGB color; uint8_t gridPixel = *tetris->grid.getPixel(index_x, index_y); if (isRowClearing) { - //flash color white and black every 200ms - color = (strip.now % 200) < 150 - ? CRGB::Gray - : CRGB::Black; + if (noFlashOnClear) { + color = CRGB::Gray; + } else { + //flash color white and black every 200ms + color = (strip.now % 200) < 150 + ? CRGB::Gray + : CRGB::Black; + } } else if (gridPixel == 0) { //BG color @@ -254,6 +260,7 @@ class TetrisAIUsermod : public Usermod { private: + static const char _name[]; public: void setup() @@ -261,6 +268,20 @@ class TetrisAIUsermod : public Usermod strip.addEffect(255, &mode_2DTetrisAI, _data_FX_MODE_2DTETRISAI); } + void addToConfig(JsonObject& root) override + { + JsonObject top = root.createNestedObject(FPSTR(_name)); + top["noFlashOnClear"] = noFlashOnClear; + } + + bool readFromConfig(JsonObject& root) override + { + JsonObject top = root[FPSTR(_name)]; + bool configComplete = !top.isNull(); + configComplete &= getJsonValue(top["noFlashOnClear"], noFlashOnClear); + return configComplete; + } + void loop() { @@ -272,6 +293,7 @@ class TetrisAIUsermod : public Usermod } }; +const char TetrisAIUsermod::_name[] PROGMEM = "TetrisAI_v2"; static TetrisAIUsermod tetrisai_v2; REGISTER_USERMOD(tetrisai_v2); \ No newline at end of file diff --git a/usermods/TetrisAI_v2/readme.md b/usermods/TetrisAI_v2/readme.md index 5ac8028967..7962a9010d 100644 --- a/usermods/TetrisAI_v2/readme.md +++ b/usermods/TetrisAI_v2/readme.md @@ -2,13 +2,16 @@ This usermod adds a self-playing Tetris game as an 'effect'. The mod requires version 0.14 or higher as it relies on matrix support. The effect was tested on an ESP32 4MB with a WS2812B 16x16 matrix. -Version 1.0 +PHOTOSENSITIVE EPILEPSY WARNING: By default the effect features a flashing animation on line clear. This can be disabled +from the usermod settings page in WLED. ## Installation -Just activate the usermod with `-D USERMOD_TETRISAI` and the effect will become available under the name 'Tetris AI'. If you are running out of flash memory, use a different memory layout (e.g. [WLED_ESP32_4MB_256KB_FS.csv](https://github.com/wled-dev/WLED/blob/main/tools/WLED_ESP32_4MB_256KB_FS.csv)). +To activate the usermod, add the following line to your platformio_override.ini +`custom_usermods = tetrisai_v2` +The effect will then become available under the name 'Tetris AI'. If you are running out of flash memory, use a different memory layout (e.g. [WLED_ESP32_4MB_256KB_FS.csv](https://github.com/wled-dev/WLED/blob/main/tools/WLED_ESP32_4MB_256KB_FS.csv)). -If needed simply add to `platformio_override.ini` (or `platformio_override.ini`): +If needed simply add to `platformio_override.ini`: ```ini board_build.partitions = tools/WLED_ESP32_4MB_256KB_FS.csv From 4f1429647143c08ab88aa594aa9d360fa7f99e9d Mon Sep 17 00:00:00 2001 From: gustebeast Date: Wed, 4 Feb 2026 18:36:18 +0000 Subject: [PATCH 3/3] Address width == 32 case for isLineFull --- usermods/TetrisAI_v2/gridbw.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/TetrisAI_v2/gridbw.h b/usermods/TetrisAI_v2/gridbw.h index 99fc9c3782..5db6e25a9b 100644 --- a/usermods/TetrisAI_v2/gridbw.h +++ b/usermods/TetrisAI_v2/gridbw.h @@ -142,7 +142,7 @@ class GridBW bool isLineFull(uint8_t y) { - return pixels[y] == (uint32_t)((1 << width) - 1); + return pixels[y] == (width >= 32 ? UINT32_MAX : (1U << width) - 1); } bool isLineReadyForRemoval(uint8_t y)