Merge pull request #3897 from muebau/fix_usermod_tetisai

reset usermod TetrisAI back to initial version
This commit is contained in:
Blaž Kristan 2024-05-27 20:22:26 +02:00 committed by GitHub
commit 3a70fb0e15
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 117 additions and 139 deletions

View File

@ -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__ */

View File

@ -127,6 +127,14 @@ public:
}
}
}
void reset()
{
gridBW.reset();
pixels.clear();
pixels.resize(width* height);
clear();
}
};
#endif /* __GRIDCOLOR_H__ */

View File

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

View File

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

View File

@ -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__ */

View File

@ -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();
}
};

View File

@ -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__ */

View File

@ -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();
}
}