
#include <stdio.h>
#include <stdlib.h>
#include "raylib.h"#define ROWS 30
#define COLS 20
int grid[ROWS][COLS] = {0};#define CELL_SIZE 30
#define BLOCK_TYPE_COUNT 13
const Color colors_block[BLOCK_TYPE_COUNT + 1] = {
{26,31,40,255},
{47,230,23,255},
{232,18,18,255},
{226,116,17,255},
{237,234,4,255},
{166,0,247,255},
{21,204,209,255},
{212,123,0,255},
{0,123,212,255},
{255,0,200,255},
{255,255,0,255},
{13,64,216,255},
{222,222,222,255},
{0,255,127,255},
};typedef struct Block {int idx;int sz;int shape[4][4];int row,col;} Block;
Block curBlock,nextBlock;
Block allBlocks[BLOCK_TYPE_COUNT] = {{1,2,{{1,1,0,0},{1,1,0,0}, }},{2,3,{{0,0,0},{1,1,0},{0,1,1},}},{3,3,{{0,0,0},{0,1,1},{1,1,0}, }},{4,3,{{0,1,0},{0,1,0},{0,1,1}, }},{5,3,{{0,0,0},{1,1,1},{0,1,0}, }},{6,3,{{0,1,0},{0,1,0},{1,1,0}, }},{7,4,{{0,0,1,0},{0,0,1,0},{0,0,1,0},{0,0,1,0}}},{8,4,{ {0,1,0,1},{0,1,0,1},{0,1,1,1},{0,0,0,0},}},{9,4,{ {1,1,1,1},{1,1,1,1},{0,1,1,0},{0,1,1,0},}},{10,4,{ {1,1,0,0},{1,1,0,0},{1,1,1,1},{1,1,1,1},}},{11,2,{ {1,0},{0,1},}},{12,2,{ {1,1},{0,1},}},{13,1,{ {1},}},};bool leftPressed = false;
bool rightPressed = false;
bool isGameOver = false;
float downInterval =0.32f*3;
float curInterval = 0;float downSpeedFactor = 1.0f;
int score = 0;
void init();
void loadTextures();
void initGame();
void checkOperation(float dt);
Vector2 getPosOfRowCol(int row, int col);
void drawGrid();
void drawCurBlock();
void driveCurBlock(float dt);
bool isBlockPositionValid(Block bloock);
void mergeBlockToGrid(Block block);
void checkOperation(float dt);
void rotateBlockClockwise(Block *block);void rotateBlockRandom(Block *block);
int eliminateFullRows();
void drawScore();
void drawNextBlock();
void drawGameOver();
int main() {init();while (!WindowShouldClose()) { float dt = GetFrameTime(); BeginDrawing();ClearBackground((Color){44,44,127,255}); checkOperation(dt);drawGrid();drawCurBlock();driveCurBlock(dt); drawScore(); drawNextBlock();drawGameOver();EndDrawing();}CloseWindow();return 0;
}void init() {SetConfigFlags(FLAG_WINDOW_HIGHDPI);InitWindow(900, 920, "Tetris");SetTargetFPS(60);loadTextures();initGame();
}void initGame() {for(int i = 0; i < ROWS; i++) {for(int j = 0; j < COLS; j++) {grid[i][j] = 0;}}score = 0;isGameOver = false;curBlock = allBlocks[GetRandomValue(0, BLOCK_TYPE_COUNT - 1)];rotateBlockRandom(&curBlock);curBlock.row = 0;curBlock.col = COLS/2 - curBlock.sz/2;nextBlock = allBlocks[GetRandomValue(0, BLOCK_TYPE_COUNT - 1)];rotateBlockRandom(&nextBlock);}void loadTextures() {}Vector2 getPosOfRowCol(int row, int col) {int x = col * CELL_SIZE + 11 + CELL_SIZE/2;int y = row * CELL_SIZE + 11 + CELL_SIZE/2;return (Vector2){x, y};
}void drawGrid() {for (int i = 0; i < ROWS; i++) {for (int j = 0; j < COLS; j++) {Vector2 pos = getPosOfRowCol(i, j);DrawRectangle(pos.x - CELL_SIZE/2, pos.y - CELL_SIZE/2, CELL_SIZE - 1, CELL_SIZE - 1, (Color){0,0,0,255});if (grid[i][j]) {DrawRectangle(pos.x - CELL_SIZE/2 + 1, pos.y - CELL_SIZE/2 + 1, CELL_SIZE - 3, CELL_SIZE - 3, colors_block[grid[i][j]]);}}}for (int i = 0; i <= ROWS; i++) {DrawLine(11, 11 + i * CELL_SIZE, 11 + COLS * CELL_SIZE, 11 + i * CELL_SIZE, LIGHTGRAY);}for (int j = 0; j <= COLS; j++) {DrawLine(11 + j * CELL_SIZE, 11, 11 + j * CELL_SIZE, 11 + ROWS * CELL_SIZE, LIGHTGRAY);}
}void drawCurBlock(){for (int i = 0; i < curBlock.sz; i++) {for (int j = 0; j < curBlock.sz; j++) {if (curBlock.shape[i][j] == 1) {int row = curBlock.row + i;int col = curBlock.col + j;if (row >= 0 && row < ROWS && col >= 0 && col < COLS) {Vector2 pos = getPosOfRowCol(row, col);DrawRectangle((int)(pos.x - CELL_SIZE/2), (int)(pos.y - CELL_SIZE/2), CELL_SIZE - 1, CELL_SIZE - 1, colors_block[curBlock.idx] );}}}}
}void driveCurBlock(float dt) {curInterval += dt*downSpeedFactor;if (curInterval >= downInterval) {curBlock.row++;curInterval = 0.0f;}if (!isBlockPositionValid(curBlock)) {curBlock.row--;if(curBlock.row<0){isGameOver = true;return ;}mergeBlockToGrid(curBlock);int eliminatedRows = eliminateFullRows();if (eliminatedRows > 0) {score += 100 * (int)pow(2, eliminatedRows - 1);}curBlock =nextBlock;curBlock.row = 0; curBlock.col = (COLS - curBlock.sz) / 2;nextBlock = allBlocks[GetRandomValue(0, BLOCK_TYPE_COUNT - 1)];rotateBlockRandom(&nextBlock); if(!isBlockPositionValid(curBlock)){printf("Game Over\n"); }}
}bool isBlockPositionValid(Block bloock){for (int i = 0; i < bloock.sz; i++) {for (int j = 0; j < bloock.sz; j++) {if (bloock.shape[i][j] == 1) {int row = bloock.row + i;int col = bloock.col + j;if((row < 0 || row >= ROWS) || col >= COLS || col < 0) { printf("row:%d col:%d grid:%d\n",row,col,grid[row][col]);return false; } if(row>=0&&grid[row][col]!=0){return false;}}}}return true;
}void mergeBlockToGrid(Block block){for (int i = 0; i < block.sz; i++) {for (int j = 0; j < block.sz; j++) {if (block.shape[i][j] == 1) {int row = block.row + i;int col = block.col + j;if (row >= 0 && row < ROWS && col >= 0 && col < COLS) {grid[row][col] = block.idx;}}}}
}void checkOperation(float dt){static float moveAccumulator = 0.0f;static const float moveDelay = 0.1f; moveAccumulator += dt;if (IsKeyDown(KEY_LEFT)) {if (moveAccumulator >= moveDelay || !leftPressed) {leftPressed = true;curBlock.col--;if(!isBlockPositionValid(curBlock)) {curBlock.col++;}moveAccumulator = 0.0f; }} else if (IsKeyDown(KEY_RIGHT)) {if (moveAccumulator >= moveDelay || !rightPressed) {rightPressed = true;curBlock.col++;if(!isBlockPositionValid(curBlock)) {curBlock.col--;}moveAccumulator = 0.0f;} }else if(IsKeyPressed(KEY_UP)){rotateBlockClockwise(&curBlock);if(!isBlockPositionValid(curBlock)){rotateBlockClockwise(&curBlock);rotateBlockClockwise(&curBlock);rotateBlockClockwise(&curBlock);}return;}else if(IsKeyPressed(KEY_SPACE)){while(isBlockPositionValid(curBlock)){curBlock.row++;}return;} if(isGameOver && IsKeyPressed(KEY_R)){initGame();}if(IsKeyDown(KEY_DOWN)){downSpeedFactor = 20.0f;}else{downSpeedFactor = 1.0f;}
}void rotateBlockClockwise(Block *block) {for (int i = 0; i < block->sz; i++) {for (int j = i + 1 ; j < block->sz; j++) { int temp = block->shape[i][j];block->shape[i][j] = block->shape[j][i];block->shape[j][i] = temp;}}for (int i = 0; i < block->sz; i++) {for (int j = 0; j < block->sz / 2; j++) {int temp = block->shape[i][j];block->shape[i][j] = block->shape[i][block->sz - 1 - j];block->shape[i][block->sz - 1 - j] = temp;}}
}void rotateBlockRandom(Block *block){int times = GetRandomValue(0, 3);for(int i = 0; i < times; i++){rotateBlockClockwise(block);}
}int eliminateFullRows(){int rowsEliminated = 0;int row = ROWS - 1;while (row >= 0) {bool isFull = true;for (int col = 0; col < COLS; col++) {if (grid[row][col] == 0) {isFull = false;break;}}if (isFull && row > 0) {for (int i = row; i > 0; i--) {for (int j = 0; j < COLS; j++) {grid[i][j] = grid[i - 1][j];}}for (int j = 0; j < COLS; j++) {grid[0][j] = 0;}rowsEliminated++;} else {row--;}} return rowsEliminated;
}void drawScore(){int left = (COLS + 2)* CELL_SIZE +11 +10; DrawText("Score",left,15,38,WHITE);DrawRectangleRounded((Rectangle){left-30,55,170,60},0.3,6,(Color){59,85,162,255});char scoreText[10];sprintf(scoreText, "%d", score);drawTextCenter(scoreText,left+55,87,38,WHITE);
}void drawNextBlock(){int left = (COLS + 2)* CELL_SIZE +11 +10; DrawText("Next",left,175,38,WHITE);DrawRectangleRounded((Rectangle){left-30,215,170,180},0.3,6,(Color){59,85,162,255});int width = nextBlock.sz * CELL_SIZE;int height = nextBlock.sz * CELL_SIZE;int startX = left ;int startY = 245;for (int i = 0; i < nextBlock.sz; i++) {for (int j = 0; j < nextBlock.sz; j++) {if (nextBlock.shape[i][j] == 1) {int x = startX + j * CELL_SIZE;int y = startY + i * CELL_SIZE;DrawRectangle(x, y, CELL_SIZE - 1, CELL_SIZE - 1, colors_block[nextBlock.idx]);}}}
}void drawGameOver(){if(!isGameOver){return;}int left = (COLS + 2)* CELL_SIZE +11 +10; DrawText("Game Over",left-30,450,38,RED);DrawText("Press R to\n Restart",left-30,550,38,WHITE);
}