控制台 贪吃蛇 游戏

贪吃蛇游戏使用的算法是一个比较简单的算法,它可以用一个Double Ended Queue实现

用一个deque按顺序存下蛇身体上所有点的位置

0.先计算蛇头移动到下一个点的位置,如果下一个点的位置还在地图内,且不与蛇身相撞,则有下面两种可能:

1.当蛇向前移动时,将下一个点的位置设置为蛇头(即从deque左端插入一个新的点),而蛇往前挪了一步,所以要把蛇尾的点删除掉(即从deque尾端删除一个点)

2.蛇头的下一个点是食物,则蛇在向前移动的同时还伸长了,所以蛇尾的点就不用删掉了,只用在deque左端插入一个新点就可以了

这部分程序代码请在阅读完整代码时注意,这就是贪吃蛇游戏的最主要算法:

        // 蛇头撞到食物没有?
        if (head == pt_tar)    // target is hit
        {
            snack.push_front(head);
            RandTarget();
            return;
        }
        else    // 蛇向前移动,不伸长
        {
            snack.push_front(head);    
            snack.pop_back();
        }

下面是完整的源代码,后面还有对方向设置函数的改进:

#include <iostream>
#include <deque>
#include <windows.h>
using namespace std;

#define MAX_WIDTH  16
#define MAX_HEIGHT 16
#define SLEEP_TIME 250

// 下面两个宏用来检测对应的键是否被按下、是否已经弹起
#define KEYDOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEYUP(vk_code)   ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// “点”类,提供==运行符的重载
class MY_POINT
{
public:
    int x, y;
    MY_POINT(){}
    MY_POINT(int _x, int _y):x(_x), y(_y){}
    bool operator==(MY_POINT &pt){return pt.x == x && pt.y == y;}
};

// “游戏状态”类,保存当前游戏地图状态,蛇的方向等
class GameStatus
{
public:
    char map[MAX_HEIGHT+1][MAX_WIDTH+1];
    deque<MY_POINT> snack;    // 用一个Double Ended Queue来存储蛇身体各点的位置
    MY_POINT pt_tar;          // 食物的位置
    
    enum STATE{RUNNING, END}; // 枚举游戏运行的状态,有运行状态和结束状态
    STATE game_state;         // 表示当前游戏处于什么状态
    
    char dir;    // 蛇运动的方向,'w','s','a','d'分别表示上、下、左、右

    GameStatus()
    {
        InitMap();    // 初始化地图,全部初始化为没有物体的点
        // 下面初始化蛇的身体
        snack.push_back(MY_POINT(MAX_WIDTH/2, MAX_HEIGHT/2));
        snack.push_back(MY_POINT(MAX_WIDTH/2, MAX_HEIGHT/2-1));
        snack.push_back(MY_POINT(MAX_WIDTH/2, MAX_HEIGHT/2-2));
        RandTarget(); // 将食物放到一个随机的位置
        dir = 's';    // 初始化蛇的方向为向下移动
        game_state = RUNNING;    // 最后将游戏置为运行状态
    }
    
    void EndGame()
    {
        game_state = END;    // 将游戏置为结束状态
    }
    
    void InitMap()           // 初始化游戏地图
    {
        memset(map, '-', sizeof map);
        for (int i = 0; i < MAX_HEIGHT; i++)
        {
            map[i][MAX_HEIGHT] = '\0';  // 末尾加'\0'保证能用字符串方式正确输出
        }
    }

    void RandTarget()    // 将食物放到一个随机的点
    {
        pt_tar.x = rand()%(MAX_WIDTH);
        pt_tar.y = rand()%(MAX_HEIGHT);
    }
    
    bool InMap(int x, int y)    // 点在地图内吗?
    {
        return x < MAX_WIDTH && y < MAX_HEIGHT && x >= 0 && y >= 0;
    }
    
    bool InMap(const MY_POINT &pt)    // InMap的重载,接受MY_POINT参数
    {
        return InMap(pt.x, pt.y);
    }
    
    // 将当前游戏状态画到地图里
    void RefreshMap()
    {
        InitMap();
        // 蛇身的每个点,设置为'x'
        for (int i = 0; i < snack.size(); i++)
        {
            map[snack[i].y][snack[i].x] = 'x';
        }
        
        // 食物对应的点设置为'@'
        map[pt_tar.y][pt_tar.x] = '@';
    }
    
    // 将地图显示出来
    void ShowMap()
    {
        system("cls");    // 清除屏幕当前的显示
        for (int i = 0; i < MAX_HEIGHT; i++)
        {
            cout << map[i] << endl;
        }
    }
    
    void NextState()    // 计算蛇再移动一步后的状态
    {
        MY_POINT head = snack[0];
        // 按蛇当前方向判断,蛇头下一个点的位置
        switch (dir)
        {
        case 's':
            head.y++;
            break;
        case 'a':
            head.x--;
            break;
        case 'd':
            head.x++;
            break;
        case 'w':
            head.y--;
            break;
        default:
            break;
        }
        
        // 蛇头下一个点是不是还在地图内
        if (!InMap(head))
        {
            EndGame();
            return;
        }
        
        // 检查蛇头是否撞到蛇身了
        for (int i = 0; i < snack.size(); i++)
        {
            if (head == snack[i])
            {
                EndGame();
                return;
            }
        }
        
        // 蛇头撞到食物没有?
        if (head == pt_tar)    // target is hit
        {
            snack.push_front(head);
            RandTarget();
            return;
        }
        else    // 蛇向前移动,不伸长
        {
            snack.push_front(head);    
            snack.pop_back();
        }
    }
    
    // 读取键盘当前的按下状态,设置蛇的运动方向
    void SetDirection()
    {
        if (KEYDOWN(0x25))        // VK_LEFT 25
            dir = 'a';
        else if (KEYDOWN(0x28))   // VK_DOWN 28
            dir = 's';
        else if (KEYDOWN(0x26))   // VK_UP 26
            dir = 'w';
        else if (KEYDOWN(0x27))   // VK_RIGHT 27
            dir = 'd';
    }
    
    // 游戏的主循环,整个游戏靠这个循环运行
    void GameMainLoop()
    {
        // 当游戏还没有终止,就继续循环
        while (game_state != END)
        {
            Sleep(SLEEP_TIME);    // 暂停一会
            SetDirection();       // 根据按键按下状态设置蛇的方向
            
            NextState();          // 计算蛇运动后下一个状态
            RefreshMap();         // 将当前蛇的状态刷新到地图里
            ShowMap();            // 将地图显示出来
        }
    }
};

int main()
{
    while (1)
    {
        GameStatus gs;
        gs.GameMainLoop();
        cout << "Game Over!" << endl;
        getchar();
    }
}
上面的程序,有一个小小的问题,比如蛇在向上运动时,如果按下了往下的键,游戏会立即结束,因为蛇是不允许直接180度调头的。

遇到这个情况,蛇应该没有反应,程序并不应该退出,于是SetDirection函数可以这样修改:

    // 读取键盘当前的按下状态,设置蛇的运动方向
    void SetDirection()
    {
        if (KEYDOWN(0x25))        // VK_LEFT 25
            dir = (dir == 'd' ? 'd' : 'a');
        else if (KEYDOWN(0x28))   // VK_DOWN 28
            dir = (dir == 'w' ? 'w' : 's');
        else if (KEYDOWN(0x26))   // VK_UP 26
            dir = (dir == 's' ? 's' : 'w');
        else if (KEYDOWN(0x27))   // VK_RIGHT 27
            dir = (dir == 'a' ? 'a' : 'd');
    }
食物的随机摆放有时会摆到蛇的身子上,有的人可能不喜欢这样的规则,这个可以通过改写RandTarget函数实现,也很简单,这就留给各位自己改了。
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值