/**********************头文件和预定义*********************/
#include
#include
#include
#include
#include
#include
#define MAX_LENGTH 100
#define h_length 60
#define v_length 50
// 小键盘键位
#define UP 72 //方向键:上
#define DOWN 80 //方向键:下
#define LEFT 75 //方向键:左
#define RIGHT 77 //方向键:右
#define SPACE 32 //暂停
//-----------------------------------------------------------------------
/**********************自定义 struct 类型和 enum 类型*********************/
enum Move_Direction
{
up,
down,
left,
right
};
enum Items
{
nothing, // 空白(表示什么都没有)
wall,
snakebody0,
snakebody1,
snakehead0,
snakehead1,
food
};
struct Snake
{
int id; // id = 0 表示 0 号蛇,id = 1 表示 1 号蛇
int length = 4; // 蛇的长度
int body_position[MAX_LENGTH][2]; // 蛇身体的位置
enum Move_Direction move_direction; // 蛇运动的方向
int isdead = 0; // 蛇是否已经死亡
void snake_move();
};
//------------------------------------------------------------------------
/**********************地图和两条蛇的定义*********************/
enum Items map[v_length][h_length] = {nothing}; // 相当于全是 nothing
struct Snake snakes[2];
int food_position[3][2];
int map_choice;
//------------------------------------------------------------------------
/************************全局函数声明*************************************/
void myprintf();
void welcome();
void pretreatment();
void init();
void choose_map();
void change_direction();
void create_food(int i, int create_time);
int is_eating_food(int row, int column);
void gameover();
//------------------------------------------------------------------------
/**********************主函数**************************/
int main()
{
pretreatment();
welcome();
// printf("hhhhh1");
init();
// system("pause");
while (1)
{
change_direction();
snakes[0].snake_move();
snakes[1].snake_move();
if (snakes[0].isdead + snakes[1].isdead)
{
gameover();
break;
}
Sleep(100);
}
return 0;
}
//-----------------------------------------------------------------------
/**********************全局函数**************************/
void myprintf(enum Items item, int row, int column)
{
COORD pos; //定义光标位置的结构体变量
pos.X = 2 * column; //横坐标
pos.Y = row; //纵坐标
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄
SetConsoleCursorPosition(handle, pos); //设置光标位置
switch (item)
{
case wall:
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7); // 墙体白色
printf("■");
break;
// 第一条蛇是绿色的
case snakebody0:
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 11);
printf("■");
break;
case snakehead0:
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 11);
printf("□");
break;
// 第二条蛇是黄色的
case snakebody1:
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 6);
printf("■");
break;
case snakehead1:
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 6);
printf("□");
break;
case food:
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 4); // 食物红色
printf("★");
break;
case nothing:
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7); // 空气(表示什么都没有)
printf(" ");
break;
default:
break;
}
}
void welcome()
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 6);
printf(" @@ @ @ \n");
printf(" @@@@ @@ @ @@ \n");
printf(" @@@@@@@@ @@ @ @@ \n");
printf(" @@@@@@ @@@@@@ @@@@@ @@@@@@@@@@@ @ @@@@@@@@@@ \n");
printf(" @@@@@@ @@@ @@@@@@ @@ @ @@ @ @@ @@ \n");
printf(" @@@@@@@@@@@@@@ @ @@@ @@@ @@ @ @ @@ \n");
printf(" @@@@ @ @ @@@@@@@@@ @ @ @ @ @ @@@@ \n");
printf(" @@@@@@@@@ @ @ @@@ @ @ @ @ @ @@@@ \n");
printf(" @@@@@ @ @ @@@ @ @ @ @ @@@ \n");
printf(" @@@@@@@@@@@@ @ @ @@@ @ @ @ @ @@@ \n");
printf(" @@ @ @@ @ @ @@@ @@@ @ @@@@ \n");
printf(" @ @ @ @ @ @@@ @@@@@@ @@@ @@ \n");
printf(" @ @@ @ @ @ @@@ @@ @ @ @ @@ \n");
printf(" @ @@ @@ @ @ @ @@@ @@ @ @@ @ @@ \n");
printf(" @ @@@ @@@@@ @ @ @@ @@ @ @@@ @ @@ \n");
printf(" @@@ @@@@ @@ @@ @@ @@ @@@@@@@@@ @ @@ \n");
printf(" @@@@@@ @@@ @@@@@ @@@@@@@@@@@ @@@@@ @@ @@ @@@ \n");
printf(" @@@@ @@ @@@@@@@ \n");
printf("\n\n\n请按 enter 继续,祝您游戏愉快!\n");
while (getch() != '\r')
{
}
printf("欢迎进入贪吃蛇游戏!\n");
printf("游戏规则如下:\n");
printf("使用键盘上的 W、A、S、D 键分别可以使蛇向上下左右移动\n");
printf("请让您的蛇尽量多地吃到食物并避免触碰到自身和墙壁\n");
printf("水平方向的墙面长度被设置为%d,\n竖直方向的墙面长度被设置为%d。\n", h_length, v_length); // 重复信息,向玩家确认
printf("\n\n现在您可以选择一张地图\n");
printf("0:正常模式\n");
printf("1:隔离模式墙模式\n");
printf("2:随机散点墙模式\n");
printf("3:固定纵横墙模式\n");
printf("4:随机水平墙模式\n");
while (1)
{
printf("输入 0~4 中的其中一个数字:");
scanf("%d", &map_choice);
if (map_choice >= 0 && map_choice <= 4)
break;
}
printf("请按 enter 继续,祝您游戏愉快!\n");
while (getch() != '\r')
{
}
// printf("请输入地图的水平和竖直尺寸(两个数字中间用空格隔开):\n");
// scanf("%d %d", &h_length, &v_length);
system("cls");
}
// 预处理函数
void pretreatment()
{
system("title Double Snakes"); // 设置cmd窗口的名字
system("mode con cols=200 lines=100"); // 设置cmd窗口的大小
CONSOLE_CURSOR_INFO curInfo; // 定义光标信息的结构体变量
curInfo.dwSize = 1; // 如果没赋值的话,光标隐藏无效
curInfo.bVisible = FALSE; // 将光标设置为不可见
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); // 获取控制台句柄
SetConsoleCursorInfo(handle, &curInfo); // 设置光标信息
srand((unsigned)time(NULL)); // 随机数播种
}
void choose_map(int map_choice)
{
int i, j;
switch (map_choice)
{
case 0:
// 没有任何模式
break;
case 1:
// 隔离模式
for (i = 1; i <= v_length - 2; i++)
{
map[i][h_length / 2] = wall;
myprintf(wall, i, h_length / 2);
}
break;
// 随机生成墙模式
case 2:
for (i = 0; i <= 19; i++)
{
// -8 是为了让行列值尽量靠中
int row = rand() % (v_length - 4);
int column = rand() % (h_length - 4);
// 如果map[row][column] + map[row - 1][column] + map[row + 1][column] + map[row][column - 1] + map[row][column + 1] == 0那么就是这几个位置都是 nothing
if (!(map[row][column] + map[row - 1][column] + map[row + 1][column] + map[row][column - 1] + map[row][column + 1]))
{
map[row][column] = wall;
myprintf(wall, row, column);
}
}
break;
case 3:
// 竖直固定墙模式
for (i = 1; i <= v_length / 3; i++)
{
if (!map[i][h_length / 2])
{
map[i][h_length / 2] = wall;
myprintf(wall, i, h_length / 2);
}
}
for (i = v_length - v_length / 3 - 1; i <= v_length - 2; i++)
{
if (!map[i][h_length / 2])
{
map[i][h_length / 2] = wall;
myprintf(wall, i, h_length / 2);
}
}
for (i = 1; i <= h_length / 3; i++)
{
map[v_length / 2][i] = wall;
myprintf(wall, v_length / 2, i);
}
for (i = h_length - h_length / 3 - 1; i <= h_length - 2; i++)
{
map[v_length / 2][i] = wall;
myprintf(wall, v_length / 2, i);
}
break;
case 4:
// 随机水平墙模式
for (i = 1; i <= 8; i++)
{
int row = rand() % (v_length - 6 * i) + 3;
int column = rand() % (h_length - 12) + 6;
int wall_length = rand() % 15 + 4;
for (j = 1; j <= wall_length; j++)
{
if (!map[row][column + j] && column + j <= h_length - 2) // 如果map[row][column + j] 不为 nothing
{
map[row][column + j] = wall;
myprintf(wall, row, column + j);
}
}
}
default:
break;
}
}
// 初始化信息函数
void init()
{
// 初始化一些信息
int i, j; // 定义循环变量 i,j
// 先初始化两条蛇的id
snakes[0].id = 0;
snakes[1].id = 1;
// 先将最上方的墙保存到数组当中
for (i = 0; i <= h_length - 1; i++)
{
map[0][i] = wall; // 最上方的墙位于第 0 行,因此第一维下标都是 0
myprintf(wall, 0, i);
}
// 因为第 0 行和第 v_length 行已经用来创建上下两面墙,因此我们的对于二维数组行数的循环范围应该扣去首尾两行,即 1 到 v_length - 2
for (i = 1; i <= v_length - 2; i++)
{
// 刻画左右两面墙,这两句代码会被重复 v_length - 1 次,最终组成两面连续的墙
map[i][0] = wall; // 左边的竖直墙
map[i][h_length - 1] = wall; // 右边的竖直墙
myprintf(wall, i, 0);
myprintf(wall, i, h_length - 1);
}
// 最后将最下方的墙保存到数组当中
printf("\n");
for (i = 0; i <= h_length - 1; i++)
{
map[v_length - 1][i] = wall; // 最下方的墙位于第 v_length - 1 行,因此第一维下标都是 v_length - 1
myprintf(wall, v_length - 1, i); // 打印最下方的墙
}
// 设置蛇的初始位置信息
// 第一条蛇位于地图左上方
for (i = 0; i <= snakes[0].length - 1; i++)
{
snakes[0].body_position[i][0] = 1;
snakes[0].body_position[i][1] = 4 - i;
// 蛇头在1行4列;蛇尾在1行1列
}
// 第二条蛇位于地图右下方
for (i = 0; i <= snakes[0].length - 1; i++)
{
snakes[1].body_position[i][0] = v_length - 2;
snakes[1].body_position[i][1] = h_length - 5 + i;
// 蛇头在v_length - 2行、h_length - 5列; 蛇尾在v_length - 2行,h_length - 2列
}
map[snakes[0].body_position[0][0]][snakes[0].body_position[0][1]] = snakehead0; // 在地图上记录 0 号蛇的蛇头
myprintf(snakehead0, snakes[0].body_position[0][0], snakes[0].body_position[0][1]); // 在屏幕上绘制 0 号蛇的蛇头
map[snakes[1].body_position[0][0]][snakes[0].body_position[0][1]] = snakehead1; // 在地图上记录 1 号蛇的蛇头
myprintf(snakehead1, snakes[1].body_position[0][0], snakes[1].body_position[0][1]); // 在屏幕上绘制 1 号蛇的蛇头
for (i = 1; i <= snakes[0].length - 1; i++) // 由于初始状态下 1 号蛇的长度和 0 号蛇的长度是相等的,所以这边 snakes[0].length - 1 就是 snakes[1].length - 1
{
map[snakes[0].body_position[i][0]][snakes[0].body_position[i][1]] = snakebody0; // 在地图上记录 0 号蛇的蛇身
myprintf(snakebody0, snakes[0].body_position[i][0], snakes[0].body_position[i][1]); // 在屏幕上绘制 0 号蛇的蛇身
map[snakes[1].body_position[i][0]][snakes[1].body_position[i][1]] = snakebody1; // 在地图上记录 1 号蛇的蛇身
myprintf(snakebody1, snakes[1].body_position[i][0], snakes[1].body_position[i][1]); // 在屏幕上绘制 0 号蛇的蛇身
}
snakes[0].move_direction = right; // 0 号蛇向右运动
snakes[1].move_direction = left; // 1 号蛇向左运动
// 创建地图
choose_map(map_choice);
// 创建 3 个食物
for (i = 0; i <= 2; i++)
{
create_food(i, 0);
}
}
void change_direction()
{
while (kbhit()) // 如果缓冲区中还有
{
switch (getch())
{
case 'W':
case 'w':
if (snakes[0].move_direction != down)
snakes[0].move_direction = up;
break;
case 'S':
case 's':
if (snakes[0].move_direction != up)
snakes[0].move_direction = down;
break;
case 'A':
case 'a':
if (snakes[0].move_direction != right)
snakes[0].move_direction = left;
break;
case 'D':
case 'd':
if (snakes[0].move_direction != left)
snakes[0].move_direction = right;
break;
case UP:
if (snakes[1].move_direction != down)
snakes[1].move_direction = up;
break;
case DOWN:
if (snakes[1].move_direction != up)
snakes[1].move_direction = down;
break;
case LEFT:
if (snakes[1].move_direction != right)
snakes[1].move_direction = left;
break;
case RIGHT:
if (snakes[1].move_direction != left)
snakes[1].move_direction = right;
break;
case SPACE:
system("pause");
break;
default:
break;
}
}
}
void create_food(int i, int create_time)
{
if (create_time == 10000) // 超过 10000 次仍然不能得到食物
{
food_position[i][0] = -1;
food_position[i][1] = -1;
} // 随机数播种
food_position[i][0] = rand() % v_length; // 随机生成行数
food_position[i][1] = rand() % h_length; // 随机生成列数
if (map[food_position[i][0]][food_position[i][1]] == nothing) // 只有在空地上才能产生食物
{
map[food_position[i][0]][food_position[i][1]] = food; // 在地图上更新食物信息
myprintf(food, food_position[i][0], food_position[i][1]); // 绘制出食物食物
}
else
{
create_food(i, create_time + 1); // 递归,再创建一次食物
}
}
int is_eating_food(int row, int column)
{
int i, j;
for (i = 0; i <= 2; i++)
{
if (food_position[i][0] == row && food_position[i][1] == column) // 只要满足吃掉的是其中一个食物
{
create_food(i, 0); // 再创建一个食物
return 1;
}
}
return 0;
}
void gameover()
{
Sleep(1000); // 反应时间
system("cls");
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 12);
printf(" @@@@ \n");
printf(" @@@ \n");
printf(" @@@ \n");
printf(" @@@@@@@@@@@@@@@@@@@@@@@@ \n");
printf(" @@@@@@@@@@@@@@@@@@@@@@@@ \n");
printf(" @@@ @@@ \n");
printf(" @@@ @@@@ @@@ \n");
printf(" @@@@ \n");
printf(" @@@@@@@@@@@@@@@@@@ \n");
printf(" @@@@ \n");
printf(" @@@ @@@ \n");
printf(" @@@@ @@@@ \n");
printf(" @@@ @@@@ \n");
printf("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ \n");
printf(" @@@@ \n");
printf(" @@@@@@@@ @@@ \n");
printf(" @@ @@ @@@ \n");
printf(" @@ @@ @@@ \n");
printf(" @@ @@ @@@ \n");
printf(" @@@@@@@@ @@@ \n");
printf(" @@@ \n");
printf(" @@@ \n");
printf(" @ @@@ \n");
printf(" @@ @@@ \n");
printf(" @@@@@ \n");
printf(" @@@ \n");
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7);
printf("游戏结束,请按 enter 继续!\n");
while (getch() != '\r')
{
}
printf("第 0 条蛇的最终长度是 %d\n", snakes[0].length);
printf("第 1 条蛇的最终长度是 %d\n", snakes[1].length);
printf("请按 enter 退出\n");
while (getch() != '\r')
{
}
}
//------------------------------------------------------------------------
/**********************结构体内部函数**************************/
void Snake::snake_move()
{
int i, j; // 定义循环变量 i,j
// 设置在这个函数中应该使用哪种绘制蛇的材质
enum Items temphead;
enum Items tempbody;
if (this->id)
{
temphead = snakehead1;
tempbody = snakebody1;
}
else
{
temphead = snakehead0;
tempbody = snakebody0;
}
int move[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; // 移动方向数组:由四个一维数组组成,分别代表向上下左右运动
int temptail[2] = {this->body_position[this->length - 1][0], this->body_position[this->length - 1][1]}; // 创建一个数组用来存放蛇尾所在位置
// 先将后面所有身体段向前移动一位
for (i = this->length - 1; i >= 1; i--)
{
// 把前一位的位置赋给后一位
this->body_position[i][0] = this->body_position[i - 1][0];
this->body_position[i][1] = this->body_position[i - 1][1];
}
myprintf(tempbody, this->body_position[0][0], this->body_position[0][1]); // 将原本蛇头的位置打印为蛇身
map[this->body_position[0][0]][this->body_position[0][1]] = tempbody; // 在地图上相应地进行更新
// 将蛇头依据方向进行移动
this->body_position[0][0] += move[this->move_direction][0];
this->body_position[0][1] += move[this->move_direction][1];
myprintf(temphead, this->body_position[0][0], this->body_position[0][1]); // 打印新的蛇头
// 如果碰壁或碰到身体设置蛇已经死亡
if (map[this->body_position[0][0]][this->body_position[0][1]] == wall || map[this->body_position[0][0]][this->body_position[0][1]] == snakebody0 || map[this->body_position[0][0]][this->body_position[0][1]] == snakebody1)
{
this->isdead = 1;
return;
}
map[this->body_position[0][0]][this->body_position[0][1]] = temphead; // 在地图上相应地进行更新
// 如果吃到食物设置蛇变长
if (is_eating_food(this->body_position[0][0], this->body_position[0][1])) // 蛇头的位置是否在食物的位置
{
// 由于尾部在地图上和画面上尾部的位置都还没有去除,因此也不必更改
this->length++;
this->body_position[this->length - 1][0] = temptail[0];
this->body_position[this->length - 1][1] = temptail[1];
}
else // 如果没吃到食物就将尾部清除
{
// 在蛇的位置信息 body_positiong 中已经清除
myprintf(nothing, temptail[0], temptail[1]); // 在画面上清除尾部
map[temptail[0]][temptail[1]] = nothing; // 在地图信息上清除尾部
}
}
本游戏玩法和贪吃蛇一致,可以双人 PK ,也可以单人操纵两蛇(推荐),锻炼大脑能力
代码面向新手,提供了非常详细的注释,语言平易近人,没有参杂过多“高端操作”,可以作为初学 C 语言的尝试项目(其实我也是初学hhh)
欢迎交流代码内容,发现不足,一起进步!
这个代码适用于 Windows 环境,如果使用 Mac 环境或 Linux 环境可能需要修改个别代码
可以使用 DEV-C++
直接编译运行,如果使用 vscode
编译运行需要将编码格式修改为 Simplified Chinese(GBK)
,用来识别特殊符号。此外无需安装其它库。
转发使用请注明出处,谢谢!
使用 C 语言 struct
来构造程序,struct
在某些层面和 C++ 的 class 有某些相似性
防闪烁方法主要通过调用系统 API ,移动光标在特定位置进行输出实现,效果可观
后续可能会出一版零基础 C 语言教程:从安装编程软件到写出《double snake》游戏的 3 天速成教程,目的在于采用碎片化叙述知识和任务导引的方式,帮助初学者建立起对于一门语言的“感觉”,不至于一开始就陷入系统冗杂的教材体系。
会在《Double Snakes》的基础上开发《Multiple Snakes》,并基于此用 C 语言手敲一遍遗传算法,帮助刚开始学计算机的同学从底层直接感受和理解算法