0基础学习C++做贪吃蛇, 边玩儿边学习!(八)switch和if分支语句——自由移动的蛇

点击链接回顾前几篇:
(一)标准输出cout——一条安静的蛇
(二)代码详解和Sleep()——蛇之闪现
(三)SetConsoleCursorPosition光标移动效果——一条前进的蛇
(四)预定义和函数调用——妄图得分的蛇
(五)for循环和作用域——可长可短的蛇
(六)结构体和while循环——各具特色的蛇
(七)数组和移动逻辑——徐徐移动的蛇

本节我们添加对键盘方向键的响应,实现按照键盘实时输入来改变前进方向,这将是一条真正自由移动的蛇!

最后效果如下:
在这里插入图片描述
首先,在程序开头预处理语句之后加入一句:

//初始方向
int Arrow = 77;

这里定义了一个int型的全局变量Arrow,初始化为77。
什么意思呢?

当我们敲击键盘时,按键对应符号的键值就会出现在STDIN(标准输入)缓存区中,一般这个值是它的ascii码,比如,当我们输入a,它的ASCII是97,当我们按照int型来读取时,就会读到65。

#include<iostream>
#include<conio.h>
using namespace std;
int main() {
    cout << _getch();
}

编译执行,输入A,键盘显示65。
_getch()函数用来获取一个字符,但不显示在屏幕上。这个函数包含在头文件conio.h中。
不同于其他按键,方向键有两个键值,需要用_getch()读两次。

方向键键值
224,72
224,80
224,75
224,77

可以看到,四个方向键的第一个键值都是224,第二个各不相同。如果我们用_getch()读取两次,第一次读到224,第二次读到77,就说明“→”被按下。

因此,我们用int型变量Arrow来记录当前方向,每当方向改变时,我们就更新Arrow。

那么如何实现变向呢?
第六篇第七篇中,我们在turn()函数中用一个长度为5的数组来记录蛇身每一个点的坐标,在每个循环中用蛇头右方那个位置的坐标来覆盖蛇尾坐标,并将h(对move()中head的引用)重定向到这个坐标,将t重定向到原来t的后一位,也就是新的尾部坐标。
回忆一下turn()函数

//倾向
void turn(int& h, int& t) {//h表示需要存储蛇头坐标的元素的下标,t表示蛇尾的
    body[t].X = body[h].X + 1;//将头部前面一格的位置保存在body[t]中
    h = t;//将t的值赋给h,此时body[h]表示的是新头部
    (t == LENGTH - 1) ? t = 0 : t++;//当t等于LENGTH时令t为0,否则t++
}

为什么之前几篇中的蛇都是向右移动呢?就是因为这里我们对body[h]的X坐标加了1,在控制台屏幕坐标系中,横向为X轴,向右为正方向,纵向为Y轴,向下为正方向。坐标原点是左上角。
所以,显然的,蛇头朝四个方向移动的坐标变化分别为:

方向横坐标纵坐标
不变Y-1
不变Y+1
X-1不变
X+1不变

所以,我们修改turn()函数为:

//设置新的蛇身数组
void turn(int& h, int& t) {
    //定义一个COORD型变量dir初值为{0,0}
    COORD dir = { 0,0 };
    //根据Arrow的值来设置dir,使之成为蛇头坐标的变化量。
    switch (Arrow) {
    case 72:
        dir.Y--;
        break;
    case 80:
        dir.Y++;
        break;
    case 75:
        dir.X--;
        break;
    case 77:
        dir.X++;
        break;
    default:
        break;
    }
    //用新的蛇头坐标覆盖原来的蛇尾坐标
    body[t].X = body[h].X + dir.X;
    body[t].Y = body[h].Y + dir.Y;
    h = t;//将t的值赋给h,此时body[h]表示的是新头部
    (t == LENGTH - 1) ? t = 0 : t++;//当t等于LENGTH时令t为0,否则t++
}

这里面出现了一个新的C++关键字,switch
用法如下:
形式:

   switch (expression)
    {
    case /* constant-expression */:
        /* code */
        break;
    case /* constant-expression */:
        /* code */
        break;
    default:
        break;
    }

先计算expression的值,然后拿他和每个case后的值对比,若相等,就从此case的冒号后执行。

break;
//此语句表示跳出while循环,或if 循环,或switch语句

所以turn()中的switch分支语句表示,按照Arrow可能的值执行不同的操作:
当Arrow等于72时,dir.Y- -
当Arrow等于80时,dir.Y++
当Arrow等于75时,dir.X- -
当Arrow等于77时,dir.X++

++是自增运算符,Y++相当于Y=Y+1(将Y+1的值赋给Y)
–同理

再看move()的变化

//移动
void move() {
    int head = LENGTH - 1;//head和tail表示头尾所在的下标。
    int  tail = 0;
    while (1) {//while(1)恒成立,此为无限循环
        Sleep(200);//休眠200毫秒
        if (_kbhit())//此两百毫秒内若有输入
            if (_getch() == 224)
                Arrow = _getch();
        draw(body[head], BodySymbol);//变头为体
        draw(body[tail], NoSymbol);//去尾
        turn(head, tail);//新的头尾
        draw(body[head], HeadSymbol);//画新头
    }
}

可以看到,在Sleep()和draw()语句之间,多了三行语句。这些语句涉及到一个很常见的C++关键字——if
他的形式是:

if(条件语句一){
执行……
}
else if(条件语句二){
……}
else if(条件语句三){}
else {
……
}

当一成立,则执行一的代码,并且下一句是整个 if 结构的下一行。
不然,判断二,若成立则执行二的代码,
……

直到else,当以上所有条件都不成立,执行else语句。
switch和if,就是计算机编程中赫赫有名的分支结构。只要用好分支结构,循环结构,顺序结构,任何现实需求都可以被转换成计算机编程逻辑。

== 表示等于,用来判断符号左右两侧的操作数是否相同,若相等,则为真,不等,则为假。
比如:

int x=5;
if(x==5) cout<<"is 5"<<endl;
else cout<<"not 5"<<endl;

先判断括号内的值。x==5成立,所以 if 的值为真,所以执行输出“is 5”。

if 条件语句可以省略else从句,如果每个分支只有一个语句,可以不写大括号。

如:

if(a>0)a++;
else if(a==0) a=0;
else a--;

具体看一下这三行。

        if (_kbhit())//此两百毫秒内若有输入
            if (_getch() == 224)
                Arrow = _getch();

第一句,若有输入(_kbhit()函数表示当前缓存区是否有输入,若有,则返回1,若无,则返回0)
第二句,获取输入,判断是否为224。

_getch()函数来自<conio.h>头文件,该函数获取一个键盘输入。(与前面见过的getch()一样,不过这是更规范的用法)
_kbhit()用来检测缓存区是否存在输入,若有,返回一个非0数(真)。

第三句,若为224,再用_getch()读一次(此时读到的是方向键的第二个键值),将值赋给Arrow()。

此时会出现一个问题,那就是当我们输入与当前前进方向相反的箭头时,蛇其实会倒退,为了解决这个问题,我们需要在赋值前对_getch()的值校验。

修改如下:

 if (_getch() == 224) {
                int newArrow = _getch();
                if (Arrow + newArrow != 152)//72+80=152,75+77也是152,因此可以这样校验
                    Arrow = newArrow;
            }

在主循环中,剩下的四句没变。
现在的move()函数作用就是初始化head和tail的指向后,进入循环while(),这个循环将贯穿游戏的全过程,我们称之为——游戏主循环
现在梳理一下游戏主循环的内容:
1,睡眠200ms;
2,判断是否有输入,若有,判断是否是方向键,若是,校验后将键值赋给Arrow;若不是或没有输入,绘制身体。
3,去头,擦尾,根据Arrow画新头。
4,继续下一循环。

最后给出全部代码:

#include <iostream>
#include<conio.h>
#include <windows.h>
//初始长度
#define LENGTH 5
//输出符号
#define NoSymbol ' '
#define BodySymbol 'o'
#define HeadSymbol 'O'
//初始方向
int Arrow = 77;
using namespace std;
//窗口句柄ppp
HANDLE handle;
//蛇体坐标数组
COORD body[LENGTH];
//显示
void draw(COORD pos, char symbol) {
    SetConsoleCursorPosition(handle, pos);//设置handle指向窗口光标位置为pos
    cout << symbol;
}
//预备
void ready() {
    for (int i = 0;i < LENGTH;i++) {
        body[i].X = i;//body数组内元素的横坐标从0到LENGTH-1递增
        body[i].Y = 10;//纵坐标不变
        draw(body[i], BodySymbol);//在数组每个元素代表的坐标处打印蛇身
    }
    draw(body[LENGTH - 1], HeadSymbol); // 将数组最后一个元素代表的坐标处重新绘制蛇头
}
//设置新的蛇身数组
void turn(int& h, int& t) {
    //定义一个COORD型变量dir初值为{0,0}
    COORD dir = { 0,0 };
    //根据Arrow的值来设置dir,使之成为蛇头坐标的变化量。
    switch (Arrow) {
    case 72:
        dir.Y--;
        break;
    case 80:
        dir.Y++;
        break;
    case 75:
        dir.X--;
        break;
    case 77:
        dir.X++;
        break;
    default:
        break;
    }
    //用新的蛇头坐标覆盖原来的蛇尾坐标
    body[t].X = body[h].X + dir.X;
    body[t].Y = body[h].Y + dir.Y;
    /*
    if (Arrow % 2)
        body[t].X = body[h].X + Arrow - 76;
    else body[t].Y = body[h].Y + Arrow / 4 - 19;
*/
    h = t;//将t的值赋给h,此时body[h]表示的是新头部
    (t == LENGTH - 1) ? t = 0 : t++;//当t等于LENGTH时令t为0,否则t++
}

//移动
void move() {
    int head = LENGTH - 1;//head和tail表示头尾所在的下标。
    int  tail = 0;
    while (1) {//while(1)恒成立,此为无限循环
        Sleep(200);//休眠200毫秒
        if (_kbhit())//此两百毫秒内若有输入
            if (_getch() == 224) {
                int newArrow = _getch();
                if (Arrow + newArrow != 152)//72+80=152,75+77也是152,因此可以这样校验
                    Arrow = newArrow;
            }
        draw(body[head], BodySymbol);//变头为体
        draw(body[tail], NoSymbol);//去尾
        turn(head, tail);//新的头尾
        draw(body[head], HeadSymbol);//画新头
    }
}
int main() {
    //获取句柄
    handle = GetStdHandle(STD_OUTPUT_HANDLE);
    //定义一个光标信息结构的对象
    CONSOLE_CURSOR_INFO cci;
    //将handle指向的窗口光标信息赋给cci
    GetConsoleCursorInfo(handle, &cci);
    //将光标隐藏
    cci.bVisible = FALSE;
    //设置handle指向窗口光标信息为cci
    SetConsoleCursorInfo(handle, &cci);
    ready();
    move();
    return 0;
}

本篇结束,下一篇我们完善键盘控制,让我们的贪吃蛇可停可动,可快可慢。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值