项目列表
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/5af35ccbd9987b475059a272badbb6d1.png#pic_center)
你觉得大学学校会决定未来的出路吗?
贪吃蛇
贪吃蛇是我们的经典游戏,所以让我们来做一条贪吃蛇吧
主要技术点:1,括号检测
2.
括号检测
1.这个会用到栈,我们遍历字符串,如果遇到左括号,其他忽略,我们就入栈;
2.继续遍历,如果遇到右括号,我们就把他们从出栈,然后判断一下是不是一对括号;
3.最后大总结判断,判断一下栈是否为空,如果为空代表成双成对,否则 里面还残留着单个括号 不匹配
#ifndef stack.h
#include<stdio.h>
#include<stdlib.h>
#include"stack.h"
/*
括号检测
1.这个会用到栈,我们遍历字符串,如果遇到左括号,其他忽略,我们就入栈;
2.继续遍历,如果遇到右括号,我们就把他们从出栈,然后判断一下是不是一对括号;
3.最后大总结判断,判断一下栈是否为空,如果为空代表成双成对,否则 里面还残留着单个括号 不匹配
*/
//判断是否为左括号 < [ ( “ ’
int isLeft(char ch)
{
switch (ch)
{
case ‘{’:
case ‘[’:
case ‘(’:
case ‘<’:
case ‘"’:
return 1;
default:
return 0;
}
}
//判断是否为右括号
int isRight(char ch)
{
switch (ch)
{
case ‘}’:
case ‘]’:
case ‘)’:
case ‘>’:
case ‘"’:
return 1;
default:
return 0;
}
}
//判断是否成对
int match(char left, char right)
{
switch (left)
{
case ‘{’: if (right == ‘}’)
return 1;
break;
case ‘[’:if (right == ‘]’)
return 1;
break;
case ‘(’:if (right == ‘)’)
return 1;
break;
case ‘<’:if (right == ‘>’)
return 1;
break;
case ‘"’:if (right == ‘"’)
return 1;
break;
}
return 0;
}
int judge(const char str)//const char str的意思是 无法改变指针指向空间的内容,但是可以改变指针。 char const str//确保这个指针不会改变,但是指针里面的内容可以改变
{
struct stack sk = createStack();
while (*str)//如果没有遍历到\0 就继续遍历
{
if (isLeft(*str))
{//如果存在左括号,就入栈
push(sk, *str);
}
//判断是否是右括号
if (isRight(*str))
{//如果是右括号,就判断是否成对
if (!empty(sk) && match(stackTop(sk), *str))//完善更改 你会发现只完成一对是不行的,因为要继续变量 so 判断NULL
{
printf("%c 括号匹配成功", stackTop(sk));
pop(sk);//弹出栈顶元素
printf("ok\n");
}
else
{
return 0;
}
}
str++;
}//可是出现了新的问题,如果全部都是左括号,就会跳出 自动中断
if (!empty(sk))
{
while (!empty(sk))
{
printf("还剩下%c", stackTop(sk));//一定要出栈,否则就会一直打印第一个
pop(sk);
}
return 0;
}
return 1;
}
int main()
{
char *str = “#include<stdio.h>#include<stdl{}ib.h>[(;”;//str是这个字符串指针,str=&字符串 反正就是指向地址首位, *一把钥匙 &一把锁 *str=内容 是指向位置空间的一个变量
int isok = judge(str);//接收返回值
if (isok)
{
printf(“匹配成功”);
}
else
{
printf("没有成双成对,或者你的程序有错误");
}
}
#endif
太可怕了,出现了问题,我滴天呐,我第一次写程序把我自己的文件作为头文件。然后出
现了问题,
注意点与学习到的知识:
1 原来。h文件必须要放到 项目目录下 而且让我写程序要规范,
2 必须带上防重复 ifndef 确实这是个好习惯
既然提出来了 我就要认真执行。
3 头文件里面还必须取掉main函数,一个项目里面只能有一个main函数。
4.我又犯了一个错误,如果突然出现很多现实未声明的错误,那么就是顺序编译的原因,尤其是定义的结构体类型要放在最前面。
5.enum 枚举 每个元素后面加“,”。 然后 DIR::RIGHT “::”表示 域运算符号 从属关系
接下来我们来做
我们首先控制了蛇头的移动,现在我们来控制蛇身的移动,那么他只需要每一个都移动到前一个位置的坐标就行了。
重点问题:
1.刚开始因为移动速度过快,我们延时整个程序,sleep(500) #延时半秒 使我们能看到小蛇
2. 控制小蛇方向时,我们发现反应迟钝,这时我们该怎么处理呢?其实使用sleep,会让全部程序都在进行下一次循环之前暂停。而我们的目的就只是让snakeMove这个函数进行延时,来让我们看见小蛇,控制不灵敏就是因为对keyCtrol 也进行了延时。
initgraph(width, height);
gameInt();
DWORD t1 , t2;//typedef unsigned long DWORD; 无符号长整型
t1 = t2 = GetTickCount();
//获得系统从开机到现在经过的毫秒数 GetTickCount是一种函数。GetTickCount返回(retrieve)从操作系统启动所经过(elapsed)的毫秒数,它的返回值是DWORD。
while (1)
{
GameDraw();
if (t2-t1> 20)
{
snakeMove();
t1=t2;
}
t2 = GetTickCount();
keyCtr();
已被注释/Sleep(500);//使整个程序 延时 500=半秒,但这里我们需要的是移动延时
其实这样子的话,就能使snakeMove这个函数循环3,4次才能被执行一次,达到专项延时效果。
在蛇吃掉食物这个功能实现时,出现了问题,
void eatFood()
{
if (food.x ==snake.coor[0].x&&food.y == snake.coor[0].y)//判断是否相等,一定记得两个等号 ==
{
food.flag = 0;
snake.size++;
}
}
这样太绝对化,只有蛇头圆心与食物圆心重合,才能使身体变长。但其实我们知道了半径
只要蛇头圆心在以食物为圆心,的半径范围内就可以了
@1._getch() 和 getch() 有什么区别?
1,带下划bai线_的函数一般是函数库内部的du函数,而不带下划线的一般是提zhi供给用户使用的函数。带dao下划线的目的是***为了防止用户定义的函数和函数库的函数重名冲突,所以直接使用也是可以的***。
@ 2.为啥要加一个close
原来是这样,你把close 如果关掉的话,你会发现在吃第一个食物的时候,响一声,后面都不响。因为这个音乐就只有几秒是有声的,你一直开着,后面自然是无声。
现在我们来梳理一下 新学到的陌生函数:
1.initgraph(width, height)
是函数名 ,功能为初始化图形系统。pathtodriver表示存放图形驱动文件的路径。头文件是graphics.h。
2.初始化数据一定要在操作前
gameint —》gamedraw
3.//播放背景音乐
mciSendString(L"open ./ress/snake_bgm.mp3 alias BGM",0,0,0);
mciSendString(L"play BGM repeat", 0, 0, 0);
A. 在字符串前加一个L作用: unicode字符集是两个字节组成的。L告示编译器使用两个字节的 unicode 字符集。
如 L"我的字符串" 表示将ANSI字符串转换成unicode的字符串,就是每个字符占用两个字节。
使用函数前必须应该包含头文件:#include<mmsystem.h>
并导入库:#pragma comment<lib.“winmm.lib”>
mciSendString是用来播放多媒体文件的API指令,可以播放MPEG,AVI,WAV,MP3,等等
函数原型如下:
MCIERROR mciSendString(
LPCTSTR lpszCommand, //命令字符串:如 open、play 、close等
LPTSTR lpszReturnString, //接受返回信息的字符串 一般为NULL
UINT cchReturn, //返回字符串的大小
HANDLE hwndCallback //回调窗口句柄
);
最后,注意
改变属性:
@4.alias,repeat
alias就是换一个别名
repeat 重复
@5.随机数种子
srand(GetTickCount());
srand跟rand很像,就是弄随机数的,里面是随机值范围,然后srand(100)就是100->0xFFFFFFFF
srand(G...)就是下图
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200814152630211.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N1bmJveTMyMw==,size_16,color_FFFFFF,t_70#pic_center)
@6.rand() % 256 随机值
food.color = RGB(rand() % 256, rand() % 256, rand() % 256);
后面是范围 表示0-255 是256以内
@延时 GetTickCount()时间差
DWORD t1 , t2;
//typedef unsigned long DWORD;
t1 = t2 = GetTickCount();
//获得系统从开机到现在经过的毫秒数 GetTickCount是一种函数。GetTickCount返回(retrieve)从操作系统启动所经过(elapsed)的毫秒数,它的返回值是DWORD。
while (1)
{
GameDraw();
if (t2-t1> 20)
{
snakeMove();
t1=t2;
}
t2 = GetTickCount();
eatFood();
keyCtr();
stop();
die();
}
@7. MessageBox(GetHWnd(), “you are fail”, “warning…”, MB_YESNO) 信息窗口
MB_OK 就是 你调用MessageBox 后, 弹出的对话框里 有个确定按钮。
如果是 MB_YESNO 的话 就是有两个按钮 是 和 否*/
(获取句柄,中间语句,表头语句,类型)
@8.暂停运行
void stop()
{
if (_kbhit())//检测到有按键
{
while (_getch() == ’ ')//getch 是需要你输入的 所以会卡在这里 你输入了 空格 才会进入while里面 会在下一个getch卡住,只有再次输入空格才会有用
{
if (_getch() == ’ ‘)//while(_getch()!=’ ')
{
break;
}
}
}
}
@9.分数设置
void outtextxy_score(int x, int y, const char *text)
{
char str[20] = “”;
sprintf(str, “%s:%d”, text, snake.score);
settextcolor(RED);
settextstyle(35, 0, “楷体”);
outtextxy(x, y, str);
}
@10.去warning
#define _CRT_SECURE_NO_WARNINGS
程序
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<graphics.h>
#include<conio.h>//_kbhit 是判断是否有键盘按下
#include<string.h>
#include<mmsystem.h>//播放音乐
#pragma comment(lib,“winmm.lib”)//动静态库 /#pragma 是一条编译器指令,是给告诉编译器你要链接一些东西,然后在后面的comment里面指明是什么东西 导入winmm.lib库,有了它才可以支持对windows 多媒体的编程./
#define width 800
#define height 600
首先 我们需要分析功能实现
解析:1.创建窗口 绘制蛇的身体
2.蛇的移动
// 3.蛇的穿墙 吃食
// 4.判断输赢 分数
// 5食物的绘制
//蛇的属性: 长度,坐标,速度,分数,方向,是否死亡
#define SNAKE_NUM 500//蛇的节数最大值是500
//定义食物的信息结构
struct FOOD
{
int x;
int y;
int r;//半径
int flag;//判断是否被吃掉
COLORREF color;
}food;//定义这些模板后,一定要初始化
void stop()
{
if (_kbhit())//检测到有按键
{
while (_getch() == ’ ')//getch 是需要你输入的 所以会卡在这里 你输入了 空格 才会进入while里面 会在下一个getch卡住,只有再次输入空格才会有用
{
if (_getch() == ’ ‘)//while(_getch()!=’ ')
{
break;
}
}
}
}
struct SNAKE
{
int size;//几节身体
POINT coor[SNAKE_NUM];//每一节蛇的坐标信息都会储存在这个数组里面 int x;//int y;可以换成POINT
int speed;//我自己感觉speed要与半径一样
int dir;
int score;
}snake;
//先尝试使用结构体数组
//cpp使用顺序编译 然后我们来枚举
enum DIR
{
UP,
DOWN,
LEFT,
RIGHT,//每个后面都要加,
};
/*
函数:控制蛇的运动
分析:我们只需要控制蛇头的运动就可以,头会带着身子走
控制运动实际就是在控制坐标
*/
void snakeMove()
{//控制蛇的身体的移动
for (int i = snake.size - 1; i > 0; i–)//这里竟出现了错误。是因为前面我初始化的是i,但是后面写的是常量snake.size
{
snake.coor[i] = snake.coor[i - 1];
}
//控制蛇头移动
switch (snake.dir)
{
case UP:
snake.coor[0].y -= snake.speed;
//是否到边界 穿墙判断
if (snake.coor[0].y < 0)
{
snake.coor[0].y = height;
}
break;
case DOWN:
snake.coor[0].y += snake.speed;
//是否到边界 穿墙判断
if (snake.coor[0].y >height)
{
snake.coor[0].y = 0;
}
break;
case LEFT:
snake.coor[0].x -= snake.speed;
//是否到边界 穿墙判断
if (snake.coor[0].x < 0)
{
snake.coor[0].x = width;
}
break;
case RIGHT:
snake.coor[0].x += snake.speed;
//是否到边界 穿墙判断
if (snake.coor[0].x > width)
{
snake.coor[0].x = 0;
}
break;
default:
break;
}
}
void outtextxy_score(int x, int y, const char *text)
{
char str[20] = “”;
sprintf(str, “%s:%d”, text, snake.score);
settextcolor(RED);
settextstyle(35, 0, “楷体”);
outtextxy(x, y, str);
}
//绘制游戏
void GameDraw()
{
BeginBatchDraw();//开始批量绘制,直到出现flushbatchdraw 或者endbatchdraw, 才会展示。 消除闪屏
setbkcolor(RGB(141, 173, 226));//设置背景颜色
cleardevice();//清楚图形屏幕窗口 在计算机术语中,device是屏幕的意思。
outtextxy_score(20, 50, "分数");
for (int i = 0; i < snake.size; i++)
{
setfillcolor(YELLOW);
solidcircle(snake.coor[i].x, snake.coor[i].y, 3);
}
//绘制食物
if (food.flag)
{
setfillcolor(food.color);//只有设置了颜色,初始化才管用
solidcircle(food.x, food.y, food.r);
}
EndBatchDraw();
}
//接触死亡
void die()
{
for (int i = 3; i < snake.size; i++)
{
if (snake.coor[0].x == snake.coor[i].x&&snake.coor[0].y == snake.coor[i].y)
{
MessageBox(GetHWnd(), “you are fail”, “warning…”, MB_YESNO);/*1.在字符串前加一个L作用: unicode字符集是两个字节组成的。L告示编译器使用两个字节的 unicode 字符集。
如 L"我的字符串" 表示将ANSI字符串转换成unicode的字符串,就是每个字符占用两个字节。
strlen(“asd”) = 3;
strlen(L"asd") = 6;
2.MB_OK 就是 你调用MessageBox 后, 弹出的对话框里 有个确定按钮。
如果是 MB_YESNO 的话 就是有两个按钮 是 和 否*/
exit(0);
}
}
}
//吃食物效果
void eatFood()
{
if (snake.coor[0].x >= food.x - food.r&&snake.coor[0].x <= food.x + food.r&&snake.coor[0].y >= food.y - food.r&&snake.coor[0].y <= food.y + food.r&&food.flag==1)//判断是否相等,一定记得两个等号 ==
{
food.flag = 0;
snake.score += 10; //吃食物分数改变
snake.size++;
mciSendString("close EAT ", 0, 0, 0);//这里还是不懂
mciSendString("open ./ress/eatfood.mp3 alias EAT", 0, 0, 0);
mciSendString("play EAT", 0, 0, 0);
}
if (!food.flag)
{
food.color = RGB(rand() % 256, rand() % 256, rand() % 256);
food.flag = 1;
food.r = rand() % 10 + 3;
food.x = rand() % width;
food.y = rand() % height;
}
}
/*
函数:控制方向 使用键盘
*/
void keyCtr()//GetAsyncKeyState是一个用来判断函数调用时指定虚拟键的状态,确定用户当前是否按下了键盘上的一个键的函数。如果按下,则返回值最高位为1。
{
//在这里,我们可以用 getch的方法,但还有另外一种。目前有两种
if (GetAsyncKeyState(‘W’) || GetAsyncKeyState(VK_UP)&&snake.dir!=DOWN)/*1 注意里面只能用大写
2 并且不能直接掉头,所以我们要进行限制
3. 加穿墙效果 调整蛇头功能
*/
{
snake.dir = DIR::UP;
}
if (GetAsyncKeyState('D') || GetAsyncKeyState(VK_RIGHT) && snake.dir != LEFT)
{
snake.dir = DIR::RIGHT;
}
if (GetAsyncKeyState('S') || GetAsyncKeyState(VK_DOWN) && snake.dir != UP)
{
snake.dir = DIR::DOWN;
}
if (GetAsyncKeyState('A') || GetAsyncKeyState(VK_LEFT) && snake.dir != RIGHT)
{
snake.dir = DIR::LEFT;
}
}
//那么我们定义好蛇的属性之后,就需要来初始化游戏
void gameInt()
{//播放背景音乐
mciSendString(“open ./ress/snake_bgm.mp3 alias BGM”,0,0,0);
mciSendString(“play BGM repeat”, 0, 0, 0);
//随机数种子
srand(GetTickCount());
snake.dir = DIR::RIGHT; //怎么初始化方向也是非常令人值得注意的
snake.size = 3;// 哦 原来如此 这里其实是struct snake.size
snake.speed = 6;
snake.score = 0;
snake.coor[2].x=10; //初始化坐标比较值得注意
snake.coor[2].y=10;
snake.coor[1].x = 30; //初始化坐标比较值得注意
snake.coor[1].y = 10;
snake.coor[0].x = 50; //初始化坐标比较值得注意
snake.coor[0].y = 10;
//初始化食物 不懂 代问
food.color = RGB(rand() % 256, rand() % 256, rand() % 256);
food.flag = 1;
food.r = rand() % 10 + 3;
food.x = rand() % width;
food.y = rand() % height;
}
int main()
{
initgraph(width, height);//画出图框
gameInt();
DWORD t1 , t2;//typedef unsigned long DWORD;
t1 = t2 = GetTickCount();//获得系统从开机到现在经过的毫秒数 GetTickCount是一种函数。GetTickCount返回(retrieve)从操作系统启动所经过(elapsed)的毫秒数,它的返回值是DWORD。
while (1)
{
GameDraw();
if (t2-t1> 20)
{
snakeMove();
t1=t2;
}
t2 = GetTickCount();
eatFood();
keyCtr();
stop();
die();
//Sleep(500);//使整个程序 延时 500=半秒,但这里我们需要的是移动延时
}
system("pause");
return 0;
}
显示分数。