俄罗斯方块(C语言实现)

文章目录

  • 游戏说明
  • 游戏效果展示
  • 游戏代码
  • 游戏代码详解
    • 游戏框架构建
    • 隐藏光标
    • 光标跳转
    • 初始化界面
    • 初始化方块信息
    • 颜色设置
    • 画出方块
    • 空格覆盖
    • 合法性判断
    • 判断得分与结束
    • 游戏主体逻辑函数
    • 从文件读取最高分
    • 更新最高分到文件
    • 主函数

游戏说明

俄罗斯方块相信大家都知道,这里就不再介绍什么游戏背景了,我这里对本代码实现的俄罗斯方块作一些说明

  1. 按方向键的左右键可实现方块的左右移动。
  2. 按方向键的下键可实现方块的加速下落。
  3. 按空格键可实现方块的顺时针旋转。
  4. 按Esc键可退出游戏。
  5. 按S键可暂停游戏,暂停游戏后按任意键继续游戏。
  6. 按R键可重新开始游戏。

除此之外,本游戏还拥有计分系统,可保存玩家的历史最高记录。

游戏效果展示

游戏代码

博友们可以将以下代码复制到自己的编译器当中运行:

#include <stdio.h> #include <Windows.h> #include <stdlib.h> #include <time.h> #include <conio.h> #define ROW 29 //游戏区行数 #define COL 20 //游戏区列数 #define DOWN 80 //方向键:下 #define LEFT 75 //方向键:左 #define RIGHT 77 //方向键:右 #define SPACE 32 //空格键 #define ESC 27 //Esc键 struct Face { int data[ROW][COL + 10]; //用于标记指定位置是否有方块(1为有,0为无) int color[ROW][COL + 10]; //用于记录指定位置的方块颜色编码 }face; struct Block { int space[4][4]; }block[7][4]; //用于存储7种基本形状方块的各自的4种形态的信息,共28种 //隐藏光标 void HideCursor(); //光标跳转 void CursorJump(int x, int y); //初始化界面 void InitInterface(); //初始化方块信息 void InitBlockInfo(); //颜色设置 void color(int num); //画出方块 void DrawBlock(int shape, int form, int x, int y); //空格覆盖 void DrawSpace(int shape, int form, int x, int y); //合法性判断 int IsLegal(int shape, int form, int x, int y); //判断得分与结束 int JudeFunc(); //游戏主体逻辑函数 void StartGame(); //从文件读取最高分 void ReadGrade(); //更新最高分到文件 void WriteGrade(); int max, grade; //全局变量 int main() { #pragma warning (disable:4996) //消除警告 max = 0, grade = 0; //初始化变量 system('title 俄罗斯方块'); //设置cmd窗口的名字 system('mode con lines=29 cols=60'); //设置cmd窗口的大小 HideCursor(); //隐藏光标 ReadGrade(); //从文件读取最高分到max变量 InitInterface(); //初始化界面 InitBlockInfo(); //初始化方块信息 srand((unsigned int)time(NULL)); //设置随机数生成的起点 StartGame(); //开始游戏 return 0; } //隐藏光标 void HideCursor() { CONSOLE_CURSOR_INFO curInfo; //定义光标信息的结构体变量 curInfo.dwSize = 1; //如果没赋值的话,隐藏光标无效 curInfo.bVisible = FALSE; //将光标设置为不可见 HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄 SetConsoleCursorInfo(handle, &curInfo); //设置光标信息 } //光标跳转 void CursorJump(int x, int y) { COORD pos; //定义光标位置的结构体变量 pos.X = x; //横坐标设置 pos.Y = y; //纵坐标设置 HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄 SetConsoleCursorPosition(handle, pos); //设置光标位置 } //初始化界面 void InitInterface() { color(7); //颜色设置为白色 for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL + 10; j++) { if (j == 0 || j == COL - 1 || j == COL + 9) { face.data[i][j] = 1; //标记该位置有方块 CursorJump(2 * j, i); printf('■'); } else if (i == ROW - 1) { face.data[i][j] = 1; //标记该位置有方块 printf('■'); } else face.data[i][j] = 0; //标记该位置无方块 } } for (int i = COL; i < COL + 10; i++) { face.data[8][i] = 1; //标记该位置有方块 CursorJump(2 * i, 8); printf('■'); } CursorJump(2 * COL, 1); printf('下一个方块:'); CursorJump(2 * COL + 4, ROW - 19); printf('左移:←'); CursorJump(2 * COL + 4, ROW - 17); printf('右移:→'); CursorJump(2 * COL + 4, ROW - 15); printf('加速:↓'); CursorJump(2 * COL + 4, ROW - 13); printf('旋转:空格'); CursorJump(2 * COL + 4, ROW - 11); printf('暂停: S'); CursorJump(2 * COL + 4, ROW - 9); printf('退出: Esc'); CursorJump(2 * COL + 4, ROW - 7); printf('重新开始:R'); CursorJump(2 * COL + 4, ROW - 5); printf('最高纪录:%d', max); CursorJump(2 * COL + 4, ROW - 3); printf('当前分数:%d', grade); } //初始化方块信息 void InitBlockInfo() { //“T”形 for (int i = 0; i <= 2; i++) block[0][0].space[1][i] = 1; block[0][0].space[2][1] = 1; //“L”形 for (int i = 1; i <= 3; i++) block[1][0].space[i][1] = 1; block[1][0].space[3][2] = 1; //“J”形 for (int i = 1; i <= 3; i++) block[2][0].space[i][2] = 1; block[2][0].space[3][1] = 1; for (int i = 0; i <= 1; i++) { //“Z”形 block[3][0].space[1][i] = 1; block[3][0].space[2][i + 1] = 1; //“S”形 block[4][0].space[1][i + 1] = 1; block[4][0].space[2][i] = 1; //“O”形 block[5][0].space[1][i + 1] = 1; block[5][0].space[2][i + 1] = 1; } //“I”形 for (int i = 0; i <= 3; i++) block[6][0].space[i][1] = 1; int temp[4][4]; for (int shape = 0; shape < 7; shape++) //7种形状 { for (int form = 0; form < 3; form++) //4种形态(已经有了一种,这里每个还需增加3种) { //获取第form种形态 for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { temp[i][j] = block[shape][form].space[i][j]; } } //将第form种形态顺时针旋转,得到第form+1种形态 for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { block[shape][form + 1].space[i][j] = temp[3 - j][i]; } } } } } //颜色设置 void color(int c) { switch (c) { case 0: c = 13; //“T”形方块设置为紫色 break; case 1: case 2: c = 12; //“L”形和“J”形方块设置为红色 break; case 3: case 4: c = 10; //“Z”形和“S”形方块设置为绿色 break; case 5: c = 14; //“O”形方块设置为黄色 break; case 6: c = 11; //“I”形方块设置为浅蓝色 break; default: c = 7; //其他默认设置为白色 break; } SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //颜色设置 //注:SetConsoleTextAttribute是一个API(应用程序编程接口) } //画出方块 void DrawBlock(int shape, int form, int x, int y) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (block[shape][form].space[i][j] == 1) //如果该位置有方块 { CursorJump(2 * (x + j), y + i); //光标跳转到指定位置 printf('■'); //输出方块 } } } } //空格覆盖 void DrawSpace(int shape, int form, int x, int y) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (block[shape][form].space[i][j] == 1) //如果该位置有方块 { CursorJump(2 * (x + j), y + i); //光标跳转到指定位置 printf(' '); //打印空格覆盖(两个空格) } } } } //合法性判断 int IsLegal(int shape, int form, int x, int y) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { //如果方块落下的位置本来就已经有方块了,则不合法 if ((block[shape][form].space[i][j] == 1) && (face.data[y + i][x + j] == 1)) return 0; //不合法 } } return 1; //合法 } //判断得分与结束 int JudeFunc() { //判断是否得分 for (int i = ROW - 2; i > 4; i--) { int sum = 0; //记录第i行的方块个数 for (int j = 1; j < COL - 1; j++) { sum += face.data[i][j]; //统计第i行的方块个数 } if (sum == 0) //该行没有方块,无需再判断其上的层次(无需再继续判断是否得分) break; //跳出循环 if (sum == COL - 2) //该行全是方块,可得分 { grade += 10; //满一行加10分 color(7); //颜色设置为白色 CursorJump(2 * COL + 4, ROW - 3); //光标跳转到显示当前分数的位置 printf('当前分数:%d', grade); //更新当前分数 for (int j = 1; j < COL - 1; j++) //清除得分行的方块信息 { face.data[i][j] = 0; //该位置得分后被清除,标记为无方块 CursorJump(2 * j, i); //光标跳转到该位置 printf(' '); //打印空格覆盖(两个空格) } //把被清除行上面的行整体向下挪一格 for (int m = i; m >1; m--) { sum = 0; //记录上一行的方块个数 for (int n = 1; n < COL - 1; n++) { sum += face.data[m - 1][n]; //统计上一行的方块个数 face.data[m][n] = face.data[m - 1][n]; //将上一行方块的标识移到下一行 face.color[m][n] = face.color[m - 1][n]; //将上一行方块的颜色编号移到下一行 if (face.data[m][n] == 1) //上一行移下来的是方块,打印方块 { CursorJump(2 * n, m); //光标跳转到该位置 color(face.color[m][n]); //颜色设置为还方块的颜色 printf('■'); //打印方块 } else //上一行移下来的是空格,打印空格 { CursorJump(2 * n, m); //光标跳转到该位置 printf(' '); //打印空格(两个空格) } } if (sum == 0) //上一行移下来的全是空格,无需再将上层的方块向下移动(移动结束) return 1; //返回1,表示还需调用该函数进行判断(移动下来的可能还有满行) } } } //判断游戏是否结束 for (int j = 1; j < COL - 1; j++) { if (face.data[1][j] == 1) //顶层有方块存在(以第1行为顶层,不是第0行) { Sleep(1000); //留给玩家反应时间 system('cls'); //清空屏幕 color(7); //颜色设置为白色 CursorJump(2 * (COL / 3), ROW / 2 - 3); if (grade>max) { printf('恭喜你打破最高记录,最高记录更新为%d', grade); WriteGrade(); } else if (grade == max) { printf('与最高记录持平,加油再创佳绩', grade); } else { printf('请继续加油,当前与最高记录相差%d', max - grade); } CursorJump(2 * (COL / 3), ROW / 2); printf('GAME OVER'); while (1) { char ch; CursorJump(2 * (COL / 3), ROW / 2 + 3); printf('再来一局?(y/n):'); scanf('%c', &ch); if (ch == 'y' || ch == 'Y') { system('cls'); main(); } else if (ch == 'n' || ch == 'N') { CursorJump(2 * (COL / 3), ROW / 2 + 5); exit(0); } else { CursorJump(2 * (COL / 3), ROW / 2 + 4); printf('选择错误,请再次选择'); } } } } return 0; //判断结束,无需再调用该函数进行判断 } //游戏主体逻辑函数 void StartGame() { int shape = rand() % 7, form = rand() % 4; //随机获取方块的形状和形态 while (1) { int t = 0; int nextShape = rand() % 7, nextForm = rand() % 4; //随机获取下一个方块的形状和形态 int x = COL / 2 - 2, y = 0; //方块初始下落位置的横纵坐标 color(nextShape); //颜色设置为下一个方块的颜色 DrawBlock(nextShape, nextForm, COL + 3, 3); //将下一个方块显示在右上角 while (1) { color(shape); //颜色设置为当前正在下落的方块 DrawBlock(shape, form, x, y); //将该方块显示在初始下落位置 if (t == 0) { t = 15000; //这里t越小,方块下落越快(可以根据此设置游戏难度) } while (--t) { if (kbhit() != 0) //若键盘被敲击,则退出循环 break; } if (t == 0) //键盘未被敲击 { if (IsLegal(shape, form, x, y + 1) == 0) //方块再下落就不合法了(已经到达底部) { //将当前方块的信息录入face当中 //face:记录界面的每个位置是否有方块,若有方块还需记录该位置方块的颜色。 for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (block[shape][form].space[i][j] == 1) { face.data[y + i][x + j] = 1; //将该位置标记为有方块 face.color[y + i][x + j] = shape; //记录该方块的颜色数值 } } } while (JudeFunc()); //判断此次方块下落是否得分以及游戏是否结束 break; //跳出当前死循环,准备进行下一个方块的下落 } else //未到底部 { DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置 y++; //纵坐标自增(下一次显示方块时就相当于下落了一格了) } } else //键盘被敲击 { char ch = getch(); //读取keycode switch (ch) { case DOWN: //方向键:下 if (IsLegal(shape, form, x, y + 1) == 1) //判断方块向下移动一位后是否合法 { //方块下落后合法才进行以下操作 DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置 y++; //纵坐标自增(下一次显示方块时就相当于下落了一格了) } break; case LEFT: //方向键:左 if (IsLegal(shape, form, x - 1, y) == 1) //判断方块向左移动一位后是否合法 { //方块左移后合法才进行以下操作 DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置 x--; //横坐标自减(下一次显示方块时就相当于左移了一格了) } break; case RIGHT: //方向键:右 if (IsLegal(shape, form, x + 1, y) == 1) //判断方块向右移动一位后是否合法 { //方块右移后合法才进行以下操作 DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置 x++; //横坐标自增(下一次显示方块时就相当于右移了一格了) } break; case SPACE: //空格键 if (IsLegal(shape, (form + 1) % 4, x, y + 1) == 1) //判断方块旋转后是否合法 { //方块旋转后合法才进行以下操作 DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置 y++; //纵坐标自增(总不能原地旋转吧) form = (form + 1) % 4; //方块的形态自增(下一次显示方块时就相当于旋转了) } break; case ESC: //Esc键 system('cls'); //清空屏幕 color(7); CursorJump(COL, ROW / 2); printf(' 游戏结束 '); CursorJump(COL, ROW / 2 + 2); exit(0); //结束程序 case 's': case 'S': //暂停 system('pause>nul'); //暂停(按任意键继续) break; case 'r': case 'R': //重新开始 system('cls'); //清空屏幕 main(); //重新执行主函数 } } } shape = nextShape, form = nextForm; //获取下一个方块的信息 DrawSpace(nextShape, nextForm, COL + 3, 3); //将右上角的方块信息用空格覆盖 } } //从文件读取最高分 void ReadGrade() { FILE* pf = fopen('俄罗斯方块最高得分记录.txt', 'r'); //以只读方式打开文件 if (pf == NULL) //打开文件失败 { pf = fopen('俄罗斯方块最高得分记录.txt', 'w'); //以只写方式打开文件(文件不存在可以自动创建该文件) fwrite(&grade, sizeof(int), 1, pf); //将max写入文件(此时max为0),即将最高历史得分初始化为0 } fseek(pf, 0, SEEK_SET); //使文件指针pf指向文件开头 fread(&max, sizeof(int), 1, pf); //读取文件中的最高历史得分到max当中 fclose(pf); //关闭文件 pf = NULL; //文件指针及时置空 } //更新最高分到文件 void WriteGrade() { FILE* pf = fopen('俄罗斯方块最高得分记录.txt', 'w'); //以只写方式打开文件 if (pf == NULL) //打开文件失败 { printf('保存最高得分记录失败\n'); exit(0); } fwrite(&grade, sizeof(int), 1, pf); //将本局游戏得分写入文件当中(更新最高历史得分) fclose(pf); //关闭文件 pf = NULL; //文件指针及时置空 }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
  • 444
  • 445
  • 446
  • 447
  • 448
  • 449
  • 450
  • 451
  • 452
  • 453
  • 454
  • 455
  • 456
  • 457
  • 458
  • 459
  • 460
  • 461
  • 462
  • 463
  • 464
  • 465
  • 466
  • 467
  • 468
  • 469
  • 470
  • 471
  • 472
  • 473
  • 474
  • 475
  • 476
  • 477
  • 478
  • 479
  • 480
  • 481
  • 482
  • 483
  • 484
  • 485
  • 486
  • 487
  • 488
  • 489
  • 490
  • 491
  • 492
  • 493
  • 494
  • 495
  • 496
  • 497
  • 498
  • 499
  • 500
  • 501
  • 502
  • 503
  • 504
  • 505
  • 506
  • 507
  • 508
  • 509
  • 510
  • 511
  • 512
  • 513
  • 514
  • 515
  • 516
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
  • 444
  • 445
  • 446
  • 447
  • 448
  • 449
  • 450
  • 451
  • 452
  • 453
  • 454
  • 455
  • 456
  • 457
  • 458
  • 459
  • 460
  • 461
  • 462
  • 463
  • 464
  • 465
  • 466
  • 467
  • 468
  • 469
  • 470
  • 471
  • 472
  • 473
  • 474
  • 475
  • 476
  • 477
  • 478
  • 479
  • 480
  • 481
  • 482
  • 483
  • 484
  • 485
  • 486
  • 487
  • 488
  • 489
  • 490
  • 491
  • 492
  • 493
  • 494
  • 495
  • 496
  • 497
  • 498
  • 499
  • 500
  • 501
  • 502
  • 503
  • 504
  • 505
  • 506
  • 507
  • 508
  • 509
  • 510
  • 511
  • 512
  • 513
  • 514
  • 515
  • 516

游戏代码详解

游戏框架构建

首先我们定义一下界面的大小,我们这里定义游戏区的行数和列数。

#define ROW 29 //游戏区行数
#define COL 20 //游戏区列数
  • 1
  • 2
  • 1
  • 2

我这里将方块堆积的区域称为游戏区,将按键提示以及方块提示的区域称为提示区。


我们还需要一个结构体,该结构体记录界面的每个位置是否有方块,若有方块还需记录该位置方块的颜色。

struct Face { int data[ROW][COL + 10]; //用于标记指定位置是否有方块(1为有,0为无) int color[ROW][COL + 10]; //用于记录指定位置的方块颜色编码 }face;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

其次,我们还需要一个结构体,该结构体当中存储着一个4行4列的二维数组,这个二维数组就用于存储单个方块的基本信息。(众所周知,4行4列的二维数组可以容纳下游戏当中的每一种方块)

而俄罗斯方块当中有7种基本形状的方块,而每种方块通过旋转后又可以得到3种方块,共28种。

因此,我们可以用该结构体定义一个7行4列的二维数组存储这28个方块的信息。

struct Block
{
int space[4][4];
}block[7][4]; //用于存储7种基本形状方块的各自的4种形态的信息,共28种
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

做到这里框架已经基本构建好了,为了提高代码的可读性,我们再根据需要用到的按键的键码值对其进行宏定义。

#define DOWN 80 //方向键:下 #define LEFT 75 //方向键:左 #define RIGHT 77 //方向键:右 #define SPACE 32 //空格键 #define ESC 27 //Esc键

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

隐藏光标

光标的作用在于提醒使用者,你接下来的输入将会在该位置出现。但在进行游戏时我们并不需要用到光标,光标在那里一闪一闪的显然是不行的,这时我们需要将光标隐藏。

//隐藏光标
void HideCursor()
{
CONSOLE_CURSOR_INFO curInfo; //定义光标信息的结构体变量
curInfo.dwSize = 1;  //如果没赋值的话,隐藏光标无效
curInfo.bVisible = FALSE; //将光标设置为不可见
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄
SetConsoleCursorInfo(handle, &curInfo); //设置光标信息
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

其中,关键结构CONSOLE_CURSOR_INFO在其头文件当中的内容如下:


设置光标信息函数在其头文件中的声明如下:

光标跳转

在屏幕上进行输出时,我们需要光标先移动到目标位置再进行输出,因此,光标跳转函数也是必不可少的。

//光标跳转 void CursorJump(int x, int y) { COORD pos; //定义光标位置的结构体变量 pos.X = x; //横坐标设置 pos.Y = y; //纵坐标设置 HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄 SetConsoleCursorPosition(handle, pos); //设置光标位置 }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

其中,关键结构COORD在其头文件当中的内容如下:

设置光标位置函数在其头文件中的声明如下:

初始化界面

初始化界面完成基本信息的打印,包括由白色方块构成的边界和按键提示语句。

对照最终效果图片,看着代码很好理解,但是需要注意两点

  1. 一个小方块在cmd命令窗口当中占两个单位的横坐标、一个单位的纵坐标。
  2. 光标跳转函数CursorJump接收的是光标将要跳至的横纵坐标。

例如,想要将光标跳转到 i 行 j 列(这里所说的行和列都是以一个方块为单位),就等价于让光标跳转到坐标(2*j,i)处。

//初始化界面
void InitInterface()
{
color(7); //颜色设置为白色
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < COL + 10; j++)
{
if (j == 0 || j == COL - 1 || j == COL + 9)
{
face.data[i][j] = 1; //标记该位置有方块
CursorJump(2 * j, i);
printf('■');
}
else if (i == ROW - 1)
{
face.data[i][j] = 1; //标记该位置有方块
printf('■');
}
else
face.data[i][j] = 0; //标记该位置无方块
}
}
for (int i = COL; i < COL + 10; i++)
{
face.data[8][i] = 1; //标记该位置有方块
CursorJump(2 * i, 8);
printf('■');
}

CursorJump(2 * COL, 1);
printf('下一个方块:');

CursorJump(2 * COL + 4, ROW - 19);
printf('左移:←');

CursorJump(2 * COL + 4, ROW - 17);
printf('右移:→');

CursorJump(2 * COL + 4, ROW - 15);
printf('加速:↓');

CursorJump(2 * COL + 4, ROW - 13);
printf('旋转:空格');

CursorJump(2 * COL + 4, ROW - 11);
printf('暂停: S');

CursorJump(2 * COL + 4, ROW - 9);
printf('退出: Esc');

CursorJump(2 * COL + 4, ROW - 7);
printf('重新开始:R');

CursorJump(2 * COL + 4, ROW - 5);
printf('最高纪录:%d', max);

CursorJump(2 * COL + 4, ROW - 3);
printf('当前分数:%d', grade);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

初始化方块信息

上面说到俄罗斯方块有7种基本形状,便是以下7种:


我们先将这7种基本形状的方块信息存储在各自的第0种形态处,如下:


然后取第0种形态顺时针旋转后得到第1种形态,取第1种形态顺时针旋转后得到第2种形态,取第2种形态顺时针旋转后得到第3种形态。这7种形状都按此方法操作,最终得到全部28种方块信息,如下:


在旋转过程中,一个方块顺时针旋转一次后其位置变换规律如下:

//初始化方块信息 void InitBlockInfo() { //“T”形 for (int i = 0; i <= 2; i++) block[0][0].space[1][i] = 1; block[0][0].space[2][1] = 1; //“L”形 for (int i = 1; i <= 3; i++) block[1][0].space[i][1] = 1; block[1][0].space[3][2] = 1; //“J”形 for (int i = 1; i <= 3; i++) block[2][0].space[i][2] = 1; block[2][0].space[3][1] = 1; for (int i = 0; i <= 1; i++) { //“Z”形 block[3][0].space[1][i] = 1; block[3][0].space[2][i + 1] = 1; //“S”形 block[4][0].space[1][i + 1] = 1; block[4][0].space[2][i] = 1; //“O”形 block[5][0].space[1][i + 1] = 1; block[5][0].space[2][i + 1] = 1; } //“I”形 for (int i = 0; i <= 3;i++) block[6][0].space[i][1] = 1; int temp[4][4]; for (int shape = 0; shape < 7; shape++) //7种形状 { for (int form = 0; form < 3; form++) //4种形态(已经有了一种,这里每个还需增加3种) { //获取第form种形态 for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { temp[i][j] = block[shape][form].space[i][j]; } } //将第form种形态顺时针旋转,得到第form+1种形态 for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { block[shape][form + 1].space[i][j] = temp[3 - j][i]; } } } } }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

颜色设置

这里的颜色设置函数所接收的参数c(0~6),代表7种形状的方块,每种方块对应自己的颜色,所对应的颜色可以自己设置。

//颜色设置
void color(int c)
{
switch (c)
{
case 0:
c = 13; //“T”形方块设置为紫色
break;
case 1:
case 2:
c = 12; //“L”形和“J”形方块设置为红色
break;
case 3:
case 4:
c = 10; //“Z”形和“S”形方块设置为绿色
break;
case 5:
c = 14; //“O”形方块设置为黄色
break;
case 6:
c = 11; //“I”形方块设置为浅蓝色
break;
default:
c = 7; //其他默认设置为白色
break;
}
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //颜色设置
//注:SetConsoleTextAttribute是一个API(应用程序编程接口)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

设置颜色函数在其头文件中的声明如下:

画出方块

方块的信息有了,接下来就是将方块在屏幕上显示出来。该函数的作用是,将第shape种形状的第form种形态的方块打印在屏幕的指定位置处。


所给x和y,指的是方块信息当中第一行第一列的方块的打印位置。

//画出方块 void DrawBlock(int shape, int form, int x, int y) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (block[shape][form].space[i][j] == 1) //如果该位置有方块 { CursorJump(2 * (x + j), y + i); //光标跳转到指定位置 printf('■'); //输出方块 } } } }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

空格覆盖

无论是游戏区方块的移动,还是提示区右上角下一个方块的显示,都需要方块位置的变换,而在变化之前肯定是要先将之前打印的方块用空格进行覆盖,然后再打印变化后的方块。

在覆盖方块时特别需要注意的是,要覆盖一个小方块需要用两个空格。

//空格覆盖
void DrawSpace(int shape, int form, int x, int y)
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
if (block[shape][form].space[i][j] == 1) //如果该位置有方块
{
CursorJump(2 * (x + j), y + i); //光标跳转到指定位置
printf('  '); //打印空格覆盖(两个空格)
}
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

合法性判断

其实在方块移动过程中,无时无刻都在判断方块下一次变化后的位置是否合法,只有合法才会允许该变化的进行。

所谓非法,就是指该方块进行了该变化后落在了本来就有方块的位置。

//合法性判断 int IsLegal(int shape, int form, int x, int y) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { //如果方块落下的位置本来就已经有方块了,则不合法 if ((block[shape][form].space[i][j] == 1) && (face.data[y + i][x + j] == 1)) return 0; //不合法 } } return 1; //合法 }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

判断得分与结束

判断得分:
从下往上判断,若某一行方块全满,则将改行方块数据清空,并将该行上方的方块全部下移,下移结束后返回1,表示还需再次调用该函数进行判断,因为被下移的行并没有进行判断,可能还存在满行。

判断结束:

  1. 直接判断游戏区最上面的一行当中是否有方块存在,若存在方块,则游戏结束。
  2. 游戏结束后,除了给出游戏结束提示语之外,如果玩家本局游戏分数大于历史最高记录,则需要更新最高分到文件当中。
  3. 游戏结束后询问玩家是否再来一局。
//判断得分与结束
int JudeFunc()
{
//判断是否得分
for (int i = ROW - 2; i > 4; i--)
{
int sum = 0; //记录第i行的方块个数
for (int j = 1; j < COL - 1; j++)
{
sum += face.data[i][j]; //统计第i行的方块个数
}
if (sum == 0) //该行没有方块,无需再判断其上的层次(无需再继续判断是否得分)
break; //跳出循环
if (sum == COL - 2) //该行全是方块,可得分
{
grade += 10; //满一行加10分
color(7); //颜色设置为白色
CursorJump(2 * COL + 4, ROW - 3); //光标跳转到显示当前分数的位置
printf('当前分数:%d', grade); //更新当前分数
for (int j = 1; j < COL - 1; j++) //清除得分行的方块信息
{
face.data[i][j] = 0; //该位置得分后被清除,标记为无方块
CursorJump(2 * j, i); //光标跳转到该位置
printf('  '); //打印空格覆盖(两个空格)
}
//把被清除行上面的行整体向下挪一格
for (int m = i; m >1; m--)
{
sum = 0; //记录上一行的方块个数
for (int n = 1; n < COL - 1; n++)
{
sum += face.data[m - 1][n]; //统计上一行的方块个数
face.data[m][n] = face.data[m - 1][n]; //将上一行方块的标识移到下一行
face.color[m][n] = face.color[m - 1][n]; //将上一行方块的颜色编号移到下一行
if (face.data[m][n] == 1) //上一行移下来的是方块,打印方块
{
CursorJump(2 * n, m); //光标跳转到该位置
color(face.color[m][n]); //颜色设置为还方块的颜色
printf('■'); //打印方块
}
else //上一行移下来的是空格,打印空格
{
CursorJump(2 * n, m); //光标跳转到该位置
printf('  '); //打印空格(两个空格)
}
}
if (sum == 0) //上一行移下来的全是空格,无需再将上层的方块向下移动(移动结束)
return 1; //返回1,表示还需调用该函数进行判断(移动下来的可能还有满行)
}
}
}
//判断游戏是否结束
for (int j = 1; j < COL - 1; j++)
{
if (face.data[1][j] == 1) //顶层有方块存在(以第1行为顶层,不是第0行)
{
Sleep(1000); //留给玩家反应时间
system('cls'); //清空屏幕
color(7); //颜色设置为白色
CursorJump(2 * (COL / 3), ROW / 2 - 3);
if (grade>max)
{
printf('恭喜你打破最高记录,最高记录更新为%d', grade);
WriteGrade();
}
else if (grade == max)
{
printf('与最高记录持平,加油再创佳绩', grade);
}
else
{
printf('请继续加油,当前与最高记录相差%d', max - grade);
}
CursorJump(2 * (COL / 3), ROW / 2);
printf('GAME OVER');
while (1)
{
char ch;
CursorJump(2 * (COL / 3), ROW / 2 + 3);
printf('再来一局?(y/n):');
scanf('%c', &ch);
if (ch == 'y' || ch == 'Y')
{
system('cls');
main();
}
else if (ch == 'n' || ch == 'N')
{
CursorJump(2 * (COL / 3), ROW / 2 + 5);
exit(0);
}
else
{
CursorJump(2 * (COL / 3), ROW / 2 + 4);
printf('选择错误,请再次选择');
}
}
}
}
return 0; //判断结束,无需再调用该函数进行判断
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101

游戏主体逻辑函数

主体逻辑:

  1. 在打印当前下落的方块前,先随机获取下一次将要下落的方块,并打印到提示区的右上角。
  2. 将当前下落的方块首先打印到游戏区顶部,给定一定的时间间隔,若在该时间内键盘未被敲击,则方块下落一格,方块下落前需先判断下落后的合法性。
  3. 若在给定时间间隔内键盘被敲击,则根据所敲击的按键给出相应反馈。
  4. 若方块落到底部,则调用“判断得分与结束”函数进行判断。
  5. 若游戏未结束,则循环进行以上步骤。
//游戏主体逻辑函数 void StartGame() { int shape = rand() % 7, form = rand() % 4; //随机获取方块的形状和形态 while (1) { int t = 0; int nextShape = rand() % 7, nextForm = rand() % 4; //随机获取下一个方块的形状和形态 int x = COL / 2 - 2, y = 0; //方块初始下落位置的横纵坐标 color(nextShape); //颜色设置为下一个方块的颜色 DrawBlock(nextShape, nextForm, COL + 3, 3); //将下一个方块显示在右上角 while (1) { color(shape); //颜色设置为当前正在下落的方块 DrawBlock(shape, form, x, y); //将该方块显示在初始下落位置 if (t == 0) { t = 15000; //这里t越小,方块下落越快(可以根据此设置游戏难度) } while (--t) { if (kbhit() != 0) //若键盘被敲击,则退出循环 break; } if (t == 0) //键盘未被敲击 { if (IsLegal(shape, form, x, y + 1) == 0) //方块再下落就不合法了(已经到达底部) { //将当前方块的信息录入face当中 //face:记录界面的每个位置是否有方块,若有方块还需记录该位置方块的颜色。 for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (block[shape][form].space[i][j] == 1) { face.data[y + i][x + j] = 1; //将该位置标记为有方块 face.color[y + i][x + j] = shape; //记录该方块的颜色数值 } } } while (JudeFunc()); //判断此次方块下落是否得分以及游戏是否结束 break; //跳出当前死循环,准备进行下一个方块的下落 } else //未到底部 { DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置 y++; //纵坐标自增(下一次显示方块时就相当于下落了一格了) } } else //键盘被敲击 { char ch = getch(); //读取keycode switch (ch) { case DOWN: //方向键:下 if (IsLegal(shape, form, x, y + 1) == 1) //判断方块向下移动一位后是否合法 { //方块下落后合法才进行以下操作 DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置 y++; //纵坐标自增(下一次显示方块时就相当于下落了一格了) } break; case LEFT: //方向键:左 if (IsLegal(shape, form, x - 1, y) == 1) //判断方块向左移动一位后是否合法 { //方块左移后合法才进行以下操作 DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置 x--; //横坐标自减(下一次显示方块时就相当于左移了一格了) } break; case RIGHT: //方向键:右 if (IsLegal(shape, form, x + 1, y) == 1) //判断方块向右移动一位后是否合法 { //方块右移后合法才进行以下操作 DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置 x++; //横坐标自增(下一次显示方块时就相当于右移了一格了) } break; case SPACE: //空格键 if (IsLegal(shape, (form + 1) % 4, x, y + 1) == 1) //判断方块旋转后是否合法 { //方块旋转后合法才进行以下操作 DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置 y++; //纵坐标自增(总不能原地旋转吧) form = (form + 1) % 4; //方块的形态自增(下一次显示方块时就相当于旋转了) } break; case ESC: //Esc键 system('cls'); //清空屏幕 color(7); CursorJump(COL, ROW / 2); printf(' 游戏结束 '); CursorJump(COL, ROW / 2 + 2); exit(0); //结束程序 case 's': case 'S': //暂停 system('pause>nul'); //暂停(按任意键继续) break; case 'r': case 'R': //重新开始 system('cls'); //清空屏幕 main(); //重新执行主函数 } } } shape = nextShape, form = nextForm; //获取下一个方块的信息 DrawSpace(nextShape, nextForm, COL + 3, 3); //将右上角的方块信息用空格覆盖 } }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110

注意: 这里只是概括性的说明了俄罗斯方块的主体逻辑,代码当中还有大量注释以供大家理解。

从文件读取最高分

首先需要使用fopen函数打开“俄罗斯方块最高记录.txt”文件,若是第一次运行该代码,则会自动创建该文件,并将历史最高记录设置为0,之后读取文件当中的历史最高记录存储在max变量当中,并关闭文件即可。

//从文件读取最高分
void ReadGrade()
{
FILE* pf = fopen('俄罗斯方块最高得分记录.txt', 'r'); //以只读方式打开文件
if (pf == NULL) //打开文件失败
{
pf = fopen('俄罗斯方块最高得分记录.txt', 'w'); //以只写方式打开文件(文件不存在可以自动创建该文件)
fwrite(&grade, sizeof(int), 1, pf); //将max写入文件(此时max为0),即将最高历史得分初始化为0
}
fseek(pf, 0, SEEK_SET); //使文件指针pf指向文件开头
fread(&max, sizeof(int), 1, pf); //读取文件中的最高历史得分到max当中
fclose(pf); //关闭文件
pf = NULL; //文件指针及时置空
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

更新最高分到文件

首先使用fopen函数打开“俄罗斯方块最高记录.txt”,然后将本局游戏的分数grade写入文件当中即可(覆盖式)。

//更新最高分到文件 void WriteGrade() { FILE* pf = fopen('俄罗斯方块最高得分记录.txt', 'w'); //以只写方式打开文件 if (pf == NULL) //打开文件失败 { printf('保存最高得分记录失败\n'); exit(0); } fwrite(&grade, sizeof(int), 1, pf); //将本局游戏得分写入文件当中(更新最高历史得分) fclose(pf); //关闭文件 pf = NULL; //文件指针及时置空 }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

主函数

主函数里面就是依次调用以上函数,但有三点需要说明

  1. 全局变量grade需要在主函数内初始化为0,不能在全局范围初始化为0,因为当玩家按下R键进行重玩时我们需要将当前分数grade重新设置为0。
  2. 随机数的生成起点建议设置在主函数当中。
  3. 主函数当中的#pragma语句是用于消除类似以下警告的:
int max, grade; //全局变量
int main()
{
#pragma warning (disable:4996) //消除警告
max = 0, grade = 0; //初始化变量
system('title 俄罗斯方块'); //设置cmd窗口的名字
system('mode con lines=29 cols=60'); //设置cmd窗口的大小
HideCursor(); //隐藏光标
ReadGrade(); //从文件读取最高分到max变量
InitInterface(); //初始化界面
InitBlockInfo(); //初始化方块信息
srand((unsigned int)time(NULL)); //设置随机数生成的起点
StartGame(); //开始游戏
return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
(0)

相关推荐