点击链接回顾前几篇:
(一)标准输出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,继续下一循环。
最后给出全部代码:文章来源:https://www.toymoban.com/news/detail-483051.html
#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;
}
本篇结束,下一篇我们完善键盘控制,让我们的贪吃蛇可停可动,可快可慢。文章来源地址https://www.toymoban.com/news/detail-483051.html
到了这里,关于0基础学习C++做贪吃蛇, 边玩儿边学习!(八)switch分支语句和键盘控制——自由移动的蛇的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!