- 基于EasyX库的C语言小游戏
- 前言
- 游戏实录
- 需求与技术难点分析
- 地图与蛇的生成
- 蛇的移动
- 游戏失败
- 源码
前言
上一篇文章我们已经做过了见缝插针的小游戏,这次再利用EasyX库做一个贪吃蛇的游戏。
游戏实录 需求与技术难点分析 地图与蛇的生成利用二维数组,这时我们就可以想到,二维数组的值的多态性,值的多态决定了地图里不止一种的表示,我们可以想到用0表示空地,>0的为蛇身。
同时,一个像素点太小了,我们可以用宏定义的方法手动设置像素块
#define BLOCK_SIZE 20 // 每个小格子的长宽大小 #define HEIGHT 30 // 高度上一共30个小格子 #define WIDTH 40 // 宽度上一共40个小格子蛇的移动
我们从键盘获取蛇的移动方向,同时初始方向为右,为了防止出现因为前一步向上后一步向下而导致的自己咬自己的bug,我们除了定义moveDirection的同时,还定义moveDirection_old变量,用于避免这种现象的产生。
同时因为蛇本身会按照初始方向移动,键盘输入是在某一个时间点进行,因此我们将这两个事件封装为两个更新函数,一个与输入无关,另一个与输入有关。
游戏失败蛇的移动本质上是数组的更新,我们在第一步设置的蛇身的值>0,由此我们可以发挥一下,将蛇头的值设置为1,将蛇身的值设置为蛇的长度,因此,数组中值最大的元素下标就对应着蛇尾,值最小的元素下标就对应着蛇头。而蛇更新的时候本质上移动的只有头和尾(如果吃到了食物的话,就只用移动头)。
游戏失败分为两种情况:
- 蛇头碰壁
这种情况我们只需要将蛇头坐标与边界值相比较就行 - 咬到了蛇身
我们需要将下一步对应的蛇头坐标对应数组中的值进行检查,判断是否大于零,大于0则咬到了自身
#include#include #include #define BLOCK_SIZE 20 // 每个小格子的长宽大小 #define HEIGHT 30 // 高度上一共30个小格子 #define WIDTH 40 // 宽度上一共40个小格子 // 全局变量定义 int Blocks[HEIGHT][WIDTH] = { 0 }; // 二维数组,用于记录所有的游戏数据 char moveDirection_old, moveDirection; // 小蛇移动方向 int food_i, food_j; // 食物的位置 int isFailure = 0; // 是否游戏失败 void moveSnake() // 移动小蛇及相关处理函数 { int i, j; for (i = 0; i < HEIGHT; i++) // 对行遍历 for (j = 0; j < WIDTH; j++) // 对列遍历 if (Blocks[i][j] > 0) // 大于0的为小蛇元素 Blocks[i][j]++; // 让其+1 int oldTail_i, oldTail_j, oldHead_i, oldHead_j; // 定义变量,存储旧蛇尾、旧蛇头坐标 int max = 0; // 用于记录最大值 for (i = 0; i < HEIGHT; i++) // 对行列遍历 { for (j = 0; j < WIDTH; j++) { if (max < Blocks[i][j]) // 如果当前元素值比max大 { max = Blocks[i][j]; // 更新max的值 oldTail_i = i; // 记录最大值的坐标,就是旧蛇尾的位置 oldTail_j = j; // } if (Blocks[i][j] == 2) // 找到数值为2 { oldHead_i = i; // 数值为2恰好是旧蛇头的位置 oldHead_j = j; // } } } int newHead_i = oldHead_i; // 设定变量存储新蛇头的位置 int newHead_j = oldHead_j; // 根据用户按键,设定新蛇头的位置 if (moveDirection == 'w') // 向上移动 newHead_i = oldHead_i - 1; else if (moveDirection == 's') // 向下移动 newHead_i = oldHead_i + 1; else if (moveDirection == 'a') // 向左移动 newHead_j = oldHead_j - 1; else if (moveDirection == 'd') // 向右移动 newHead_j = oldHead_j + 1; // 如果蛇头超出边界,或者蛇头碰到蛇身,游戏失败 if (newHead_i >= HEIGHT || newHead_i < 0 || newHead_j >= WIDTH || newHead_j < 0 || Blocks[newHead_i][newHead_j]>0) { isFailure = 1; // 游戏失败 return; // 函数返回 } Blocks[newHead_i][newHead_j] = 1; // 新蛇头位置数值为1 if (newHead_i == food_i && newHead_j == food_j) // 如果新蛇头正好碰到食物 { food_i = rand() % (HEIGHT) ; // 食物重新随机位置 food_j = rand() % (WIDTH ); // // 不对旧蛇尾处理,相当于蛇的长度+1 } else // 新蛇头没有碰到食物 Blocks[oldTail_i][oldTail_j] = 0; // 旧蛇尾变成空白,不吃食物时保持蛇的长度不变 } void startup() // 初始化函数 { int i; Blocks[HEIGHT / 2][WIDTH / 2] = 1; // 画面中间画蛇头,数字为1 for (i = 1; i <= 4; i++) // 向左依次4个蛇身,数值依次为2,3,4,5 Blocks[HEIGHT / 2][WIDTH / 2 - i] = i + 1; moveDirection = moveDirection_old= 'd'; // 初始向右移动 food_i = rand() % (HEIGHT) ; // 初始化随机食物位置 food_j = rand() % (WIDTH ); // initgraph(WIDTH * BLOCK_SIZE, HEIGHT * BLOCK_SIZE); // 新开画面 setlinecolor(RGB(200, 200, 200)); // 设置线条颜色 BeginBatchDraw(); // 开始批量绘制 } void show() // 绘制函数 { cleardevice(); // 清屏 int i, j; for (i = 0; i < HEIGHT; i++) // 对二维数组所有元素遍历 { for (j = 0; j < WIDTH; j++) { if (Blocks[i][j] > 0) // 元素大于0表示是蛇,这里让蛇的身体颜色色调渐变 setfillcolor(HSVtoRGB(Blocks[i][j] * 10, 0.9, 1)); else setfillcolor(RGB(150, 150, 150)); // 元素为0表示为空,颜色为灰色 // 在对应位置处,以对应颜色绘制小方格 fillrectangle(j * BLOCK_SIZE, i * BLOCK_SIZE, (j + 1) * BLOCK_SIZE, (i + 1) * BLOCK_SIZE); } } setfillcolor(RGB(0, 255, 0)); // 食物为绿色 // 绘制食物小方块 fillrectangle(food_j * BLOCK_SIZE, food_i * BLOCK_SIZE, (food_j + 1) * BLOCK_SIZE, (food_i + 1) * BLOCK_SIZE); if (isFailure) // 如果游戏失败 { setbkmode(TRANSPARENT); // 文字字体透明 settextcolor(RGB(255, 0, 0));// 设定文字颜色 settextstyle(80, 0, _T("宋体")); // 设定文字大小、样式 outtextxy(240, 220, _T("游戏失败")); // 输出文字内容 } FlushBatchDraw(); // 批量绘制 } void updateWithoutInput() // 与输入无关的更新函数 { if (isFailure) // 如果游戏失败,函数返回 return; static int waitIndex = 1; // 静态局部变量,初始化时为1 waitIndex++; // 每一帧+1 if (waitIndex == 10) // 如果等于10才执行,这样小蛇每隔10帧移动一次 { moveSnake(); // 调用小蛇移动函数 waitIndex = 1; // 再变成1 } } void updateWithInput() // 和输入有关的更新函数 { if (_kbhit() && isFailure == 0) // 如果有按键输入,并且不失败 { char input = _getch(); // 获得按键输入 switch (input) { //注意防止自咬现象(即前后两次运动方向相反) case 'a': moveDirection = input; if (moveDirection_old != 'd') { moveSnake(); moveDirection_old = moveDirection; } else moveDirection = moveDirection_old; break; case 'w': moveDirection = input; if (moveDirection_old != 's') { moveSnake(); moveDirection_old = moveDirection; } else moveDirection = moveDirection_old; break; case 's': moveDirection = input; if (moveDirection_old != 'w') { moveSnake(); moveDirection_old = moveDirection; } else moveDirection = moveDirection_old; break; case 'd': moveDirection = input; if (moveDirection_old != 'a') { moveSnake(); moveDirection_old = moveDirection; } else moveDirection = moveDirection_old; break; } } } int main() // 主函数 { startup(); // 初始化函数,仅执行一次 while (1) // 一直循环 { show(); // 进行绘制 updateWithoutInput(); // 和输入无关的更新 updateWithInput(); // 和输入有关的更新 } return 0; }



