mirror of
https://github.com/wled/WLED.git
synced 2025-07-22 18:26:32 +00:00
commit
c77f6c5f7b
117
usermods/TetrisAI_v2/gridbw.h
Normal file
117
usermods/TetrisAI_v2/gridbw.h
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* @file : gridbw.h
|
||||||
|
* @brief : contains the tetris grid as binary so black and white version
|
||||||
|
******************************************************************************
|
||||||
|
* @attention
|
||||||
|
*
|
||||||
|
* Copyright (c) muebau 2023
|
||||||
|
* All rights reserved.</center></h2>
|
||||||
|
*
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __GRIDBW_H__
|
||||||
|
#define __GRIDBW_H__
|
||||||
|
|
||||||
|
#include <iterator>
|
||||||
|
#include <vector>
|
||||||
|
#include "pieces.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
class GridBW
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
public:
|
||||||
|
uint8_t width;
|
||||||
|
uint8_t height;
|
||||||
|
std::vector<uint32_t> pixels;
|
||||||
|
|
||||||
|
GridBW(uint8_t width, uint8_t height):
|
||||||
|
width(width),
|
||||||
|
height(height),
|
||||||
|
pixels(height)
|
||||||
|
{
|
||||||
|
if (width > 32)
|
||||||
|
{
|
||||||
|
throw std::invalid_argument("maximal width is 32");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void placePiece(Piece* piece, uint8_t x, uint8_t y)
|
||||||
|
{
|
||||||
|
for (uint8_t row = 4 - piece->getRotation().height; row < 4; row++)
|
||||||
|
{
|
||||||
|
pixels[y + (row - (4 - piece->getRotation().height))] |= piece->getGridRow(x, row, width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void erasePiece(Piece* piece, uint8_t x, uint8_t y)
|
||||||
|
{
|
||||||
|
for (uint8_t row = 4 - piece->getRotation().height; row < 4; row++)
|
||||||
|
{
|
||||||
|
pixels[y + (row - (4 - piece->getRotation().height))] &= ~piece->getGridRow(x, row, width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool noCollision(Piece* piece, uint8_t x, uint8_t y)
|
||||||
|
{
|
||||||
|
//if it touches a wall it is a collision
|
||||||
|
if (x > (this->width - piece->getRotation().width) || y > this->height - piece->getRotation().height)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t row = 4 - piece->getRotation().height; row < 4; row++)
|
||||||
|
{
|
||||||
|
if (piece->getGridRow(x, row, width) & pixels[y + (row - (4 - piece->getRotation().height))])
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void findLandingPosition(Piece* piece)
|
||||||
|
{
|
||||||
|
// move down until the piece bumps into some occupied pixels or the 'wall'
|
||||||
|
while (noCollision(piece, piece->x, piece->landingY))
|
||||||
|
{
|
||||||
|
piece->landingY++;
|
||||||
|
}
|
||||||
|
|
||||||
|
//at this point the positon is 'in the wall' or 'over some occupied pixel'
|
||||||
|
//so the previous position was the last correct one (clamped to 0 as minimum).
|
||||||
|
piece->landingY = piece->landingY > 0 ? piece->landingY - 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cleanupFullLines()
|
||||||
|
{
|
||||||
|
uint8_t offset = 0;
|
||||||
|
|
||||||
|
//from "height - 1" to "0", so from bottom row to top
|
||||||
|
for (uint8_t row = height; row-- > 0; )
|
||||||
|
{
|
||||||
|
//full line?
|
||||||
|
if (isLineFull(row))
|
||||||
|
{
|
||||||
|
offset++;
|
||||||
|
pixels[row] = 0x0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset > 0)
|
||||||
|
{
|
||||||
|
pixels[row + offset] = pixels[row];
|
||||||
|
pixels[row] = 0x0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isLineFull(uint8_t y)
|
||||||
|
{
|
||||||
|
return pixels[y] == (uint32_t)((1 << width) - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* __GRIDBW_H__ */
|
132
usermods/TetrisAI_v2/gridcolor.h
Normal file
132
usermods/TetrisAI_v2/gridcolor.h
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* @file : gridcolor.h
|
||||||
|
* @brief : contains the tetris grid as 8bit indexed color version
|
||||||
|
******************************************************************************
|
||||||
|
* @attention
|
||||||
|
*
|
||||||
|
* Copyright (c) muebau 2023
|
||||||
|
* All rights reserved.</center></h2>
|
||||||
|
*
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __GRIDCOLOR_H__
|
||||||
|
#define __GRIDCOLOR_H__
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <vector>
|
||||||
|
#include "gridbw.h"
|
||||||
|
#include "gridcolor.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
class GridColor
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
public:
|
||||||
|
uint8_t width;
|
||||||
|
uint8_t height;
|
||||||
|
GridBW gridBW;
|
||||||
|
std::vector<uint8_t> pixels;
|
||||||
|
|
||||||
|
GridColor(uint8_t width, uint8_t height):
|
||||||
|
width(width),
|
||||||
|
height(height),
|
||||||
|
gridBW(width, height),
|
||||||
|
pixels(width* height)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
for (uint8_t y = 0; y < height; y++)
|
||||||
|
{
|
||||||
|
gridBW.pixels[y] = 0x0;
|
||||||
|
for (int8_t x = 0; x < width; x++)
|
||||||
|
{
|
||||||
|
*getPixel(x, y) = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void placePiece(Piece* piece, uint8_t x, uint8_t y)
|
||||||
|
{
|
||||||
|
for (uint8_t pieceY = 0; pieceY < piece->getRotation().height; pieceY++)
|
||||||
|
{
|
||||||
|
for (uint8_t pieceX = 0; pieceX < piece->getRotation().width; pieceX++)
|
||||||
|
{
|
||||||
|
if (piece->getPixel(pieceX, pieceY))
|
||||||
|
{
|
||||||
|
*getPixel(x + pieceX, y + pieceY) = piece->pieceData->colorIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void erasePiece(Piece* piece, uint8_t x, uint8_t y)
|
||||||
|
{
|
||||||
|
for (uint8_t pieceY = 0; pieceY < piece->getRotation().height; pieceY++)
|
||||||
|
{
|
||||||
|
for (uint8_t pieceX = 0; pieceX < piece->getRotation().width; pieceX++)
|
||||||
|
{
|
||||||
|
if (piece->getPixel(pieceX, pieceY))
|
||||||
|
{
|
||||||
|
*getPixel(x + pieceX, y + pieceY) = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void cleanupFullLines()
|
||||||
|
{
|
||||||
|
uint8_t offset = 0;
|
||||||
|
//from "height - 1" to "0", so from bottom row to top
|
||||||
|
for (uint8_t y = height; y-- > 0; )
|
||||||
|
{
|
||||||
|
if (gridBW.isLineFull(y))
|
||||||
|
{
|
||||||
|
offset++;
|
||||||
|
for (uint8_t x = 0; x < width; x++)
|
||||||
|
{
|
||||||
|
pixels[y * width + x] = 0;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset > 0)
|
||||||
|
{
|
||||||
|
if (gridBW.pixels[y])
|
||||||
|
{
|
||||||
|
for (uint8_t x = 0; x < width; x++)
|
||||||
|
{
|
||||||
|
pixels[(y + offset) * width + x] = pixels[y * width + x];
|
||||||
|
pixels[y * width + x] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gridBW.cleanupFullLines();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* getPixel(uint8_t x, uint8_t y)
|
||||||
|
{
|
||||||
|
return &pixels[y * width + x];
|
||||||
|
}
|
||||||
|
|
||||||
|
void sync()
|
||||||
|
{
|
||||||
|
for (uint8_t y = 0; y < height; y++)
|
||||||
|
{
|
||||||
|
gridBW.pixels[y] = 0x0;
|
||||||
|
for (int8_t x = 0; x < width; x++)
|
||||||
|
{
|
||||||
|
gridBW.pixels[y] <<= 1;
|
||||||
|
if (*getPixel(x, y) != 0)
|
||||||
|
{
|
||||||
|
gridBW.pixels[y] |= 0x1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* __GRIDCOLOR_H__ */
|
184
usermods/TetrisAI_v2/pieces.h
Normal file
184
usermods/TetrisAI_v2/pieces.h
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* @file : pieces.h
|
||||||
|
* @brief : contains the tetris pieces with their colors indecies
|
||||||
|
******************************************************************************
|
||||||
|
* @attention
|
||||||
|
*
|
||||||
|
* Copyright (c) muebau 2022
|
||||||
|
* All rights reserved.</center></h2>
|
||||||
|
*
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __PIECES_H__
|
||||||
|
#define __PIECES_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <bitset>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cassert>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#define numPieces 7
|
||||||
|
|
||||||
|
struct PieceRotation
|
||||||
|
{
|
||||||
|
uint8_t width;
|
||||||
|
uint8_t height;
|
||||||
|
uint16_t rows;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PieceData
|
||||||
|
{
|
||||||
|
uint8_t rotCount;
|
||||||
|
uint8_t colorIndex;
|
||||||
|
PieceRotation rotations[4];
|
||||||
|
};
|
||||||
|
|
||||||
|
PieceData piecesData[numPieces] = {
|
||||||
|
// I
|
||||||
|
{
|
||||||
|
2,
|
||||||
|
1,
|
||||||
|
{
|
||||||
|
{ 1, 4, 0b0001000100010001},
|
||||||
|
{ 4, 1, 0b0000000000001111}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// O
|
||||||
|
{
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
{ 2, 2, 0b0000000000110011}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Z
|
||||||
|
{
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
{
|
||||||
|
{ 3, 2, 0b0000000001100011},
|
||||||
|
{ 2, 3, 0b0000000100110010}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// S
|
||||||
|
{
|
||||||
|
2,
|
||||||
|
4,
|
||||||
|
{
|
||||||
|
{ 3, 2, 0b0000000000110110},
|
||||||
|
{ 2, 3, 0b0000001000110001}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// L
|
||||||
|
{
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
{
|
||||||
|
{ 2, 3, 0b0000001000100011},
|
||||||
|
{ 3, 2, 0b0000000001110100},
|
||||||
|
{ 2, 3, 0b0000001100010001},
|
||||||
|
{ 3, 2, 0b0000000000010111}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// J
|
||||||
|
{
|
||||||
|
4,
|
||||||
|
6,
|
||||||
|
{
|
||||||
|
{ 2, 3, 0b0000000100010011},
|
||||||
|
{ 3, 2, 0b0000000001000111},
|
||||||
|
{ 2, 3, 0b0000001100100010},
|
||||||
|
{ 3, 2, 0b0000000001110001}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// T
|
||||||
|
{
|
||||||
|
4,
|
||||||
|
7,
|
||||||
|
{
|
||||||
|
{ 3, 2, 0b0000000001110010},
|
||||||
|
{ 2, 3, 0b0000000100110001},
|
||||||
|
{ 3, 2, 0b0000000000100111},
|
||||||
|
{ 2, 3, 0b0000001000110010}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
class Piece
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
public:
|
||||||
|
uint8_t x;
|
||||||
|
uint8_t y;
|
||||||
|
PieceData* pieceData;
|
||||||
|
uint8_t rotation;
|
||||||
|
uint8_t landingY;
|
||||||
|
|
||||||
|
Piece(uint8_t pieceIndex = 0):
|
||||||
|
x(0),
|
||||||
|
y(0),
|
||||||
|
rotation(0),
|
||||||
|
landingY(0)
|
||||||
|
{
|
||||||
|
this->pieceData = &piecesData[pieceIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
this->rotation = 0;
|
||||||
|
this->x = 0;
|
||||||
|
this->y = 0;
|
||||||
|
this->landingY = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getGridRow(uint8_t x, uint8_t y, uint8_t width)
|
||||||
|
{
|
||||||
|
if (x < width)
|
||||||
|
{
|
||||||
|
//shift the row with the "top-left" position to the "x" position
|
||||||
|
auto shiftx = (width - 1) - x;
|
||||||
|
auto topleftx = (getRotation().width - 1);
|
||||||
|
|
||||||
|
auto finalShift = shiftx - topleftx;
|
||||||
|
auto row = getRow(y);
|
||||||
|
auto finalResult = row << finalShift;
|
||||||
|
|
||||||
|
return finalResult;
|
||||||
|
}
|
||||||
|
return 0xffffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t getRow(uint8_t y)
|
||||||
|
{
|
||||||
|
if (y < 4)
|
||||||
|
{
|
||||||
|
return (getRotation().rows >> (12 - (4 * y))) & 0xf;
|
||||||
|
}
|
||||||
|
return 0xf;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getPixel(uint8_t x, uint8_t y)
|
||||||
|
{
|
||||||
|
if(x > getRotation().width - 1 || y > getRotation().height - 1 )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x < 4 && y < 4)
|
||||||
|
{
|
||||||
|
return (getRow((4 - getRotation().height) + y) >> (3 - ((4 - getRotation().width) + x))) & 0x1;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PieceRotation getRotation()
|
||||||
|
{
|
||||||
|
return this->pieceData->rotations[rotation];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* __PIECES_H__ */
|
64
usermods/TetrisAI_v2/rating.h
Normal file
64
usermods/TetrisAI_v2/rating.h
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* @file : rating.h
|
||||||
|
* @brief : contains the tetris rating of a grid
|
||||||
|
******************************************************************************
|
||||||
|
* @attention
|
||||||
|
*
|
||||||
|
* Copyright (c) muebau 2022
|
||||||
|
* All rights reserved.</center></h2>
|
||||||
|
*
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __RATING_H__
|
||||||
|
#define __RATING_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <float.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <vector>
|
||||||
|
#include "rating.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
class Rating
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
public:
|
||||||
|
uint8_t minHeight;
|
||||||
|
uint8_t maxHeight;
|
||||||
|
uint16_t holes;
|
||||||
|
uint8_t fullLines;
|
||||||
|
uint16_t bumpiness;
|
||||||
|
uint16_t aggregatedHeight;
|
||||||
|
double score;
|
||||||
|
uint8_t width;
|
||||||
|
std::vector<uint8_t> lineHights;
|
||||||
|
|
||||||
|
Rating(uint8_t width):
|
||||||
|
width(width),
|
||||||
|
lineHights(width)
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
this->minHeight = 0;
|
||||||
|
this->maxHeight = 0;
|
||||||
|
|
||||||
|
for (uint8_t line = 0; line < this->width; line++)
|
||||||
|
{
|
||||||
|
this->lineHights[line] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->holes = 0;
|
||||||
|
this->fullLines = 0;
|
||||||
|
this->bumpiness = 0;
|
||||||
|
this->aggregatedHeight = 0;
|
||||||
|
this->score = -DBL_MAX;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* __RATING_H__ */
|
33
usermods/TetrisAI_v2/readme.md
Normal file
33
usermods/TetrisAI_v2/readme.md
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
Version 1.0
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Just activate the usermod with `-D USERMOD_TETRISAI` and the effect will become available under the name 'Tetris AI'.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
### Sliders and boxes
|
||||||
|
|
||||||
|
#### Sliders
|
||||||
|
|
||||||
|
* 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)
|
||||||
|
|
||||||
|
#### 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 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)
|
||||||
|
|
||||||
|
## 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.
|
302
usermods/TetrisAI_v2/tetrisai.h
Normal file
302
usermods/TetrisAI_v2/tetrisai.h
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* @file : ai.h
|
||||||
|
* @brief : contains the heuristic
|
||||||
|
******************************************************************************
|
||||||
|
* @attention
|
||||||
|
*
|
||||||
|
* Copyright (c) muebau 2023
|
||||||
|
* All rights reserved.</center></h2>
|
||||||
|
*
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __AI_H__
|
||||||
|
#define __AI_H__
|
||||||
|
|
||||||
|
#include "gridbw.h"
|
||||||
|
#include "rating.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
class TetrisAI
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
public:
|
||||||
|
double aHeight;
|
||||||
|
double fullLines;
|
||||||
|
double holes;
|
||||||
|
double bumpiness;
|
||||||
|
bool findWorstMove = false;
|
||||||
|
|
||||||
|
uint8_t countOnes(uint32_t vector)
|
||||||
|
{
|
||||||
|
uint8_t count = 0;
|
||||||
|
while (vector)
|
||||||
|
{
|
||||||
|
vector &= (vector - 1);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateRating(GridBW grid, Rating* rating)
|
||||||
|
{
|
||||||
|
rating->minHeight = 0;
|
||||||
|
rating->maxHeight = 0;
|
||||||
|
rating->holes = 0;
|
||||||
|
rating->fullLines = 0;
|
||||||
|
rating->bumpiness = 0;
|
||||||
|
rating->aggregatedHeight = 0;
|
||||||
|
fill(rating->lineHights.begin(), rating->lineHights.end(), 0);
|
||||||
|
|
||||||
|
uint32_t columnvector = 0x0;
|
||||||
|
uint32_t lastcolumnvector = 0x0;
|
||||||
|
for (uint8_t row = 0; row < grid.height; row++)
|
||||||
|
{
|
||||||
|
columnvector |= grid.pixels[row];
|
||||||
|
|
||||||
|
//first (highest) column makes it
|
||||||
|
if (rating->maxHeight == 0 && columnvector)
|
||||||
|
{
|
||||||
|
rating->maxHeight = grid.height - row;
|
||||||
|
}
|
||||||
|
|
||||||
|
//if column vector is full we found the minimal height (or it stays zero)
|
||||||
|
if (rating->minHeight == 0 && (columnvector == (uint32_t)((1 << grid.width) - 1)))
|
||||||
|
{
|
||||||
|
rating->minHeight = grid.height - row;
|
||||||
|
}
|
||||||
|
|
||||||
|
//line full if all ones in mask :-)
|
||||||
|
if (grid.isLineFull(row))
|
||||||
|
{
|
||||||
|
rating->fullLines++;
|
||||||
|
}
|
||||||
|
|
||||||
|
//holes are basically a XOR with the "full" columns
|
||||||
|
rating->holes += countOnes(columnvector ^ grid.pixels[row]);
|
||||||
|
|
||||||
|
//calculate the difference (XOR) between the current column vector and the last one
|
||||||
|
uint32_t columnDelta = columnvector ^ lastcolumnvector;
|
||||||
|
|
||||||
|
//process every new column
|
||||||
|
uint8_t index = 0;
|
||||||
|
while (columnDelta)
|
||||||
|
{
|
||||||
|
//if this is a new column
|
||||||
|
if (columnDelta & 0x1)
|
||||||
|
{
|
||||||
|
//update hight of this column
|
||||||
|
rating->lineHights[(grid.width - 1) - index] = grid.height - row;
|
||||||
|
|
||||||
|
// update aggregatedHeight
|
||||||
|
rating->aggregatedHeight += grid.height - row;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
columnDelta >>= 1;
|
||||||
|
}
|
||||||
|
lastcolumnvector = columnvector;
|
||||||
|
}
|
||||||
|
|
||||||
|
//compare every two columns to get the difference and add them up
|
||||||
|
for (uint8_t column = 1; column < grid.width; column++)
|
||||||
|
{
|
||||||
|
rating->bumpiness += abs(rating->lineHights[column - 1] - rating->lineHights[column]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(double aHeight, double fullLines, double holes, double bumpiness):
|
||||||
|
aHeight(aHeight),
|
||||||
|
fullLines(fullLines),
|
||||||
|
holes(holes),
|
||||||
|
bumpiness(bumpiness)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void findBestMove(GridBW grid, Piece *piece)
|
||||||
|
{
|
||||||
|
vector<Piece> pieces = {*piece};
|
||||||
|
findBestMove(grid, &pieces);
|
||||||
|
*piece = pieces[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
void findBestMove(GridBW grid, std::vector<Piece> *pieces)
|
||||||
|
{
|
||||||
|
findBestMove(grid, pieces->begin(), pieces->end());
|
||||||
|
}
|
||||||
|
|
||||||
|
void findBestMove(GridBW grid, std::vector<Piece>::iterator start, std::vector<Piece>::iterator end)
|
||||||
|
{
|
||||||
|
Rating bestRating(grid.width);
|
||||||
|
findBestMove(grid, start, end, &bestRating);
|
||||||
|
}
|
||||||
|
|
||||||
|
void findBestMove(GridBW grid, std::vector<Piece>::iterator start, std::vector<Piece>::iterator end, Rating* bestRating)
|
||||||
|
{
|
||||||
|
grid.cleanupFullLines();
|
||||||
|
Rating curRating(grid.width);
|
||||||
|
Rating deeperRating(grid.width);
|
||||||
|
Piece piece = *start;
|
||||||
|
|
||||||
|
// for every rotation of the piece
|
||||||
|
for (piece.rotation = 0; piece.rotation < piece.pieceData->rotCount; piece.rotation++)
|
||||||
|
{
|
||||||
|
// put piece to top left corner
|
||||||
|
piece.x = 0;
|
||||||
|
piece.y = 0;
|
||||||
|
|
||||||
|
//test for every column
|
||||||
|
for (piece.x = 0; piece.x <= grid.width - piece.getRotation().width; piece.x++)
|
||||||
|
{
|
||||||
|
//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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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__ */
|
150
usermods/TetrisAI_v2/tetrisaigame.h
Normal file
150
usermods/TetrisAI_v2/tetrisaigame.h
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* @file : tetrisaigame.h
|
||||||
|
* @brief : main tetris functions
|
||||||
|
******************************************************************************
|
||||||
|
* @attention
|
||||||
|
*
|
||||||
|
* Copyright (c) muebau 2022
|
||||||
|
* All rights reserved.</center></h2>
|
||||||
|
*
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __TETRISAIGAME_H__
|
||||||
|
#define __TETRISAIGAME_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <vector>
|
||||||
|
#include "pieces.h"
|
||||||
|
#include "gridcolor.h"
|
||||||
|
#include "tetrisbag.h"
|
||||||
|
#include "tetrisai.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
class TetrisAIGame
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
bool animateFallOfPiece(Piece* piece, bool skip)
|
||||||
|
{
|
||||||
|
if (skip || piece->y >= piece->landingY)
|
||||||
|
{
|
||||||
|
piece->y = piece->landingY;
|
||||||
|
grid.gridBW.placePiece(piece, piece->x, piece->landingY);
|
||||||
|
grid.placePiece(piece, piece->x, piece->y);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// eraese last drawing
|
||||||
|
grid.erasePiece(piece, piece->x, piece->y);
|
||||||
|
|
||||||
|
//move piece down
|
||||||
|
piece->y++;
|
||||||
|
|
||||||
|
// draw piece
|
||||||
|
grid.placePiece(piece, piece->x, piece->y);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
uint8_t width;
|
||||||
|
uint8_t height;
|
||||||
|
uint8_t nLookAhead;
|
||||||
|
TetrisBag bag;
|
||||||
|
GridColor grid;
|
||||||
|
TetrisAI ai;
|
||||||
|
Piece curPiece;
|
||||||
|
PieceData* piecesData;
|
||||||
|
enum States { INIT, TEST_GAME_OVER, GET_NEXT_PIECE, FIND_BEST_MOVE, ANIMATE_MOVE, ANIMATE_GAME_OVER } state = INIT;
|
||||||
|
|
||||||
|
TetrisAIGame(uint8_t width, uint8_t height, uint8_t nLookAhead, PieceData* piecesData, uint8_t nPieces):
|
||||||
|
width(width),
|
||||||
|
height(height),
|
||||||
|
nLookAhead(nLookAhead),
|
||||||
|
bag(nPieces, 1, nLookAhead),
|
||||||
|
grid(width, height + 4),
|
||||||
|
ai(),
|
||||||
|
piecesData(piecesData)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void nextPiece()
|
||||||
|
{
|
||||||
|
grid.cleanupFullLines();
|
||||||
|
bag.queuePiece();
|
||||||
|
}
|
||||||
|
|
||||||
|
void findBestMove()
|
||||||
|
{
|
||||||
|
ai.findBestMove(grid.gridBW, &bag.piecesQueue);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool animateFall(bool skip)
|
||||||
|
{
|
||||||
|
return animateFallOfPiece(&(bag.piecesQueue[0]), skip);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isGameOver()
|
||||||
|
{
|
||||||
|
//if there is something in the 4 lines of the hidden area the game is over
|
||||||
|
return grid.gridBW.pixels[0] || grid.gridBW.pixels[1] || grid.gridBW.pixels[2] || grid.gridBW.pixels[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
void poll()
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case INIT:
|
||||||
|
reset();
|
||||||
|
state = TEST_GAME_OVER;
|
||||||
|
break;
|
||||||
|
case TEST_GAME_OVER:
|
||||||
|
if (isGameOver())
|
||||||
|
{
|
||||||
|
state = ANIMATE_GAME_OVER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = GET_NEXT_PIECE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GET_NEXT_PIECE:
|
||||||
|
nextPiece();
|
||||||
|
state = FIND_BEST_MOVE;
|
||||||
|
break;
|
||||||
|
case FIND_BEST_MOVE:
|
||||||
|
findBestMove();
|
||||||
|
state = ANIMATE_MOVE;
|
||||||
|
break;
|
||||||
|
case ANIMATE_MOVE:
|
||||||
|
if (!animateFall(false))
|
||||||
|
{
|
||||||
|
state = TEST_GAME_OVER;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ANIMATE_GAME_OVER:
|
||||||
|
static auto curPixel = grid.pixels.size();
|
||||||
|
grid.pixels[curPixel] = 254;
|
||||||
|
|
||||||
|
if (curPixel == 0)
|
||||||
|
{
|
||||||
|
state = INIT;
|
||||||
|
curPixel = grid.pixels.size();
|
||||||
|
}
|
||||||
|
curPixel--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
grid.clear();
|
||||||
|
bag.init();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* __TETRISAIGAME_H__ */
|
100
usermods/TetrisAI_v2/tetrisbag.h
Normal file
100
usermods/TetrisAI_v2/tetrisbag.h
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* @file : tetrisbag.h
|
||||||
|
* @brief : the tetris implementation of a random piece generator
|
||||||
|
******************************************************************************
|
||||||
|
* @attention
|
||||||
|
*
|
||||||
|
* Copyright (c) muebau 2022
|
||||||
|
* All rights reserved.</center></h2>
|
||||||
|
*
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __TETRISBAG_H__
|
||||||
|
#define __TETRISBAG_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "tetrisbag.h"
|
||||||
|
|
||||||
|
class TetrisBag
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
public:
|
||||||
|
uint8_t nPieces;
|
||||||
|
uint8_t nBagLength;
|
||||||
|
uint8_t bagIdx;
|
||||||
|
std::vector<uint8_t> bag;
|
||||||
|
std::vector<Piece> piecesQueue;
|
||||||
|
|
||||||
|
TetrisBag(uint8_t nPieces, uint8_t nBagLength, uint8_t queueLength):
|
||||||
|
nPieces(nPieces),
|
||||||
|
nBagLength(nBagLength),
|
||||||
|
bag(nPieces * nBagLength),
|
||||||
|
piecesQueue(queueLength)
|
||||||
|
{
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
void init()
|
||||||
|
{
|
||||||
|
//will shuffle the bag at first use
|
||||||
|
bagIdx = nPieces - 1;
|
||||||
|
|
||||||
|
for (uint8_t bagIndex = 0; bagIndex < nPieces * nBagLength; bagIndex++)
|
||||||
|
{
|
||||||
|
bag[bagIndex] = bagIndex % nPieces;
|
||||||
|
}
|
||||||
|
|
||||||
|
//will init the queue
|
||||||
|
for (uint8_t index = 0; index < piecesQueue.size(); index++)
|
||||||
|
{
|
||||||
|
queuePiece();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void shuffleBag()
|
||||||
|
{
|
||||||
|
uint8_t temp;
|
||||||
|
uint8_t swapIdx;
|
||||||
|
for (int index = nPieces - 1; index > 0; index--)
|
||||||
|
{
|
||||||
|
//get candidate to swap
|
||||||
|
swapIdx = rand() % index;
|
||||||
|
|
||||||
|
//swap it!
|
||||||
|
temp = bag[swapIdx];
|
||||||
|
bag[swapIdx] = bag[index];
|
||||||
|
bag[index] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Piece getNextPiece()
|
||||||
|
{
|
||||||
|
bagIdx++;
|
||||||
|
if (bagIdx >= nPieces)
|
||||||
|
{
|
||||||
|
shuffleBag();
|
||||||
|
bagIdx = 0;
|
||||||
|
}
|
||||||
|
return Piece(bag[bagIdx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void queuePiece()
|
||||||
|
{
|
||||||
|
//move vector to left
|
||||||
|
std::rotate(piecesQueue.begin(), piecesQueue.begin() + 1, piecesQueue.end());
|
||||||
|
piecesQueue[piecesQueue.size() - 1] = getNextPiece();
|
||||||
|
}
|
||||||
|
|
||||||
|
void queuePiece(uint8_t idx)
|
||||||
|
{
|
||||||
|
//move vector to left
|
||||||
|
std::rotate(piecesQueue.begin(), piecesQueue.begin() + 1, piecesQueue.end());
|
||||||
|
piecesQueue[piecesQueue.size() - 1] = Piece(idx % nPieces);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* __TETRISBAG_H__ */
|
222
usermods/TetrisAI_v2/usermod_v2_tetrisai.h
Normal file
222
usermods/TetrisAI_v2/usermod_v2_tetrisai.h
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "wled.h"
|
||||||
|
#include "FX.h"
|
||||||
|
#include "fcn_declare.h"
|
||||||
|
|
||||||
|
#include "tetrisaigame.h"
|
||||||
|
// By: muebau
|
||||||
|
|
||||||
|
typedef struct TetrisAI_data
|
||||||
|
{
|
||||||
|
unsigned long lastTime = 0;
|
||||||
|
TetrisAIGame tetris;
|
||||||
|
uint8_t intelligence;
|
||||||
|
uint8_t rotate;
|
||||||
|
bool showNext;
|
||||||
|
bool showBorder;
|
||||||
|
uint8_t colorOffset;
|
||||||
|
uint8_t colorInc;
|
||||||
|
uint8_t mistaceCountdown;
|
||||||
|
} tetrisai_data;
|
||||||
|
|
||||||
|
void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data)
|
||||||
|
{
|
||||||
|
SEGMENT.fill(SEGCOLOR(1));
|
||||||
|
|
||||||
|
//GRID
|
||||||
|
for (auto index_y = 4; index_y < tetris->grid.height; 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)
|
||||||
|
{
|
||||||
|
//BG color
|
||||||
|
color = SEGCOLOR(1);
|
||||||
|
}
|
||||||
|
//game over animation
|
||||||
|
else if(*tetris->grid.getPixel(index_x, index_y) == 254)
|
||||||
|
{
|
||||||
|
//use fg
|
||||||
|
color = SEGCOLOR(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//spread the color over the whole palette
|
||||||
|
uint8_t colorIndex = *tetris->grid.getPixel(index_x, index_y) * 32;
|
||||||
|
colorIndex += tetrisai_data->colorOffset;
|
||||||
|
color = ColorFromPalette(SEGPALETTE, colorIndex, 255, NOBLEND);
|
||||||
|
}
|
||||||
|
|
||||||
|
SEGMENT.setPixelColorXY(index_x, index_y - 4, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tetrisai_data->colorOffset += tetrisai_data->colorInc;
|
||||||
|
|
||||||
|
//NEXT PIECE AREA
|
||||||
|
if (tetrisai_data->showNext)
|
||||||
|
{
|
||||||
|
//BORDER
|
||||||
|
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++)
|
||||||
|
{
|
||||||
|
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() - 6, index_y, SEGCOLOR(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//NEXT PIECE
|
||||||
|
int piecesOffsetX = SEGMENT.virtualWidth() - 4;
|
||||||
|
int piecesOffsetY = 1;
|
||||||
|
for (uint8_t nextPieceIdx = 1; nextPieceIdx < tetris->nLookAhead; nextPieceIdx++)
|
||||||
|
{
|
||||||
|
uint8_t pieceNbrOffsetY = (nextPieceIdx - 1) * 5;
|
||||||
|
|
||||||
|
Piece piece(tetris->bag.piecesQueue[nextPieceIdx]);
|
||||||
|
|
||||||
|
for (uint8_t pieceY = 0; pieceY < piece.getRotation().height; pieceY++)
|
||||||
|
{
|
||||||
|
for (uint8_t pieceX = 0; pieceX < piece.getRotation().width; pieceX++)
|
||||||
|
{
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////
|
||||||
|
// 2D Tetris AI //
|
||||||
|
////////////////////////////
|
||||||
|
uint16_t mode_2DTetrisAI()
|
||||||
|
{
|
||||||
|
if (!strip.isMatrix || !SEGENV.allocateData(sizeof(tetrisai_data)))
|
||||||
|
{
|
||||||
|
// not a 2D set-up
|
||||||
|
SEGMENT.fill(SEGCOLOR(0));
|
||||||
|
return 350;
|
||||||
|
}
|
||||||
|
TetrisAI_data* tetrisai_data = reinterpret_cast<TetrisAI_data*>(SEGENV.data);
|
||||||
|
|
||||||
|
const uint16_t cols = SEGMENT.virtualWidth();
|
||||||
|
const uint16_t rows = SEGMENT.virtualHeight();
|
||||||
|
|
||||||
|
//range 0 - 1024ms => 1024/255 ~ 4
|
||||||
|
uint16_t msDelayMove = 1024 - (4 * SEGMENT.speed);
|
||||||
|
int16_t msDelayGameOver = msDelayMove / 4;
|
||||||
|
|
||||||
|
//range 0 - 2 (not including current)
|
||||||
|
uint8_t nLookAhead = SEGMENT.intensity ? (SEGMENT.intensity >> 7) + 2 : 1;
|
||||||
|
//range 0 - 16
|
||||||
|
tetrisai_data->colorInc = SEGMENT.custom2 >> 4;
|
||||||
|
|
||||||
|
if (!tetrisai_data->tetris || (tetrisai_data->tetris.nLookAhead != nLookAhead
|
||||||
|
|| tetrisai_data->showNext != SEGMENT.check1
|
||||||
|
|| tetrisai_data->showBorder != SEGMENT.check2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// do we need space for a border?
|
||||||
|
if (tetrisai_data->showBorder)
|
||||||
|
{
|
||||||
|
gridWidth = gridWidth - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tetrisai_data->tetris = TetrisAIGame(gridWidth, gridHeight, nLookAhead, piecesData, numPieces);
|
||||||
|
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));
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tetrisai_data->tetris.state == TetrisAIGame::ANIMATE_MOVE)
|
||||||
|
{
|
||||||
|
if (millis() - tetrisai_data->lastTime > msDelayMove)
|
||||||
|
{
|
||||||
|
drawGrid(&tetrisai_data->tetris, tetrisai_data);
|
||||||
|
tetrisai_data->lastTime = millis();
|
||||||
|
tetrisai_data->tetris.poll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (tetrisai_data->tetris.state == TetrisAIGame::ANIMATE_GAME_OVER)
|
||||||
|
{
|
||||||
|
if (millis() - tetrisai_data->lastTime > msDelayGameOver)
|
||||||
|
{
|
||||||
|
drawGrid(&tetrisai_data->tetris, tetrisai_data);
|
||||||
|
tetrisai_data->lastTime = millis();
|
||||||
|
tetrisai_data->tetris.poll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (tetrisai_data->tetris.state == TetrisAIGame::FIND_BEST_MOVE)
|
||||||
|
{
|
||||||
|
if (SEGMENT.check3)
|
||||||
|
{
|
||||||
|
if(tetrisai_data->mistaceCountdown == 0)
|
||||||
|
{
|
||||||
|
tetrisai_data->tetris.ai.findWorstMove = true;
|
||||||
|
tetrisai_data->tetris.poll();
|
||||||
|
tetrisai_data->tetris.ai.findWorstMove = false;
|
||||||
|
tetrisai_data->mistaceCountdown = SEGMENT.custom3;
|
||||||
|
}
|
||||||
|
tetrisai_data->mistaceCountdown--;
|
||||||
|
}
|
||||||
|
tetrisai_data->tetris.poll();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tetrisai_data->tetris.poll();
|
||||||
|
}
|
||||||
|
|
||||||
|
return FRAMETIME;
|
||||||
|
} // mode_2DTetrisAI()
|
||||||
|
static const char _data_FX_MODE_2DTETRISAI[] PROGMEM = "Tetris AI@!,Look ahead,Intelligence,Rotate color,Mistake free,Show next,Border,Mistakes;Game Over,!,Border;!;2;sx=127,ix=64,c1=255,c2=0,c3=31,o1=1,o2=1,o3=0,pal=11";
|
||||||
|
|
||||||
|
class TetrisAIUsermod : public Usermod
|
||||||
|
{
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
public:
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
strip.addEffect(255, &mode_2DTetrisAI, _data_FX_MODE_2DTETRISAI);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t getId()
|
||||||
|
{
|
||||||
|
return USERMOD_ID_TETRISAI;
|
||||||
|
}
|
||||||
|
};
|
@ -180,6 +180,7 @@
|
|||||||
#define USERMOD_ID_STAIRWAY_WIPE 44 //Usermod "stairway-wipe-usermod-v2.h"
|
#define USERMOD_ID_STAIRWAY_WIPE 44 //Usermod "stairway-wipe-usermod-v2.h"
|
||||||
#define USERMOD_ID_ANIMARTRIX 45 //Usermod "usermod_v2_animartrix.h"
|
#define USERMOD_ID_ANIMARTRIX 45 //Usermod "usermod_v2_animartrix.h"
|
||||||
#define USERMOD_ID_HTTP_PULL_LIGHT_CONTROL 46 //usermod "usermod_v2_HttpPullLightControl.h"
|
#define USERMOD_ID_HTTP_PULL_LIGHT_CONTROL 46 //usermod "usermod_v2_HttpPullLightControl.h"
|
||||||
|
#define USERMOD_ID_TETRISAI 47 //Usermod "usermod_v2_tetris.h"
|
||||||
|
|
||||||
//Access point behavior
|
//Access point behavior
|
||||||
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
|
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
|
||||||
|
@ -209,6 +209,10 @@
|
|||||||
#include "../usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h"
|
#include "../usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USERMOD_TETRISAI
|
||||||
|
#include "../usermods/TetrisAI_v2/usermod_v2_tetrisai.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
void registerUsermods()
|
void registerUsermods()
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
@ -405,4 +409,8 @@ void registerUsermods()
|
|||||||
#ifdef USERMOD_STAIRCASE_WIPE
|
#ifdef USERMOD_STAIRCASE_WIPE
|
||||||
usermods.add(new StairwayWipeUsermod());
|
usermods.add(new StairwayWipeUsermod());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USERMOD_TETRISAI
|
||||||
|
usermods.add(new TetrisAIUsermod());
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user