IE盒子

搜索
查看: 136|回复: 0

C语言零基础项目:俄罗斯方块游戏!详细思路+源码分享

[复制链接]

2

主题

9

帖子

15

积分

新手上路

Rank: 1

积分
15
发表于 2023-1-7 21:16:44 | 显示全部楼层 |阅读模式
每天一个C语言小项目,提升你的编程能力!
俄罗斯方块是童年的经典游戏~~
由小方块组成的不同形状的板块陆续从屏幕上方落下来,玩家通过调整板块的位置和方向,使它们在屏幕底部拼出完整的一条或几条。这些完整的横条会随即消失,给新落下来的板块腾出空间,与此同时,玩家得到分数奖励。没有被消除掉的方块不断堆积起来,一旦堆到屏幕顶端,玩家便告输,游戏结束。
本次用C语言来实现!这个程序在界面上没有做额外的修饰,重点放在游戏手感和消息处理过程。游戏手感上做了一些细节处理,仔细体验感受一下。
效果如图:


其中也是加入了按键操作功能:"A"左移一格;"D"右移一格;"W"旋转方块;S 下移一格;空格键让方块下落到底),"ESC"健退出游戏,用户还可以自己根据自定义习惯的按键来操作游戏。
编译环境:Visual Studio 2019/2022,EasyX插件
代码展示:

#include <stdio.h>
#include <graphics.h>
#include <conio.h>
#include <time.h>



/////////////////////////////////////////////
// 定义常量、枚举量、结构体、全局变量
/////////////////////////////////////////////

#define        WIDTH        10                // 游戏区宽度
#define        HEIGHT        22                // 游戏区高度
#define        UNIT        20                // 每个游戏区单位的实际像素

// 定义操作类型
enum CMD
{
        CMD_ROTATE,                                                // 方块旋转
        CMD_LEFT, CMD_RIGHT, CMD_DOWN,        // 方块左、右、下移动
        CMD_SINK,                                                // 方块沉底
        CMD_QUIT                                                // 退出游戏
};

// 定义绘制方块的方法
enum DRAW
{
        SHOW,        // 显示方块
        CLEAR,        // 擦除方块
        FIX                // 固定方块
};

// 定义七种俄罗斯方块
struct BLOCK
{
        WORD dir[4];        // 方块的四个旋转状态
        COLORREF color;        // 方块的颜色
};
BLOCK g_Blocks[7] = {        {0x0F00, 0x4444, 0x0F00, 0x4444, RED},                // I
                                                {0x0660, 0x0660, 0x0660, 0x0660, BLUE},                // 口
                                                {0x4460, 0x02E0, 0x0622, 0x0740, MAGENTA},        // L
                                                {0x2260, 0x0E20, 0x0644, 0x0470, YELLOW},        // 反 L
                                                {0x0C60, 0x2640, 0x0C60, 0x2640, CYAN},                // Z
                                                {0x0360, 0x4620, 0x0360, 0x4620, GREEN},        // 反 Z
                                                {0x4E00, 0x4C40, 0x0E40, 0x4640, BROWN}};        // T

// 定义当前方块、下一个方块的信息
struct BLOCKINFO
{
        byte id;        // 方块 ID
        char x, y;        // 方块在游戏区中的坐标
        byte dir:2;        // 方向
}        g_CurBlock, g_NextBlock;

// 定义游戏区
BYTE g_World[WIDTH][HEIGHT] = {0};



/////////////////////////////////////////////
// 函数声明
/////////////////////////////////////////////

void Init();                                                                                        // 初始化游戏
void Quit();                                                                                        // 退出游戏
void NewGame();                                                                                        // 开始新游戏
void GameOver();                                                                                // 结束游戏
CMD  GetCmd();                                                                                        // 获取控制命令
void DispatchCmd(CMD _cmd);                                                                // 分发控制命令
void NewBlock();                                                                                // 生成新的方块
bool CheckBlock(BLOCKINFO _block);                                                // 检测指定方块是否可以放下
void DrawUnit(int x, int y, COLORREF c, DRAW _draw);        // 画单元方块
void DrawBlock(BLOCKINFO _block, DRAW _draw = SHOW);        // 画方块
void OnRotate();                                                                                // 旋转方块
void OnLeft();                                                                                        // 左移方块
void OnRight();                                                                                        // 右移方块
void OnDown();                                                                                        // 下移方块
void OnSink();                                                                                        // 沉底方块



/////////////////////////////////////////////
// 函数定义
/////////////////////////////////////////////

// 主函数
void main()
{
        Init();

        CMD c;
        while(true)
        {
                c = GetCmd();
                DispatchCmd(c);

                // 按退出时,显示对话框咨询用户是否退出
                if (c == CMD_QUIT)
                {
                        HWND wnd = GetHWnd();
                        if (MessageBox(wnd, _T("您要退出游戏吗?"), _T("提醒"), MB_OKCANCEL | MB_ICONQUESTION) == IDOK)
                                Quit();
                }
        }
}


// 初始化游戏
void Init()
{
        initgraph(640, 480);
        srand((unsigned)time(NULL));
        setbkmode(TRANSPARENT);                        // 设置图案填充的背景色为透明

        // 显示操作说明
        settextstyle(14, 0, _T("宋体"));
        outtextxy(20, 330, _T("操作说明"));
        outtextxy(20, 350, _T("上:旋转"));
        outtextxy(20, 370, _T("左:左移"));
        outtextxy(20, 390, _T("右:右移"));
        outtextxy(20, 410, _T("下:下移"));
        outtextxy(20, 430, _T("空格:沉底"));
        outtextxy(20, 450, _T("ESC:退出"));

        // 设置坐标原点
        setorigin(220, 20);

        // 绘制游戏区边界
        rectangle(-1, -1, WIDTH * UNIT, HEIGHT * UNIT);
        rectangle((WIDTH + 1) * UNIT - 1, -1, (WIDTH + 5) * UNIT, 4 * UNIT);

        // 开始新游戏
        NewGame();
}


// 退出游戏
void Quit()
{
        closegraph();
        exit(0);
}


// 开始新游戏
void NewGame()
{
        // 清空游戏区
        setfillcolor(BLACK);
        solidrectangle(0, 0, WIDTH * UNIT - 1, HEIGHT * UNIT - 1);
        ZeroMemory(g_World, WIDTH * HEIGHT);

        // 生成下一个方块
        g_NextBlock.id = rand() % 7;
        g_NextBlock.dir = rand() % 4;
        g_NextBlock.x = WIDTH + 1;
        g_NextBlock.y = HEIGHT - 1;

        // 获取新方块
        NewBlock();
}


// 结束游戏
void GameOver()
{
        HWND wnd = GetHWnd();
        if (MessageBox(wnd, _T("游戏结束。\n您想重新来一局吗?"), _T("游戏结束"), MB_YESNO | MB_ICONQUESTION) == IDYES)
                NewGame();
        else
                Quit();
}


// 获取控制命令
DWORD m_oldtime;
CMD GetCmd()
{
        // 获取控制值
        while(true)
        {
                // 如果超时,自动下落一格
                DWORD newtime = GetTickCount();
                if (newtime - m_oldtime >= 500)
                {
                        m_oldtime = newtime;
                        return CMD_DOWN;
                }

                // 如果有按键,返回按键对应的功能
                if (_kbhit())
                {
                        switch(_getch())
                        {
                                case 'w':
                                case 'W':        return CMD_ROTATE;
                                case 'a':
                                case 'A':        return CMD_LEFT;
                                case 'd':
                                case 'D':        return CMD_RIGHT;
                                case 's':
                                case 'S':        return CMD_DOWN;
                                case 27:        return CMD_QUIT;
                                case ' ':        return CMD_SINK;
                                case 0:
                                case 0xE0:
                                        switch(_getch())
                                        {
                                                case 72:        return CMD_ROTATE;
                                                case 75:        return CMD_LEFT;
                                                case 77:        return CMD_RIGHT;
                                                case 80:        return CMD_DOWN;
                                        }
                        }
                }

                // 延时 (降低 CPU 占用率)
                Sleep(20);
        }
}


// 分发控制命令
void DispatchCmd(CMD _cmd)
{
        switch(_cmd)
        {
                case CMD_ROTATE:        OnRotate();                break;
                case CMD_LEFT:                OnLeft();                break;
                case CMD_RIGHT:                OnRight();                break;
                case CMD_DOWN:                OnDown();                break;
                case CMD_SINK:                OnSink();                break;
                case CMD_QUIT:                break;
        }
}


// 生成新的方块
void NewBlock()
{
        g_CurBlock.id = g_NextBlock.id,                g_NextBlock.id = rand() % 7;
        g_CurBlock.dir = g_NextBlock.dir,        g_NextBlock.dir = rand() % 4;
        g_CurBlock.x = (WIDTH - 4) / 2;
        g_CurBlock.y = HEIGHT + 2;

        // 下移新方块直到有局部显示
        WORD c = g_Blocks[g_CurBlock.id].dir[g_CurBlock.dir];
        while((c & 0xF) == 0)
        {
                g_CurBlock.y--;
                c >>= 4;
        }

        // 绘制新方块
        DrawBlock(g_CurBlock);

        // 绘制下一个方块
        setfillcolor(BLACK);
        solidrectangle((WIDTH + 1) * UNIT, 0, (WIDTH + 5) * UNIT - 1, 4 * UNIT - 1);
        DrawBlock(g_NextBlock);

        // 设置计时器,用于判断自动下落
        m_oldtime = GetTickCount();
}


// 画单元方块
void DrawUnit(int x, int y, COLORREF c, DRAW _draw)
{
        // 计算单元方块对应的屏幕坐标
        int left = x * UNIT;
        int top = (HEIGHT - y - 1) * UNIT;
        int right = (x + 1) * UNIT - 1;
        int bottom = (HEIGHT - y) * UNIT - 1;

        // 画单元方块
        switch(_draw)
        {
                case SHOW:
                        // 画普通方块
                        setlinecolor(0x006060);
                        roundrect(left + 1, top + 1, right - 1, bottom - 1, 5, 5);
                        setlinecolor(0x003030);
                        roundrect(left, top, right, bottom, 8, 8);
                        setfillcolor(c);
                        setlinecolor(LIGHTGRAY);
                        fillrectangle(left + 2, top + 2, right - 2, bottom - 2);
                        break;

                case FIX:
                        // 画固定的方块
                        setfillcolor(RGB(GetRValue(c) * 2 / 3, GetGValue(c) * 2 / 3, GetBValue(c) * 2 / 3));
                        setlinecolor(DARKGRAY);
                        fillrectangle(left + 1, top + 1, right - 1, bottom - 1);
                        break;

                case CLEAR:
                        // 擦除方块
                        setfillcolor(BLACK);
                        solidrectangle(x * UNIT, (HEIGHT - y - 1) * UNIT, (x + 1) * UNIT - 1, (HEIGHT - y) * UNIT - 1);
                        break;
        }
}


// 画方块
void DrawBlock(BLOCKINFO _block, DRAW _draw)
{
        WORD b = g_Blocks[_block.id].dir[_block.dir];
        int x, y;

        for(int i = 0; i < 16; i++, b <<= 1)
                if (b & 0x8000)
                {
                        x = _block.x + i % 4;
                        y = _block.y - i / 4;
                        if (y < HEIGHT)
                                DrawUnit(x, y, g_Blocks[_block.id].color, _draw);
                }
}


// 检测指定方块是否可以放下
bool CheckBlock(BLOCKINFO _block)
{
        WORD b = g_Blocks[_block.id].dir[_block.dir];
        int x, y;

        for(int i = 0; i < 16; i++, b <<= 1)
                if (b & 0x8000)
                {
                        x = _block.x + i % 4;
                        y = _block.y - i / 4;
                        if ((x < 0) || (x >= WIDTH) || (y < 0))
                                return false;

                        if ((y < HEIGHT) && (g_World[x][y]))
                                return false;
                }

        return true;
}


// 旋转方块
void OnRotate()
{
        // 获取可以旋转的 x 偏移量
        int dx;
        BLOCKINFO tmp = g_CurBlock;
        tmp.dir++;                                        if (CheckBlock(tmp))        {        dx = 0;                goto rotate;        }
        tmp.x = g_CurBlock.x - 1;        if (CheckBlock(tmp))        {        dx = -1;        goto rotate;        }
        tmp.x = g_CurBlock.x + 1;        if (CheckBlock(tmp))        {        dx = 1;                goto rotate;        }
        tmp.x = g_CurBlock.x - 2;        if (CheckBlock(tmp))        {        dx = -2;        goto rotate;        }
        tmp.x = g_CurBlock.x + 2;        if (CheckBlock(tmp))        {        dx = 2;                goto rotate;        }
        return;

rotate:
        // 旋转
        DrawBlock(g_CurBlock, CLEAR);
        g_CurBlock.dir++;
        g_CurBlock.x += dx;
        DrawBlock(g_CurBlock);
}


// 左移方块
void OnLeft()
{
        BLOCKINFO tmp = g_CurBlock;
        tmp.x--;
        if (CheckBlock(tmp))
        {
                DrawBlock(g_CurBlock, CLEAR);
                g_CurBlock.x--;
                DrawBlock(g_CurBlock);
        }
}


// 右移方块
void OnRight()
{
        BLOCKINFO tmp = g_CurBlock;
        tmp.x++;
        if (CheckBlock(tmp))
        {
                DrawBlock(g_CurBlock, CLEAR);
                g_CurBlock.x++;
                DrawBlock(g_CurBlock);
        }
}


// 下移方块
void OnDown()
{
        BLOCKINFO tmp = g_CurBlock;
        tmp.y--;
        if (CheckBlock(tmp))
        {
                DrawBlock(g_CurBlock, CLEAR);
                g_CurBlock.y--;
                DrawBlock(g_CurBlock);
        }
        else
                OnSink();        // 不可下移时,执行“沉底方块”操作
}


// 沉底方块
void OnSink()
{
        int i, x, y;

        // 连续下移方块
        DrawBlock(g_CurBlock, CLEAR);
        BLOCKINFO tmp = g_CurBlock;
        tmp.y--;
        while (CheckBlock(tmp))
        {
                g_CurBlock.y--;
                tmp.y--;
        }
        DrawBlock(g_CurBlock, FIX);

        // 固定方块在游戏区
        WORD b = g_Blocks[g_CurBlock.id].dir[g_CurBlock.dir];
        for(i = 0; i < 16; i++, b <<= 1)
                if (b & 0x8000)
                {
                        if (g_CurBlock.y - i / 4 >= HEIGHT)
                        {        // 如果方块的固定位置超出高度,结束游戏
                                GameOver();
                                return;
                        }
                        else
                                g_World[g_CurBlock.x + i % 4][g_CurBlock.y - i / 4] = 1;
                }

        // 检查是否需要消掉行,并标记
        BYTE remove = 0;        // 低 4 位用来标记方块涉及的 4 行是否有消除行为
        for(y = g_CurBlock.y; y >= max(g_CurBlock.y - 3, 0); y--)
        {
                i = 0;
                for(x = 0; x < WIDTH; x++)
                        if (g_World[x][y] == 1)
                                i++;

                if (i == WIDTH)
                {
                        remove |= (1 << (g_CurBlock.y - y));
                        setfillcolor(LIGHTGREEN);
                        setlinecolor(LIGHTGREEN);
                        setfillstyle(BS_HATCHED, HS_DIAGCROSS);
                        fillrectangle(0, (HEIGHT - y - 1) * UNIT + UNIT / 2 - 5, WIDTH * UNIT - 1, (HEIGHT - y - 1) * UNIT + UNIT / 2 + 5);
                        setfillstyle(BS_SOLID);
                }
        }

        if (remove)        // 如果产生整行消除
        {
                // 延时 300 毫秒
                Sleep(300);

                // 擦掉刚才标记的行
                IMAGE img;
                for(i = 0; i < 4; i++, remove >>= 1)
                {
                        if (remove & 1)
                        {
                                for(y = g_CurBlock.y - i + 1; y < HEIGHT; y++)
                                        for(x = 0; x < WIDTH; x++)
                                        {
                                                g_World[x][y - 1] = g_World[x][y];
                                                g_World[x][y] = 0;
                                        }

                                getimage(&img, 0, 0, WIDTH * UNIT, (HEIGHT - (g_CurBlock.y - i + 1)) * UNIT);
                                putimage(0, UNIT, &img);
                        }
                }
        }

        // 产生新方块
        NewBlock();
}
大家赶紧去动手试试吧!
此外,我也给大家分享我收集的其他资源,从最零基础开始的教程到C语言C++项目案例,帮助大家在学习C语言的道路上披荆斩棘!
C语言C++编程学习视频分享:




整理分享(多年学习的源码、项目实战视频、项目笔记,基础入门教程)最重要的是你可以在群里面交流提问编程问题哦!
欢迎转行和学习编程的伙伴,利用更多的资料学习成长比自己琢磨更快哦!
【C语言/C++/数据结构与算法粉丝学习群】
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表