当前位置: 首页 > news >正文

怎样制作个人网站摄影网站投稿

怎样制作个人网站,摄影网站投稿,山东省招投标信息网,网站开发教程图文贪吃蛇游戏运行画面-CSDN直播 目录 贪吃蛇游戏运行画面-CSDN直播 1. 实验目标 2. Win32 API介绍 2.1 Win32 API 2.2 控制台程序#xff08;Console#xff09; 2.3 控制台屏幕上的坐标COORD 2.4 GetStdHandle 2.5 GetConsoleCursorlnfo 2.5.1 CONSOLE_CURSOR_INFO …贪吃蛇游戏运行画面-CSDN直播 目录 贪吃蛇游戏运行画面-CSDN直播 1. 实验目标 2. Win32 API介绍 2.1 Win32 API 2.2 控制台程序Console 2.3 控制台屏幕上的坐标COORD 2.4 GetStdHandle 2.5 GetConsoleCursorlnfo 2.5.1 CONSOLE_CURSOR_INFO 2.6 SetConsoleCursorlnfo 2.7 SetConsoleCursorPosition 2.8 GetAsyncKeyState 3. 贪吃蛇准备阶段 3.1 地图 3.1.1 本地化 3.1.2 类型 3.1.3 setlocale函数 3.14 宽字符的打印 3.1.5 地图坐标 3.2 蛇身和食物 3.3 链表定义蛇身 3.4 结构体维护贪吃蛇游戏 3.5 枚举定义蛇的方向和游戏状态 3.6 确定游戏流程设计 4. 游戏开始GameStart 4.1 设置游戏窗口大小和名字以及隐藏光标 4.2 打印欢迎界面 4.3 绘制地图 4.4 初始化蛇身  4.5 创建食物 5. 游戏运行(GameRun) 5.1 打印帮助信息(PrintHelpInfo) 5.2 按键判断与打印得分 5.3 蛇身移动SnakeMove 5.3.1 判断移动过程中是否遇到食物NextIsFood 5.3.1.1 吃食物EatFood 5.3.1.2 不吃食物NoFood 5.3.2 撞到墙游戏结束KillByWall  5.3.3 咬到自身游戏结束KillBySelf 6. 游戏结束GameEnd 6.1 总代码 1. 实验目标 使用C语言在Windows环境的控制台中模拟实现经典小游戏贪吃蛇 实现的基本功能为 贪吃蛇地图绘制蛇吃食物的功能上、下、左、右方向键控制蛇的动作 蛇撞墙死亡 蛇撞自身死亡 计算得分 蛇身加速、减速 暂停游戏 要实现这些功能首先我们必须具备一些知识的储备。例如 C语言函数、枚举、结构体、动态 内存管理、预处理指令、链表、Win32 API等。 2. Win32 API介绍 本次实现贪吃蛇会使用到的一些Win32 API知识那么就学习一下 2.1 Win32 API Windows 这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外它同时也是一个很大的服务中心调用这个服务中心的各种服务每一种服务就是一个函数可以帮应用程序达到开启视窗、描绘图形、使用周边设备等目的由于这些函数服务的对象是应用程序Application所以便称之为 Application Programming Interface简称 API 函数。WIN32 API 也就是 Microsoft Windows 32 位平台的应用程序编程接口。 其实说人话就是如果你要基于Windows操作系统来编写一些程序则Windows会提供各种接口便于你完成一些功能 2.2 控制台程序Console 平常我们运行起来的黑框程序其实就是控制台程序 我们可以使用cmd命令来设置控制台窗口的长宽设置控制台窗口的大小30行100列 mode con cols100 lines30 也可以通过命令设置控制台窗口的名字 title 贪吃蛇 这些能在控制台窗口执行的命令也可以调用C语言函数system来执行。例如 注意system是执行系统命令使用system需要包含头文件stdlib.h #include stdlib.hint main() {//设置控制台窗口的长度设置控制台窗口的大小30行100列system(mode con cols100 lines30);//设置cmd窗口名称system(title 贪吃蛇);return 0; } 运行效果图 执行完后我们会发现窗口大小调制好了但窗口名却没有这是因为程序已经结束了。 解决方法 getchar(); 执行到这行会停下来等待接收一个字符system(pause) 执行到这行命令程序会暂停 2.3 控制台屏幕上的坐标COORD 注意使用COORD需要包含头文件windows.h COORD是WindowsAPI中定义的一个结构体表示一个字符在控制台屏幕幕缓冲区上的坐标坐标系00的原点位于缓冲区的顶部左侧单元格。 typedef struct _COORD {SHORT X;SHORT Y; } COORD, *PCOORD; 给坐标赋值 COORD pos { 10, 15 }; 2.4 GetStdHandle 使用需要包含头文件windows.h GetStdHandle是一个Windows API函数。它用于从一个特定的标准设备标准输入、标准输出或标准错误中取得一个句柄用来标识不同设备的数值使用这个句柄可以操作设备。 HANDLE GetStdHandle(DWORD nStdHandle); 说人话要操作特定的控制台程序就要获得它的操作权限能要识别你在操作谁 实例 2.5 GetConsoleCursorlnfo 检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息 BOOL WINAPI GetConsoleCursorInfo(          HANDLE                                               hConsoleOutput,          PCONSOLE_CURSOR_INFO              lpConsoleCursorInfo     );  PCONSOLE_CURSOR_INFO是指向CONSOLE_CURSOR_INFO 结构的指针该结构接收有关主机游标(光标)的信息 总结使用GetConsoleCursorlnfo传入的第一个参数是句柄为何需要句柄原因是如要隐藏光标首先需要获得当前控制台对应的光标信息。第二个参数是结构体指针 HANDLE hOutput NULL;//获取标准输出设备的句柄(⽤来标识不同设备的数值)hOutput GetStdHandle(STD_OUTPUT_HANDLE);//定义结构体变量CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(hOutput, CursorInfo);//获取控制台光标信息//调用完这个函数后就能把hOutput对应的光标信息填充到这个结构体变量中去 2.5.1 CONSOLE_CURSOR_INFO 这个结构体包含有关控制台光标的信息 typedef struct _CONSOLE_CURSOR_INFO {DWORD dwSize;BOOL bVisible; } CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO; dwSize由光标填充的字符单元格的百分比。 此值介于1到100之间。 光标外观会变化范围从完全填充单元格到单元底部的水平线条。 bVisible游标的可见性。 如果光标可见则此成员为 TRUE。 CursorInfo.bVisible false; //隐藏控制台光标 注意使用false需要包含头文件stdbool.h 2.6 SetConsoleCursorlnfo 设置指定控制台屏幕缓冲区的光标的大小和可见性。 BOOL WINAPI SetConsoleCursorInfo(HANDLE hConsoleOutput,const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo ); 实例 #include stdio.h #include stdlib.h #include windows.h #include stdbool.hint main() {HANDLE hOutput GetStdHandle(STD_OUTPUT_HANDLE);//隐藏光标操作CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(hOutput, CursorInfo);//获得控制台光标信息CursorInfo.bVisible false;//隐藏控制台光标false需包含头文件stdbool.hSetConsoleCursorInfo(hOutput, CursorInfo);//设置控制台光标状态return 0; }调试 可以看出当执行完CursorInfo.bVisible false;的时候光标还是会显示只有执行完SetConsoleCursorInfo函数后光标才会隐藏。 在举一个例子修改光标可见大小 运行结果 2.7 SetConsoleCursorPosition 设置指定控制台屏幕缓冲区中的光标位置我们将想要设置的坐标信息放在COORD类型的pos中调用SetConsoleCursorPosition函数将光标位置设置到指定的位置。 BOOL WINAPI SetConsoleCursorPosition(HANDLE hConsoleOutput,COORD pos ); 第一个参数传入的是句柄第二个参数传入的是坐标信息也就是COORD类型的结构体变量。 实例 #include stdio.h #include windows.h int main() {COORD pos { 10,5 };HANDLE hOutput NULL;//获得标准输出的句柄(用来标识不同设备的数值)hOutput GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为posSetConsoleCursorPosition(hOutput, pos);printf(haha\n);return 0; } 运行结果 如果我们不去设置指定光标位置那么haha就会在这里被输出 由于日后我们可能会多次使用设置指定光标位置所以我们不妨封装一个设置光标位置的函数Setpos //设置光标位置 void SetPos(short x, short y) {COORD pos { x,y };HANDLE hOutput NULL;//获得标准输出的句柄(用来标识不同设备的数值)hOutput GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为posSetConsoleCursorPosition(hOutput, pos); } 2.8 GetAsyncKeyState 获取按键情况GetAsyncKeyState的函数原型如下 SHORT GetAsyncKeyState(int vKey ); 这个函数需要你传一个虚拟键值进去然后该函数会检测传进去的虚拟键值所代表的按键是否被按过函数通过返回值来分辨按键的状态。返回值类似是short 如果返回的这个数据的二进制位的最高位为1则代表按键状态是按下 如果返回的这个数据的二进制位的最高位为0则代表按键状态是抬起 如果返回的这个数据的二进制位的最低位为1则代表该键被按过 如果返回的这个数据的二进制位的最低位为0则代表该键没被按过 如果我们要判断⼀个键是否被按过可以检测GetAsyncKeyState返回值的最低值是否为1 #define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) 0x1) ? 1 : 0 ) 举个例子 虚拟键值表如下 https://learn.microsoft.com/zh-cn/windows/win32/inputdev/virtual-key-codes 3. 贪吃蛇准备阶段 3.1 地图 我们最终的贪吃蛇大纲要是这个样子那我们的地图如何布置呢 这里不得不讲一下控制台窗口的一些知识如果想在控制台的窗口中指定位置输出信息我们得知道该位置的坐标所以首先介绍一下控制台窗口的坐标知识。 控制台窗口的坐标如下所示横向的是X轴从左向右依次增长纵向是Y轴从上到下依次增长。 在游戏地图上我们打印墙体使用宽字符□打印蛇使用宽字符●打印食物使用宽字符★ 普通的字符是占一个字节的这类宽字符是占用2个字节。 这里再简单的讲一下C语言的国际化特性相关的知识过去C语言并不适合非英语国家地区使用。 C语言最初假定字符都是自己的。但是这些假定并不是在世界的任何地方都适用。 C语言字符默认是采用ASCII编码的ASCII字符集采用的是单字节编码且只使用了单字节中的低7位最高位是没有使用的可表示为0xxxxxxx可以看到ASCII字符集共包含128个字符在英语国家中128个字符是基本够用的但是在其他国家语言中比如在法语中字母上方有注音符号它就无法用 ASCII 码表示。于是一些欧洲国家就决定利用字节中闲置的最高位编入新的符号。比如法语中的é的编码为130⼆进制10000010。这样一来这些欧洲国家使用的编码体系可以表示最多256个符号。但是这里又出现了新的问题。不同的国家有不同的字母因此哪怕它们都使用256个符号的编码方式代表的字母却不一样。比如130在法语编码中代表了é在希伯来语编码中却代表了字母Gimel 在俄语编码中又会代表另一个符号。但是不管怎样所有这些编码方式中0--127表示的符号是一样的不一样的只是128--255的这一段。 至于亚洲国家的文字使用的符号就更多了汉字就多达10万左右。一个字节只能表示256种符号肯定是不够的就必须使用多个字节表达一个符号。比如简体中文常见的编码方式是 GB2312使用两个字节表示一个汉字所以理论上最多可以表 256 x 256 65536 个符号。 后来为了使C语言适应国际化C语言的标准中不断加入了国际化的支持。比如加入和宽字符的类型wchar_t 和宽字符的输入和输出函数加入和locale.h头文件其中提供了允许程序员针对特定地区通常是国家或者说某种特定语言的地理区域调整程序行为的函数。 3.1.1 locale.h本地化 locale.h提供的函数用于控制C标准库中对于不同的地区会产生不一样行为的部分。 在标准可以中依赖地区的部分有以下几项 数字量的格式 货币量的格式 字符集 日期和时间的表示形式 3.1.2 类型 通过修改地区程序可以改变它的行为来适应世界的不同区域。但地区的改变可能会影响库的许多部分其中一部分可能是我们不希望修改的。所以C语言支持针对不同的类项进行修改下面的一个宏指定一个类项 LC_COLLATE:影响字符串比较函数 strco1l()和 strxfrm()。LC_CTYPE影响字符处理函数的行为。LC_MONETARY影响货币格式。 LC_NUMERIC:影响printf()的数字格式。 LC_TIME影响时间格式strftime()和wcsftime()。LC_ALL针对所有类项修改将以上所有类别设置为给定的语言环境。 3.1.3 setlocale函数 使用setlocale函数需要包含头文件locale.h char* setlocale (int category, const char* locale); setlocale 函数用于修改当前地区可以针对一个类项修改也可以针对所有类项。 setlocale 的第一个参数可以是前面说明的类项中的一个那么每次只会影响一个类项如果第一个参数是LC_ALL就会影响所有的类项。 C标准给第二个参数仅定义了2种可能取值C和。C是正常模式C语言默认的模式则是本地模式 在任意程序执行开始都会隐藏式执行调用: setlocale(LC_ALL, C); 当地区设置为C时库函数按正常方式执行小数点是一个点。 当程序运行起来后想改变地区就只能显示调用setlocale函数。用作为第2个参数调用setlocale函数就可以切换到本地模式这种模式下程序会适应本地环境。比如切换到我们的本地模式后就支持宽字符汉字的输出等。 setlocale(LC_ALL, );//切换本地环境 扩展 setlocale的返回值是一个字符串指针表示已经设置好的格式。如果调用失败则返回空指针NULL。setlocale()可以用来查询当前地区这时第二个参数设为NULL就可以了。 #include locale.hint main() {char* loc;loc setlocale(LC_ALL, NULL);printf(默认的本地信息%s\n, loc);loc setlocale(LC_ALL, );printf(设置后的本地信息%s\n, loc);return 0; } 运行结果 3.14 宽字符的打印 那如果想在屏幕上打印宽字符怎么打印呢宽字符的字面量必须加上前缀L否则C语言会把字面量当作窄字符类型处理。前缀L在单引号前面表示宽字符宽字符的打印使用wprintf对应wprintf()的占位符为%lc在双引号前面表示宽字符串对应wprintf()的占位符为%ls #include stdio.h #include locale.hint main() {setlocale(LC_ALL, );wchar_t ch1 L中;wchar_t ch2 L国;wchar_t ch3 L□;wchar_t ch4 L☆;printf(%c%c\n, a, b);wprintf(L%lc\n, ch1);wprintf(L%lc\n, ch2);wprintf(L%lc\n, ch3);wprintf(L%lc\n, ch4);return 0; } 运行结果 从输出的结果来看我们发现一个普通字符占一个字符的位置但是打印一个汉字字符占用2个字符的位置那么我们如果要在贪吃蛇中使用宽字符就得处理好地图上坐标的计算。 普通字符和宽字符打印出宽度的展示如下 3.1.5 地图坐标 我们假设实现一个棋盘27行58列的棋盘行和列可以根据自己的情况修改再围绕地图画出墙 如下 注意 列最好是2的倍数因为宽字符占2位我们可以看一个正常字符占的大小我们不难发现一个字符占的大小的宽度是比较窄的但是高度是比较长的 3.2 蛇身和食物 初始化状态假设蛇的长度是5蛇身的每个节点是●在固定的一个坐标处比如(24, 5)处开始出现蛇连续5个节点。注意蛇的每个节点的x坐标必须是2的倍数否则可能会出现蛇的一个节点有一半出现在墙体中另外一半在墙外的现象坐标不好对齐。 关于食物就是在墙体内随机生成成一个坐标x坐标必须是2的倍数坐标不能和蛇的身体重合然后打印★。 3.3 链表定义蛇身 在游戏运行的过程中蛇每次吃一个食物蛇的身体就会变长一节如果我们使用链表存储蛇的信 息那么蛇的每一节其实就是链表的每个节点。每个节点只要记录好蛇身节点在地图上的坐标就行所以蛇节点结构如下 typedef struct SnakeNode {int x;int y;struct SnakeNode* next; }SnakeNode, * pSnakeNode; 3.4 结构体维护贪吃蛇游戏 要管理整条贪吃蛇我们再封装⼀个Snake的结构来维护整条贪吃蛇 typedef struct Snake //定义贪吃蛇 {pSnakeNode pSnake;//维护整条蛇的指针pSnakeNode pFood;//维护⻝物的指针enum DIRECTION Dir;//蛇头的⽅向默认是向右enum GAME_STATUS Status;//游戏状态int Socre;//当前获得分数int foodWeight;//默认每个⻝物10分int SleepTime;//每⾛⼀步休眠时间蛇休眠的时间休眠的时间越短蛇的速度越快休眠的时间越长蛇的速度越慢 }Snake, * pSnake; 3.5 枚举定义蛇的方向和游戏状态 蛇的方向可以一一列举使用枚举 //方向 enum DIRECTION {UP 1,DOWN,LEFT,RIGHT }; 游戏状态可以一一列举使用枚举 //游戏状态 enum GAME_STATUS {OK,//正常运⾏KILL_BY_WALL,//撞墙KILL_BY_SELF,//咬到⾃⼰ESC//强制退出游戏 }; 3.6 确定游戏流程设计 4. 游戏开始GameStart 首先我们先创建3个文件 snake.h ---- 贪吃蛇游戏中类型的声明函数的声明   snake.c ---- 函数的实现 test.c     ---- 贪吃蛇游戏的测试 游戏主逻辑test.c #include snake.hvoid test() {srand((unsigned int)time(NULL));int ch 0;do{ Snake ps { 0 };//游戏开始前的初始化GameStart(ps);//游戏玩的过程GameRun(ps);//游戏结束GameEnd(ps);SetPos(16,13);printf(再来一局吗(Y/N):);scanf( %c, ch);} while (ch Y || ch y); }int main() {//适配本地中文环境setlocale(LC_ALL, );test();SetPos(0, 27);return 0; } 4.1 设置游戏窗口大小和名字以及隐藏光标 //游戏开始 void GameStart(pSnake ps) {//设置控制台的信息窗口大小窗口名system(mode con cols100 lines30);system(title 贪吃蛇);//隐藏光标HANDLE hOutput NULL;hOutput GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(hOutput, CursorInfo);CursorInfo.bVisible false;SetConsoleCursorInfo(hOutput, CursorInfo);//打印欢迎信息WelcomeToGame(); } 首先我们要让窗口大小100行30列有人是不是会疑惑为什么不是58行27列我们不是之前给的图片就是这样的嘛 那是因为这只是地图的大小地图大小外还有提示的信息就比如下图一样所以我们给的大小就会比地图大小还要大一些 4.2 打印欢迎界面 效果图 要实现这个效果图首先肯定要用到COORD和SetConsoleCursorPosition来设置光标位置 //定位光标位置 void SetPos(int x, int y) {COORD pos { x,y };HANDLE hOutput NULL;hOutput GetStdHandle(STD_OUTPUT_HANDLE);//在屏幕上设置指定光标位置SetConsoleCursorPosition(hOutput, pos); } 接下来就是打印欢迎信息 //打印欢迎信息 void WelcomeToGame() {//欢迎信息SetPos(35,10);printf(欢迎来带贪吃蛇小游戏\n);SetPos(38, 20);//暂停system(pause);//清屏system(cls);//功能介绍SetPos(15, 10);printf(用 ↑ . ↓ . ← . → 来控制蛇的移动F3是加速F4是减速);SetPos(15, 11);printf(加速将得到更高的分数。);SetPos(38, 20);system(pause);system(cls); } 4.3 绘制地图 创建地图就是将墙打印出来因为是宽字符打印所以使用wprintf函数打印格式串前使用L 打印地图的关键是要算好坐标才能在想要的位置打印墙体。 因为打印墙体字符L□我们后续会经常使用所以我们把它封住成宏 #define WALL L□ 打印墙体代码 //绘制地图 void CreateMap() {SetPos(0, 0);int i 0;//一个宽字符占2位所以i加的是2//i到56就行了因为输出WALL就能把56个57空间给占了//上框框for (i 0; i 56; i 2){wprintf(L%lc, WALL);}//下框框SetPos(0, 26);for (i 0; i 56; i 2){wprintf(L%lc, WALL);}//左框框//这里i只要加1的原因是宽字符和正常字符的高度是一样的//只有宽字符和正常字符的宽度有差2倍for (i 1; i 26; i){SetPos(0, i);wprintf(L%lc, WALL);}//右框框for (i 1; i 26; i){SetPos(56, i);wprintf(L%lc, WALL);} } 效果图 4.4 初始化蛇身  我们可以按照下面这张图来进行初始化蛇身与食物 蛇最开始长度为5节每节对应链表的一个节点蛇身的每一个节点都有自己的坐标。 创建5个节点然后将每个节点存放在链表中进行管理。创建完蛇身后将蛇的每一节打印在屏幕上。 再设置当前游戏的状态蛇移动的速度默认的方向初始成绩蛇的状态每个食物的分数。 蛇身打印的宽字符由于我们后面可能会多次打印蛇身宽字符L●所以我们索性把它封装成宏 #define BODY L● 1.打印蛇身 //初始化蛇身 //ps是维护整条蛇的地址 void InitSnake(pSnake ps) {pSnakeNode cur NULL;//按照图片上的指示创建5个节点int i 0;for (i 0; i 5; i){cur (pSnakeNode)malloc(sizeof(SnakeNode));if (cur NULL){perror(InitSnake():malloc());return;}cur-x POS_X 2 * i;cur-y POS_Y;cur-next NULL;//创建好后就进行头插cur-next ps-pSnake;ps-pSnake cur;} 我把行和列封装成一个宏这样的好处是方便后续修改 #define POS_X 24 #define POS_Y 5 头插法的解析 2.创建好后打印蛇身 //创建好后就打印蛇的身体while (cur){SetPos(cur-x, cur-y);wprintf(L%lc, BODY);cur cur-next;} 3.初始化贪吃蛇的其他数据 //初始化贪吃蛇的数据ps-SleepTime 200;ps-Socre 0;ps-Status OK;ps-Dir RIGHT;ps-foodWeight 10;ps-pFood NULL; 4.初始化蛇身总代码 //初始化蛇身 void InitSnake(pSnake ps) {pSnakeNode cur NULL;//按照图片上的指示创建5个节点int i 0;for (i 0; i 5; i){cur (pSnakeNode)malloc(sizeof(SnakeNode));if (cur NULL){perror(InitSnake():malloc());return;}cur-x POS_X 2 * i;cur-y POS_Y;cur-next NULL;//创建好后就进行头插cur-next ps-pSnake;ps-pSnake cur;}//创建好后就打印蛇的身体while (cur){SetPos(cur-x, cur-y);wprintf(L%lc, BODY);cur cur-next;}//初始化贪吃蛇的数据ps-SleepTime 200;ps-Socre 0;ps-Status OK;ps-Dir RIGHT;ps-foodWeight 10;ps-pFood NULL; } 4.5 创建食物 首先创建食物我们需要考虑的问题 食物是随机出现的所以坐标就是随机的但是生成的坐标x必须是2的倍数生成的坐标必须在墙内生成的坐标不能在蛇的身上 由于打印食物宽字符L★后续可能会多次用到所以我们把它封装成一个宏 #define FOOD L★ 1.创建食物坐标 int x 0;int y 0; agin://使食物坐标必须要在墙内并且x的坐标必须要是2的倍数do{x rand() % 53 2;y rand() % 25 1;} while (x % 2 ! 0);//判断坐标是否在蛇的身上pSnakeNode cur ps-pSnake;while (cur){if (x cur-x y cur-y){goto agin;}cur cur-next;} x rand() % 53 2; rand() % 53生成范围是0~52后面加2所以x的范围则是2~54 y rand() % 25 1; rand() % 25 1;生成范围是0~24后面加1所以y的范围则是1到25 首先我们已经保证生成的坐标是在墙内 然后我们还要判断x是否是2的倍数所以我们用了个do-while循环来进行判断 后续我们又用了while循环来判断生成的食物节点是否与蛇身的某个节点重合如果一样我们就用goto语句来进行跳转使坐标重新生成  使用rand()我们需要包含头文件stdlib.h 为了防止生成的随机数与后续再次执行的程序一样我用了srand((unsigned int)time(NULL));来修改种子如果有不懂srand和time的同学可以看我之前写的博客(猜数字游戏)然后使用srand函数需要包含头文件stdlib.h使用time需要包含头文件time.h 走到这一步我们生成的x与y坐标就已经符合要求了接下来就是生成一个节点(pFood)让x与y赋值给pFood中的x与y最后让维护蛇指针内的pFood指针来指向这个节点 pSnakeNode pFood (pSnakeNode)malloc(sizeof(SnakeNode));if (pFood NULL){perror(CreateFood()::malloc());return;}pFood-x x;pFood-y y;SetPos(x, y);wprintf(L%lc, FOOD);ps-pFood pFood; 创建食物总代码 //创建食物 void CreateFood(pSnake ps) {int x 0;int y 0; agin://使食物坐标必须要在墙内并且x的坐标必须要是2的倍数do{x rand() % 53 2;y rand() % 25 1;} while (x % 2 ! 0);//判断坐标是否在蛇的身上pSnakeNode cur ps-pSnake;while (cur){if (x cur-x y cur-y){goto agin;}cur cur-next;}pSnakeNode pFood (pSnakeNode)malloc(sizeof(SnakeNode));if (pFood NULL){perror(CreateFood()::malloc());return;}pFood-x x;pFood-y y;SetPos(x, y);wprintf(L%lc, FOOD);ps-pFood pFood; } 5. 游戏运行(GameRun) 首先我们再次看一遍游戏流程设计 游戏运行期间右侧打印帮助信息提示玩家 根据游戏状态检查游戏是否继续如果是状态是OK游戏继续否则游戏结束。如果游戏继续就是检测按键情况确定蛇下一步的方向或者是否加速减速是否暂停或者退出游戏。确定了蛇的方向和速度蛇就可以移动了。 5.1 打印帮助信息(PrintHelpInfo) 首先我们可以看到程序运行起来后右侧的帮助信息 因为我们设置游戏窗口的时候设置的是100列30行所以设置帮助信息我是一开始是把光标定位到62列17行大家可以按自己的想法来不一定要和我一样 //打印帮助信息 void PrintHelpInfo() {SetPos(62, 17);printf(1.不能穿墙不能咬到自己);SetPos(62, 18);printf(2.用↑.↓.←.→分别控制蛇的移动。);SetPos(62, 19);printf(3.F3是加速F4是减速);SetPos(62, 20);printf(ESC:退出游戏 SPACE:暂停游戏); } 5.2 按键判断与打印得分 首先我们需要知道每个键的虚拟键值如下 https://learn.microsoft.com/zh-cn/windows/win32/inputdev/virtual-key-codes 然后我整理出了本程序需要用到的虚拟键值 然后我们就要判断我们是否有按过这些键就可以用到前面GetAsyncKeyState中定义的宏 #define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) 0x1) ? 1 : 0 ) 游戏玩的过程总代码 //游戏玩的过程 void GameRun(pSnake ps) {//打印帮助信息PrintHelpInfo();do{//打印当前得分SetPos(62, 10);printf(得分%d, ps-Socre);SetPos(62, 11);printf(每个食物得分%-2d, ps-foodWeight);if (KEY_PRESS(VK_UP) ps-Dir ! DOWN){ps-Dir UP;}else if (KEY_PRESS(VK_DOWN) ps-Dir ! UP){ps-Dir DOWN;}else if (KEY_PRESS(VK_LEFT) ps-Dir ! RIGHT){ps-Dir LEFT;}else if (KEY_PRESS(VK_RIGHT) ps-Dir ! LEFT){ps-Dir RIGHT;}else if (KEY_PRESS(VK_SPACE)){//进行休眠pause();}else if (KEY_PRESS(VK_F3)){//加速if (ps-SleepTime 50){ps-SleepTime - 30;ps-foodWeight 2;}}else if (KEY_PRESS(VK_F4)){//减速if (ps-foodWeight 2){ps-SleepTime 30;ps-foodWeight - 2;}}else if (KEY_PRESS(VK_ESCAPE)){//按ESC键退出ps-Status ESC;break;}//蛇每走一步都需要休眠休眠时间越短蛇移动速度就越快Sleep(ps-SleepTime);SnakeMove(ps);//判断是否撞墙了KillByWall(ps);//判断是否撞到自己了KillBySelf(ps);} while (ps-Status OK); } 当我们按下了上下左右的时候我们还要与当前蛇行走的方向进行判断 当我们按上的时候蛇当前运动方向是不能向下的当我们按下的时候蛇当前运动方向是不能向上的当我们按左的时候蛇当前运动方向是不能向右的当我们按右的时候蛇当前运动方向是不能向左的当我们按ESC键的时候我们就把当前游戏状态进行修改,然后直接退出循环当我们按下F3就会加速休眠时间变短但时间不可能减成负数索性我们就规定当休眠时间大于等于50的时候我们才能加速当然休眠时间变短一次吃食物分数就会变多当我们按下F4就会减速休眠时变长一次吃的食物分数变少但是我们不能把分数减到0吧不能吃一个食物一分都不得所以我们规定只有食物分数大于2时我们按了F4才有效果 注意事项(打印每个食物得分) 打印每个食物的得分有个小细节就是要用%2d(或者%-2d,%3d,%-4d都可以)就是不要用%d来打印因为一开始默认每个食物的得分是10分后续我们是可以用F3和F4来控制蛇移动的速度速度决定每吃一个食物所得分数。如果我们速度慢下来了食物所得分数就会变少由原来的10分变成8分或者更低但因为一开始我们默认打印了10分然后减了一次速度按理来说是要打印8分但用%d打印的结果是80原因是10后面的0没有被覆盖掉 5.3 蛇身移动SnakeMove 当然我们在蛇身移动前我们可以让蛇先休眠一下 蛇身移动的主要思想 先创建下一个节点根据移动方向和蛇头的坐标蛇移动到下一个位置的坐标。  确定了下一个位置后看下一个位置是否是食物NextIsFood是食物就做吃食物处理 EatFood如果不是食物则做前进一步的处理NoFood。  蛇身移动后判断此次移动是否会造成撞墙KillByWall或者撞上自己蛇身KillBySelf从而影响游戏的状态。 移动过程 向上和向下 向上走首先创建的新节点是在蛇头上面由图可以看出新节点y坐标就是蛇头坐标y减1而新节点x坐标则是和蛇头x坐标一样向下走首先创建的新节点是在蛇头下面由图可以看出新节点y坐标就是蛇头坐标y加1而新节点x坐标则是和蛇头x坐标一样 向左和向右 向左走首先创建的新节点是在蛇头左边由图可以看出新节点x坐标是蛇头节点x坐标减2减2的原因是宽字符打印宽度占2位新节点y坐标则是和蛇头节点y坐标一样向右走首先创建的新节点是在蛇头右边由图可以看出新节点x坐标是蛇头节点x坐标加2加2的原因是宽字符打印宽度占2位新节点y坐标则是和蛇头节点y坐标一样 //蛇移动过程 void SnakeMove(pSnake ps) {pSnakeNode pNextNode (pSnakeNode)malloc(sizeof(SnakeNode));if (pNextNode NULL){perror(SnakeMove()::malloc());return;}switch (ps-Dir){case UP:pNextNode-x ps-pSnake-x;pNextNode-y ps-pSnake-y - 1;break;case DOWN:pNextNode-x ps-pSnake-x;pNextNode-y ps-pSnake-y 1;break;case LEFT:pNextNode-x ps-pSnake-x - 2;pNextNode-y ps-pSnake-y;break;case RIGHT:pNextNode-x ps-pSnake-x 2;pNextNode-y ps-pSnake-y;break;}//移动的过程中判断是否遇到食物if (NextIsFood(pNextNode, ps)){//遇到食物EatFood(pNextNode, ps);}else{//没遇到食物NotEatFood(pNextNode, ps);} } 5.3.1 判断移动过程中是否遇到食物NextIsFood 当然在蛇移动的过程中可能移动的下一个节点就是食物所以我们还要判断是否遇到了食物 ​ //判断移动过程中是否碰到食物 int NextIsFood(pSnakeNode pNextNode, pSnake ps) {return (pNextNode-x ps-pFood-x pNextNode-y ps-pFood-y); } 相等返回1不相等返回0 5.3.1.1 吃食物EatFood 如果返回值是1则我们遇到了食物那么我们就把新节点头插到贪吃蛇身上然后打印蛇身 同时我们之前定义食物的时候还动态malloc了一块空间我们既然用新节点的空间头插到贪吃蛇上面那我们就应该将原本食物的节点给销毁free 吃掉了食之后我们原先在地图上的食物就被覆盖了那么此时我们就应该再创建一个食物 同时我们吃掉了一个食物之后我们的分数也应该变高就让原先的分数加上一个食物的分数 //下一步要走的位置处就是食物就吃掉食物 void EatFood(pSnakeNode pNextNode, pSnake ps) {pNextNode-next ps-pSnake;ps-pSnake pNextNode;pSnakeNode cur ps-pSnake;ps-Socre ps-foodWeight;//打印蛇身while (cur){SetPos(cur-x, cur-y);wprintf(L%lc, BODY);cur cur-next;}free(ps-pFood);CreateFood(ps); } 首先我不知道看到这里的小伙伴会不会有这个疑问 我们知道食物和新节点是不同的地址两次malloc开辟出来的但是他们两个空间重合了食物和下一个节点在地图上存放空间重合所以会不会不理解释放食物为什么不会把下一个节点空间也释放了 原因 创建出来的下一个节点和食物这个节点只是x和y坐标一样节点是完全不同的两个节点所以释放食物对创建出来的下一个节点没有影响 5.3.1.2 不吃食物NoFood 当下一个节点不是食物的时候我们就先将下一个节点头插到贪吃蛇上面 同时我们需要知道本来贪吃蛇是已经被打印出来了的初始化蛇身的时候所以我们只需要将新的头节点打印出来同时将尾节点打印成空格并释放我们就能在视觉上达到贪吃蛇走一步的效果 至于找到尾结点对我们来说可是很轻松的用while循环就能办到 注意SetPos到尾结点位置之后要打印两个空格注意是两个空格因为尾结点宽字符占2位 //pSnakeNode pNextNode 是下⼀个节点的地址 //pSnake ps 维护蛇的指针 //下一步要走的位置处不是食物就不吃食物 void NotEatFood(pSnakeNode pNextNode, pSnake ps) {pNextNode-next ps-pSnake;ps-pSnake pNextNode;pSnakeNode cur ps-pSnake;while (cur-next-next){SetPos(cur-x, cur-y);//每走一步顺便把蛇身节点打印出来wprintf(L%lc, BODY);cur cur-next;}//没遇到食物首先要将新节点头插还要找到最后一个节点给释放掉//释放前要找到尾结点的位置然后输出两个空//这样才能把尾节点图表给覆盖掉SetPos(cur-next-x, cur-next-y);printf( );free(cur-next);cur-next NULL; } 我不知道有没有小伙伴没看我前面介绍有这一个疑问反正当时我是有这个疑问的 原因是这里就是不进行打印的使用的是上一次打印留下的图形走到cur-next-next是为了释放下一个节点所以这个函数没有进行清屏而是用printf(  );来把最后一个图形覆盖掉 5.3.2 撞到墙游戏结束KillByWall  判断蛇是否撞到墙我们只需要判断贪吃蛇的头节点是否在墙壁所圈定的范围之内如果不在这个范围内那就证明蛇已经撞到墙了 接着我们需要将游戏的状态更改为 KILL_BY_WALL 当蛇向后走了一步时判断到状态不为 OK就会跳出循环游戏结束 //判断是否撞墙了 void KillByWall(pSnake ps) {if (ps-pSnake-x 0 ||ps-pSnake-x 56 ||ps-pSnake-y 0 ||ps-pSnake-y 26)ps-Status KILL_BY_WALL; } 5.3.3 咬到自身游戏结束KillBySelf 我们要判断蛇是否会撞到自己我们只需要将头节点和蛇身的每一个坐标一一比对当发现有相同的时候就说明蛇已经咬到自己了 接着我们需要将游戏的状态更改为 KILL_BY_SELF 当蛇向后走了一步时判断到状态不为 OK就会跳出循环游戏结束 //检测是否撞自己 void KillBySelf(pSnake ps) {pSnakeNode cur ps-pSnake-next;while (cur){if (ps-pSnake-x cur-x ps-pSnake-y cur-y){ps-Status KILL_BY_SELF;return;}cur cur-next;} } 6. 游戏结束GameEnd 游戏状态不再是OK游戏继续的时候要告知游戏结束的原因并且释放蛇身节点与食物节点。 至于删除蛇身我们可以定义两个指针一个指向要删除的节点一个指向下一个节点 这是因为如果我们将该节点的空间释放掉之后我们就找不到下一个节点了所以我们才需要两个节点而循环的条件就是当指针cur指向 NULL 的时候循环停止 在释放完之后不忘释放食物的空间 /游戏结束 void GameEnd(pSnake ps) {SetPos(18, 10);switch (ps-Status){case KILL_BY_WALL:printf(很遗憾撞墙了游戏结束);break;case KILL_BY_SELF:printf(很遗憾撞到自身了游戏结束);break;case ESC:printf(按了ESC键正常退出);break;}//释放蛇身节点和食物节点pSnakeNode cur ps-pSnake;pSnakeNode pNextNode NULL;while (cur){pNextNode cur;cur cur-next;free(pNextNode);}free(ps-pFood);ps NULL; }6.1 总代码 test.c #define _CRT_SECURE_NO_WARNINGS 1 #include snake.hvoid test() {srand((unsigned int)time(NULL));int ch 0;do{ Snake ps { 0 };//游戏开始前的初始化GameStart(ps);//游戏玩的过程GameRun(ps);//游戏结束GameEnd(ps);SetPos(16,13);printf(再来一局吗(Y/N):);scanf( %c, ch);} while (ch Y || ch y); }int main() {//适配本地中文环境setlocale(LC_ALL, );test();SetPos(0, 27);return 0; }snake.h #pragma once #include stdio.h #include windows.h #include stdbool.h #include locale.h #include stdlib.h//随机数 #include time.h#define WALL L□ #define BODY L● #define FOOD L★ #define POS_X 24 #define POS_Y 5 #define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) 0x1) ? 1 : 0 )//定义蛇身节点 typedef struct SnakeNode {int x;int y;struct SnakeNode* next; }SnakeNode, *pSnakeNode;//定义贪吃蛇 typedef struct Snake {pSnakeNode pSnake;//维护整条蛇的指针pSnakeNode pFood;//维护食物的指针enum DIRECTION Dir;//蛇头的方向默认是向右enum GAME_STATUS Status;//游戏状态int Socre;//当前获得分数int foodWeight;//默认每个食物10分int SleepTime;//每走一步休眠时间 }Snake, * pSnake;//定义蛇的方向 enum DIRECTION {UP 1,DOWN,LEFT,RIGHT };//游戏状态 enum GAME_STATUS {OK,//正常运行KILL_BY_WALL,//撞墙KILL_BY_SELF,//咬到自己ESC, //按了ESC键退出正常退出 };//游戏开始前的准备 void GameStart(pSnake ps); //打印欢迎信息 void WelcomeToGame(); //定位控制台光标位置 void SetPos(int x, int y); //绘制地图 void CreateMap(); //初始化蛇身 void InitSnake(pSnake ps); //创建食物 void CreateFood(pSnake ps);//游戏运行的整个逻辑 void GameRun(pSnake ps); //打印帮助信息 void PrintHelpInfo(); //蛇移动过程 void SnakeMove(pSnake ps); //判断蛇头的下一步要走的位置处是否是食物 int NextIsFood(pSnakeNode pNextNode, pSnake ps); //下一步要走的位置处就是食物就吃掉食物 void EatFood(pSnakeNode pNextNode, pSnake ps); //下一步要走的位置处不是食物不吃食物 void NotEatFood(pSnakeNode pNextNode, pSnake ps); //检测是否撞墙 void KillByWall(pSnake ps); //检测是否撞自己 void KillBySelf(pSnake ps);//游戏结束处理善后工作 void GameEnd(pSnake ps); snake.c #define _CRT_SECURE_NO_WARNINGS 1 #include snake.h//定位光标位置 void SetPos(int x, int y) {COORD pos { x,y };HANDLE hOutput NULL;hOutput GetStdHandle(STD_OUTPUT_HANDLE);//在屏幕上设置指定光标位置SetConsoleCursorPosition(hOutput, pos); }//打印欢迎信息 void WelcomeToGame() {//欢迎信息SetPos(35,10);printf(欢迎来带贪吃蛇小游戏\n);SetPos(38, 20);//暂停system(pause);//清屏system(cls);//功能介绍SetPos(15, 10);printf(用 ↑ . ↓ . ← . → 来控制蛇的移动F3是加速F4是减速);SetPos(15, 11);printf(加速将得到更高的分数。);SetPos(38, 20);system(pause);system(cls); }//绘制地图 void CreateMap() {SetPos(0, 0);int i 0;//一个宽字符占2位所以i加的是2//i到56就行了因为输出WALL就能把56个57空间给占了//上框框for (i 0; i 56; i 2){wprintf(L%lc, WALL);}//下框框SetPos(0, 26);for (i 0; i 56; i 2){wprintf(L%lc, WALL);}//左框框//这里i只要加1的原因是宽字符和正常字符的高度是一样的//只有宽字符和正常字符的宽度有差2倍for (i 1; i 26; i){SetPos(0, i);wprintf(L%lc, WALL);}//右框框for (i 1; i 26; i){SetPos(56, i);wprintf(L%lc, WALL);}}//初始化蛇身 void InitSnake(pSnake ps) {pSnakeNode cur NULL;//按照图片上的指示创建5个节点int i 0;for (i 0; i 5; i){cur (pSnakeNode)malloc(sizeof(SnakeNode));if (cur NULL){perror(InitSnake():malloc());return;}cur-x POS_X 2 * i;cur-y POS_Y;cur-next NULL;//创建好后就进行头插cur-next ps-pSnake;ps-pSnake cur;}//创建好后就打印蛇的身体while (cur){SetPos(cur-x, cur-y);wprintf(L%lc, BODY);cur cur-next;}//初始化贪吃蛇的数据ps-SleepTime 200;ps-Socre 0;ps-Status OK;ps-Dir RIGHT;ps-foodWeight 10;ps-pFood NULL; }//创建食物 void CreateFood(pSnake ps) {int x 0;int y 0; agin://使食物坐标必须要在墙内并且x的坐标必须要是2的倍数do{x rand() % 53 2;y rand() % 25 1;} while (x % 2 ! 0);//判断坐标是否在蛇的身上pSnakeNode cur ps-pSnake;while (cur){if (x cur-x y cur-y){goto agin;}cur cur-next;}pSnakeNode pFood (pSnakeNode)malloc(sizeof(SnakeNode));if (pFood NULL){perror(CreateFood()::malloc());return;}pFood-x x;pFood-y y;SetPos(x, y);wprintf(L%lc, FOOD);ps-pFood pFood; }//游戏开始 void GameStart(pSnake ps) {//设置控制台的信息窗口大小窗口名system(mode con cols100 lines30);system(title 贪吃蛇);//隐藏光标HANDLE hOutput NULL;hOutput GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(hOutput, CursorInfo);CursorInfo.bVisible false;SetConsoleCursorInfo(hOutput, CursorInfo);//打印欢迎信息WelcomeToGame();//绘制地图CreateMap();//初始化蛇身InitSnake(ps);//创建食物CreateFood(ps); }//打印帮助信息 void PrintHelpInfo() {SetPos(62, 17);printf(1.不能穿墙不能咬到自己);SetPos(62, 18);printf(2.用↑.↓.←.→分别控制蛇的移动。);SetPos(62, 19);printf(3.F3是加速F4是减速);SetPos(62, 20);printf(ESC:退出游戏 SPACE:暂停游戏); }//暂停过程 void pause() {while (1){Sleep(100);if (KEY_PRESS(VK_SPACE)){break;}} }//判断移动过程中是否碰到食物 int NextIsFood(pSnakeNode pNextNode, pSnake ps) {return (pNextNode-x ps-pFood-x pNextNode-y ps-pFood-y); }//下一步要走的位置处就是食物就吃掉食物 void EatFood(pSnakeNode pNextNode, pSnake ps) {pNextNode-next ps-pSnake;ps-pSnake pNextNode;pSnakeNode cur ps-pSnake;ps-Socre ps-foodWeight;//打印蛇身while (cur){SetPos(cur-x, cur-y);wprintf(L%lc, BODY);cur cur-next;}free(ps-pFood);CreateFood(ps); }//下一步要走的位置处不是食物不吃食物 void NotEatFood(pSnakeNode pNextNode, pSnake ps) {pNextNode-next ps-pSnake;ps-pSnake pNextNode;pSnakeNode cur ps-pSnake;while (cur-next-next){SetPos(cur-x, cur-y);//每走一步顺便把蛇身节点打印出来wprintf(L%lc, BODY);cur cur-next;}//没遇到食物首先要将新节点头插还要找到最后一个节点给释放掉//释放前要找到尾结点的位置然后输出两个空//这样才能把尾节点图表给覆盖掉SetPos(cur-next-x, cur-next-y);printf( );free(cur-next);cur-next NULL; }//判断是否撞墙了 void KillByWall(pSnake ps) {if (ps-pSnake-x 0 ||ps-pSnake-x 56 ||ps-pSnake-y 0 ||ps-pSnake-y 26)ps-Status KILL_BY_WALL; }//检测是否撞自己 void KillBySelf(pSnake ps) {pSnakeNode cur ps-pSnake-next;while (cur){if (ps-pSnake-x cur-x ps-pSnake-y cur-y){ps-Status KILL_BY_SELF;return;}cur cur-next;} }//蛇移动过程 void SnakeMove(pSnake ps) {pSnakeNode pNextNode (pSnakeNode)malloc(sizeof(SnakeNode));if (pNextNode NULL){perror(SnakeMove()::malloc());return;}switch (ps-Dir){case UP:pNextNode-x ps-pSnake-x;pNextNode-y ps-pSnake-y - 1;break;case DOWN:pNextNode-x ps-pSnake-x;pNextNode-y ps-pSnake-y 1;break;case LEFT:pNextNode-x ps-pSnake-x - 2;pNextNode-y ps-pSnake-y;break;case RIGHT:pNextNode-x ps-pSnake-x 2;pNextNode-y ps-pSnake-y;break;}//移动的过程中判断是否遇到食物if (NextIsFood(pNextNode, ps)){//遇到食物EatFood(pNextNode, ps);}else{//没遇到食物NotEatFood(pNextNode, ps);} }//游戏玩的过程 void GameRun(pSnake ps) {//打印帮助信息PrintHelpInfo();do{//打印当前得分SetPos(62, 10);printf(得分%d, ps-Socre);SetPos(62, 11);printf(每个食物得分%-2d, ps-foodWeight);if (KEY_PRESS(VK_UP) ps-Dir ! DOWN){ps-Dir UP;}else if (KEY_PRESS(VK_DOWN) ps-Dir ! UP){ps-Dir DOWN;}else if (KEY_PRESS(VK_LEFT) ps-Dir ! RIGHT){ps-Dir LEFT;}else if (KEY_PRESS(VK_RIGHT) ps-Dir ! LEFT){ps-Dir RIGHT;}else if (KEY_PRESS(VK_SPACE)){//进行休眠pause();}else if (KEY_PRESS(VK_F3)){//加速if (ps-SleepTime 50){ps-SleepTime - 30;ps-foodWeight 2;}}else if (KEY_PRESS(VK_F4)){//减速if (ps-foodWeight 2){ps-SleepTime 30;ps-foodWeight - 2;}}else if (KEY_PRESS(VK_ESCAPE)){//按ESC键退出ps-Status ESC;break;}//蛇每走一步都需要休眠休眠时间越短蛇移动速度就越快Sleep(ps-SleepTime);SnakeMove(ps);//判断是否撞墙了KillByWall(ps);//判断是否撞到自己了KillBySelf(ps);} while (ps-Status OK); }//游戏结束 void GameEnd(pSnake ps) {SetPos(18, 10);switch (ps-Status){case KILL_BY_WALL:printf(很遗憾撞墙了游戏结束);break;case KILL_BY_SELF:printf(很遗憾撞到自身了游戏结束);break;case ESC:printf(按了ESC键正常退出);break;}//释放蛇身节点和食物节点pSnakeNode cur ps-pSnake;pSnakeNode pNextNode NULL;while (cur){pNextNode cur;cur cur-next;free(pNextNode);}free(ps-pFood);ps NULL; }
http://www.dnsts.com.cn/news/51243.html

相关文章:

  • 工程建设云网站优秀个人网站主页
  • 网站建设公司的方案wordpress如何修改代码
  • 萧山做网站公司wordpress地址改不了
  • 北京中航空港建设工程有限公司网站梅州市工程建设交易中心网站
  • 凡科做网站好吗外贸平台实训总结
  • 企业网站推广计划书网络关键词排名软件
  • 怎么用dw设计网站页面淮南论坛
  • 南昌市做网站wordpress 文章缩放
  • 公司网站开发排名盐城网站建设官网
  • 大丰网站建设找哪家好php手机网站制作
  • 网站设计就业前景wordpress注册邮箱失效
  • 网址大全网站网站描述标签优化
  • 河南郑州网站关键词排名系统东莞抖音推广合作
  • 用服务器建立网站吗医院网站开发方案
  • 深圳网站设计公司招聘汕头模板网建站
  • 织梦cms官方网站网站建设买了域名
  • 网站设计建设代理机构网站营销公司简介
  • 苏州企业如何建站长治seo顾问
  • 怎么给网站加图标网站收录的页面被k出来
  • 免费网站外链推广wordpress三栏怎么实现
  • 我在学校志愿队做网站的经历app软件开发策划书
  • 网站开发注册流程以及收费wordpress twenty eleven
  • 青海网站维护软件推广代理
  • 手机版网站建设合同范本可以在线做动图的网站
  • 做易经类的网站可以做动漫的网站
  • 天津河北区做网站小白怎么做无货源电商
  • 办公室装修专业网站自己编程制作app软件
  • 手机app 网站建设如何开网站详细步骤图
  • 中国十大景观设计公司福州百度关键词优化
  • 基础网站建设公司四川建设行政主管部门官方网站