当前位置:   article > 正文

c语言——俄罗斯方块_c语言俄罗斯方块

c语言俄罗斯方块

一、游戏效果



二. 游戏背景

俄罗斯方块是久负盛名的游戏,它也和贪吃蛇,扫雷等游戏位列经典游戏的⾏列。

《俄罗斯方块》(Tetris,俄文:Тетрис)是一款由俄罗斯人阿列克谢·帕基特诺夫于1984年6月发明的休闲游戏。

该游戏曾经被多家公司代理过。经过多轮诉讼后,该游戏的代理权最终被任天堂获得。 任天堂对于俄罗斯方块来说意义重大,因为将它与GB搭配在一起后,获得了巨大的成功。 

《俄罗斯方块》的基本规则是移动、旋转和摆放游戏自动输出的各种方块,使之排列成完整的一行或多行并且消除得分。

三、游戏开发日志

基本逻辑结构与贪吃蛇一致 

四、游戏实现

我们有了贪吃蛇的知识储备及了解WIN32 API,理解下面内容也就不难了。

4.1  游戏逻辑主体

  1. #include"tetris.h"
  2. int main()
  3. {
  4. //修改当前地区为本地模式,为了⽀持中⽂宽字符的打印
  5. setlocale(LC_ALL, "");
  6. srand((unsigned int)time(NULL));
  7. char ch=0;
  8. do
  9. {
  10. GameInit();
  11. GameRun();
  12. GameOver();
  13. system("cls");
  14. SetPos(20, 12);
  15. printf("再来一局吗?(Y/N):\n");
  16. SetPos(22, 15);
  17. ch = getchar();
  18. getchar();//清理\n
  19. } while (ch == 'y' || ch == 'Y');
  20. SetPos(0, 29);
  21. return 0;
  22. }

4.2  游戏初始化的实现

4.2.1  设置窗口大小、名称及隐藏光标

  1. //设置控制台窗⼝的⼤⼩,30⾏,30列
  2. system("mode con cols=60 lines=30");
  3. //设置cmd窗⼝名称
  4. system("title 俄罗斯方块");
  5. //获取标准输出的句柄(⽤来标识不同设备的数值)
  6. HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
  7. //影藏光标操作
  8. CONSOLE_CURSOR_INFO CursorInfo;
  9. GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
  10. CursorInfo.bVisible = false; //隐藏控制台光标
  11. SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态

4.2.2 打印欢迎界面 

  1. void WelcomeToGame()
  2. {
  3. SetPos(20, 10);
  4. printf("欢迎来到俄罗斯方块!");
  5. SetPos(20, 23);//让按任意键继续的出现的位置好看点
  6. system("pause");
  7. system("cls");
  8. SetPos(14, 10);
  9. printf("↑为变换.↓为加速.←为左移.→为右移\n");
  10. SetPos(20, 23);//让按任意键继续的出现的位置好看点
  11. system("pause");
  12. system("cls");
  13. }

 4.2.3  初始化地图

  1. void CreateMap()
  2. {
  3. memset(IF.pos, 0, sizeof(int)*30*60);//对数组初始化(如果不初始化,FAIL后第二次循环会IF.pos会保留上次数据)
  4. int i = 0;
  5. //墙体轮廓
  6. SetPos(40, 0);
  7. for (i = 40; i < 60; i += 2)
  8. {
  9. wprintf(L"%lc", WALL);
  10. }
  11. SetPos(0,29);
  12. for (i = 0; i < 60; i += 2)
  13. {
  14. IF.pos[i][29] = 1;
  15. wprintf(L"%lc", WALL);
  16. }
  17. //x是0,y从1开始增⻓
  18. for (i = 0; i < 30; i++)
  19. {
  20. IF.pos[0][i] = 1;//标记此处有方块
  21. SetPos(0, i);
  22. wprintf(L"%c", WALL);
  23. }
  24. for (i = 0; i < 30; i++)
  25. {
  26. IF.pos[38][i] = 1;
  27. SetPos(38, i);
  28. wprintf(L"%c", WALL);
  29. }
  30. for (i = 0; i < 30; i++)
  31. {
  32. IF.pos[58][i] = 1;
  33. SetPos(58, i);
  34. wprintf(L"%c", WALL);
  35. }
  36. SetPos(40, 10);
  37. for (i = 40; i < 60; i += 2)
  38. {
  39. IF.pos[i][10] = 1;
  40. wprintf(L"%lc", WALL);
  41. }
  42. //提示字体
  43. SetPos(40, 1);
  44. printf("下一个方块:");
  45. SetPos(44, 12);
  46. printf("左移:←");
  47. SetPos(44, 14);
  48. printf("右移:→");
  49. SetPos(44, 16);
  50. printf("加速:↓");
  51. SetPos(44, 18);
  52. printf("变换:↑");
  53. SetPos(44, 20);
  54. printf("暂停: 空格");
  55. SetPos(44, 22);
  56. printf("退出: Esc");
  57. SetPos(44, 24);
  58. printf("重新开始:R");
  59. SetPos(44, 26);
  60. printf("当前分数:%d", grade);
  61. }

注解: 

1)一个宽字符占2个字节,我们设置 cols=60 lines=30,实际就是长宽各30个字节大小

2)如上图所示,游戏展示区长为20,高为30,右上部分为下一个方块提示区域,正下面为游戏操作提示区

3)横轴为x轴,向右坐标逐渐增大,向下为y轴,坐标逐渐增大。(x,y)确定宽字符(小方块)的位置,因为宽字符(小方块)占2个字节宽度,所以x轴坐标没有奇数值

4)为了方便检测下落方块是否靠墙、落地及碰到其它方块,我们定义一个大数组pos[60][30],一一对应地图上每个宽字符(小方块)的位置,用大数组pos[60][30]记录墙体位置及落地的方块位置

  1. typedef struct InterFace
  2. {
  3. int pos[60][30]; //用于标记整个界面有方块的位置(1为有,0为无)
  4. }InterFace;
  5. InterFace IF;

4.2.4 初始化方块信息

  1. void InitBlockInfo()
  2. {
  3. //'T'字型
  4. int i = 0;
  5. for (i = 0; i < 3; i++)
  6. {
  7. block[0][0].space[1][i] = 1;
  8. }
  9. block[0][0].space[2][1] = 1;
  10. //‘L'字型
  11. for (i = 1; i < 4; i++)
  12. {
  13. block[1][0].space[i][1] = 1;
  14. }
  15. block[1][0].space[3][2] = 1;
  16. //'J'字形
  17. for (i = 1; i < 4; i++)
  18. {
  19. block[2][0].space[i][2] = 1;
  20. }
  21. block[2][0].space[3][1] = 1;
  22. //’Z'字型
  23. for (i = 0; i < 2; i++)
  24. {
  25. block[3][0].space[1][i] = 1;
  26. }
  27. for (i = 1; i < 3; i++)
  28. {
  29. block[3][0].space[2][i] = 1;
  30. }
  31. //‘S'字型
  32. for (i = 1; i < 3; i++)
  33. {
  34. block[4][0].space[1][i] = 1;
  35. }
  36. for (i = 0; i < 2; i++)
  37. {
  38. block[4][0].space[2][i] = 1;
  39. }
  40. //'田’字型
  41. int j = 0;
  42. for (i = 1; i < 3; i++)
  43. {
  44. for (j = 1; j < 3; j++)
  45. {
  46. block[5][0].space[i][j] = 1;
  47. }
  48. }
  49. //长条型
  50. for (i = 0; i < 4; i++)
  51. {
  52. block[6][0].space[i][1] = 1;
  53. }
  54. int m = 0;
  55. int n = 0;
  56. int tmp[4][4];
  57. for (i = 0; i < 7; i++)
  58. {
  59. block[i][0].speed = 200;//初始化下落速度
  60. block[i][0].blockstate = NORMAL;
  61. }
  62. for (i = 0; i < LINES; i++)
  63. {
  64. for (j = 0; j < COLS-1; j++)
  65. {
  66. for (m = 0; m < 4; m++)
  67. {
  68. for (n = 0; n < 4; n++)
  69. {
  70. tmp[m][n] = block[i][j].space[m][n];
  71. }
  72. }
  73. for (m = 0; m < 4; m++)
  74. {
  75. for (n = 0; n < 4; n++)
  76. {
  77. block[i][j + 1].space[n][3-m] = tmp[m][n];
  78. }
  79. }
  80. block[i][j + 1].speed = 200;//初始化下落速度
  81. block[i][j + 1].blockstate =NORMAL;//初始化方块状态
  82. }
  83. }
  84. }

注解: 

1)我们定义结构体二位数组,用于存储7种基本形状方块的各自的4种形态的信息,共28种。

  1. #define COLS 4
  2. #define LINES 7
  3. enum BlockState{CHANGE,LEFT,RIGHT,DOWN,NORMAL};
  4. typedef struct Block
  5. {
  6. int space[4][4];
  7. int speed;
  8. enum BlockState blockstate;
  9. }Block;
  10. Block block[LINES][COLS] ;//结构体二位数组,用于存储7种基本形状方块的各自的4种形态的信息,共28种

 2)首先初始化7种基本形状方块,然后基本形状方块通过顺时针旋转依次得到各自剩下的三种形态。

4.3  游戏运行的实现

  1. void GameRun()
  2. {
  3. int i = rand() % 7, j = rand() % 4; //随机获取方块的形状
  4. do
  5. {
  6. int x = 18;
  7. int y = 0;
  8. int next_i = rand() % 7, next_j = rand() % 4;
  9. PrintBlock(next_i, next_j, 46, 4);//绘制提示框图形
  10. PrintBlock(i, j, 18, 0);//绘制方块初始位置
  11. while(IsLegal(i, j, x, y ))//不合法,说明到底了
  12. {
  13. if (KEY_PRESS(VK_UP))
  14. {
  15. block[i][j].blockstate = CHANGE;
  16. }
  17. else if (KEY_PRESS(VK_DOWN))
  18. {
  19. block[i][j].blockstate = DOWN;
  20. }
  21. else if (KEY_PRESS(VK_LEFT))
  22. {
  23. block[i][j].blockstate = LEFT;//左移
  24. }
  25. else if (KEY_PRESS(VK_RIGHT))
  26. {
  27. block[i][j].blockstate = RIGHT;//右移
  28. }
  29. else if (KEY_PRESS(VK_SPACE))
  30. {
  31. pause();//暂停
  32. }
  33. else if (KEY_PRESS(VK_ESCAPE))
  34. {
  35. state = END;//退出
  36. break;
  37. }
  38. else if (KEY_PRESS(R))
  39. {
  40. state = RESTART;//重新开始
  41. break;
  42. }
  43. else//什么都没按
  44. {
  45. block[i][j].blockstate = NORMAL;
  46. }
  47. BlockMove(block, &i, &j, &x,&y);
  48. PrintBlock(i, j, x, y);//绘制方块位置
  49. Sleep(block[i][j].speed);//每一帧休息的时间
  50. }
  51. i = next_i;
  52. j = next_j;
  53. PrintSpace(i,j, 46, 4);//覆盖提示框图形
  54. } while (state ==RUN);
  55. }

4.3.1  合法判断(碰撞检测)

  1. //碰撞检测
  2. int IsLegal(int i, int j, int x, int y)
  3. {
  4. for (int m = 0; m < 4; m++)
  5. {
  6. for (int n = 0; n< 4; n++)
  7. {
  8. //如果方块落下的位置本来就已经有方块了,则不合法
  9. if ((block[i][j].space[m][n] == 1) && (IF.pos[x + n * 2][y + m] == 1))
  10. return 0; //不合法
  11. }
  12. }
  13. return 1; //合法
  14. }

4.3.2  覆盖方块

  1. void PrintSpace(int i, int j, int x, int y)//覆盖指定位置方块
  2. {
  3. for (int m = 0; m< 4; m++)
  4. {
  5. for (int n = 0; n < 4; n++)
  6. {
  7. if (block[i][j].space[m][n] == 1)
  8. {
  9. SetPos(x+2*n,y+m); //光标跳转到指定位置
  10. printf(" "); //打印空格覆盖(两个空格)
  11. }
  12. }
  13. }
  14. }

4.3.3  绘制方块

  1. void PrintBlock(int i, int j, int x, int y)
  2. {
  3. int m = 0;
  4. int n = 0;
  5. for (m = 0; m < 4; m++)
  6. {
  7. for (n = 0; n < 4; n++)
  8. {
  9. if (block[i][j].space[m][n] == 1)
  10. {
  11. //IF.pos[x+n*2][y+m] = 1;//记录该位置有方块
  12. SetPos(x + n * 2, y + m);
  13. wprintf(L"%c", BIOCK);
  14. }
  15. }
  16. }
  17. }

 4.3.4   判断满行消除

  1. int JudeIsErase()
  2. {
  3. int sign = 0;
  4. int i = 0;
  5. for (i = 28; i > 3; i--)
  6. {
  7. int count = 0;
  8. for (int j = 2; j < 38; j += 2)
  9. {
  10. if (IF.pos[j][i] == 1)
  11. {
  12. count++;
  13. }
  14. }
  15. if (count == 18)//整行全是方块
  16. {
  17. sign = 1;
  18. grade += 10;
  19. SetPos(44, 26);
  20. printf("当前分数:%d", grade);//更新当前分数
  21. for (int a = 2; a < 38; a += 2)
  22. {
  23. SetPos(a, i);
  24. printf(" ");
  25. IF.pos[a][i] = 0;
  26. }
  27. //将该行以上全部下移一格
  28. for (int m = i; m > 3; m--)
  29. {
  30. for (int n = 2; n < 38; n += 2)
  31. {
  32. IF.pos[n][m] = IF.pos[n][m - 1]; //将上一行方块移到下一行
  33. if (IF.pos[n][m]==1) //上一行移下来的是方块,打印方块
  34. {
  35. SetPos(n, m); //光标跳转到该位置
  36. wprintf(L"%c", BIOCK); //打印方块
  37. }
  38. else //上一行移下来的是空格,打印空格
  39. {
  40. SetPos(n, m); //光标跳转到该位置
  41. printf(" "); //打印空格(两个空格)
  42. }
  43. }
  44. }
  45. }
  46. }
  47. return sign;
  48. }

注:

1)用大数组pos[60][30],从下往上判断是否满行,如果满行,则将将该行以上全部下移一格,这样就达到消行的目的

2)若第十四第十三行都满行了,当检测到第十四行满行,将第十三行及以上全部下移一格,再次循环,从现十三行(原十二行)开始检测,此时会漏掉现十四行(原十三行),所以引进标识变量sign,只要有消行,则变为1,当sign=0时,表示全部方块没有满行了

4.4.5   判断游戏失败

  1. void JudeIsOver()
  2. {
  3. for (int i = 2; i < 38; i+=2)
  4. {
  5. if (IF.pos[i][1] == 1) //第一行有方块存在
  6. {
  7. state = FAIL;
  8. }
  9. }
  10. }

4.4.6  方块移动

  1. void BlockMove(Block block[][COLS], int *i, int *j, int *x, int *y)
  2. {
  3. switch (block[*i][*j].blockstate)
  4. {
  5. case CHANGE:
  6. if (IsLegal(*i, ((*j) + 1) % 4, *x, (*y )+1) == 1) //判断方块变化后(变化的同时下降一格)是否碰撞
  7. {
  8. //方块旋转后合法才进行以下操作
  9. PrintSpace(*i, *j, *x, *y); //用空格覆盖当前方块所在位置
  10. (*j) = ((*j) + 1) % 4;
  11. (*y)++;
  12. //PrintBlock(i, j, x, y);//绘制下一个变化的方块
  13. block[*i][*j].blockstate = NORMAL;
  14. block[*i][*j].speed = 200;
  15. }
  16. //else //方块再下落就碰撞了(已经到达底部)
  17. //{
  18. // //将当前方块的信息录入IF当中
  19. // for (int m = 0; m< 4; m++)
  20. // {
  21. // for (int n = 0; n< 4; n++)
  22. // {
  23. // if (block[*i][*j].space[m][n] == 1)
  24. // {
  25. // IF.pos[(*x )+n*2][(*y )+m] = 1; //将该位置标记为有方块
  26. //
  27. // }
  28. // }
  29. // }
  30. // while (JudeIsErase()); //判断此次方块下落是否得分
  31. // JudeIsOver();
  32. //}
  33. break;
  34. case LEFT:
  35. if (IsLegal(*i, *j,(*x )-2, *y ) == 1) //判断方块左移后是否合法
  36. {
  37. //方块左移后合法才进行以下操作
  38. PrintSpace(*i, *j, *x, *y); //用空格覆盖当前方块所在位置
  39. (*x) = (*x) - 2;
  40. block[*i][*j].blockstate = NORMAL;
  41. block[*i][*j].speed = 200;
  42. //PrintBlock(i, j, x, y);//绘制下一个变化的方块
  43. }
  44. break;
  45. case RIGHT:
  46. if (IsLegal(*i, *j, (*x) + 2, *y) == 1) //判断方块右移后是否合法
  47. {
  48. //方块右移后合法才进行以下操作
  49. PrintSpace(*i, *j, *x, *y); //用空格覆盖当前方块所在位置
  50. (*x) = (*x) + 2;
  51. block[*i][*j].blockstate = NORMAL;
  52. block[*i][*j].speed = 200;
  53. //PrintBlock(i, j, x, y);//绘制下一个变化的方块
  54. }
  55. break;
  56. case DOWN:
  57. if (IsLegal(*i, *j,* x , (*y) + 1) == 1) //判断方块下移后是否合法
  58. {
  59. //方块下移后合法才进行以下操作
  60. PrintSpace(*i,* j, *x,* y); //用空格覆盖当前方块所在位置
  61. (*y)++;
  62. block[*i][*j].blockstate = NORMAL;
  63. if (block[*i][*j].speed>50)//避免连续按,变成负数,会出现bug
  64. {
  65. block[*i][*j].speed -= 50;
  66. }
  67. }
  68. else //碰撞
  69. {
  70. //将当前方块的信息录入IF当中
  71. for (int m = 0; m < 4; m++)
  72. {
  73. for (int n = 0; n < 4; n++)
  74. {
  75. if (block[*i][*j].space[m][n] == 1)
  76. {
  77. IF.pos[(*x) + n * 2][(*y) + m] = 1; //将该位置标记为有方块
  78. }
  79. }
  80. }
  81. while (JudeIsErase()); //判断此次方块下落是否得分
  82. JudeIsOver();
  83. }
  84. break;
  85. case NORMAL:
  86. if (IsLegal(*i, *j,*x, (*y) + 1) == 1) //方块再下落合法
  87. {
  88. //方块右移后合法才进行以下操作
  89. PrintSpace(*i, *j, *x,* y); //用空格覆盖当前方块所在位置
  90. (*y)++;
  91. block[*i][*j].blockstate = NORMAL;
  92. block[*i][*j].speed=200;
  93. }
  94. else //碰撞
  95. {
  96. //将当前方块的信息录入IF当中
  97. for (int m = 0; m < 4; m++)
  98. {
  99. for (int n = 0; n < 4; n++)
  100. {
  101. if (block[*i][*j].space[m][n] == 1)
  102. {
  103. IF.pos[(*x) + n * 2][(*y) + m] = 1; //将该位置标记为有方块
  104. }
  105. }
  106. }
  107. while (JudeIsErase()); //判断此次方块下落是否得分
  108. JudeIsOver();
  109. }
  110. break;
  111. }
  112. }

注:

1)这里我们根据方块状态判断方块的下一步位置和形状,需要改变参数,所以传的是地址

2)只有在正常下落和加速下落才需要描述不合法时(碰撞时)状况,因为此时已经到底了。变换方块时不需要描述不合法(碰撞时)状况(因为'L'型,在变换时容易与边界发生碰撞,若此时将方块位置记录在pos[60][30]的大数组中,导致跳出该循环,进行下一个方块判断,导致该方块悬浮在半空的BUG)

4.4   游戏结束的实现

  1. enum GameState{ RUN = 1,END,FAIL,RESTART };
  2. enum GameState state;
  3. void GameOver()
  4. {
  5. switch (state)
  6. {
  7. case END:
  8. SetPos(14, 15);
  9. printf("您主动退出游戏\n");
  10. SetPos(14, 18);
  11. system("pause");
  12. break;
  13. case FAIL:
  14. Sleep(1000); //留给玩家反应时间
  15. system("cls"); //清空屏幕
  16. SetPos(20, 10);
  17. printf("小垃圾,游戏结束!\n");
  18. SetPos(20, 14);
  19. printf("你的最终得分为: %d ", grade);
  20. SetPos(20, 18);
  21. system("pause");
  22. break;
  23. case RESTART:
  24. system("cls"); //清空屏幕
  25. SetPos(26, 10);
  26. printf("重新开始!\n");
  27. SetPos(22, 14);
  28. printf("小垃圾,你准备好了吗?\n");
  29. Sleep(2000);
  30. main();
  31. break;
  32. }
  33. }

注:

1)对游戏的三个状态分别进行描述。

五、完整代码

5.1   tetris.h

  1. #include<Windows.h>
  2. #include <locale.h>
  3. #include<stdbool.h>
  4. #include<stdio.h>
  5. #include<stdlib.h>
  6. #include<time.h>
  7. #include<windows.h>
  8. #define WALL L'□'
  9. #define BIOCK L'■'
  10. #define COLS 4
  11. #define LINES 7
  12. #define R 0x52
  13. #define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)
  14. enum GameState{ RUN = 1,END,FAIL,RESTART };
  15. enum GameState state;
  16. enum BlockState{CHANGE,LEFT,RIGHT,DOWN,NORMAL};
  17. int grade;
  18. typedef struct InterFace
  19. {
  20. int pos[60][30]; //用于标记整个界面有方块的位置(1为有,0为无)
  21. }InterFace;
  22. InterFace IF;
  23. typedef struct Block
  24. {
  25. int space[4][4];
  26. int speed;
  27. enum BlockState blockstate;
  28. }Block;
  29. Block block[LINES][COLS] ;//结构体二位数组,用于存储7种基本形状方块的各自的4种形态的信息,共28种
  30. void GameInit();
  31. void CreateMap();
  32. void InitBlockInfo();
  33. void GameRun();
  34. void PrintBlock(int i,int j,int x,int y);//ij 为方块形状,x,y为打印坐标
  35. void BlockMove(Block block[][COLS], int *i, int *j, int *x, int *y);
  36. void PrintSpace(int i, int j, int x, int y);
  37. int JudeIsErase();
  38. void JudeIsOver();
  39. void GameOver();

5.2   tetris.c

  1. #include"tetris.h"
  2. //设置光标的坐标
  3. void SetPos(short x, short y)
  4. {
  5. COORD pos = { x, y };
  6. HANDLE hOutput = NULL;
  7. //获取标准输出的句柄(⽤来标识不同设备的数值)
  8. hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
  9. //设置标准输出上光标的位置为pos
  10. SetConsoleCursorPosition(hOutput, pos);
  11. }
  12. void WelcomeToGame()
  13. {
  14. SetPos(20, 10);
  15. printf("欢迎来到俄罗斯方块!");
  16. SetPos(20, 23);//让按任意键继续的出现的位置好看点
  17. system("pause");
  18. system("cls");
  19. SetPos(14, 10);
  20. printf("↑为变换.↓为加速.←为左移.→为右移\n");
  21. SetPos(20, 23);//让按任意键继续的出现的位置好看点
  22. system("pause");
  23. system("cls");
  24. }
  25. void CreateMap()
  26. {
  27. memset(IF.pos, 0, sizeof(int)*30*60);//对数组初始化(如果不初始化,FAIL后第二次循环会IF.pos会保留上次数据)
  28. int i = 0;
  29. //墙体轮廓
  30. SetPos(40, 0);
  31. for (i = 40; i < 60; i += 2)
  32. {
  33. wprintf(L"%lc", WALL);
  34. }
  35. SetPos(0,29);
  36. for (i = 0; i < 60; i += 2)
  37. {
  38. IF.pos[i][29] = 1;
  39. wprintf(L"%lc", WALL);
  40. }
  41. //x是0,y从1开始增⻓
  42. for (i = 0; i < 30; i++)
  43. {
  44. IF.pos[0][i] = 1;//标记此处有方块
  45. SetPos(0, i);
  46. wprintf(L"%c", WALL);
  47. }
  48. for (i = 0; i < 30; i++)
  49. {
  50. IF.pos[38][i] = 1;
  51. SetPos(38, i);
  52. wprintf(L"%c", WALL);
  53. }
  54. for (i = 0; i < 30; i++)
  55. {
  56. IF.pos[58][i] = 1;
  57. SetPos(58, i);
  58. wprintf(L"%c", WALL);
  59. }
  60. SetPos(40, 10);
  61. for (i = 40; i < 60; i += 2)
  62. {
  63. IF.pos[i][10] = 1;
  64. wprintf(L"%lc", WALL);
  65. }
  66. //提示字体
  67. SetPos(40, 1);
  68. printf("下一个方块:");
  69. SetPos(44, 12);
  70. printf("左移:←");
  71. SetPos(44, 14);
  72. printf("右移:→");
  73. SetPos(44, 16);
  74. printf("加速:↓");
  75. SetPos(44, 18);
  76. printf("变换:↑");
  77. SetPos(44, 20);
  78. printf("暂停: 空格");
  79. SetPos(44, 22);
  80. printf("退出: Esc");
  81. SetPos(44, 24);
  82. printf("重新开始:R");
  83. /*SetPos(44, 26);
  84. printf("最高纪录:%d", max);*/
  85. SetPos(44, 26);
  86. printf("当前分数:%d", grade);
  87. /*getchar();*/
  88. }
  89. void InitBlockInfo()
  90. {
  91. //'T'字型
  92. int i = 0;
  93. for (i = 0; i < 3; i++)
  94. {
  95. block[0][0].space[1][i] = 1;
  96. }
  97. block[0][0].space[2][1] = 1;
  98. //‘L'字型
  99. for (i = 1; i < 4; i++)
  100. {
  101. block[1][0].space[i][1] = 1;
  102. }
  103. block[1][0].space[3][2] = 1;
  104. //'J'字形
  105. for (i = 1; i < 4; i++)
  106. {
  107. block[2][0].space[i][2] = 1;
  108. }
  109. block[2][0].space[3][1] = 1;
  110. //’Z'字型
  111. for (i = 0; i < 2; i++)
  112. {
  113. block[3][0].space[1][i] = 1;
  114. }
  115. for (i = 1; i < 3; i++)
  116. {
  117. block[3][0].space[2][i] = 1;
  118. }
  119. //‘S'字型
  120. for (i = 1; i < 3; i++)
  121. {
  122. block[4][0].space[1][i] = 1;
  123. }
  124. for (i = 0; i < 2; i++)
  125. {
  126. block[4][0].space[2][i] = 1;
  127. }
  128. //'田’字型
  129. int j = 0;
  130. for (i = 1; i < 3; i++)
  131. {
  132. for (j = 1; j < 3; j++)
  133. {
  134. block[5][0].space[i][j] = 1;
  135. }
  136. }
  137. //长条型
  138. for (i = 0; i < 4; i++)
  139. {
  140. block[6][0].space[i][1] = 1;
  141. }
  142. int m = 0;
  143. int n = 0;
  144. int tmp[4][4];
  145. for (i = 0; i < 7; i++)
  146. {
  147. block[i][0].speed = 200;//初始化下落速度
  148. block[i][0].blockstate = NORMAL;
  149. }
  150. for (i = 0; i < LINES; i++)
  151. {
  152. for (j = 0; j < COLS-1; j++)
  153. {
  154. for (m = 0; m < 4; m++)
  155. {
  156. for (n = 0; n < 4; n++)
  157. {
  158. tmp[m][n] = block[i][j].space[m][n];
  159. }
  160. }
  161. for (m = 0; m < 4; m++)
  162. {
  163. for (n = 0; n < 4; n++)
  164. {
  165. block[i][j + 1].space[n][3 - m] = tmp[m][n];
  166. }
  167. }
  168. block[i][j + 1].speed = 200;//初始化下落速度
  169. block[i][j + 1].blockstate =NORMAL;//初始化方块状态
  170. }
  171. }
  172. }
  173. void GameInit()
  174. {
  175. //设置控制台窗⼝的⼤⼩,30⾏,30列
  176. system("mode con cols=60 lines=30");
  177. //设置cmd窗⼝名称
  178. system("title 俄罗斯方块");
  179. //获取标准输出的句柄(⽤来标识不同设备的数值)
  180. HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
  181. //影藏光标操作
  182. CONSOLE_CURSOR_INFO CursorInfo;
  183. GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
  184. CursorInfo.bVisible = false; //隐藏控制台光标
  185. SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
  186. //打印欢迎界⾯
  187. WelcomeToGame();
  188. //初始化地图
  189. CreateMap();
  190. //初始化方块信息
  191. InitBlockInfo();
  192. //初始化游戏状态
  193. state = RUN;
  194. }
  195. void PrintBlock(int i, int j, int x, int y)
  196. {
  197. int m = 0;
  198. int n = 0;
  199. for (m = 0; m < 4; m++)
  200. {
  201. for (n = 0; n < 4; n++)
  202. {
  203. if (block[i][j].space[m][n] == 1)
  204. {
  205. //IF.pos[x+n*2][y+m] = 1;//记录该位置有方块
  206. SetPos(x + n * 2, y + m);
  207. wprintf(L"%c", BIOCK);
  208. }
  209. }
  210. }
  211. }
  212. void pause()//暂停
  213. {
  214. while (1)
  215. {
  216. Sleep(300);
  217. if (KEY_PRESS(VK_SPACE))
  218. {
  219. break;
  220. }
  221. }
  222. }
  223. //碰撞检测
  224. int IsLegal(int i, int j, int x, int y)
  225. {
  226. for (int m = 0; m < 4; m++)
  227. {
  228. for (int n = 0; n< 4; n++)
  229. {
  230. //如果方块落下的位置本来就已经有方块了,则不合法
  231. if ((block[i][j].space[m][n] == 1) && (IF.pos[x + n * 2][y + m] == 1))
  232. return 0; //不合法
  233. }
  234. }
  235. return 1; //合法
  236. }
  237. void PrintSpace(int i, int j, int x, int y)//覆盖指定位置方块
  238. {
  239. for (int m = 0; m< 4; m++)
  240. {
  241. for (int n = 0; n < 4; n++)
  242. {
  243. if (block[i][j].space[m][n] == 1)
  244. {
  245. SetPos(x+2*n,y+m); //光标跳转到指定位置
  246. printf(" "); //打印空格覆盖(两个空格)
  247. }
  248. }
  249. }
  250. }
  251. int JudeIsErase()
  252. {
  253. int sign = 0;
  254. int i = 0;
  255. for (i = 28; i > 3; i--)
  256. {
  257. int count = 0;
  258. for (int j = 2; j < 38; j += 2)
  259. {
  260. if (IF.pos[j][i] == 1)
  261. {
  262. count++;
  263. }
  264. }
  265. if (count == 18)//整行全是方块
  266. {
  267. sign = 1;
  268. grade += 10;
  269. SetPos(44, 26);
  270. printf("当前分数:%d", grade);//更新当前分数
  271. for (int a = 2; a < 38; a += 2)
  272. {
  273. SetPos(a, i);
  274. printf(" ");
  275. IF.pos[a][i] = 0;
  276. }
  277. //将该行以上全部下移一格
  278. for (int m = i; m > 3; m--)
  279. {
  280. for (int n = 2; n < 38; n += 2)
  281. {
  282. IF.pos[n][m] = IF.pos[n][m - 1]; //将上一行方块移到下一行
  283. if (IF.pos[n][m]==1) //上一行移下来的是方块,打印方块
  284. {
  285. SetPos(n, m); //光标跳转到该位置
  286. wprintf(L"%c", BIOCK); //打印方块
  287. }
  288. else //上一行移下来的是空格,打印空格
  289. {
  290. SetPos(n, m); //光标跳转到该位置
  291. printf(" "); //打印空格(两个空格)
  292. }
  293. }
  294. }
  295. }
  296. }
  297. return sign;
  298. }
  299. void JudeIsOver()
  300. {
  301. for (int i = 2; i < 38; i+=2)
  302. {
  303. if (IF.pos[i][1] == 1) //第一行有方块存在
  304. {
  305. state = FAIL;
  306. }
  307. }
  308. }
  309. void BlockMove(Block block[][COLS], int *i, int *j, int *x, int *y)
  310. {
  311. switch (block[*i][*j].blockstate)
  312. {
  313. case CHANGE:
  314. if (IsLegal(*i, ((*j) + 1) % 4, *x, (*y )+1) == 1) //判断方块变化后(变化的同时下降一格)是否碰撞
  315. {
  316. //方块旋转后合法才进行以下操作
  317. PrintSpace(*i, *j, *x, *y); //用空格覆盖当前方块所在位置
  318. (*j) = ((*j) + 1) % 4;
  319. (*y)++;
  320. //PrintBlock(i, j, x, y);//绘制下一个变化的方块
  321. block[*i][*j].blockstate = NORMAL;
  322. block[*i][*j].speed = 200;
  323. }
  324. //else //方块再下落就碰撞了(已经到达底部)
  325. //{
  326. // //将当前方块的信息录入IF当中
  327. // for (int m = 0; m< 4; m++)
  328. // {
  329. // for (int n = 0; n< 4; n++)
  330. // {
  331. // if (block[*i][*j].space[m][n] == 1)
  332. // {
  333. // IF.pos[(*x )+n*2][(*y )+m] = 1; //将该位置标记为有方块
  334. //
  335. // }
  336. // }
  337. // }
  338. // while (JudeIsErase()); //判断此次方块下落是否得分
  339. // JudeIsOver();
  340. //}
  341. break;
  342. case LEFT:
  343. if (IsLegal(*i, *j,(*x )-2, *y ) == 1) //判断方块左移后是否合法
  344. {
  345. //方块左移后合法才进行以下操作
  346. PrintSpace(*i, *j, *x, *y); //用空格覆盖当前方块所在位置
  347. (*x) = (*x) - 2;
  348. block[*i][*j].blockstate = NORMAL;
  349. block[*i][*j].speed = 200;
  350. //PrintBlock(i, j, x, y);//绘制下一个变化的方块
  351. }
  352. break;
  353. case RIGHT:
  354. if (IsLegal(*i, *j, (*x) + 2, *y) == 1) //判断方块右移后是否合法
  355. {
  356. //方块右移后合法才进行以下操作
  357. PrintSpace(*i, *j, *x, *y); //用空格覆盖当前方块所在位置
  358. (*x) = (*x) + 2;
  359. block[*i][*j].blockstate = NORMAL;
  360. block[*i][*j].speed = 200;
  361. //PrintBlock(i, j, x, y);//绘制下一个变化的方块
  362. }
  363. break;
  364. case DOWN:
  365. if (IsLegal(*i, *j,* x , (*y) + 1) == 1) //判断方块下移后是否合法
  366. {
  367. //方块下移后合法才进行以下操作
  368. PrintSpace(*i,* j, *x,* y); //用空格覆盖当前方块所在位置
  369. (*y)++;
  370. block[*i][*j].blockstate = NORMAL;
  371. if (block[*i][*j].speed>50)//避免连续按,变成负数,会出现bug
  372. {
  373. block[*i][*j].speed -= 50;
  374. }
  375. }
  376. else //碰撞
  377. {
  378. //将当前方块的信息录入IF当中
  379. for (int m = 0; m < 4; m++)
  380. {
  381. for (int n = 0; n < 4; n++)
  382. {
  383. if (block[*i][*j].space[m][n] == 1)
  384. {
  385. IF.pos[(*x) + n * 2][(*y) + m] = 1; //将该位置标记为有方块
  386. }
  387. }
  388. }
  389. while (JudeIsErase()); //判断此次方块下落是否得分
  390. JudeIsOver();
  391. }
  392. break;
  393. case NORMAL:
  394. if (IsLegal(*i, *j,*x, (*y) + 1) == 1) //方块再下落合法
  395. {
  396. //方块右移后合法才进行以下操作
  397. PrintSpace(*i, *j, *x,* y); //用空格覆盖当前方块所在位置
  398. (*y)++;
  399. block[*i][*j].blockstate = NORMAL;
  400. block[*i][*j].speed=200;
  401. }
  402. else //碰撞
  403. {
  404. //将当前方块的信息录入IF当中
  405. for (int m = 0; m < 4; m++)
  406. {
  407. for (int n = 0; n < 4; n++)
  408. {
  409. if (block[*i][*j].space[m][n] == 1)
  410. {
  411. IF.pos[(*x) + n * 2][(*y) + m] = 1; //将该位置标记为有方块
  412. }
  413. }
  414. }
  415. while (JudeIsErase()); //判断此次方块下落是否得分
  416. JudeIsOver();
  417. }
  418. break;
  419. }
  420. }
  421. void GameRun()
  422. {
  423. int i = rand() % 7, j = rand() % 4; //随机获取方块的形状
  424. do
  425. {
  426. int x = 18;
  427. int y = 0;
  428. int next_i = rand() % 7, next_j = rand() % 4;
  429. PrintBlock(next_i, next_j, 46, 4);//绘制提示框图形
  430. PrintBlock(i, j, 18, 0);//绘制方块初始位置
  431. while(IsLegal(i, j, x, y ))//不合法,说明到底了
  432. {
  433. if (KEY_PRESS(VK_UP))
  434. {
  435. block[i][j].blockstate = CHANGE;
  436. }
  437. else if (KEY_PRESS(VK_DOWN))
  438. {
  439. block[i][j].blockstate = DOWN;
  440. }
  441. else if (KEY_PRESS(VK_LEFT))
  442. {
  443. block[i][j].blockstate = LEFT;//左移
  444. }
  445. else if (KEY_PRESS(VK_RIGHT))
  446. {
  447. block[i][j].blockstate = RIGHT;//右移
  448. }
  449. else if (KEY_PRESS(VK_SPACE))
  450. {
  451. pause();//暂停
  452. }
  453. else if (KEY_PRESS(VK_ESCAPE))
  454. {
  455. state = END;//退出
  456. break;
  457. }
  458. else if (KEY_PRESS(R))
  459. {
  460. state = RESTART;//重新开始
  461. break;
  462. }
  463. else//什么都没按
  464. {
  465. block[i][j].blockstate = NORMAL;
  466. }
  467. BlockMove(block, &i, &j, &x,&y);
  468. PrintBlock(i, j, x, y);//绘制方块位置
  469. Sleep(block[i][j].speed);//每一帧休息的时间
  470. }
  471. i = next_i;
  472. j = next_j;
  473. PrintSpace(i,j, 46, 4);//覆盖提示框图形
  474. } while (state ==RUN);
  475. }
  476. void GameOver()
  477. {
  478. switch (state)
  479. {
  480. case END:
  481. SetPos(14, 15);
  482. printf("您主动退出游戏\n");
  483. SetPos(14, 18);
  484. system("pause");
  485. break;
  486. case FAIL:
  487. Sleep(1000); //留给玩家反应时间
  488. system("cls"); //清空屏幕
  489. SetPos(20, 10);
  490. printf("小垃圾,游戏结束!\n");
  491. SetPos(20, 14);
  492. printf("你的最终得分为: %d ", grade);
  493. SetPos(20, 18);
  494. system("pause");
  495. break;
  496. case RESTART:
  497. system("cls"); //清空屏幕
  498. SetPos(26, 10);
  499. printf("重新开始!\n");
  500. SetPos(22, 14);
  501. printf("小垃圾,你准备好了吗?\n");
  502. Sleep(2000);
  503. main();
  504. break;
  505. }
  506. }

5.3  test.c

  1. #include"tetris.h"
  2. int main()
  3. {
  4. //修改当前地区为本地模式,为了⽀持中⽂宽字符的打印
  5. setlocale(LC_ALL, "");
  6. srand((unsigned int)time(NULL));
  7. char ch=0;
  8. do
  9. {
  10. GameInit();
  11. GameRun();
  12. GameOver();
  13. system("cls");
  14. SetPos(20, 12);
  15. printf("再来一局吗?(Y/N):\n");
  16. SetPos(22, 15);
  17. ch = getchar();
  18. getchar();//清理\n
  19. } while (ch == 'y' || ch == 'Y');
  20. SetPos(0, 29);
  21. return 0;
  22. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/641187
推荐阅读
相关标签
  

闽ICP备14008679号