俄罗斯方块游戏

1 篇文章 0 订阅
0 篇文章 0 订阅

大二上学期前两周的课程设计写(更多的是借鉴)了一个500多行的俄罗斯方块游戏,毕竟也是花了两天时间把被人的看懂,然后花了两天多的时间敲出来,又花了两天时间写了4000多字的项目报告,所有有必要在博客中写下来以留作纪念。

struct BLOCK
{
    int dir[4]; // 方块的四个旋转状态
    COLORREF color; // 方块的颜色
}  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
};

用十六进制数转化为16位的二进制数,在将16位的二进制看成4*4的矩阵,1构成的图形即方块的形状。上面的代码表示7中形状的方块,每个方块有4中旋转状态和一种颜色。

for(i = 0; i < 16; i++, b <<= 1)
    {
        if (b & 0x8000)     
        {
            x = block.x + i % 4;
            y = block.y - i / 4;
            if (y < HIG)
                DrawUnit(x, y, Blocks[block.id].color);
        }
    }

上面的代码将十六进制代表的4*4矩阵中1所在的位置计算出来,发送给DrawUnit函数,DrawUnit函数将所有的1画出了,就形成了完整的方块。
if (b & 0x8000) b(十六进制数)和0x8000做与运算是为了测试最高位是否为1。每一次都是取b的最高一位来判断(b & 0x8000,这是位且,屏蔽了低位)。然后再用b <<= 1; 把次高位移到最高位。

游戏截图:
这里写图片描述

源代码(在VC++6.0中可运行,需要用到easyX图形库):

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

//////////////////////////////////////////////////
//定义常量、全局变量、结构体、枚举
//////////////////////////////////////////////////
#define WID 10  //宽为10个单元
#define HIG 20  //高为20个单元
#define UNIT 30 //单元方块的大小

struct BLOCK
{
    int dir[4]; // 方块的四个旋转状态
    COLORREF color; // 方块的颜色
}  Blocks[7] = {    
    {0x0F00, 0x4444, 0x0F00, 0x4444, RED},      // 1
    {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 BLOCKID
{
    int id; // 方块 ID
    char x, y;  // 方块在游戏区中的坐标
    unsigned int dir:2; //方向    dir:2 表示dir这个成员只用2个bit.
}NowBlock, NextBlock;

enum CMD
{
    ROTATE, LEFT, RIGHT, DOWN, SINK, QUIT
};

//时间变量
long int old_time, new_time;
//固定方块区
char FixBlock[WID][HIG] = {0};
//分数
int score;

//////////////////////////////////////////////////
//函数声明
//////////////////////////////////////////////////
void Init();
void NewGame();
void CreatBlock();
void DrawBlock(BLOCKID block);
void DrawUnit(int x, int y, COLORREF color);
void ClearBlock(BLOCKID block);
void ClearUnit(int x, int y, COLORREF color = BLACK);
CMD GetCommand();
void SendCommand(CMD cmd);
void Go_Down();
void Go_Left();
void Go_Right();
void Go_Sink();
void Go_Rotate();
bool CheckBlock(BLOCKID temp);
void GameOver();
void Quit();

//建立游戏窗口
void Init()
{
    //建立窗口并设置大小
    initgraph(500, 645);
    setbkcolor(TRANSPARENT);    // 设置图案填充的背景色为透明
    setorigin(20, 20);  // 设置坐标原点
    // 绘制游戏区边界
    rectangle(-1, -1, WID * UNIT, HIG * UNIT);      //大矩形
    line((WID + 1) * UNIT -1, -1,(WID + 1) * UNIT -1, HIG * UNIT);  //竖直线
    line((WID + 1) * UNIT -1, -1, 550, -1); //横线1
    line((WID + 1) * UNIT -1, 4 * UNIT, 550, 4 * UNIT);//横线2
    line((WID + 1) * UNIT -1, 8 * UNIT, 550, 8 * UNIT);//横线3
    line((WID + 1) * UNIT -1,  HIG * UNIT,550, HIG * UNIT);//横线4

    //插入操作说明
    settextstyle(22, 0, _T("黑体"));
    outtextxy((WID + 2) * UNIT, 9 * UNIT, _T("操作说明"));
    outtextxy((WID + 2) * UNIT, 11 * UNIT, _T("上:旋转"));
    outtextxy((WID + 2) * UNIT, 12.5 * UNIT, _T("左:左移"));
    outtextxy((WID + 2) * UNIT, 14 * UNIT, _T("右:右移"));
    outtextxy((WID + 2) * UNIT, 15.5 * UNIT, _T("下:加速"));
    outtextxy((WID + 2) * UNIT-8, 17 * UNIT, _T("空格:沉底"));
    outtextxy((WID + 2) * UNIT-4, 18.5 * UNIT, _T("ESC:退出"));
    settextstyle(36, 0, _T("隶书"));
    outtextxy((WID + 2) * UNIT, 4.5 * UNIT, _T("得分"));

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

//开始新游戏
void NewGame()
{
    // 清空游戏区
    setfillcolor(BLACK);
    solidrectangle(0, 0, WID * UNIT - 1, HIG * UNIT - 1);//实心的矩形
    ZeroMemory(FixBlock, WID * HIG);
    //FixBlock[WID][HIG] = 0;
    //memset((FixBlock),0,(WID * HIG));
    /*
    ZeroMemory函数
      void ZeroMemory( PVOID Destination, SIZE_T Length );
    参数
      Destination :指向一块准备用0来填充的内存区域的开始地址。
      Length :准备用0来填充的内存区域的大小,按字节来计算。
    */

    score = 0;  //初始化分数
    char s[10];
    sprintf(s, "%d", score);
    //显示分数
    settextstyle(40, 0, _T("黑体"));
    setfillcolor(BLACK);
    setlinecolor(BLACK);
    fillrectangle((WID + 1) * UNIT, 6 * UNIT, (WID + 10) * UNIT, 8 * UNIT - 1);
    outtextxy((WID + 2) * UNIT + 25, 6 * UNIT, _T(s));

    //初始化下一个方块
    srand((unsigned int)time(NULL));
    NextBlock.id = rand() % 7;
    NextBlock.x = WID + 1;
    NextBlock.y = HIG - 1;
    NextBlock.dir = rand() % 4;

    //创建新的方块
    CreatBlock();
}

void CreatBlock()
{
    NowBlock.x = 3;
    NowBlock.y = HIG + 2;
    NowBlock.id = NextBlock.id;
    NowBlock.dir = NextBlock.dir;

    NextBlock.id = rand() % 7;
    NextBlock.dir = rand() % 4;

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

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

//画方块
void DrawBlock(BLOCKID block)
{
    int b = Blocks[block.id].dir[block.dir];
    int x, y;
    int i;

    for(i = 0; i < 16; i++, b <<= 1)
    {
        if (b & 0x8000)     //和0x8000做与运算是为了测试最高位是否为1.
        {
            x = block.x + i % 4;
            y = block.y - i / 4;
            if (y < HIG)
                DrawUnit(x, y, Blocks[block.id].color);
        }
    }
    /*
    和0x8000做与运算是为了测试最高位是否为1.
    b左移一位的意义在与,b这个值以二进制来看,是否还有某位为1. 
    把每个俄罗斯图形都看成是一个4x4的矩阵,
    可以在纸上画个4x4的方块图,
    从左边第一列,从上往下开始,依次开始编码,
    如果某个块对应俄罗斯图为实心块,编码时为1,否则为0. 

    从for(int i=0; i<16; i++)可以看出,要画16次。
    每一次都是取b的最高一位来判断(b & 0x8000,这是位且,屏蔽了低位)。
    画完后再用b <<= 1; 把次高位移到最高位。
    */
}

void ClearBlock(BLOCKID block)
{
    int b = Blocks[block.id].dir[block.dir];
    int x, y;
    int i;

    for(i = 0; i < 16; i++, b <<= 1)
    {
        if (b & 0x8000)     //和0x8000做与运算是为了测试最高位是否为1.
        {
            x = block.x + i % 4;
            y = block.y - i / 4;
            if (y < HIG)
                ClearUnit(x, y, Blocks[block.id].color);
        }
    }
}

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

    // 画单元方块
    setfillcolor(color);
    setlinecolor(WHITE);
    fillrectangle(left + 2, top + 2, right - 2, bottom - 2);
}

void ClearUnit(int x, int y, COLORREF color)
{
    // 计算单元方块对应的屏幕坐标
    int left = x * UNIT;
    int top = (HIG - y - 1) * UNIT;
    int right = (x + 1) * UNIT - 1;
    int bottom = (HIG - y) * UNIT - 1;

    setfillcolor(BLACK);
    solidrectangle(x * UNIT, (HIG - y - 1) * UNIT, (x + 1) * UNIT - 1, (HIG - y) * UNIT - 1);
}

CMD GetCommand()
{
    CMD cmd;
    while(true)
    {
        new_time = GetTickCount();
        // 不控制,默认自动下落一格
        if (new_time - old_time > 500)
        {
            old_time = new_time;
            return DOWN;
        }
         // 如果有按键,返回按键对应的功能
        if (kbhit())
        {
            switch(getch())
            {
            case 72:
                cmd = ROTATE;
                return cmd;
            case 80:
                cmd = DOWN;
                return cmd;
            case 75:
                cmd = LEFT;
                return cmd;
            case 77:
                cmd = RIGHT;
                return cmd;
            case 27:    //ESC键
                cmd = QUIT;
                return cmd;
            case ' ':
                cmd = SINK;
                return cmd;
            }
        }
    }
}

void SendCommand(CMD cmd)
{
    switch(cmd)
    {
    case DOWN:
        Go_Down();
        break;
    case LEFT:
        Go_Left();
        break;
    case RIGHT:
        Go_Right();
        break;
    case ROTATE:
        Go_Rotate();
        break;
    case SINK:
        Go_Sink();
        break;
    case QUIT:
        // 按退出时,显示对话框咨询用户是否退出
        HWND wnd = GetHWnd();
        if (MessageBox(wnd, _T("您确定要退出游戏吗?"), _T("提醒"), MB_OKCANCEL | MB_ICONQUESTION) == IDOK)
            Quit();
    }
}

void Go_Down()
{
    BLOCKID temp = NowBlock;
    temp.y--;   

    if (CheckBlock(temp))
    {
        ClearBlock(NowBlock);
        NowBlock.y--;
        DrawBlock(NowBlock);
    }
    else
        Go_Sink();  //不能下移时,执行沉淀操作
}

void Go_Left()
{
    BLOCKID temp = NowBlock;
    temp.x--;

    if (CheckBlock(temp))
    {
        ClearBlock(NowBlock);
        NowBlock.x--;
        DrawBlock(NowBlock);
    }
}

void Go_Right()
{
    BLOCKID temp = NowBlock;
    temp.x++;

    if (CheckBlock(temp))
    {
        ClearBlock(NowBlock);
        NowBlock.x++;
        DrawBlock(NowBlock);
    }
}

//沉淀、消行
void Go_Sink()
{
    int x, y, i, n;
    int m = 0;  //可消的行数

    ClearBlock(NowBlock);
    BLOCKID temp = NowBlock;
    temp.y--;
    while (CheckBlock(temp))
    {
        NowBlock.y--;
        temp.y--;
    }

    DrawBlock(NowBlock);

    //固定方块
    int b = Blocks[NowBlock.id].dir[NowBlock.dir];
    for (i = 0; i < 16; i++, b = b << 1)
    {
        if (b & 0x8000)
        {
            x = NowBlock.x + i % 4;
            y = NowBlock.y - i / 4;

            if (y >= HIG)
            {
                GameOver();
                return;
            }
            else
                FixBlock[x][y] = 1;
        }
    }

    //4位用来标记方块涉及的 4 行是否有消除行为
    char remove = 0;        //标记是否要消掉行
    for (y = NowBlock.y; y >= 0; y--)
    {
        n = 0;
        for (x = 0; x < WID; x++)
        {
            if (FixBlock[x][y] == 1)
                n++;
        }

        if (n == WID)
        {
            m++;
            remove = remove | (1 << (NowBlock.y - y));
            setfillcolor(BLACK);
            fillrectangle(0, (HIG - y - 1) * UNIT , 
            WID * UNIT - 1, (HIG - y) * UNIT - 2);
        }
    }

    if(m != 0)
    {
        //更新分数并显示;
        if (m == 1)
            score = score + 10;
        else if (m == 2)
            score = score + 30;
        else if (m == 3 )
            score = score + 50;
        else
            score = score + 100;

        char s[10];
        sprintf(s, "%d", score);
        setfillcolor(BLACK);
        setlinecolor(BLACK);
        fillrectangle((WID + 1) * UNIT, 6 * UNIT, (WID + 10) * UNIT, 8 * UNIT - 1);
        settextstyle(40, 0, _T("黑体"));
        outtextxy((WID + 2) * UNIT + 15, 6 * UNIT, _T(s));
    }

    if (remove)
    {
        Sleep(200);
        // 擦掉刚才标记的行
        IMAGE img;  //C++类
        for(i = 0; i < 4; i++, remove >>= 1)
        {
            if (remove & 1)
            {
                for(y = NowBlock.y - i + 1; y < HIG; y++)
                    for(x = 0; x < WID; x++)
                    {
                        FixBlock[x][y - 1] = FixBlock[x][y];
                        FixBlock[x][y] = 0;
                    }
                    //调用getimage()保存屏幕图像
                    getimage(&img, 0, 0, WID * UNIT, (HIG - (NowBlock.y - i + 1)) * UNIT);
                    //用函数putimage()输出getimage()保存的屏幕图像。
                    putimage(0, UNIT, &img);
            }
        }
    }
    CreatBlock();
}

void Go_Rotate()
{
    // 获取可以旋转的 x 偏移量
    int dx;
    BLOCKID temp = NowBlock;
    temp.dir++;                  
    if (CheckBlock(temp))    
    {  
        dx = 0; 
        goto rotate; 
    }
    temp.x = NowBlock.x - 1;   
    if (CheckBlock(temp))    
    {   
        dx = -1;
        goto rotate;    
    }
    temp.x = NowBlock.x + 1;
    if (CheckBlock(temp))
    {
        dx = 1;
        goto rotate;
    }
    temp.x = NowBlock.x - 2;
    if (CheckBlock(temp))
    {
        dx = -2;
        goto rotate;
    }
    temp.x = NowBlock.x + 2;
    if (CheckBlock(temp))
    {
        dx = 2;
        goto rotate;
    }
    return;

rotate:
    // 旋转
    ClearBlock(NowBlock);
    NowBlock.dir++;
    NowBlock.x += dx;
    DrawBlock(NowBlock);
}


bool CheckBlock(BLOCKID temp)
{
    int b = Blocks[temp.id].dir[temp.dir];
    int x, y, i;

    for (i = 0; i < 16; i++, b = b << 1)
    {
        if (b & 0x8000)
        {
            x = temp.x + i % 4;
            y = temp.y - i / 4;
            if ((x < 0) || (x >= WID) || (y < 0))
                return false;
            if ((y < HIG) && (FixBlock[x][y]))
                return false;
        }
    }
    return true;
}

// 结束游戏
void GameOver()
{
    HWND wnd = GetHWnd();

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

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

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

    CMD cmd;
    while(true)
    {
        cmd = GetCommand();
        SendCommand(cmd);
    }

    getch();
    closegraph();
    return 0;
}

EasyX下载地址:http://easyx.cn/
参考网站:http://codebus.easyx.cn/krissi/post/tetris

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值