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

描述

每天一个C语言小项目,提升你的编程能力!

俄罗斯方块是童年的经典游戏~~

由小方块组成的不同形状的板块陆续从屏幕上方落下来,玩家通过调整板块的位置和方向,使它们在屏幕底部拼出完整的一条或几条。这些完整的横条会随即消失,给新落下来的板块腾出空间,与此同时,玩家得到分数奖励。没有被消除掉的方块不断堆积起来,一旦堆到屏幕顶端,玩家便告输,游戏结束。

本次用C语言来实现!这个程序在界面上没有做额外的修饰,重点放在游戏手感和消息处理过程。游戏手感上做了一些细节处理,仔细体验感受一下。

效果如图:

C语言

其中也是加入了按键操作功能:"A"左移一格;"D"右移一格;"W"旋转方块;S 下移一格;空格键让方块下落到底),"ESC"健退出游戏,用户还可以自己根据自定义习惯的按键来操作游戏。

编译环境:Visual Studio 2019/2022,EasyX插件

代码展示:

 

#include 
#include 
#include 
#include 






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


#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("游戏结束。
您想重新来一局吗?"), _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();
}

 

大家赶紧去动手试试吧!

  审核编辑:汤梓红

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分