mirror of
https://github.com/wled/WLED.git
synced 2025-04-24 06:47:18 +00:00
Merge pull request #3897 from muebau/fix_usermod_tetisai
reset usermod TetrisAI back to initial version
This commit is contained in:
commit
3a70fb0e15
@ -34,7 +34,7 @@ public:
|
||||
{
|
||||
if (width > 32)
|
||||
{
|
||||
throw std::invalid_argument("maximal width is 32");
|
||||
this->width = 32;
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,6 +112,17 @@ public:
|
||||
{
|
||||
return pixels[y] == (uint32_t)((1 << width) - 1);
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
if (width > 32)
|
||||
{
|
||||
width = 32;
|
||||
}
|
||||
|
||||
pixels.clear();
|
||||
pixels.resize(height);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* __GRIDBW_H__ */
|
@ -127,6 +127,14 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
gridBW.reset();
|
||||
pixels.clear();
|
||||
pixels.resize(width* height);
|
||||
clear();
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* __GRIDCOLOR_H__ */
|
@ -32,7 +32,7 @@ public:
|
||||
uint8_t fullLines;
|
||||
uint16_t bumpiness;
|
||||
uint16_t aggregatedHeight;
|
||||
double score;
|
||||
float score;
|
||||
uint8_t width;
|
||||
std::vector<uint8_t> lineHights;
|
||||
|
||||
@ -57,7 +57,7 @@ public:
|
||||
this->fullLines = 0;
|
||||
this->bumpiness = 0;
|
||||
this->aggregatedHeight = 0;
|
||||
this->score = -DBL_MAX;
|
||||
this->score = -FLT_MAX;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,16 +1,22 @@
|
||||
# Tetris AI effect usermod
|
||||
|
||||
This usermod brings you a effect brings a self playing Tetris game. The mod needs version 0.14 or above as it is based on matrix support. The effect was tested on an ESP32 with a WS2812B 16x16 matrix.
|
||||
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
|
||||
|
||||
## Installation
|
||||
|
||||
Just activate the usermod with `-D USERMOD_TETRISAI` and the effect will become available under the name 'Tetris AI'.
|
||||
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/Aircoookie/WLED/blob/main/tools/WLED_ESP32_4MB_256KB_FS.csv)).
|
||||
|
||||
If needed simply add to `platformio_override.ini` (or `platformio_override.ini`):
|
||||
|
||||
```ini
|
||||
board_build.partitions = tools/WLED_ESP32_4MB_256KB_FS.csv
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
It is best to set the background color to black, the border color to light grey and the game over color (foreground) to dark grey.
|
||||
It is best to set the background color to black 🖤, the border color to light grey 🤍, the game over color (foreground) to dark grey 🩶, and color palette to 'Rainbow' 🌈.
|
||||
|
||||
### Sliders and boxes
|
||||
|
||||
@ -19,15 +25,18 @@ It is best to set the background color to black, the border color to light grey
|
||||
* speed: speed the game plays
|
||||
* look ahead: how many pieces is the AI allowed to know the next pieces (0 - 2)
|
||||
* intelligence: how good the AI will play
|
||||
* Rotate color: make the colors shift (rotate) every few cicles
|
||||
* Mistakes free: how many good moves between mistakes (if activated)
|
||||
* Rotate color: make the colors shift (rotate) every few moves
|
||||
* Mistakes free: how many good moves between mistakes (if enabled)
|
||||
|
||||
#### Checkboxes
|
||||
|
||||
* show next: if true a space of 5 pixels from the right is used to show the next pieces. The whole segment is used for the grid otherwise.
|
||||
* show next: if true, a space of 5 pixels from the right will be used to show the next pieces. Otherwise the whole segment is used for the grid.
|
||||
* show border: if true an additional column of 1 pixel is used to draw a border between the grid and the next pieces
|
||||
* mistakes: if true the worst instead of the best move is choosen every few moves (read above)
|
||||
* mistakes: if true, the worst decision will be made every few moves instead of the best (see above).
|
||||
|
||||
## Best results
|
||||
|
||||
If the speed is set to be a little bit faster than a good human could play with maximal intelligence and very few mistakes it makes people furious/happy at a party.
|
||||
If the speed is set to be a little bit faster than a good human could play with maximal intelligence and very few mistakes it makes people furious/happy at a party 😉.
|
||||
|
||||
## Limits
|
||||
The game grid is limited to a maximum width of 32 and a maximum height of 255 due to the internal structure of the code. The canvas of the effect will be centred in the segment if the segment exceeds the maximum width or height.
|
@ -22,10 +22,10 @@ class TetrisAI
|
||||
{
|
||||
private:
|
||||
public:
|
||||
double aHeight;
|
||||
double fullLines;
|
||||
double holes;
|
||||
double bumpiness;
|
||||
float aHeight;
|
||||
float fullLines;
|
||||
float holes;
|
||||
float bumpiness;
|
||||
bool findWorstMove = false;
|
||||
|
||||
uint8_t countOnes(uint32_t vector)
|
||||
@ -107,10 +107,10 @@ public:
|
||||
rating->score = (aHeight * (rating->aggregatedHeight)) + (fullLines * (rating->fullLines)) + (holes * (rating->holes)) + (bumpiness * (rating->bumpiness));
|
||||
}
|
||||
|
||||
TetrisAI(): TetrisAI(-0.510066, 0.760666, -0.35663, -0.184483)
|
||||
TetrisAI(): TetrisAI(-0.510066f, 0.760666f, -0.35663f, -0.184483f)
|
||||
{}
|
||||
|
||||
TetrisAI(double aHeight, double fullLines, double holes, double bumpiness):
|
||||
TetrisAI(float aHeight, float fullLines, float holes, float bumpiness):
|
||||
aHeight(aHeight),
|
||||
fullLines(fullLines),
|
||||
holes(holes),
|
||||
@ -178,9 +178,9 @@ public:
|
||||
if(findWorstMove)
|
||||
{
|
||||
//init rating for worst
|
||||
if(bestRating->score == -DBL_MAX)
|
||||
if(bestRating->score == -FLT_MAX)
|
||||
{
|
||||
bestRating->score = DBL_MAX;
|
||||
bestRating->score = FLT_MAX;
|
||||
}
|
||||
|
||||
// update if we found a worse one
|
||||
@ -202,101 +202,6 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool findBestMoveNonBlocking(GridBW grid, std::vector<Piece>::iterator start, std::vector<Piece>::iterator end, Rating* bestRating)
|
||||
{
|
||||
//vector with pieces
|
||||
//for every piece
|
||||
//for every
|
||||
switch (expression)
|
||||
{
|
||||
case INIT:
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool findBestMoveNonBlocking(GridBW grid, std::vector<Piece>::iterator start, std::vector<Piece>::iterator end, Rating* bestRating)
|
||||
{
|
||||
//INIT
|
||||
grid.cleanupFullLines();
|
||||
Rating curRating(grid.width);
|
||||
Rating deeperRating(grid.width);
|
||||
Piece piece = *start;
|
||||
|
||||
// for every rotation of the piece
|
||||
piece.rotation = 0;
|
||||
|
||||
//HANDLE
|
||||
while (piece.rotation < piece.pieceData->rotCount)
|
||||
{
|
||||
// put piece to top left corner
|
||||
piece.x = 0;
|
||||
piece.y = 0;
|
||||
|
||||
//test for every column
|
||||
piece.x = 0;
|
||||
while (piece.x <= grid.width - piece.getRotation().width)
|
||||
{
|
||||
|
||||
//todo optimise by the use of the previous grids height
|
||||
piece.landingY = 0;
|
||||
//will set landingY to final position
|
||||
grid.findLandingPosition(&piece);
|
||||
|
||||
// draw piece
|
||||
grid.placePiece(&piece, piece.x, piece.landingY);
|
||||
|
||||
if(start == end - 1)
|
||||
{
|
||||
//at the deepest level
|
||||
updateRating(grid, &curRating);
|
||||
}
|
||||
else
|
||||
{
|
||||
//go deeper to take another piece into account
|
||||
findBestMove(grid, start + 1, end, &deeperRating);
|
||||
curRating = deeperRating;
|
||||
}
|
||||
|
||||
// eraese piece
|
||||
grid.erasePiece(&piece, piece.x, piece.landingY);
|
||||
|
||||
if(findWorstMove)
|
||||
{
|
||||
//init rating for worst
|
||||
if(bestRating->score == -DBL_MAX)
|
||||
{
|
||||
bestRating->score = DBL_MAX;
|
||||
}
|
||||
|
||||
// update if we found a worse one
|
||||
if (bestRating->score > curRating.score)
|
||||
{
|
||||
*bestRating = curRating;
|
||||
(*start) = piece;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// update if we found a better one
|
||||
if (bestRating->score < curRating.score)
|
||||
{
|
||||
*bestRating = curRating;
|
||||
(*start) = piece;
|
||||
}
|
||||
}
|
||||
piece.x++;
|
||||
}
|
||||
piece.rotation++;
|
||||
}
|
||||
|
||||
//EXIT
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* __AI_H__ */
|
@ -54,6 +54,7 @@ public:
|
||||
uint8_t width;
|
||||
uint8_t height;
|
||||
uint8_t nLookAhead;
|
||||
uint8_t nPieces;
|
||||
TetrisBag bag;
|
||||
GridColor grid;
|
||||
TetrisAI ai;
|
||||
@ -65,6 +66,7 @@ public:
|
||||
width(width),
|
||||
height(height),
|
||||
nLookAhead(nLookAhead),
|
||||
nPieces(nPieces),
|
||||
bag(nPieces, 1, nLookAhead),
|
||||
grid(width, height + 4),
|
||||
ai(),
|
||||
@ -142,8 +144,10 @@ public:
|
||||
|
||||
void reset()
|
||||
{
|
||||
grid.clear();
|
||||
bag.init();
|
||||
grid.width = width;
|
||||
grid.height = height + 4;
|
||||
grid.reset();
|
||||
bag.reset();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -25,6 +25,7 @@ private:
|
||||
public:
|
||||
uint8_t nPieces;
|
||||
uint8_t nBagLength;
|
||||
uint8_t queueLength;
|
||||
uint8_t bagIdx;
|
||||
std::vector<uint8_t> bag;
|
||||
std::vector<Piece> piecesQueue;
|
||||
@ -32,6 +33,7 @@ public:
|
||||
TetrisBag(uint8_t nPieces, uint8_t nBagLength, uint8_t queueLength):
|
||||
nPieces(nPieces),
|
||||
nBagLength(nBagLength),
|
||||
queueLength(queueLength),
|
||||
bag(nPieces * nBagLength),
|
||||
piecesQueue(queueLength)
|
||||
{
|
||||
@ -95,6 +97,15 @@ public:
|
||||
std::rotate(piecesQueue.begin(), piecesQueue.begin() + 1, piecesQueue.end());
|
||||
piecesQueue[piecesQueue.size() - 1] = Piece(idx % nPieces);
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
bag.clear();
|
||||
bag.resize(nPieces * nBagLength);
|
||||
piecesQueue.clear();
|
||||
piecesQueue.resize(queueLength);
|
||||
init();
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* __TETRISBAG_H__ */
|
||||
|
@ -18,6 +18,12 @@ typedef struct TetrisAI_data
|
||||
uint8_t colorOffset;
|
||||
uint8_t colorInc;
|
||||
uint8_t mistaceCountdown;
|
||||
uint16_t segcols;
|
||||
uint16_t segrows;
|
||||
uint16_t segOffsetX;
|
||||
uint16_t segOffsetY;
|
||||
uint16_t effectWidth;
|
||||
uint16_t effectHeight;
|
||||
} tetrisai_data;
|
||||
|
||||
void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data)
|
||||
@ -49,7 +55,7 @@ void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data)
|
||||
color = ColorFromPalette(SEGPALETTE, colorIndex, 255, NOBLEND);
|
||||
}
|
||||
|
||||
SEGMENT.setPixelColorXY(index_x, index_y - 4, color);
|
||||
SEGMENT.setPixelColorXY(tetrisai_data->segOffsetX + index_x, tetrisai_data->segOffsetY + index_y - 4, color);
|
||||
}
|
||||
}
|
||||
tetrisai_data->colorOffset += tetrisai_data->colorInc;
|
||||
@ -61,14 +67,14 @@ void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data)
|
||||
if (tetrisai_data->showBorder)
|
||||
{
|
||||
//draw a line 6 pixels from right with the border color
|
||||
for (auto index_y = 0; index_y < SEGMENT.virtualHeight(); index_y++)
|
||||
for (auto index_y = 0; index_y < tetrisai_data->effectHeight; index_y++)
|
||||
{
|
||||
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() - 6, index_y, SEGCOLOR(2));
|
||||
SEGMENT.setPixelColorXY(tetrisai_data->segOffsetX + tetrisai_data->effectWidth - 6, tetrisai_data->segOffsetY + index_y, SEGCOLOR(2));
|
||||
}
|
||||
}
|
||||
|
||||
//NEXT PIECE
|
||||
int piecesOffsetX = SEGMENT.virtualWidth() - 4;
|
||||
int piecesOffsetX = tetrisai_data->effectWidth - 4;
|
||||
int piecesOffsetY = 1;
|
||||
for (uint8_t nextPieceIdx = 1; nextPieceIdx < tetris->nLookAhead; nextPieceIdx++)
|
||||
{
|
||||
@ -83,7 +89,7 @@ void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data)
|
||||
if (piece.getPixel(pieceX, pieceY))
|
||||
{
|
||||
uint8_t colIdx = ((piece.pieceData->colorIndex * 32) + tetrisai_data->colorOffset);
|
||||
SEGMENT.setPixelColorXY(piecesOffsetX + pieceX, piecesOffsetY + pieceNbrOffsetY + pieceY, ColorFromPalette(SEGPALETTE, colIdx, 255, NOBLEND));
|
||||
SEGMENT.setPixelColorXY(tetrisai_data->segOffsetX + piecesOffsetX + pieceX, tetrisai_data->segOffsetY + piecesOffsetY + pieceNbrOffsetY + pieceY, ColorFromPalette(SEGPALETTE, colIdx, 255, NOBLEND));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -116,62 +122,86 @@ uint16_t mode_2DTetrisAI()
|
||||
//range 0 - 16
|
||||
tetrisai_data->colorInc = SEGMENT.custom2 >> 4;
|
||||
|
||||
if (!tetrisai_data->tetris || (tetrisai_data->tetris.nLookAhead != nLookAhead
|
||||
if (tetrisai_data->tetris.nLookAhead != nLookAhead
|
||||
|| tetrisai_data->segcols != cols
|
||||
|| tetrisai_data->segrows != rows
|
||||
|| tetrisai_data->showNext != SEGMENT.check1
|
||||
|| tetrisai_data->showBorder != SEGMENT.check2
|
||||
)
|
||||
)
|
||||
)
|
||||
{
|
||||
tetrisai_data->segcols = cols;
|
||||
tetrisai_data->segrows = rows;
|
||||
tetrisai_data->showNext = SEGMENT.check1;
|
||||
tetrisai_data->showBorder = SEGMENT.check2;
|
||||
|
||||
//not more than 32 as this is the limit of this implementation
|
||||
uint8_t gridWidth = cols < 32 ? cols : 32;
|
||||
uint8_t gridHeight = rows;
|
||||
//not more than 32 columns and 255 rows as this is the limit of this implementation
|
||||
uint8_t gridWidth = cols > 32 ? 32 : cols;
|
||||
uint8_t gridHeight = rows > 255 ? 255 : rows;
|
||||
|
||||
tetrisai_data->effectWidth = 0;
|
||||
tetrisai_data->effectHeight = 0;
|
||||
|
||||
// do we need space for the 'next' section?
|
||||
if (tetrisai_data->showNext)
|
||||
{
|
||||
// make space for the piece and one pixel of space
|
||||
gridWidth = gridWidth - 5;
|
||||
//does it get to tight?
|
||||
if (gridWidth + 5 > cols)
|
||||
{
|
||||
// yes, so make the grid smaller
|
||||
// make space for the piece and one pixel of space
|
||||
gridWidth = (gridWidth - ((gridWidth + 5) - cols));
|
||||
}
|
||||
tetrisai_data->effectWidth += 5;
|
||||
|
||||
// do we need space for a border?
|
||||
if (tetrisai_data->showBorder)
|
||||
{
|
||||
gridWidth = gridWidth - 1;
|
||||
if (gridWidth + 5 + 1 > cols)
|
||||
{
|
||||
gridWidth -= 1;
|
||||
}
|
||||
tetrisai_data->effectWidth += 1;
|
||||
}
|
||||
}
|
||||
|
||||
tetrisai_data->effectWidth += gridWidth;
|
||||
tetrisai_data->effectHeight += gridHeight;
|
||||
|
||||
tetrisai_data->segOffsetX = cols > tetrisai_data->effectWidth ? ((cols - tetrisai_data->effectWidth) / 2) : 0;
|
||||
tetrisai_data->segOffsetY = rows > tetrisai_data->effectHeight ? ((rows - tetrisai_data->effectHeight) / 2) : 0;
|
||||
|
||||
tetrisai_data->tetris = TetrisAIGame(gridWidth, gridHeight, nLookAhead, piecesData, numPieces);
|
||||
tetrisai_data->tetris.state = TetrisAIGame::States::INIT;
|
||||
SEGMENT.fill(SEGCOLOR(1));
|
||||
}
|
||||
|
||||
if (tetrisai_data->intelligence != SEGMENT.custom1)
|
||||
{
|
||||
tetrisai_data->intelligence = SEGMENT.custom1;
|
||||
double dui = 0.2 - (0.2 * (tetrisai_data->intelligence / 255.0));
|
||||
float dui = 0.2f - (0.2f * (tetrisai_data->intelligence / 255.0f));
|
||||
|
||||
tetrisai_data->tetris.ai.aHeight = -0.510066 + dui;
|
||||
tetrisai_data->tetris.ai.fullLines = 0.760666 - dui;
|
||||
tetrisai_data->tetris.ai.holes = -0.35663 + dui;
|
||||
tetrisai_data->tetris.ai.bumpiness = -0.184483 + dui;
|
||||
tetrisai_data->tetris.ai.aHeight = -0.510066f + dui;
|
||||
tetrisai_data->tetris.ai.fullLines = 0.760666f - dui;
|
||||
tetrisai_data->tetris.ai.holes = -0.35663f + dui;
|
||||
tetrisai_data->tetris.ai.bumpiness = -0.184483f + dui;
|
||||
}
|
||||
|
||||
if (tetrisai_data->tetris.state == TetrisAIGame::ANIMATE_MOVE)
|
||||
{
|
||||
if (millis() - tetrisai_data->lastTime > msDelayMove)
|
||||
|
||||
if (strip.now - tetrisai_data->lastTime > msDelayMove)
|
||||
{
|
||||
drawGrid(&tetrisai_data->tetris, tetrisai_data);
|
||||
tetrisai_data->lastTime = millis();
|
||||
tetrisai_data->lastTime = strip.now;
|
||||
tetrisai_data->tetris.poll();
|
||||
}
|
||||
}
|
||||
else if (tetrisai_data->tetris.state == TetrisAIGame::ANIMATE_GAME_OVER)
|
||||
{
|
||||
if (millis() - tetrisai_data->lastTime > msDelayGameOver)
|
||||
if (strip.now - tetrisai_data->lastTime > msDelayGameOver)
|
||||
{
|
||||
drawGrid(&tetrisai_data->tetris, tetrisai_data);
|
||||
tetrisai_data->lastTime = millis();
|
||||
tetrisai_data->lastTime = strip.now;
|
||||
tetrisai_data->tetris.poll();
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user