一篇文章教会你写一个贪吃蛇小游戏(纯C语言)

news2024/11/24 1:14:39

一篇文章教会你写一个贪吃蛇小游戏

  • 1、游戏展示
  • 2、游戏功能
  • 3、Win32 API
    • 3.1 控制台程序
    • 3.2 控制台屏幕上的坐标COORD
    • 3.3 GetStdHandle函数
    • 3.4 GetConsoleCursorInfo函数
      • 3.4.1 CONSOLE_CURSOR_INFO结构体
    • 3.5 SetConsoleCursorInfo函数
    • 3.6 SetConsoleCursorPosition函数
    • 3.7 GetAsyncKeyState函数
  • 4、设计贪吃蛇地图
    • 4.1 <locale.h>
    • 4.2 setlocale函数
    • 4.3 宽字符的打印
    • 4.4 地图坐标及蛇身和食物
  • 5. 数据结构设计
    • 5.1 蛇节点
    • 5.2 蛇状态结构
    • 5.3 蛇的方向
    • 5.4 游戏状态
  • 7. 游戏整体流程
  • 8. 核心逻辑实现
    • 8.1 游戏主逻辑
    • 8.2 游戏开始
      • 8.2.0 SetPos函数的封装
      • 8.2.1 欢迎界面和提示
      • 8.2.2 创建地图
      • 8.2.3 初始化蛇身
      • 8.2.4 创建食物
    • 8.3 游戏运行
      • 8.3.1 帮助信息
      • 8.3.2 蛇身移动
      • 8.3.3 吃食物的三种情况
      • 8.3.4 G掉的2种情况
      • 8.3.5 暂停函数
    • 8.4 游戏结束

1、游戏展示

在这里插入图片描述

2、游戏功能

实现基本的功能

• 贪吃蛇地图绘制
• 蛇吃⻝物的功能(上、下、左、右⽅向键控制蛇的动作)
• 蛇撞墙死亡
• 蛇撞⾃⾝死亡
• 计算得分
• 蛇⾝加速、减速
• 暂停游戏

3、Win32 API

Win32 API是一套由Microsoft提供的应用程序编程接口,用于开发Windows平台上的应用程序。它包括了丰富的函数、数据结构和消息机制,允许开发者与操作系统进行交互。这些接口覆盖了各个方面,如图形用户界面(GUI)、文件和输入输出、多媒体、网络通信等。通过调用这些API,开发者可以实现窗口创建、消息处理、事件响应、内存管理等功能,从而构建功能完善的Windows应用程序。Win32 API是基于C语言的,但也可以通过其他编程语言进行调用。

实现贪吃蛇会使⽤到的⼀些Win32API知识 ,下面我们来看看

3.1 控制台程序

平常我们运行起来的cmd命令框程序其实就是控制台程序

比如我们可以使用cmd命令来设置控制台窗口的大小

mode con cols=100 lines=30

也可以通过命令设置控制台窗口的名字

title 爱学习的鱼佬
在这里插入图片描述

这些能在控制台窗口执行的命令,也可以调用C语言函数system来执行。例如:

#include<stdio.h>
int main(){
	system("mode con cols=100 lines=30");
	system("title 爱学习的鱼佬");

	return 0;
}

3.2 控制台屏幕上的坐标COORD

COORD是Windows API中定义的⼀个结构体,表示⼀个字符在控制台屏幕上的坐标

typedef struct _COORD {
    SHORT X;
    SHORT Y;
} COORD, *PCOORD;

给坐标赋值:

COORD pos={10,15};

3.3 GetStdHandle函数

GetStdHandle 函数是用于获取标准输入、标准输出和标准错误输出的句柄(handle)的 Windows API 函数。它通常用于控制台应用程序,允许你访问这些标准流以进行输入和输出操作。

以下是 GetStdHandle 函数的基本用法:

#include <windows.h>

int main() {
    HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
    HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
    HANDLE hStderr = GetStdHandle(STD_ERROR_HANDLE);

    // 使用 hStdout, hStdin, 和 hStderr 进行输入、输出和报错操作

    return 0;
}

这个示例演示如何获取标准输出、标准输入和标准错误输出的句柄,并将它们存储在 HANDLE 变量中。你可以使用这些句柄来执行与控制台输入和输出相关的操作,例如写入到控制台或从控制台读取数据。

请注意,GetStdHandle 函数需要包含 <windows.h> 头文件,并且通常与其他 Windows 控制台函数一起使用,如 WriteFileReadFile 用于实际的输入和输出操作。

3.4 GetConsoleCursorInfo函数

GetConsoleCursorInfo 函数是一个用于获取控制台光标信息的 Windows API 函数。它允许你检索控制台光标的可见性和闪烁属性以及光标的大小。

以下是 GetConsoleCursorInfo 函数的基本用法:

#include <windows.h>

int main() {
    HANDLE hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_CURSOR_INFO cursorInfo;

    if (GetConsoleCursorInfo(hConsoleOutput, &cursorInfo)) {
        // cursorInfo.dwSize 表示光标的大小
        // cursorInfo.bVisible 表示光标是否可见
        // cursorInfo.dwSize 和 cursorInfo.bVisible 可以用于读取光标的属性
    } else {
        // 处理获取光标信息失败的情况
    }

    return 0;
}

在上述示例中,我们首先获取标准输出句柄,并然后使用 GetConsoleCursorInfo 函数来检索控制台光标的信息,包括光标的大小 (dwSize) 和可见性 (bVisible)。获取成功时,你可以读取这些属性,以了解当前光标的状态。

这个函数通常在控制台应用程序中用于获取和修改光标的属性,例如改变光标的可见性或大小。你还可以使用其他控制台函数来设置新的光标属性,如 SetConsoleCursorInfo 来修改光标的属性。

3.4.1 CONSOLE_CURSOR_INFO结构体

CONSOLE_CURSOR_INFO 是一个结构体,用于控制和设置控制台光标的属性。这个结构体通常用于控制光标的大小和可见性。

在 Windows API 中,CONSOLE_CURSOR_INFO 结构体的定义如下:

typedef struct _CONSOLE_CURSOR_INFO {
    DWORD dwSize;    // 光标的百分比高度 (1 到 100)
    BOOL bVisible;   // 光标是否可见
} CONSOLE_CURSOR_INFO;
  • dwSize 表示光标的大小,以百分比高度的形式表示,1 到 100 之间的值。光标的高度由控制台的字符单元高度和 dwSize 决定。
  • bVisible 表示光标的可见性,TRUE 表示光标可见,FALSE 表示光标不可见。

这个结构体通常与 GetConsoleCursorInfoSetConsoleCursorInfo 函数一起使用,用于获取和设置控制台光标的属性。通过设置 CONSOLE_CURSOR_INFO 结构体的属性,你可以控制控制台中光标的外观和行为。

例如,可以使用这个结构体来设置光标的大小和可见性,然后将其传递给 SetConsoleCursorInfo 函数,从而更改控制台光标的属性

3.5 SetConsoleCursorInfo函数

SetConsoleCursorInfo 函数是一个用于设置控制台光标信息的 Windows API 函数。它允许你更改控制台光标的可见性、闪烁属性以及光标的大小。

以下是 SetConsoleCursorInfo 函数的基本用法:

#include <windows.h>

int main() {
    HANDLE hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_CURSOR_INFO cursorInfo;

    cursorInfo.dwSize = 25; // 设置光标的大小,单位是百分之一
    cursorInfo.bVisible = TRUE; // 设置光标可见

    if (SetConsoleCursorInfo(hConsoleOutput, &cursorInfo)) {
        // 光标属性设置成功
    } else {
        // 处理设置光标属性失败的情况
    }

    return 0;
}

在上述示例中,我们首先获取标准输出句柄,并然后使用 SetConsoleCursorInfo 函数来设置控制台光标的信息,包括光标的大小 (dwSize) 和可见性 (bVisible)。你可以根据需要将这些属性设置为所需的值。

这个函数通常在控制台应用程序中用于自定义控制台光标的属性,例如更改光标的大小或可见性。通过调用 SetConsoleCursorInfo 函数,你可以实时更改控制台光标的外观。如果需要获取光标信息,可以使用 GetConsoleCursorInfo 函数。

3.6 SetConsoleCursorPosition函数

SetConsoleCursorPosition 函数是一个 Windows API 函数,用于设置控制台窗口的光标位置。通过调用这个函数,你可以将光标移动到指定的控制台窗口坐标位置。

以下是 SetConsoleCursorPosition 函数的基本用法:

#include <windows.h>

int main() {
    HANDLE hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    COORD cursorPosition;

    cursorPosition.X = 10; // 设置光标的水平位置
    cursorPosition.Y = 5;  // 设置光标的垂直位置

    if (SetConsoleCursorPosition(hConsoleOutput, cursorPosition)) {
        // 光标位置设置成功
    } else {
        // 处理设置光标位置失败的情况
    }

    return 0;
}

在上述示例中,我们首先获取标准输出句柄,然后创建一个 COORD 结构体,其中包括水平位置(X)和垂直位置(Y)。接着,我们使用 SetConsoleCursorPosition 函数来将光标移动到指定的控制台窗口坐标位置。

这个函数通常在控制台应用程序中用于控制光标的移动,以便在控制台上绘制文本或执行其他操作。通过设置光标位置,你可以控制光标在控制台窗口中的位置。

3.7 GetAsyncKeyState函数

GetAsyncKeyState 是一个 Windows API 函数,用于检查指定虚拟键码对应的键是否处于按下状态。它可以用来检测键盘上的按键是否被按下,而且不会阻塞程序执行,因此适用于实现基本的键盘输入检测。

以下是 GetAsyncKeyState 函数的基本用法:

#include <windows.h>

int main() {
    // 检查某个键是否被按下,比如检查A键是否被按下
    SHORT keyState = GetAsyncKeyState('A');

    // 检查键的状态
    if (keyState & 0x8000) {
        // A键被按下
    } else {
        // A键没有被按下
    }

    return 0;
}

在上述示例中,我们使用 GetAsyncKeyState 函数检查键盘上的’A’键是否被按下。函数返回一个 SHORT 类型的值,其中高位表示键的状态,如果键被按下,高位的最低位(最低有效位)将被设置为1,即0x8000。

这个函数通常用于游戏开发、输入检测以及其他需要实时键盘输入的应用程序,因为它不会阻塞程序,可以在循环中持续检查键的状态。需要注意的是,GetAsyncKeyState 可以检测虚拟键码而不仅限于字符键,因此你可以使用虚拟键码来检查其他键盘上的按键。

每个按键都有对应的虚拟键码(Virtual-Key Codes),以下是一些常用键的虚拟键码:

  • 左键鼠标按钮: VK_LBUTTON (0x01)
  • 右键鼠标按钮: VK_RBUTTON (0x02)
  • 中键鼠标按钮: VK_MBUTTON (0x04)
  • Backspace键: VK_BACK (0x08)
  • Tab键: VK_TAB (0x09)
  • Enter键: VK_RETURN (0x0D)
  • Shift键: VK_SHIFT (0x10)
  • Ctrl键: VK_CONTROL (0x11)
  • Alt键: VK_MENU (0x12)
  • Pause键: VK_PAUSE (0x13)
  • Caps Lock键: VK_CAPITAL (0x14)
  • Esc键: VK_ESCAPE (0x1B)
  • 空格键: VK_SPACE (0x20)
  • Page Up键: VK_PRIOR (0x21)
  • Page Down键: VK_NEXT (0x22)
  • End键: VK_END (0x23)
  • Home键: VK_HOME (0x24)
  • 左方向键: VK_LEFT (0x25)
  • 上方向键: VK_UP (0x26)
  • 右方向键: VK_RIGHT (0x27)
  • 下方向键: VK_DOWN (0x28)
  • 0键 (主键盘): 0x30
  • A键: 0x41
  • F1键: VK_F1 (0x70)
  • F2键: VK_F2 (0x71)
  • F3键: VK_F3 (0x72)
  • …以此类推,F4至F12键的虚拟键码为0x73至0x7C

GetAsyncKeyState 的返回值是short类型,在上⼀次调用 GetAsyncKeyState 函数后,如果返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0

所以我们要判断⼀个键是否被按过,可以检测 GetAsyncKeyState 返回值的最低值是否为1

#define KEY_PRESS(VK)  ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)

4、设计贪吃蛇地图

我们先看设计好的界面,控制台窗口的坐标如下所示,横向的是X轴,从左向右依次增长,纵向是Y轴,从上到下依次增长
在这里插入图片描述

在游戏地图上,我们打印墙体使用宽字符:□,打印蛇使用宽字符●,打印食物使用宽字符★
普通的字符是占⼀个字节的,这类宽字符是占⽤2个字节。
C语言的国际化特性相关的知识,过去C语言并不适合非英语国家(地区)使用。

C语言字符默认是采用ASCII编码的,ASCI字符集采用的是单字节编码,且只使用了单字节中的低7位,最高位是没有使用的,可表示为0xxxxxxxx;可以看到,ASCII字符集共包含128个字符,在英语国家中,128个字符是基本够用的,但是,在其他国家语言中,比如,在法语中,字母上方有注音符号,它就无法用 ASCII 码表示。于是,一些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的e的编码为130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。但是,这里又出现了新的问题。不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。比如,130在法语编码中代表了,在希伯来语编码中却代表了字母Gimel(区),在俄语编码中又会代表另一个符号。但是不管怎样,所有这些编码方式中,0–127表示的符号是一样的,不一样的只是128--255的这一段

至于亚洲国家的文字,使用的符号就更多了,汉字就多达10万左右。一个字节只能表示256种符号,肯定是不够的,就必须使用多个字节表达一个符号。比如,简体中文常见的编码方式是 GB2312,使用两个字节表示一个汉字,所以理论上最多可以表示 256 x 256=65536 个字符

后来为了使C语言适应国际化,C语言的标准中不断加入了国际化的支持。比如:加入和宽字符的类型wchar_t 和宽字符的输入和输出函数,加入<locale.h>头文件,其中提供了允许程序员针对特定地区 (通常是国家或者说某种特定语言的地理区域)调整程序行为的函数。

4.1 <locale.h>

<locale.h> 是C语言标准库中的头文件,提供了有关本地化(localization)和本地化相关函数的支持。本地化涉及根据特定地区或文化习惯的需求来格式化文本、日期、时间和数字等。这有助于使程序适应不同的地域文化。

在C语言中,<locale.h> 提供了设置地区和语言环境的函数,例如 setlocale,允许程序员根据所需的语言环境进行设置。它还提供了函数来处理在不同地区设置中使用的格式化、排序、货币和日期时间信息。

一些常见的函数和功能包括:

  • setlocale: 设置程序的当前地区和语言环境,允许程序根据特定的语言环境来处理数据。
  • localeconv: 返回一个描述当前地区设置中的数值格式的结构体。
  • strftime: 根据指定的格式化字符串将日期和时间格式化为本地化格式。
  • printfscanf 系列函数中的格式化标志: 例如 %n%Ld 等,可以受到地区设置的影响而改变输出或输入的格式。

这些函数可以根据地区设置调整货币符号、日期格式、时间格式、数字分隔符等,从而使程序在不同的文化环境下更适用和易懂。

<locale.h> 头文件中,定义了一系列常量用于设置不同的本地化范畴。这些常量用于确定在程序中所需的不同本地化设置。以下是一些常用的常量:

  • LC_ALL: 代表所有的本地化设置。
  • LC_COLLATE: 字符串排序规则的设置。
  • LC_CTYPE: 字符分类和转换规则的设置。
  • LC_MONETARY: 通货格式的设置。
  • LC_NUMERIC: 数值格式的设置。
  • LC_TIME: 时间和日期格式的设置。

这些常量可以作为 setlocale() 函数的参数,用于设置程序中的不同本地化范畴。例如,setlocale(LC_TIME, "en_US") 用于设置日期和时间格式为美国英语,setlocale(LC_MONETARY, "fr_FR") 用于设置货币格式为法国法语等。

4.2 setlocale函数

char *setlocale(int category, const char *locale);
  • category 是要设置的本地化范畴,可以是诸如 LC_ALLLC_COLLATELC_CTYPELC_MONETARYLC_NUMERICLC_TIME 等预定义常量之一。
  • locale 是一个字符串,表示要设置的本地化环境,比如 “zh_CN” 表示中文的本地化设置。

示例用法如下:

#include <stdio.h>
#include <locale.h>

int main() {
    setlocale(LC_ALL, "en_US.UTF-8");

    // 其他程序逻辑...

    return 0;
}

"zh_CN.UTF-8" 中的 "zh_CN" 是表示中文(中国)的语言代码,"UTF-8" 表示使用 UTF-8 编码

需要注意的是,setlocale 函数在不同的操作系统或环境下可能有不同的支持程度,有些环境可能并不支持特定的本地化设置。

C标准给第二个参数仅定义了2种可能取值:"C"和” "

在任意程序执行开始,都会隐藏式执行调用:

setlocale(LC_ALL, "C");

当地区设置为"C"时,库函数按正常方式执行,小数点是一个点。当程序运行起来后想改变地区,就只能显式调用setlocale函数。用" "作为第2个参数,调用setlocale函数就可以切换到本地模式,这种模式下程序会适应本地环境。

setlocale(LC_ALL, " ");//切换到本地环境

4.3 宽字符的打印

在 C 语言中,你可以使用 wchar_t 类型和一些相关的宽字符函数来处理和打印宽字符。

首先,确保你的编译环境支持宽字符,并且使用宽字符格式的输出函数。

#include <stdio.h>
#include <wchar.h>
#include <locale.h>

int main() {
    setlocale(LC_ALL, ""); // 设置本地化环境,以支持宽字符
    
    char ch1='a';
    char ch2='b'; //普通字符

    wchar_t wideChar1 = L'你'; 
    wchar_t wideChar2 = L'●'; // 使用宽字符
	
    printf("%c%c\n",ch1,ch2); //输出普通字符
    wprintf(L"%lc\n%lc\n", wideChar1,wideChar2); // 使用wprintf输出宽字符

    return 0;
}
  • wchar_t 是用于表示宽字符的 C 语言数据类型。
  • wprintf 是用于打印宽字符的函数,L"..." 表示宽字符常量。

setlocale(LC_ALL, ""); 这行代码设置程序的本地化环境以支持宽字符的处理和显示。使用 wprintfwchar_t 类型可以正确地处理和打印宽字符。

输出结果
在这里插入图片描述

从输出的结果来看,我们发现一个普通字符占一个字符的位置但是打印一个汉字字符或特殊符号,占用2个字符的位置,那么我们如果要在贪吃蛇中使用宽字符,就得处理好地图上坐标的计算。

4.4 地图坐标及蛇身和食物

按照我们上面实现的效果,这里的游戏地图区域是58列,27行达到的效果,当然你也可以根据自己的情况进行修改

在这里插入图片描述

初始化状态,假设蛇的长度是5,蛇身的每个节点是●,在固定的一个坐标处,比如上面的地图中(20,6)处开始出现蛇,连续5个节点。

注意:蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的一个节点有一半儿出现在墙体中,另外一般在墙外的现象,坐标不好对齐。关于食物,就是在墙体内随机生成一个坐标**(x坐标必须是2的倍数)**,坐标不能和蛇的身体重合,然后打印大。

5. 数据结构设计

5.1 蛇节点

在游戏运行的过程中,蛇每次吃一个食物,蛇的身体就会变长一节,如果我们使用链表存储蛇的信息,那么蛇的每一节其实就是链表的每个节点。每个节点只要记录好蛇身节点在地图上的坐标就行.所以蛇节点结构如下:

typedef struct SnakeNode
{
    int x;
    int y;
    struct SnakeNode* next;
}SnakeNode, * pSnakeNode;

pSnakeNode: 这个别名是一个指向 SnakeNode 结构体的指针类型,使得你可以更简洁地声明指向 SnakeNode 的指针变量

5.2 蛇状态结构

typedef struct Snake
{
    pSnakeNode _pSnake;//维护整条蛇的指针
    pSnakeNode _pFood;//维护食物的指针
    enum DIRECTION _Dir;//蛇头的方向默认是向右
    enum GAME_STATUS _Status;//游戏状态
    int _Socre;//当前获得分数
    int _Add;//默认每个食物10分
    int _SleepTime;//每走一步休眠时间
}Snake, * pSnake;

5.3 蛇的方向

enum DIRECTION
{
    UP = 1,
    DOWN,
    LEFT,
    RIGHT
};

5.4 游戏状态

enum GAME_STATUS
{
    OK,//正常运行
    KILL_BY_WALL,//撞墙
    KILL_BY_SELF,//咬到自己
    END_NOMAL//正常结束
};

7. 游戏整体流程

在这里插入图片描述

8. 核心逻辑实现

8.1 游戏主逻辑

#include "Snake.h"

void test()
{
    int ch = 0;
    srand((unsigned int)time(NULL));

    do
    {
        Snake snake = { 0 };
        GameStart(&snake);
        GameRun(&snake);
        GameEnd(&snake);
        SetPos(20, 15);
        printf("再来一局吗?(Y/N):");
        ch = getchar();
        getchar();//清理\n

    } while (ch == 'Y');
    SetPos(0, 27);
}

int main()
{
    //修改当前地区为本地模式,为了支持中文宽字符的打印
    setlocale(LC_ALL, "");
    //测试逻辑
    test();
    return 0;
}

8.2 游戏开始

void GameStart(pSnake ps)
{
    system("mode con cols=100 lines=30");
    system("title 贪吃蛇");

    //获取标准输出的句柄(用来标识不同设备的数值)
    HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

    //隐藏光标操作
    CONSOLE_CURSOR_INFO CursorInfo;
    GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
    CursorInfo.bVisible = false; //隐藏控制台光标
    SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态

    //打印欢迎界面
    WelcomeToGame();
    //打印地图
    CreateMap();
    //初始化蛇
    InitSnake(ps);
    //创造第一个食物
    CreateFood(ps);
}
  1. 设置控制台窗口大小和标题
    • system("mode con cols=100 lines=30"); 用于设置控制台窗口的大小,将其设定为 100 列和 30 行。
    • system("title 贪吃蛇"); 设置了控制台窗口的标题为 “贪吃蛇”。
  2. 隐藏控制台光标
    • 使用 GetStdHandle 获取标准输出的句柄 hOutput,然后通过 GetConsoleCursorInfo 获取控制台光标的信息。
    • 将光标状态设置为不可见:CursorInfo.bVisible = false;
    • 最后通过 SetConsoleCursorInfo 设置控制台光标的状态。
  3. 显示欢迎界面
    • 调用 WelcomeToGame() 函数,这个函数可能打印一些欢迎信息和游戏的介绍。
  4. 打印地图、初始化蛇和创建第一个食物
    • CreateMap() 函数负责绘制游戏地图。
    • InitSnake() 初始化蛇的数据结构和显示蛇的初始状态。
    • CreateFood() 创建游戏中的第一个食物。

8.2.0 SetPos函数的封装

void SetPos(short x, short y)
{
    COORD pos = { x, y };
    HANDLE hOutput = NULL;

    hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

    SetConsoleCursorPosition(hOutput, pos);
}
  1. 定义函数 SetPos:
    • void SetPos(short x, short y):指定了该函数接受两个参数,分别是 x 和 y 坐标。
  2. 设置光标位置
    • COORD pos = { x, y };:创建了一个 COORD 类型的结构体变量 pos,用给定的 x 和 y 坐标值初始化。
    • HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);:获取标准输出的句柄,用来标识控制台窗口。
    • SetConsoleCursorPosition(hOutput, pos);:使用 SetConsoleCursorPosition 函数将控制台光标的位置设置为 pos 所指定的坐标。

8.2.1 欢迎界面和提示

void WelcomeToGame()
{
    SetPos(40, 15);
    printf("欢迎来到贪吃蛇小游戏");
    SetPos(40, 25);//让按任意键继续的出现的位置好看点
    system("pause");
    system("cls");
    SetPos(25, 12);
    printf("用 ↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速\n");
    SetPos(25, 13);
    printf("加速将能得到更高的分数。\n");
    SetPos(40, 25);//让按任意键继续的出现的位置好看点
    system("pause");
    system("cls");
}
  1. 设置光标位置并打印欢迎信息
    • SetPos(40, 15); printf("欢迎来到贪吃蛇小游戏"); 将光标移动到 (40, 15) 的位置,打印欢迎信息 “欢迎来到贪吃蛇小游戏”。
  2. 等待用户按下任意键继续
    • SetPos(40, 25); 设置光标位置,这是为了让下一次的 system("pause"); 显示在一个美观的位置。
    • system("pause"); 暂停程序执行,等待用户按下任意键继续。
  3. 清屏
    • system("cls"); 清空控制台屏幕。
  4. 打印游戏操作说明
    • SetPos(25, 12); printf("用 ↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速\n"); 在指定位置打印游戏操作说明,告诉用户如何控制蛇的移动以及使用 F3 和 F4 来加速或减速。
    • SetPos(25, 13); printf("加速将能得到更高的分数。\n"); 给出一些游戏策略建议。
  5. 再次等待用户按下任意键继续
    • SetPos(40, 25); 再次设置光标位置,保证下一次的 system("pause"); 显示在一个合适的位置。
    • system("pause"); 再次暂停程序执行,等待用户按下任意键继续。

这个函数的目的是为了向玩家展示游戏的欢迎信息和操作说明,在游戏开始前给玩家提供一些基本的操作提示和规则。

欢迎界面

在这里插入图片描述

提示界面

在这里插入图片描述

8.2.2 创建地图

创建地图就是将墙打印出来,因为是宽字符打印,所有使用wprintf函数,打印格式串前使用L打印地图的关键是要算好坐标,才能在想要的位置打印墙体。

墙体打印的宽字符:

#define WALL L'□'

创建地图

void CreateMap()
{
    int i = 0;

    SetPos(0, 0);
    for (i = 0; i < 58; i += 2)
    {
        wprintf(L"%c", WALL);
    }

    SetPos(0, 26);
    for (i = 0; i < 58; i += 2)
    {
        wprintf(L"%c", WALL);
    }


    for (i = 1; i < 26; i++)
    {
        SetPos(0, i);
        wprintf(L"%c", WALL);
    }

    for (i = 1; i < 26; i++)
    {
        SetPos(56, i);
        wprintf(L"%c", WALL);
    }
}

绘制游戏地图

  • SetPos(0, 0); 将光标移动到 (0, 0) 的位置开始绘制游戏地图。
  • 通过 for 循环和 wprintf 函数,从 (0,0) 到 (56,0) 绘制顶部墙体,由 WALL 符号构成。
  • 从 (0,26) 到 (56,26) 绘制底部墙体。
  • 从 (0,1) 到 (0,25) 绘制左侧墙体。
  • 从 (56,1) 到 (56,25) 绘制右侧墙体。

8.2.3 初始化蛇身

蛇最开始长度为5节,每节对应链表的一个节点,蛇身的每一个节点都有自己的坐标
创建5个节点,然后将每个节点存放在链表中进行管理。创建完蛇身后,将蛇的每一节打印在屏幕上

蛇身打印的宽字符

#define BODY L'●'

初始化蛇身函数

void InitSnake(pSnake ps)
{
    pSnakeNode cur = NULL;
    int i = 0;
    //创建蛇身节点,并初始化坐标
    for (i = 0; i < 5; i++)
    {
        //创建蛇身的节点
        cur = (pSnakeNode)malloc(sizeof(SnakeNode));
        if (cur == NULL)
        {
            perror("InitSnake()::malloc()");
            return;
        }
        //设置坐标
        cur->next = NULL;
        cur->x = POS_X + i * 2;
        cur->y = POS_Y;

        //头插法
        if (ps->_pSnake == NULL)
        {
            ps->_pSnake = cur;
        }
        else
        {
            cur->next = ps->_pSnake;
            ps->_pSnake = cur;
        }
    }

    //打印蛇的身体
    cur = ps->_pSnake;
    while (cur)
    {
        SetPos(cur->x, cur->y);
        wprintf(L"%c", BODY);
        cur = cur->next;
    }

    //初始化贪吃蛇数据
    ps->_SleepTime = 200;
    ps->_Socre = 0;
    ps->_Status = OK;
    ps->_Dir = RIGHT;
    ps->_Add = 10;
}

创建贪吃蛇身体节点

  • 使用 for 循环创建贪吃蛇的五个身体段。
  • 每个段都是使用 malloc 动态分配的。
  • 为每个段设置坐标,创建了贪吃蛇身体的水平线。

打印贪吃蛇身体

  • 创建段之后,代码通过遍历贪吃蛇的链表,将光标移动到每个段的坐标并使用 wprintf 打印贪吃蛇的身体,使用宏 BODY 符号。

初始化游戏变量

  • 最后,函数初始化各种游戏相关变量,如休眠时间、得分、方向和额外得分规则。

8.2.4 创建食物

注意

x坐标必须是2的倍数
食物的坐标不能和蛇身每个节点的坐标重复

食物打印的宽字符

#define FOOD L'★'

创建食物函数

void CreateFood(pSnake ps)
{
    int x = 0;
    int y = 0;

again:
    //产生的x坐标应该是2的倍数,这样才可能和蛇头坐标对齐。
    do
    {
        x = rand() % 53 + 2;
        y = rand() % 25 + 1;
    } while (x % 2 != 0);

    pSnakeNode cur = ps->_pSnake;//获取指向蛇头的指针
    //食物不能和蛇身冲突
    while (cur)
    {
        if (cur->x == x && cur->y == y)
        {
            goto again;
        }
        cur = cur->next;
    }

    pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode)); //创建食物
    if (pFood == NULL)
    {
        perror("CreateFood::malloc()");
        return;
    }
    else
    {
        pFood->x = x;
        pFood->y = y;
        SetPos(pFood->x, pFood->y);
        wprintf(L"%c", FOOD);
        ps->_pFood = pFood;
    }
}

生成食物位置

  • 使用 do-while 循环生成食物的 x 和 y 坐标。
  • x 坐标被限制为偶数,以便与贪吃蛇的头部坐标对齐。

避免食物与蛇身冲突

  • 代码通过检查新生成的食物位置,确保它不会与贪吃蛇的身体重叠。
  • 如果新的食物坐标与蛇身重叠,代码会跳回 again 标签处重新生成食物位置。

创建食物节点

  • 如果食物的位置是有效的,就会动态分配一个新的节点来表示食物。
  • 食物节点的坐标被设置为新的 x 和 y 坐标,然后在控制台上的该位置打印食物的符号,并将食物节点指针保存在游戏状态中。

8.3 游戏运行

游戏运行期间,右侧打印帮助信息,提示玩家

根据游戏状态检查游戏是否继续,如果是状态是OK,游戏继续,否则游戏结束

如果游戏继续,就是检测按键情况,确定蛇下一步的方向,或者是否加速减速,是否暂停或者退出游戏。

void GameRun(pSnake ps)
{
    //打印右侧帮助信息
    PrintHelpInfo();
    do
    {
        SetPos(64, 10);
        printf("得分:%d ", ps->_Socre);
        printf("每个食物得分:%d分", ps->_Add);
        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_ESCAPE))
        {
            ps->_Status = END_NOMAL;
            break;
        }
        else if (KEY_PRESS(VK_F3))
        {
            if (ps->_SleepTime >= 50)
            {
                ps->_SleepTime -= 30;
                ps->_Add += 2;
            }
        }
        else if (KEY_PRESS(VK_F4))
        {
            if (ps->_SleepTime < 350)
            {
                ps->_SleepTime += 30;
                ps->_Add -= 2;
                if (ps->_SleepTime == 350)
                {
                    ps->_Add = 1;
                }
            }
        }
        //蛇每次一定之间要休眠的时间,时间短,蛇移动速度就快
        Sleep(ps->_SleepTime);
        SnakeMove(ps);

    } while (ps->_Status == OK);
}

这段代码实现了游戏的运行逻辑,在一个循环中持续地更新游戏状态并响应玩家的输入。

  1. 打印帮助信息
    • PrintHelpInfo() 被调用来显示游戏帮助信息。
  2. 游戏循环
    • 游戏的主要逻辑被包含在一个 do-while 循环中。
    • 检查玩家的按键输入,根据按键不同做出相应的动作。
    • 根据按键状态来改变蛇头的移动方向。
    • 如果按下空格键,游戏会暂停。
    • 如果按下 ESC 键,游戏状态被设为正常结束(END_NOMAL),并跳出循环。
  3. 加速和减速
    • 如果按下 F3,游戏中蛇的移动速度会加快,得分增加。
    • 如果按下 F4,游戏中蛇的移动速度会减慢,得分减少。
  4. 蛇的移动
    • 游戏根据当前设置的移动速度来控制蛇的移动。
    • 使用 Sleep() 函数来控制每次蛇移动之间的时间间隔。
    • 调用 SnakeMove() 函数来处理蛇的移动逻辑。
  5. 循环终止
    • 游戏循环会持续进行,直到游戏状态变为非正常状态(不是 OK)为止。

8.3.1 帮助信息

void PrintHelpInfo()
{
    //打印提示信息
    SetPos(64, 15);
    printf("不能穿墙,不能咬到自己\n");
    SetPos(64, 16);
    printf("用↑.↓.←.→分别控制蛇的移动.");
    SetPos(64, 17);
    printf("F1 为加速,F2 为减速\n");
    SetPos(64, 18);
    printf("ESC :退出游戏.space:暂停游戏.");
    SetPos(64, 20);
    printf("爱学习的鱼佬");
}

在这里插入图片描述

8.3.2 蛇身移动

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//如果没有食物
    {
        NoFood(pNextNode, ps);
    }

    KillByWall(ps);
    KillBySelf(ps);
}
  1. 创建下一个节点:利用 malloc() 分配内存来创建一个新的蛇身节点 pNextNode
  2. 根据方向确定下一个节点的坐标:根据蛇头当前的方向,计算出蛇头下一个移动的位置。
  3. 检查下一个位置是否是食物
    • 如果下一个位置是食物,调用 EatFood(pNextNode, ps) 函数来处理吃食物的逻辑。
    • 如果不是食物,调用 NoFood(pNextNode, ps) 函数来处理不吃食物的逻辑。
  4. 检查游戏结束的条件
    • 调用 KillByWall(ps)KillBySelf(ps) 函数来检查是否撞墙或者咬到自己,如果游戏结束,则会设定相应的游戏状态。

8.3.3 吃食物的三种情况

int NextIsFood(pSnakeNode psn, pSnake ps)
{
    return (psn->x == ps->_pFood->x) && (psn->y == ps->_pFood->y);
}



void EatFood(pSnakeNode psn, pSnake ps)
{
    //头插法
    psn->next = ps->_pSnake;
    ps->_pSnake = psn;
    pSnakeNode cur = ps->_pSnake;
    //打印蛇
    while (cur)
    {
        SetPos(cur->x, cur->y);
        wprintf(L"%c", BODY);
        cur = cur->next;
    }
    ps->_Socre += ps->_Add;

    free(ps->_pFood);
    CreateFood(ps);
}


void NoFood(pSnakeNode psn, pSnake ps)
{
    //头插法
    psn->next = ps->_pSnake;
    ps->_pSnake = psn;
    pSnakeNode cur = ps->_pSnake;
    //打印蛇
    while (cur->next->next)
    {
        SetPos(cur->x, cur->y);
        wprintf(L"%c", BODY);
        cur = cur->next;
    }

    //最后一个位置打印空格,然后释放节点
    SetPos(cur->next->x, cur->next->y);
    printf("  ");
    free(cur->next);
    cur->next = NULL;
}
  1. NextIsFood:该函数检查下一个位置是否有食物。如果坐标与食物位置匹配,返回真;否则返回假。
  2. EatFood:当下一个位置有食物时调用此函数。它采用链表方法,在蛇的开头插入表示下一个位置的新节点,从而延长蛇的身体。它更新蛇的分数,移除被吃掉的食物(通过释放内存),然后创建新的食物以继续游戏。
  3. NoFood:当下一个位置没有食物时调用此函数。它也采用链表方法,在蛇的开头插入表示下一个位置的新节点。然而,它通过移除最后一个节点来管理蛇尾,模拟蛇的移动。然后释放最后一个节点的内存,并设置适当的指针。

8.3.4 G掉的2种情况

int 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;
        return 1;
    }
    return 0;
}

int 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 1;
        }
        cur = cur->next;
    }
    return 0;
}
  1. KillByWall 检查蛇头是否碰到了游戏地图的边界。如果蛇头在地图的边缘,函数将设置蛇的状态为 KILL_BY_WALL 并返回 1(表示蛇碰到了墙壁),否则返回 0。
  2. KillBySelf 检查蛇头是否碰到了自己的身体。它遍历了蛇身链表(除了头节点),检查蛇头的坐标是否与任何其他节点的坐标相匹配。如果发生了碰撞,函数将设置蛇的状态为 KILL_BY_SELF 并返回 1(表示蛇碰到了自己的身体),否则返回 0。

8.3.5 暂停函数

void pause()
{
    while (1)
    {
        Sleep(300);
        if (KEY_PRESS(VK_SPACE))
        {
            break;
        }
    }
}

它会在游戏执行时进行循环,每 300 毫秒检查一次是否按下空格键。当检测到空格键按下时,循环结束,游戏继续执行

8.4 游戏结束

void GameEnd(pSnake ps)
{
    pSnakeNode cur = ps->_pSnake;
    SetPos(24, 12);
    switch (ps->_Status)
    {
    case END_NOMAL:
        printf("您主动退出游戏\n");
        break;
    case KILL_BY_SELF:
        printf("您撞上自己了 ,游戏结束!\n");
        break;
    case KILL_BY_WALL:
        printf("您撞墙了,游戏结束!\n");
        break;
    }

    while (cur)
    {
        pSnakeNode del = cur;
        cur = cur->next;
        free(del);
    }
}

根据游戏状态在屏幕上显示相应的消息,例如玩家主动退出游戏、蛇撞到自己或撞到墙壁导致游戏结束。此后,它会释放蛇身的节点,并清理分配的内存

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1194144.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

自动私信引流软件的运行分享,与开发需要到的技术分析

先来看实操成果&#xff0c;↑↑需要的同学可看我名字↖↖↖↖↖&#xff0c;或评论888无偿分享 一、引言 随着移动互联网的普及和人们对便捷生活的追求&#xff0c;引流APP已成为越来越多企业的必备工具。本文将为大家详细介绍开发引流APP需要用到的技术和流程&#xff0c;帮…

CSS3渐变颜色

CSS3 渐变可以让你在两个或多个指定的颜色之间显示平稳的过渡。 CSS3渐变有两种类型&#xff1a;线性渐变&#xff08;Linear Gradients&#xff09;和径向渐变&#xff08;Radial Gradients&#xff09;。 线性渐变&#xff08;Linear Gradients&#xff09;&#xff1a; 线性…

阻塞队列+定时器+常见的锁策略

1)阻塞队列:是一个线程安全的队列&#xff0c;是可以保证线程安全的 1.1)如果当前队列为空&#xff0c;尝试出队列&#xff0c;进入阻塞状态&#xff0c;一直阻塞到队列里面的元素不为空 1.2)如果当前队列满了&#xff0c;尝试入队列&#xff0c;也会产生阻塞&#xff0c;一直阻…

Ionic 组件 ion-item-divider ion-item-group ion-item-sliding ion-label ion-note

1 ion-item-divider Item dividers是块元素&#xff0c;可用于分隔列表中的items 。它们类似于列表标题&#xff0c;但它们不应该只放在列表的顶部&#xff0c;而应该放在items之间。 <ion-list><ion-item-group><ion-item-divider><ion-label> Secti…

javascript 操作mysql数据库

目录 一&#xff1a;Javascript访问MYSQL 二&#xff1a;JavaScript中操作Mysql数据库实例 一&#xff1a;Javascript访问MYSQL 1、下载MYSQL的ODBC连接 2、在JS中建立ODBC连接如下&#xff1a; var con new ActiveXObject("ADODB.Connection"); con.Connection…

Linux软件包(源码包和二进制包)

Linux下的软件包众多&#xff0c;且几乎都是经 GPL 授权、免费开源&#xff08;无偿公开源代码&#xff09;的。这意味着如果你具备修改软件源代码的能力&#xff0c;只要你愿意&#xff0c;可以随意修改。 GPL&#xff0c;全称 General Public License&#xff0c;中文名称“通…

jbase代码生成器(成型篇)

上一篇说到通用码表可以解决百分之八十的基础维护功能&#xff0c;剩下的百分二十的需要级联维护的界面可以用代码生成器生成代码&#xff0c;基于生成的代码拷贝再组装界面&#xff0c;来解决这百分之二十的工作量里的百分之八十工作量。 首先实现代码生成器 Class Jbase.Ma…

C语言实现给出一位数不大于7位的整型数字,取整数从右端开始的4~7位数字

完整代码&#xff1a; // 给出一位数不大于7位的整型数字&#xff0c;取整数从右端开始的4&#xff5e;7位数字 //就是一个数为abcdefg&#xff0c;取它从右端开始的4&#xff5e;7位数字&#xff0c;就为dcba //如果位数不足7位时&#xff0c;会在数字高位补0 //例如一个数为…

leetcode 62

leetcode 62 题目 解题思路 class Solution { public:int uniquePaths(int m, int n) {vector<vector<int>> f(m, vector<int>(n));for(int i0; i<m; i){f[i][0] 1;}for(int j0; j<n; j){f[0][j] 1;}for(int i1; i<m; i){for(int j1; j<n; j){…

如何记录血压的波动情况

import pandas as pd from plotnine import * import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [Microsoft YaHei] 记录时间(time)、收缩压(SBP)、舒张压(DBP)&#xff1a; df pd.DataFrame({ time: [2023-11-01 08:30, 2023-11-02 21:00, 2023-11-0…

【星海随笔】SDN neutron (一)

一、SDN的原理&#xff1a; 控制平面与数据平面分离&#xff1a;传统网络中&#xff0c;网络设备同时承担控制和数据转发功能&#xff0c;而SDN将这两个功能分离&#xff0c;使得网络控制集中在一个中心控制器上。 中心控制器&#xff1a;SDN架构中的中心控制器负责网络的全局…

【小黑送书—第五期】>>《MATLAB科学计算从入门到精通》

从代码到函数&#xff0c;从算法到实战&#xff0c;从问题到应用&#xff0c;由浅入深掌握科学计算方法&#xff0c;高效解决实际问题。 从代码到函数&#xff0c;掌握多种经典算法 跨越多个领域&#xff0c;精通各类科学计算 多种应用实例&#xff0c;高效解决实际问题 今天给…

【数据结构】Lambda

⭐ 作者&#xff1a;小胡_不糊涂 &#x1f331; 作者主页&#xff1a;小胡_不糊涂的个人主页 &#x1f4c0; 收录专栏&#xff1a;浅谈数据结构 &#x1f496; 持续更文&#xff0c;关注博主少走弯路&#xff0c;谢谢大家支持 &#x1f496; Lambda表达式 1. 背景1.1 语法1.2 函…

零基础必知的Python简介!

文章目录 前言1.Python 简介2.Python 发展历史3.Python 特点3.为什么是Python而不是其他语言&#xff1f;4.Python的种类总结关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Python工具包②Python…

sqli-bypass wp

sqli-bypass靶场 level 1 尝试注入点 1 ,1&#xff0c;1,1",1"" 》存在字符型单引号注入 id1and(1)-- >提示存在sql注入 bypass and、()、--都可能存在被屏蔽掉 尝试#代替-- id1and(1)%23 》 正常回显&#xff0c;说明–被屏蔽了&#xff0c;and&#xf…

相机标定:张正友标定原理

本文来自公众号“AI大道理” —————— 计算机视觉的源头是相机&#xff0c;因此我们有必要对相机有所了解。 原始相机拍摄的图像一般都会有所畸变&#xff0c;导致画面和实际观测的有所排查&#xff0c;为了让相机拍摄的图像和肉眼观察的一致&#xff0c;就需要进行相机标…

双网卡多网卡时win11如何设置网卡优先级

背景&#xff1a; 电脑需要同时使用多个网卡&#xff0c;一个用于被远程、另一个用于打开网页。 电脑打开网页时&#xff0c;走的是哪个网卡&#xff0c;是根据网卡优先级来的。 打开控制面板、网络和Internet、网络和共享中心 点击左侧 更改适配器设置。我这里可见两个网卡…

C语言—统计从键盘输入的一行英文句子的字符个数

流程图 代码 #include <stdio.h>int main() {int count0;printf("请输入英文字符&#xff0c;回车确认&#xff1a;");while (getchar()!\n){count count 1;}printf("共输入%d个字符\n", count);system("pause");return 0; }请输入英文字…

Git的安装,简介以及常用命令【超详细】

目录 一. Git简介 分布式特点 优缺点 Git 与 SVN 区别 二. Git安装 三. Git常用命令 四. Git的文件状态 1.文件状态 2.工作区域 一. Git简介 Git 是一个开源的分布式版本控制系统&#xff0c;可以有效、高速地处理从很小到非常大的项目版本管理。 也是Linus Torvalds…

粘性定位-最下面怎么出现留白

问题&#xff1a;出现了留白 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>Document</title…