锻炼左右脑协调的摸鱼必备游戏——《Double Snakes》完整C语言代码!

锻炼左右脑协调的贪吃蛇游戏——《Double Snakes》完整C语言代码

(一)运行效果

  • 游戏开始:
  • 游戏进行:
  • 游戏结束:

(二)完整代码

/**********************头文件和预定义*********************/
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <windows.h>
#include <string.h>
#include <time.h>
#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;     // 在地图信息上清除尾部
    }
}

(三)README

  • 本游戏玩法和贪吃蛇一致,可以双人 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 语言手敲一遍遗传算法,帮助刚开始学计算机的同学从底层直接感受和理解算法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值