#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <cstring>
#include <string>
// 全局常量
const int BLOCK_SIZE = 30;
const int WIDTH = 10;
const int HEIGHT = 20;
const int WINDOW_WIDTH = WIDTH * BLOCK_SIZE + 220;
const int WINDOW_HEIGHT = HEIGHT * BLOCK_SIZE;
const int PREVIEW_X = WIDTH * BLOCK_SIZE + 30;
const int PREVIEW_Y = 80;
const int INIT_DROP_INTERVAL = 500;
const int MIN_DROP_INTERVAL = 100;
const int DROP_SPEED_STEP = 50;
const int SPEED_UP_LINES = 10;
const int GOLD_PER_LINE = 100;
const int LOOP_DELAY = 10;
// 方块形状定义(7种×4旋转)
const int shapes[7][4] = {
{0x000F, 0x2222, 0x000F, 0x2222}, // I
{0x0066, 0x0066, 0x0066, 0x0066}, // O
{0x00E2, 0x2620, 0x00E2, 0x2620}, // T
{0x0071, 0x2260, 0x0071, 0x2260}, // L
{0x0074, 0x6220, 0x0074, 0x6220}, // J
{0x006C, 0x2320, 0x006C, 0x2320}, // S
{0x00C6, 0x3220, 0x00C6, 0x3220} // Z
};
// 皮肤结构体
struct Skin {
std::string name;
int price;
bool isOwned;
SDL_Color color;
} skins[3] = {
{"默认皮肤", 0, true, {255, 255, 255, 255}},
{"方块皮肤", 300, false, {0, 255, 0, 255}},
{"钻石皮肤", 500, false, {0, 255, 255, 255}}
};
int currSkinIdx = 0;
SDL_Color currSkinColor = skins[0].color;
// SDL全局资源
SDL_Window* window = nullptr;
SDL_Renderer* renderer = nullptr;
TTF_Font* font = nullptr;
// 游戏数据
struct GameData {
int gameArea[HEIGHT][WIDTH] = {{0}};
int currShape;
int currRot;
int currX;
int currY;
int nextShape;
int nextRot;
int gold = 0;
int lineTotal = 0;
int dropInterval = INIT_DROP_INTERVAL;
} gameData;
// 函数声明
bool initSDL();
void closeSDL();
void drawText(int x, int y, const std::string& text, SDL_Color color);
void showMainMenuSDL();
void showShopMenuSDL();
void initGame();
void createBlock();
bool checkCollision(int x, int y, int rot, int shape);
void drawGameSDL();
void rotateBlock();
void moveBlock(int dx, int dy);
void clearLines();
bool isGameOver();
void startGameSDL();
bool exchangeSkin(int skinIdx);
void useSkin(int skinIdx);
// -------------------------- 主函数 --------------------------
int main() {
srand(static_cast<unsigned int>(time(nullptr)));
if (!initSDL()) {
return 1;
}
showMainMenuSDL();
closeSDL();
return 0;
}
// -------------------------- 1. SDL初始化与释放(修复字体加载) --------------------------
bool initSDL() {
// 初始化SDL视频和字体
if (SDL_Init(SDL_INIT_VIDEO) < 0 || TTF_Init() == -1) {
return false;
}
// 创建窗口
window = SDL_CreateWindow(
"俄罗斯方块(图形版)",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
WINDOW_WIDTH, WINDOW_HEIGHT,
SDL_WINDOW_SHOWN
);
if (!window) {
TTF_Quit();
SDL_Quit();
return false;
}
// 创建渲染器
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (!renderer) {
SDL_DestroyWindow(window);
TTF_Quit();
SDL_Quit();
return false;
}
// 依次尝试常见系统字体路径(适配多设备)
font = TTF_OpenFont("/system/fonts/Roboto-Regular.ttf", 18);
if (!font) font = TTF_OpenFont("/system/fonts/DroidSans.ttf", 18);
if (!font) font = TTF_OpenFont("/storage/emulated/0/ansystem/fonts/simhei.ttf", 18);
font = TTF_OpenFont("/storage/emulated/0/ansystem/fonts/simhei.ttf", 18);
if (!font) {
SDL_Log("字体加载失败!错误原因:%s", TTF_GetError()); // 打印错误原因
} else {
SDL_Log("字体加载成功!");
}
if (!font) { // 字体加载失败则报错
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
TTF_Quit();
SDL_Quit();
return false;
}
return true;
}
void closeSDL() {
TTF_CloseFont(font);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
TTF_Quit();
SDL_Quit();
}
// -------------------------- 2. SDL文字绘制 --------------------------
void drawText(int x, int y, const std::string& text, SDL_Color color) {
SDL_Surface* textSurface = TTF_RenderUTF8_Solid(font, text.c_str(), color);
if (!textSurface) return;
SDL_Texture* textTexture = SDL_CreateTextureFromSurface(renderer, textSurface);
if (!textTexture) {
SDL_FreeSurface(textSurface);
return;
}
SDL_Rect renderRect = {x, y, textSurface->w, textSurface->h};
SDL_RenderCopy(renderer, textTexture, nullptr, &renderRect);
SDL_DestroyTexture(textTexture);
SDL_FreeSurface(textSurface);
}
// -------------------------- 3. 主菜单(SDL图形化) --------------------------
void showMainMenuSDL() {
const std::string menuItems[3] = {"进入游戏", "皮肤商店", "退出游戏"};
int selectedIdx = 0;
bool running = true;
SDL_Event e;
while (running) {
// 事件处理
while (SDL_PollEvent(&e) != 0) {
if (e.type == SDL_QUIT) running = false;
else if (e.type == SDL_KEYDOWN) {
switch (e.key.keysym.sym) {
case SDLK_UP:
selectedIdx = (selectedIdx - 1 + 3) % 3;
break;
case SDLK_DOWN:
selectedIdx = (selectedIdx + 1) % 3;
break;
case SDLK_SPACE:
if (selectedIdx == 0) {
startGameSDL();
} else if (selectedIdx == 1) {
showShopMenuSDL();
} else {
running = false;
}
break;
}
}
}
// 绘制菜单
SDL_SetRenderDrawColor(renderer, 15, 15, 15, 255);
SDL_RenderClear(renderer);
drawText(WINDOW_WIDTH / 2 - 80, 60, "俄罗斯方块", {255, 200, 0, 255});
drawText(WINDOW_WIDTH / 2 - 100, 100, "操作:↑下选 | ↓下选 | 空格确认", {200, 200, 200, 255});
int menuY = 180;
for (int i = 0; i < 3; i++) {
SDL_Color textColor = (i == selectedIdx) ? SDL_Color{255, 0, 0, 255} : SDL_Color{255, 255, 255, 255};
drawText(WINDOW_WIDTH / 2 - 30, menuY + i * 60, menuItems[i], textColor);
}
std::string goldText = "当前金币:" + std::to_string(gameData.gold) + " 枚";
drawText(WINDOW_WIDTH / 2 - 60, menuY + 180, goldText, {0, 255, 255, 255});
SDL_RenderPresent(renderer);
SDL_Delay(LOOP_DELAY);
}
}
// -------------------------- 4. 皮肤商店(SDL图形化) --------------------------
void showShopMenuSDL() {
const std::string shopItems[3] = {"兑换皮肤", "使用皮肤", "返回主菜单"};
int selectedIdx = 0;
bool running = true;
SDL_Event e;
while (running) {
// 事件处理
while (SDL_PollEvent(&e) != 0) {
if (e.type == SDL_QUIT) running = false;
else if (e.type == SDL_KEYDOWN) {
switch (e.key.keysym.sym) {
case SDLK_UP:
selectedIdx = (selectedIdx - 1 + 3) % 3;
break;
case SDLK_DOWN:
selectedIdx = (selectedIdx + 1) % 3;
break;
case SDLK_SPACE:
if (selectedIdx == 0) {
// 兑换皮肤提示
drawText(WINDOW_WIDTH / 2 - 80, 300, "请按 1/2/3 选择兑换的皮肤", {255, 255, 0, 255});
SDL_RenderPresent(renderer);
SDL_Delay(500);
bool wait = true;
while (wait) {
if (SDL_PollEvent(&e) != 0 && e.type == SDL_KEYDOWN) {
int skinChoice = e.key.keysym.sym - SDLK_1 + 1;
std::string tip = (skinChoice >= 1 && skinChoice <= 3 && exchangeSkin(skinChoice - 1))
? "兑换成功!" : "兑换失败(金币不足/已拥有)!";
drawText(WINDOW_WIDTH / 2 - 60, 350, tip, {0, 255, 0, 255});
SDL_RenderPresent(renderer);
SDL_Delay(1000);
wait = false;
}
}
} else if (selectedIdx == 1) {
// 使用皮肤提示
drawText(WINDOW_WIDTH / 2 - 80, 300, "请按 1/2/3 选择使用的皮肤", {255, 255, 0, 255});
SDL_RenderPresent(renderer);
SDL_Delay(500);
bool wait = true;
while (wait) {
if (SDL_PollEvent(&e) != 0 && e.type == SDL_KEYDOWN) {
int skinChoice = e.key.keysym.sym - SDLK_1 + 1;
std::string tip;
if (skinChoice >= 1 && skinChoice <= 3 && skins[skinChoice - 1].isOwned) {
useSkin(skinChoice - 1);
tip = "皮肤已切换为:" + skins[skinChoice - 1].name;
} else {
tip = "无效选择(未拥有该皮肤)!";
}
drawText(WINDOW_WIDTH / 2 - 100, 350, tip, {0, 255, 0, 255});
SDL_RenderPresent(renderer);
SDL_Delay(1000);
wait = false;
}
}
} else {
running = false; // 返回主菜单
}
break;
}
}
}
// 绘制商店界面
SDL_SetRenderDrawColor(renderer, 15, 15, 15, 255);
SDL_RenderClear(renderer);
drawText(WINDOW_WIDTH / 2 - 60, 30, "皮肤商店", {255, 200, 0, 255});
std::string goldText = "当前金币:" + std::to_string(gameData.gold) + " 枚";
drawText(WINDOW_WIDTH / 2 - 60, 70, goldText, {0, 255, 255, 255});
// 皮肤列表表头
drawText(50, 120, "编号 | 名称 | 价格 | 状态 | 颜色预览", {200, 200, 200, 255});
drawText(50, 140, "----------------------------------------", {200, 200, 200, 255});
// 绘制皮肤信息
int skinY = 170;
for (int i = 0; i < 3; i++) {
drawText(50, skinY, std::to_string(i + 1), {255, 255, 255, 255});
drawText(90, skinY, skins[i].name, {255, 255, 255, 255});
drawText(180, skinY, std::to_string(skins[i].price) + " 金币", {255, 255, 255, 255});
drawText(280, skinY, skins[i].isOwned ? "已拥有" : "未拥有",
skins[i].isOwned ? SDL_Color{0, 255, 0, 255} : SDL_Color{255, 0, 0, 255});
// 颜色预览方块
SDL_SetRenderDrawColor(renderer, skins[i].color.r, skins[i].color.g, skins[i].color.b, skins[i].color.a);
SDL_Rect colorRect = {380, skinY - 5, 20, 20};
SDL_RenderFillRect(renderer, &colorRect);
skinY += 40;
}
// 绘制商店操作选项
drawText(WINDOW_WIDTH / 2 - 100, 300, "================ 商店操作 ================", {255, 255, 255, 255});
drawText(WINDOW_WIDTH / 2 - 100, 330, "操作:↑上选 | ↓下选 | 空格确认", {200, 200, 200, 255});
int shopOptY = 370;
for (int i = 0; i < 3; i++) {
SDL_Color textColor = (i == selectedIdx) ? SDL_Color{255, 0, 0, 255} : SDL_Color{255, 255, 255, 255};
drawText(WINDOW_WIDTH / 2 - 40, shopOptY + i * 50, shopItems[i], textColor);
}
SDL_RenderPresent(renderer);
SDL_Delay(LOOP_DELAY);
}
}
// -------------------------- 5. 皮肤兑换与使用 --------------------------
bool exchangeSkin(int skinIdx) {
if (skins[skinIdx].isOwned || gameData.gold < skins[skinIdx].price) return false;
gameData.gold -= skins[skinIdx].price;
skins[skinIdx].isOwned = true;
return true;
}
void useSkin(int skinIdx) {
if (skinIdx >= 0 && skinIdx < 3 && skins[skinIdx].isOwned) {
currSkinIdx = skinIdx;
currSkinColor = skins[skinIdx].color;
}
}
// -------------------------- 6. 游戏核心逻辑 --------------------------
void initGame() {
memset(gameData.gameArea, 0, sizeof(gameData.gameArea));
gameData.gold = 0;
gameData.lineTotal = 0;
gameData.dropInterval = INIT_DROP_INTERVAL;
gameData.nextShape = rand() % 7;
createBlock();
}
void createBlock() {
gameData.currShape = gameData.nextShape;
gameData.currRot = 0;
gameData.currX = WIDTH / 2 - 2;
gameData.currY = 0;
gameData.nextShape = rand() % 7;
gameData.nextRot = 0;
}
bool checkCollision(int x, int y, int rot, int shape) {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (shapes[shape][rot] & (1 << (15 - (i * 4 + j)))) {
int nx = x + j;
int ny = y + i;
if (nx < 0 || nx >= WIDTH || ny >= HEIGHT) return true;
if (ny >= 0 && gameData.gameArea[ny][nx] != 0) return true;
}
}
}
return false;
}
void rotateBlock() {
int newRot = (gameData.currRot + 1) % 4;
if (!checkCollision(gameData.currX, gameData.currY, newRot, gameData.currShape)) {
gameData.currRot = newRot;
}
}
void moveBlock(int dx, int dy) {
int newX = gameData.currX + dx;
int newY = gameData.currY + dy;
if (!checkCollision(newX, newY, gameData.currRot, gameData.currShape)) {
gameData.currX = newX;
gameData.currY = newY;
} else if (dy == 1) { // 下落碰撞则固定方块
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (shapes[gameData.currShape][gameData.currRot] & (1 << (15 - (i * 4 + j)))) {
int ny = gameData.currY + i;
int nx = gameData.currX + j;
if (ny >= 0) gameData.gameArea[ny][nx] = 1;
}
}
}
clearLines();
createBlock();
}
}
void clearLines() {
int lines = 0;
for (int i = HEIGHT - 1; i >= 0; i--) {
bool full = true;
for (int j = 0; j < WIDTH; j++) {
if (gameData.gameArea[i][j] == 0) {
full = false;
break;
}
}
if (full) {
lines++;
for (int k = i; k > 0; k--) {
memcpy(gameData.gameArea[k], gameData.gameArea[k - 1], sizeof(int) * WIDTH);
}
memset(gameData.gameArea[0], 0, sizeof(int) * WIDTH);
i++; // 重新检查当前行
}
}
gameData.gold += lines * GOLD_PER_LINE;
gameData.lineTotal += lines;
// 加速逻辑
int level = gameData.lineTotal / SPEED_UP_LINES;
gameData.dropInterval = INIT_DROP_INTERVAL - level * DROP_SPEED_STEP;
if (gameData.dropInterval < MIN_DROP_INTERVAL) gameData.dropInterval = MIN_DROP_INTERVAL;
}
bool isGameOver() {
return checkCollision(gameData.currX, gameData.currY, gameData.currRot, gameData.currShape);
}
void drawGameSDL() {
// 清屏
SDL_SetRenderDrawColor(renderer, 15, 15, 15, 255);
SDL_RenderClear(renderer);
// 绘制游戏区边框
SDL_SetRenderDrawColor(renderer, 100, 100, 100, 255);
SDL_Rect gameRect = {10, 10, WIDTH * BLOCK_SIZE, HEIGHT * BLOCK_SIZE};
SDL_RenderDrawRect(renderer, &gameRect);
// 绘制已固定的方块
SDL_SetRenderDrawColor(renderer, currSkinColor.r, currSkinColor.g, currSkinColor.b, 200);
for (int i = 0; i < HEIGHT; i++) {
for (int j = 0; j < WIDTH; j++) {
if (gameData.gameArea[i][j] == 1) {
SDL_Rect blockRect = {10 + j * BLOCK_SIZE, 10 + i * BLOCK_SIZE, BLOCK_SIZE - 1, BLOCK_SIZE - 1};
SDL_RenderFillRect(renderer, &blockRect);
}
}
}
// 绘制当前下落的方块
SDL_SetRenderDrawColor(renderer, currSkinColor.r, currSkinColor.g, currSkinColor.b, 255);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (shapes[gameData.currShape][gameData.currRot] & (1 << (15 - (i * 4 + j)))) {
int x = 10 + (gameData.currX + j) * BLOCK_SIZE;
int y = 10 + (gameData.currY + i) * BLOCK_SIZE;
SDL_Rect blockRect = {x, y, BLOCK_SIZE - 1, BLOCK_SIZE - 1};
SDL_RenderFillRect(renderer, &blockRect);
}
}
}
// 绘制下一个方块预览
drawText(PREVIEW_X, 30, "下一个方块", {200, 200, 200, 255});
SDL_SetRenderDrawColor(renderer, 100, 100, 100, 255);
SDL_Rect previewRect = {PREVIEW_X - 5, PREVIEW_Y - 5, 4 * BLOCK_SIZE + 10, 4 * BLOCK_SIZE + 10};
SDL_RenderDrawRect(renderer, &previewRect);
SDL_SetRenderDrawColor(renderer, currSkinColor.r, currSkinColor.g, currSkinColor.b, 255);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (shapes[gameData.nextShape][gameData.nextRot] & (1 << (15 - (i * 4 + j)))) {
int x = PREVIEW_X + j * BLOCK_SIZE;
int y = PREVIEW_Y + i * BLOCK_SIZE;
SDL_Rect blockRect = {x, y, BLOCK_SIZE - 1, BLOCK_SIZE - 1};
SDL_RenderFillRect(renderer, &blockRect);
}
}
}
// 绘制信息面板
drawText(PREVIEW_X, PREVIEW_Y + 5 * BLOCK_SIZE, "金币:" + std::to_string(gameData.gold), {0, 255, 255, 255});
drawText(PREVIEW_X, PREVIEW_Y + 6 * BLOCK_SIZE, "消行:" + std::to_string(gameData.lineTotal), {0, 255, 255, 255});
drawText(PREVIEW_X, PREVIEW_Y + 8 * BLOCK_SIZE, "操作说明:", {200, 200, 200, 255});
drawText(PREVIEW_X, PREVIEW_Y + 9 * BLOCK_SIZE, "W=旋转 A=左移 D=右移", {200, 200, 200, 255});
drawText(PREVIEW_X, PREVIEW_Y + 10 * BLOCK_SIZE, "S=加速下落 Q=返回菜单", {200, 200, 200, 255});
}
void startGameSDL() {
initGame();
Uint32 lastDropTime = SDL_GetTicks();
bool backToMenu = false;
SDL_Event e;
while (!backToMenu) {
// 事件处理
while (SDL_PollEvent(&e) != 0) {
if (e.type == SDL_QUIT) backToMenu = true;
else if (e.type == SDL_KEYDOWN) {
switch (e.key.keysym.sym) {
case SDLK_w: rotateBlock(); break;
case SDLK_a: moveBlock(-1, 0); break;
case SDLK_d: moveBlock(1, 0); break;
case SDLK_s: moveBlock(0, 1); lastDropTime = SDL_GetTicks(); break;
case SDLK_q: backToMenu = true; break;
}
}
}
// 自动下落
Uint32 currTime = SDL_GetTicks();
if (currTime - lastDropTime >= gameData.dropInterval) {
moveBlock(0, 1);
lastDropTime = currTime;
}
// 绘制与游戏结束处理
if (isGameOver()) {
drawGameSDL();
drawText(WINDOW_WIDTH / 2 - 80, WINDOW_HEIGHT / 2, "游戏结束!", {255, 0, 0, 255});
drawText(WINDOW_WIDTH / 2 - 100, WINDOW_HEIGHT / 2 + 30, "最终金币:" + std::to_string(gameData.gold), {255, 255, 255, 255});
drawText(WINDOW_WIDTH / 2 - 100, WINDOW_HEIGHT / 2 + 60, "按Q返回菜单 | 其他键重启游戏", {255, 255, 255, 255});
SDL_RenderPresent(renderer);
// 等待按键
bool waitForKey = true;
while (waitForKey) {
if (SDL_PollEvent(&e) != 0 && e.type == SDL_KEYDOWN) {
if (e.key.keysym.sym == SDLK_q) backToMenu = true;
else initGame();
waitForKey = false;
}
}
} else {
drawGameSDL();
SDL_RenderPresent(renderer);
}
SDL_Delay(LOOP_DELAY);
}
}