#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#define TFT_DC 9
#define TFT_CS 10
#define VERT_PIN A0
#define HORZ_PIN A1
#define SEL_PIN 2
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
int const blockSize = 15;
int const displayHeight = 20;
int const displayWidth = 10;
/*
The actual grid is represented by the even coordinates,
while the middle points are represented by the odd coordinates.
*/
class Point {
public:
int x;
int y;
Point(int x = 0, int y = 0) {
this->x = x;
this->y = y;
}
Point rotate90ccw() {
return Point(-y, x);
}
Point rotate90cw() const {
return Point(y, -x);
}
Point operator + (Point const oth) const {
return Point(x + oth.x, y + oth.y);
}
Point operator - (Point const oth) const {
return Point(x - oth.x, y - oth.y);
}
Point operator * (int scale) const {
return Point(x * scale, y * scale);
}
bool gameValid(uint16_t (&board)[displayWidth][displayHeight]) {
return (x % 2 == 0) && (y % 2 == 0) &&
0 <= x && (x / 2) < displayWidth &&
0 <= y && (y / 2) < displayHeight &&
board[x / 2][y / 2] == 0;
}
void debug() {
Serial.print("Point: ");
Serial.print(x);
Serial.print(" ");
Serial.println(y);
}
};
//Darken 565 color was generated by ChatGPT, so no clu
uint16_t darken_565_color(uint16_t color, float factor = 0.1) {
// Extract the red, green, and blue components
uint16_t red = (color >> 11) & 0x1F;
uint16_t green = (color >> 5) & 0x3F;
uint16_t blue = color & 0x1F;
// Darken each component
red = (uint16_t)(red * (1.0f - factor));
green = (uint16_t)(green * (1.0f - factor));
blue = (uint16_t)(blue * (1.0f - factor));
// Clamp values to ensure they are within valid range
red = (red > 0x1F) ? 0x1F : red;
green = (green > 0x3F) ? 0x3F : green;
blue = (blue > 0x1F) ? 0x1F : blue;
// Reassemble the color
return (red << 11) | (green << 5) | blue;
}
void displayBlock(int x, int y, int color) {
tft.fillRect(x * blockSize, y * blockSize,
blockSize, blockSize,
darken_565_color(color, 0.3));
tft.fillRect(x * blockSize + 1, y * blockSize + 1,
blockSize - 2, blockSize - 2,
color);
}
class Tetromino {
public:
Point center;
int blockCount;
Point* blocks;
uint16_t color;
Tetromino(Point center, int blockCount, Point* blocks, uint16_t color){
this->center = center;
this->blockCount = blockCount;
this->blocks = blocks;
this->color = color;
}
Tetromino(int type) {
if (type == 0)
(*this) = Tetromino({1, 1}, 4, new Point[4]{Point(1, 1), Point(-1, 1), Point(1, -1), Point(-1, -1)}, ILI9341_YELLOW);
else if (type == 1) {
(*this) = Tetromino({2, 2}, 4, new Point[4]{Point(0, 0), Point(-2, 0), Point(2, 0), Point(2, -2)}, ILI9341_ORANGE);
} else if(type == 2) {
(*this) = Tetromino({2, 2}, 4, new Point[4]{Point(0, 0), Point(-2, 0), Point(2, 0), Point(-2, -2)}, ILI9341_BLUE);
} else if(type == 3) {
(*this) = Tetromino({2, 2}, 4, new Point[4]{Point(0, 0), Point(-2, 0), Point(2, 0), Point(4, 0)}, ILI9341_CYAN);
} else if(type == 4){
(*this) = Tetromino({2, 2}, 4, new Point[4]{Point(0, 0), Point(-2, 0), Point(0, -2), Point(2, -2)}, ILI9341_GREEN);
} else if (type == 5) {
(*this) = Tetromino({2, 2}, 4, new Point[4]{Point(0, 0), Point(-2, 0), Point(2, 0), Point(0, -2)}, ILI9341_PURPLE);
} else if (type == 6) {
(*this) = Tetromino({2, 2}, 4, new Point[4]{Point(0, 0), Point(-2, -2), Point(2, 0), Point(0, -2)}, ILI9341_RED);
}
}
void display(uint16_t (&board)[displayWidth][displayHeight]) {
for (int i = 0; i < blockCount; i++) {
if ((blocks[i] + center).gameValid(board) == true) {
displayBlock((center.x + blocks[i].x) / 2 , (center.y + blocks[i].y) / 2, color);
}
}
}
void undisplay(uint16_t (&board)[displayWidth][displayHeight]) {
for(int i = 0; i < blockCount; i++) {
if((blocks[i] + center).gameValid(board) == true) {
displayBlock((center.x + blocks[i].x) / 2 , (center.y + blocks[i].y) / 2, 0);
}
}
}
bool checkObject(uint16_t (&board)[displayWidth][displayHeight]) {
for(int i = 0; i < blockCount; i++ ) {
if((blocks[i] + center).gameValid(board) == false)
return false;
}
return true;
}
void transform(Point oth) {
this->center.x += oth.x;
this->center.y += oth.y;
}
//Transform restricted based on game rules.
bool gameTransform(Point oth, uint16_t (&board)[displayWidth][displayHeight]) {
this->undisplay(board);
this->transform(oth);
bool verdict = true;
if(!this->checkObject(board)) {
this->transform(Point(0, 0)-oth);
verdict = false;
}
this->display(board);
return verdict;
}
void rotate90ccw() {
for(int i = 0; i < blockCount; i++) {
blocks[i] = blocks[i].rotate90ccw();
}
}
void rotate90cw() {
for(int i = 0; i < blockCount; i++) {
blocks[i] = blocks[i].rotate90cw();
}
}
void gameRotate(uint16_t (&board)[displayWidth][displayHeight]) {
this->undisplay(board);
this->rotate90ccw();
if (!this->checkObject(board)) {
this->rotate90cw();
}
this->display(board);
}
void solidify(uint16_t (&board)[displayWidth][displayHeight]) {
for (int i = 0; i < blockCount; i++) {
Point block = blocks[i] + center;
if ((blocks[i] + center).gameValid(board) == true) {
board[block.x / 2][block.y / 2] = color;
}
}
}
void debug() {
Serial.println("Debug state:");
center.debug();
Serial.print("blockCount: ");
Serial.println(blockCount);
for(int i = 0; i < blockCount; i++) {
blocks[i].debug();
}
Serial.print("color: ");
Serial.println(color);
}
};
/*
//Taken straight from the library for easy reference.
// Color definitions
#define ILI9341_BLACK 0x0000 ///< 0, 0, 0
#define ILI9341_NAVY 0x000F ///< 0, 0, 123
#define ILI9341_DARKGREEN 0x03E0 ///< 0, 125, 0
#define ILI9341_DARKCYAN 0x03EF ///< 0, 125, 123
#define ILI9341_MAROON 0x7800 ///< 123, 0, 0
#define ILI9341_PURPLE 0x780F ///< 123, 0, 123
#define ILI9341_OLIVE 0x7BE0 ///< 123, 125, 0
#define ILI9341_LIGHTGREY 0xC618 ///< 198, 195, 198
#define ILI9341_DARKGREY 0x7BEF ///< 123, 125, 123
#define ILI9341_BLUE 0x001F ///< 0, 0, 255
#define ILI9341_GREEN 0x07E0 ///< 0, 255, 0
#define ILI9341_CYAN 0x07FF ///< 0, 255, 255
#define ILI9341_RED 0xF800 ///< 255, 0, 0
#define ILI9341_MAGENTA 0xF81F ///< 255, 0, 255
#define ILI9341_YELLOW 0xFFE0 ///< 255, 255, 0
#define ILI9341_WHITE 0xFFFF ///< 255, 255, 255
#define ILI9341_ORANGE 0xFD20 ///< 255, 165, 0
#define ILI9341_GREENYELLOW 0xAFE5 ///< 173, 255, 41
#define ILI9341_PINK 0xFC18 ///< 255, 130, 198
*/
void PrintTestPieces() {
uint16_t board[displayWidth][displayHeight];
for(int i = 0; i < 7; i++) {
Tetromino tromino = Tetromino(i);
tromino.transform({4,6 + 2 + 6 * i});
for(int j = 0;j < 4; j++) {
tromino.display(board);
tromino.transform(Point(8, 0));
tromino.rotate90ccw();
}
}
}
const unsigned long gameStepMillis = 1000;
const unsigned long joystickStepMillis = 50;
class Game {
public:
uint16_t board[displayWidth][displayHeight] = {0};
Tetromino* activeObject;
Game() {
activeObject = nullptr;
}
void spawnObject() {
int col = random(1, displayWidth - 4);
int type = random(1, 7);
activeObject = new Tetromino(type);
activeObject->transform(Point(col * 2, 0));
activeObject->display(board);
//activeObject->debug();
}
//
void solidify() {
activeObject->solidify(board);
activeObject = nullptr;
}
void advanceIteration() {
unsigned long static lastTimestamp = 0;
unsigned long currTimestamp = millis();
if (lastTimestamp + gameStepMillis < currTimestamp) {
lastTimestamp = currTimestamp;
if (activeObject == nullptr) {
spawnObject();
} else {
bool moveSuccesful = activeObject->gameTransform(Point(0, 2), board);
//activeObject->debug();
if (moveSuccesful == false) {
solidify();
//refreshGrid();
checkLines();
}
}
} else {
return ;
}
}
//why is my exxecution killed.
void refreshGrid() {
//tft.fillScreen(0);
for(int i = 0; i < displayWidth; i++)
for(int j = 0; j < displayHeight; j++)
displayBlock(i, j, board[i][j]);
}
int checkLines() {
int targetLine = displayHeight - 1;
int points = 0;
for(int j = displayHeight - 1; 0 <= j; j--) {
bool lineIsClear = true;
for(int i = 0; i < displayWidth; i++) {
lineIsClear &= (0 < board[i][j]);
}
if (lineIsClear == false) {
for(int i = 0;i < displayWidth; i++) {
if (targetLine != j) {
board[i][targetLine] = board[i][j];
board[i][j] = 0;
}
}
targetLine--;
} else {
points++;
for(int i = 0; i < displayWidth; i++)
board[i][j] = 0;
}
}
refreshGrid();
return points;
}
void processInput() {
unsigned long static lastTimestamp = 0;
unsigned long currTimestamp = millis();
if (lastTimestamp + joystickStepMillis < currTimestamp) {
int vert = analogRead(VERT_PIN);
int horz = analogRead(HORZ_PIN);
bool selPressed = digitalRead(SEL_PIN) == LOW;
if(horz == 0) {
activeObject->gameTransform(Point(2, 0), board);
lastTimestamp = currTimestamp;
} else if (horz == 1023) {
activeObject->gameTransform(Point(-2, 0),board);
lastTimestamp = currTimestamp;
}
if (vert == 1023) {
activeObject->gameRotate(board);
lastTimestamp = currTimestamp;
} else if (vert == 0){
activeObject->gameTransform(Point(0, 2), board);
lastTimestamp = currTimestamp;
}
if (selPressed == true) {
//TODO: implement substitution of pieces.
}
}
}
void clearLine() {
}
};
Game basegame;
void setup() {
Serial.begin(115200);
tft.begin();
pinMode(VERT_PIN, INPUT);
pinMode(HORZ_PIN, INPUT);
pinMode(SEL_PIN, INPUT_PULLUP);
randomSeed(millis());
basegame = Game();
}
void loop() {
basegame.advanceIteration();
basegame.processInput();
}