Add flashing effect on line clear in TetrisAI_v2. (#5320)

* Add flashing effect on line clear in TetrisAI_v2.
See https://imgur.com/1dsNCVd for a demo.

* Address feedback for tetris flashing effect.

* Address width == 32 case for isLineFull
This commit is contained in:
gustebeast
2026-03-02 10:11:26 -08:00
committed by GitHub
parent 5790309371
commit bc229b8cb6
5 changed files with 102 additions and 14 deletions

View File

@@ -5,9 +5,12 @@
#include "tetrisaigame.h"
// By: muebau
bool noFlashOnClear = false;
typedef struct TetrisAI_data
{
unsigned long lastTime = 0;
unsigned long clearingStartTime = 0;
TetrisAIGame tetris;
uint8_t intelligence;
uint8_t rotate;
@@ -31,16 +34,27 @@ 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) {
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
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 +62,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 +184,7 @@ void 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 +199,21 @@ void 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)
@@ -229,6 +258,7 @@ class TetrisAIUsermod : public Usermod
{
private:
static const char _name[];
public:
void setup()
@@ -236,6 +266,20 @@ public:
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()
{
@@ -247,6 +291,7 @@ public:
}
};
const char TetrisAIUsermod::_name[] PROGMEM = "TetrisAI_v2";
static TetrisAIUsermod tetrisai_v2;
REGISTER_USERMOD(tetrisai_v2);

View File

@@ -25,11 +25,18 @@ public:
uint8_t width;
uint8_t height;
std::vector<uint32_t> pixels;
// When a row fills, we mark it here first so it can flash before being
// fully removed.
std::vector<bool> 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 @@ public:
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 @@ public:
//full line?
if (isLineFull(row))
{
offset++;
pixels[row] = 0x0;
if (clearedLinesReadyForRemoval) {
offset++;
pixels[row] = 0x0;
doneRemovingClearedLines = true;
} else {
clearingRows[row] = true;
}
continue;
}
@@ -105,11 +134,20 @@ public:
pixels[row] = 0x0;
}
}
if (doneRemovingClearedLines) {
clearingRows.assign(height, false);
clearedLinesReadyForRemoval = false;
}
}
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)
{
return clearedLinesReadyForRemoval && isLineFull(y);
}
void reset()
@@ -121,6 +159,8 @@ public:
pixels.clear();
pixels.resize(height);
clearingRows.assign(height, false);
clearedLinesReadyForRemoval = false;
}
};

View File

@@ -82,7 +82,7 @@ public:
//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++)

View File

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

View File

@@ -68,7 +68,7 @@ public:
}
//line full if all ones in mask :-)
if (grid.isLineFull(row))
if (grid.isLineReadyForRemoval(row))
{
rating->fullLines++;
}