An article teaches you how to write a small snake game (pure C language)

1. Game display

Insert image description here

2. Game functions

Implement basic functions:

• Snake map drawing
• Function of snake eating food (up, down, left and right arrow keys control the snake’s movements)
• The snake hits the wall and dies
• The snake hits itself and dies
• Calculate the score
• The snake accelerates and decelerates a>
• Pause game

3、Win32 API

Win32 API is a set of application programming interfaces provided by Microsoft for developing applications on the Windows platform. It includes a rich set of functions, data structures, and message mechanisms that allow developers to interact with the operating system. These interfaces cover various aspects, such as graphical user interface (GUI), files and input and output, multimedia, network communication, etc. By calling these APIs, developers can implement functions such as window creation, message processing, event response, memory management, etc., thereby building fully functional Windows applications. The Win32 API is based on the C language, but can also be called from other programming languages.

Let’s take a look at some Win32API knowledge used to implement Snake.

3.1 Console program

The cmd command box program we usually run is actually a console program

For example, we can use the cmd command to set the size of the console window

mode con cols=100 lines=30

You can also set the name of the console window through the command

title 爱学习的鱼佬
Insert image description here

These commands that can be executed in the console window can also be executed by calling the C language function system. For example:

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

	return 0;
}

3.2 Coordinates COORD on the console screen

COORD is a structure defined in the Windows API, which represents the coordinates of a character on the console screen.

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

Assign values ​​to coordinates:

COORD pos={
    
    10,15};

3.3 GetStdHandle function

GetStdHandleFunctions are Windows API functions used to obtain handles to standard input, standard output, and standard error output. It is typically used in console applications to allow you to access these standard streams for input and output operations.

The following is the basic usage of the GetStdHandle function:

#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;
}

This example shows how to get handles to standard output, standard input, and standard error and store them in HANDLE variables. You can use these handles to perform operations related to console input and output, such as writing to or reading data from the console.

Please note that the GetStdHandle function requires the <windows.h> header file and is typically used with other Windows console functions such asWriteFile and ReadFile are used for actual input and output operations.

3.4 GetConsoleCursorInfo function

GetConsoleCursorInfofunction is a Windows API function used to obtain console cursor information. It allows you to retrieve the console cursor's visibility and blink properties as well as the cursor's size.

The following is the basic usage of the GetConsoleCursorInfo function:

#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;
}

In the above example, we first obtain the standard output handle and then use the GetConsoleCursorInfo function to retrieve information about the console cursor, including the size of the cursor (dwSize) and visibility (bVisible). When the acquisition is successful, you can read these properties to understand the current cursor state.

This function is usually used in console applications to obtain and modify the properties of the cursor, such as changing the visibility or size of the cursor. You can also use other console functions to set new cursor properties, such as SetConsoleCursorInfo to modify the cursor properties.

3.4.1 CONSOLE_CURSOR_INFO structure

CONSOLE_CURSOR_INFOIs a structure used to control and set the properties of the console cursor. This structure is typically used to control the size and visibility of the cursor.

In the Windows API, the CONSOLE_CURSOR_INFO structure is defined as follows:

typedef struct _CONSOLE_CURSOR_INFO {
    
    
    DWORD dwSize;    // 光标的百分比高度 (1 到 100)
    BOOL bVisible;   // 光标是否可见
} CONSOLE_CURSOR_INFO;
  • dwSize represents the size of the cursor, expressed as a percentage of the height, a value between 1 and 100. The height of the cursor is determined by the console's character unit height and dwSize.
  • bVisible indicates the visibility of the cursor, TRUE indicates that the cursor is visible, and FALSE indicates that the cursor is invisible.

This structure is usually used with the GetConsoleCursorInfo and SetConsoleCursorInfo functions to get and set the properties of the console cursor. You can control the appearance and behavior of the cursor in the console by setting the properties of the CONSOLE_CURSOR_INFO structure.

For example, you can use this structure to set the size and visibility of the cursor and then pass it to the SetConsoleCursorInfo function to change the properties of the console cursor

3.5 SetConsoleCursorInfo function

SetConsoleCursorInfofunction is a Windows API function used to set console cursor information. It allows you to change the console cursor's visibility, blinking properties, and cursor size.

The following is the basic usage of the SetConsoleCursorInfo function:

#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;
}

In the above example, we first obtain the standard output handle, and then use the SetConsoleCursorInfo function to set the console cursor information, including the cursor size (dwSize) and visibility (bVisible). You can set these properties to the desired values ​​as needed.

This function is typically used in console applications to customize the properties of the console cursor, such as changing the cursor's size or visibility. You can change the appearance of the console cursor in real time by calling the SetConsoleCursorInfo function. If you need to get cursor information, you can use the GetConsoleCursorInfo function.

3.6 SetConsoleCursorPosition function

SetConsoleCursorPositionfunction is a Windows API function that sets the cursor position of the console window. By calling this function, you can move the cursor to the specified console window coordinate position.

The following is the basic usage of the SetConsoleCursorPosition function:

#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;
}

In the above example, we first get the standard output handle and then create a COORD structure, which includes the horizontal position (X) and the vertical position (Y). Next, we use the SetConsoleCursorPosition function to move the cursor to the specified console window coordinate position.

This function is typically used in console applications to control the movement of the cursor to draw text or perform other operations on the console. By setting the cursor position, you can control the position of the cursor in the console window.

3.7 GetAsyncKeyState function

GetAsyncKeyStateIs a Windows API function used to check whether the key corresponding to the specified virtual key code is pressed. It can be used to detect whether keys on the keyboard are pressed without blocking program execution, so it is suitable for basic keyboard input detection.

The following is the basic usage of the GetAsyncKeyState function:

#include <windows.h>

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

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

    return 0;
}

In the above example, we use the GetAsyncKeyState function to check if the 'A' key on the keyboard is pressed. The function returns a value of type SHORT, where the high bit represents the status of the key. If the key is pressed, the lowest bit (least significant bit) of the high bit will be set to 1, which is 0x8000.

This function is usually used in game development, input detection, and other applications that require real-time keyboard input, because it does not block the program and can continuously check the status of the keys in the loop. Note that GetAsyncKeyState can detect virtual key codes and not just character keys, so you can use virtual key codes to check keys on other keyboards.

Each key has a corresponding virtual key code (Virtual-Key Codes). The following are the virtual key codes of some commonly used keys:

  • Left mouse button: VK_LBUTTON (0x01)
  • Right key 鼠标錉钮: VK_RBUTTON (0x02)
  • 中键鼠标按钮: VK_MBUTTON (0x04)
  • Backspacekey: VK_BACK (0x08)
  • Tabkey: VK_TAB (0x09)
  • Enter键: VK_RETURN (0x0D)
  • Shiftkey: VK_SHIFT (0x10)
  • Ctrl键: VK_CONTROL (0x11)
  • Alt键: VK_MENU (0x12)
  • Pausekey: VK_PAUSE (0x13)
  • Caps Lock键: VK_CAPITAL (0x14)
  • Esckey: VK_ESCAPE (0x1B)
  • Empty key: VK_SPACE (0x20)
  • Page Up键: VK_PRIOR (0x21)
  • Page Down键: VK_NEXT (0x22)
  • End key: VK_END (0x23)
  • Home键: VK_HOME (0x24)
  • Left direction key: VK_LEFT (0x25)
  • Upward key: VK_UP (0x26)
  • Right key: VK_RIGHT (0x27)
  • Downward key: VK_DOWN (0x28)
  • 0key (main key): 0x30
  • Akey: 0x41
  • F1键: VK_F1 (0x70)
  • F2 key: VK_F2 (0x71)
  • F3键: VK_F3 (0x72)
  • ...and so on, the virtual key codes of the F4 to F12 keys are 0x73 to 0x7C

GetAsyncKeyStateThe return value of is of type short. After the last call to the GetAsyncKeyState function, if the 16-bit the highest bit is 1, which means the button is pressed. If the highest bit is 0, it means the button is lifted; if the lowest bit is set to 1, it means, The button has been pressed, otherwise it is 0short

So we need to determine whether a key has been pressed. We can check whether the lowest value returned by GetAsyncKeyState is 1.

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

4. Design a snake map

Let’s first look at the designed interface. The coordinates of the console window are as follows. The horizontal axis is the X-axis, which grows from left to right, and the vertical axis is the Y-axis, which grows from top to bottom.
Insert image description here

On the game map, we use wide characters for printing walls: □, wide characters for printing snakes ●, and wide characters for printing food★
Ordinary characters occupy one character. section, this type of wide character occupies 2 bytes.
Knowledge related to the international characteristics of C language. In the past, C language was not suitable for use in non-English speaking countries (regions).

C language characters are encoded in ASCII by default. The ASCI character set uses single-byte encoding, and only the lower 7 bits of the single byte are used. The highest bit is not used and can be expressed as < /span>). As a result, the encoding system used in these European countries can represent up to 256 symbols. However, a new problem arises here. Different countries have different letters, so even if they all use a 256-symbol encoding, the letters they represent are different. For example, 130 is represented in the French encoding, but represents the letter Gimel (district) in the Hebrew encoding, and represents another symbol in the Russian encoding. But no matter what, in all these encoding methods, the symbols represented by 0-127 are the same, the only difference is this paragraph of 0xxxxxxxx; It can be seen that the ASCII character set contains a total of 128 characters. In English-speaking countries, 128 characters are basically enough. However, in other national languages, such as French, If there is a phonetic symbol above a letter, it cannot be represented by ASCII code. As a result, some European countries decided to use the idle highest bits in the bytes to encode new symbols. For example, the encoding of e in French is 130 (binary10000010128--255

As for the characters of Asian countries, they use even more symbols, with as many as 100,000 Chinese characters. One byte can only represent 256 symbols, which is definitely not enough. Multiple bytes must be used to express one symbol. For example, the common encoding method for Simplified Chinese is GB2312, which uses two bytes to represent a Chinese character, so in theory it can represent up to 256 x 256=65536 characters

Later, in order to adapt the C language to internationalization, internationalization support was continuously added to the C language standard. For example: adding wide character typeswchar_t and wide character input and output functions, adding<locale.h> the header file, which provides the programmer with the ability to target specific locales (usually is a function of the country or geographic region of a specific language) that adjusts the behavior of the program.

4.1 <locale.h>

<locale.h>It is a header file in the C language standard library and provides support for localization and localization-related functions. Localization involves formatting text, dates, times, numbers, etc. according to the needs of a specific region or cultural convention. This helps adapt the program to different regional cultures.

In C language, <locale.h> provides functions for setting the region and locale, such as setlocale, allowing programmers to set the locale according to the desired locale. set up. It also provides functions to handle formatting, sorting, currency, and date-time information used in different locale settings.

Some common functions and functions include:

  • setlocale: Sets the program's current region and locale, allowing the program to process data according to a specific locale.
  • localeconv: Returns a structure describing the numeric format in the current locale.
  • strftime: Format the date and time into a localized format according to the specified format string.
  • printfFormatting flags in the and scanf series of functions : for example %n and %Ld etc., the output or input format can be changed due to the influence of regional settings.

These functions can adjust currency symbols, date formats, time formats, number separators, etc. according to regional settings, thereby making the program more applicable and understandable in different cultural environments.

In the <locale.h> header file, a series of constants are defined for setting different localization categories. These constants are used to determine the different localization settings required in the program. Here are some commonly used constants:

  • LC_ALL: Represents all localization settings.
  • LC_COLLATE: String collation settings.
  • LC_CTYPE: Settings of character classification and conversion rules.
  • LC_MONETARY: Currency format setting.
  • LC_NUMERIC: Numeric format setting.
  • LC_TIME: Time and date format settings.

These constants can be used as parameters of the setlocale() function to set different localization categories in the program. For example, setlocale(LC_TIME, "en_US") is used to format date and time in US English, setlocale(LC_MONETARY, "fr_FR") is used to format currency in French French, etc.

4.2 setlocale function

char *setlocale(int category, const char *locale);
  • category is the localization category to be set, which can be such as LC_ALL, LC_COLLATE, LC_CTYPE, One of the predefined constants such as LC_MONETARY, LC_NUMERIC, LC_TIME, etc.
  • localeIs a string indicating the localization environment to be set, such as "zh_CN" indicating Chinese localization settings.

Example usage is as follows:

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

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

    // 其他程序逻辑...

    return 0;
}

"zh_CN.UTF-8""zh_CN" in "UTF-8" represents the use of UTF-8 encoding is the language code that represents Chinese (China),

It should be noted thatsetlocale functions may have different levels of support in different operating systems or environments, and some environments may not support specific localization settings.

The C standard defines only two possible values ​​for the second parameter: "C" and ""

At the beginning of any program execution, the call will be executed implicitly:

setlocale(LC_ALL, "C");

When the locale is set to"C", the library functions execute normally, with the decimal point being a dot. When the program is running and you want to change the region, you can only explicitly call the setlocale function. Using " " as the second parameter, calling the setlocale function can switch to local mode. In this mode, the program will adapt to the local environment.

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

4.3 Printing of wide characters

In C language, you can use the wchar_t type and some related wide character functions to handle and print wide characters.

First, make sure your compilation environment supports wide characters and use the output functions in wide character format.

#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_tIs a C language data type used to represent wide characters.
  • wprintf is a function for printing wide characters, L"..." represents a wide character constant.

setlocale(LC_ALL, ""); This line of code sets up the program's locale to support wide character processing and display. Wide characters are handled and printed correctly using the wprintf and wchar_t types.

Output results
Insert image description here

Judging from the output results, we found that an ordinary character occupies one character position, but when printing a Chinese character or special symbol, it occupies 2 character positions. So if we want to use wide characters in Snake, we must handle it well. Calculation of coordinates on a map.

4.4 Map coordinates, snake body and food

According to the effect we achieved above, the game map area here is 58 columns and 27 rows. Of course, you can also modify it according to your own situation.

Insert image description here

In the initialization state, assume that the length of the snake is 5, and each node of the snake body is ●. The snake starts to appear at a fixed coordinate, such as (20,6) in the map above, with 5 consecutive nodes.

Note:The x-coordinate of each node of the snake must be a multiple of 2, otherwise it may It will appear that half of a node of the snake appears in the wall, and generally outside the wall, and the coordinates are not well aligned. Regarding food, it is to randomly generate a coordinate ** (x coordinate must be a multiple of 2)** within the wall. The coordinates cannot be the same as the snake's body Overlay and then print large.

5. Data structure design

5.1 Snake node

During the running of the game, every time the snake eats a piece of food, the snake's body will become one section longer. If we use a linked list to store the snake's information, then each section of the snake is actually each node of the linked list. Each node only needs to record the coordinates of the snake body node on the map. So the snake node structure is as follows:

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

pSnakeNode: This alias is a pointer type pointing to the SnakeNode structure, allowing you to declare a pointer variable pointing to SnakeNode more concisely

5.2 Snake state structure

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 Direction of snake

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

5.4 Game status

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

7. Overall game process

Insert image description here

8. Core logic implementation

8.1 Game main logic

#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 Game starts

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. Set the console window size and title:
    • system("mode con cols=100 lines=30");Used to set the size of the console window, setting it to 100 columns and 30 rows.
    • system("title 贪吃蛇");Set the title of the console window to "Snake".
  2. Hide console cursor:
    • Use GetStdHandle to get the handle to standard output hOutput, and then use GetConsoleCursorInfo to get the console cursor information.
    • Set cursor status to invisible:CursorInfo.bVisible = false;
    • Finally pass SetConsoleCursorInfo to set the status of the console cursor.
  3. Display welcome screen:
    • Calls the WelcomeToGame() function. This function may print some welcome information and game introduction.
  4. Print the map, initialize the snake and create the first food:
    • CreateMap()The function is responsible for drawing the game map.
    • InitSnake()Initializes the snake's data structure and displays the snake's initial state.
    • CreateFood()Create the first food in the game.

8.2.0 Encapsulation of SetPos function

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

    hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

    SetConsoleCursorPosition(hOutput, pos);
}
  1. Constant function SetPos:
    • void SetPos(short x, short y): Specifies that the function accepts two parameters, namely x and y coordinates.
  2. Set cursor position:
    • COORD pos = { x, y };: Creates a structure variable of type COORD pos, initialized with the given x and y coordinate values.
    • HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);: Get the handle of standard output, used to identify the console window.
    • SetConsoleCursorPosition(hOutput, pos);: Use the SetConsoleCursorPosition function to set the position of the console cursor to the coordinates specified by pos.

8.2.1 Welcome interface and prompts

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. Set cursor position and print welcome message:
    • SetPos(40, 15); printf("欢迎来到贪吃蛇小游戏");Move the cursor to the position (40, 15) and print the welcome message "Welcome to the Snake Game".
  2. Wait for user to press any key to continue:
    • SetPos(40, 25); sets the cursor position so that the next system("pause"); will be displayed in a beautiful position.
    • system("pause");Pauses program execution and waits for the user to press any key to continue.
  3. 清屏
    • system("cls");Clear the console screen.
  4. Print game instructions:
    • SetPos(25, 12); printf("用 ↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速\n");Print the game instructions at the designated location, telling the user how to control the movement of the snake and use F3 and F4 to speed up or slow down.
    • SetPos(25, 13); printf("加速将能得到更高的分数。\n");Give some game strategy suggestions.
  5. Wait again for the user to press any key to continue:
    • SetPos(40, 25); Set the cursor position again to ensure that the next time system("pause"); is displayed in a suitable position.
    • system("pause");Halts program execution again, waiting for the user to press any key to continue.

The purpose of this function is to display the welcome message and operation instructions of the game to the player, and to provide the player with some basic operation tips and rules before the game starts.

Welcome Screen

Insert image description here

Prompt interface

Insert image description here

8.2.2 Create a map

Creating a map means printing out the wall. Because it is printing with wide characters, all the wprintf functions are used. The key to printing the map is to use L before printing the format string. It is necessary to calculate the coordinates so that the wall can be printed at the desired location.

Wide characters for wall printing:

#define WALL L'□'

Create a map

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);
    }
}

Draw the game map:

  • SetPos(0, 0);Move the cursor to the position (0, 0) to start drawing the game map.
  • Draw the top wall from (0,0) to (56,0) throughfor loop and wprintf function, given by WALL symbol composition.
  • Draw the bottom wall from (0,26) to (56,26).
  • Draw the left wall from (0,1) to (0,25).
  • Draw the right wall from (56,1) to (56,25).

8.2.3 Initializing the snake body

The initial length of the snake is 5 sections, each section corresponds to a node in the linked list, and each node of the snake body has its own coordinates
Create 5 nodes, and then add each Nodes are stored in linked lists for management. After creating the snake, print each section of the snake on the screen

Wide characters printed on snake body:

#define BODY L'●'

Initialize snake body function

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;
}

Create Snake Body Node:

  • Create the snake's five body segments using a for loop.
  • Each segment is dynamically allocated using malloc .
  • Setting coordinates for each segment creates horizontal lines for the snake's body.

Print snake body:

  • After creating the segment, the code traverses the snake's linked list, moves the cursor to the coordinates of each segment and uses wprintf to print the body of the snake, using the macroBODY symbol.

Initialize game variables:

  • Finally, the function initializes various game-related variables such as sleep time, score, direction, and extra score rules.

8.2.4 Creating food

Caution :

The x coordinate must be a multiple of 2
The coordinates of the food cannot overlap with the coordinates of each node of the snake body

Wide characters for food printing:

#define FOOD L'★'

Create a food function

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;
    }
}

Produced food position :

  • Use do-while loop to generate the x and y coordinates of the food.
  • The x-coordinate is constrained to an even number to align with the snake's head coordinates.

Avoid conflicts between food and snakes:

  • The code checks the position of the newly generated food to ensure that it does not overlap with the snake's body.
  • If the new food coordinates overlap with the snake body, the code will jump back to the again tag to regenerate the food position.

Create food node:

  • If the food's location is valid, a new node will be dynamically allocated to represent the food.
  • The food node's coordinates are set to the new x and y coordinates, then the food's symbol is printed at that location on the console, and the food node pointer is saved in the game state.

8.3 Game operation

While the game is running, help information is printed on the right to remind the player

Check whether the game continues based on the game status. If the status is OK, the game continues, otherwise the game ends.

If the game continues, it will detect the button presses and determine the snake's next direction, or whether to accelerate or decelerate, pause or exit the game.

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);
}

This code implements the game's running logic, continuously updating the game state and responding to player input in a loop.

  1. Information about stamped seal:
    • PrintHelpInfo()Called to display game help information.
  2. Playing loop:
    • The main logic of the game is contained in a do-while loop.
    • Check the player's key input and take corresponding actions according to the different key presses.
    • Change the moving direction of the snake head according to the button status.
    • If you press the space bar, the game will pause.
    • If the ESC key is pressed, the game state is set to end normally (END_NOMAL) and the loop is exited.
  3. Acceleration and reduction speed:
    • If you press F3, the snake will move faster in the game and your score will increase.
    • If you press F4, the snake's movement speed in the game will slow down and your score will be reduced.
  4. Snake movement:
    • The game controls the movement of the snake based on the currently set movement speed.
    • Use the Sleep() function to control the time between each snake movement.
    • Call the SnakeMove() function to handle the snake's movement logic.
  5. End of cycle:
    • The game loop continues until the game state changes to an abnormal state (not OK).

8.3.1 Help information

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("爱学习的鱼佬");
}

Insert image description here

8.3.2 Snake body movement

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. Create the next node: Use malloc() to allocate memory to create a new snake body node pNextNode.
  2. Determine the coordinates of the next node based on the direction: Calculate the next moving position of the snake head based on the current direction of the snake head.
  3. Check if the next position is food:
    • If the next position is food, call the EatFood(pNextNode, ps) function to handle the logic of eating food.
    • If it is not food, call the NoFood(pNextNode, ps) function to handle the logic of not eating food.
  4. Check game ending conditions:
    • Calls the KillByWall(ps) and KillBySelf(ps) functions to check whether it hits the wall or bites itself. If the game ends, the corresponding game state will be set.

8.3.3 Three situations of eating food

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: This function checks whether there is food at the next location. Returns true if the coordinates match the food location; false otherwise.
  2. EatFood: This function is called when there is food at the next location. It uses a linked list method to extend the body of the snake by inserting a new node representing the next position at the beginning of the snake. It updates the snake's score, removes the eaten food (by freeing memory), and then creates new food to continue the game.
  3. NoFood: This function is called when there is no food at the next location. It also uses a linked list approach, inserting a new node representing the next position at the beginning of the snake. However, it manages the snake's tail by removing the last node, simulating the movement of a snake. Then the memory of the last node is released and the appropriate pointer is set.

8.3.4 Two cases of G loss

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 Check whether the snake head touches the boundary of the game map. If the snake's head is at the edge of the map, the function sets the snake's status to KILL_BY_WALL and returns 1 (indicating that the snake hit the wall), otherwise it returns 0.
  2. KillBySelf Check whether the snake head touches its own body. It traverses the snake body linked list (except the head node), checking whether the coordinates of the snake head match the coordinates of any other node. If a collision occurs, the function sets the snake's status to KILL_BY_SELF and returns 1 (indicating that the snake touched its own body), otherwise it returns 0.

8.3.5 Pause function

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

It loops while the game is executing, checking every 300 milliseconds to see if the space bar was pressed. When the space bar press is detected, the loop ends and the game continues execution

8.4 Game over

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);
    }
}

Display corresponding messages on the screen based on the game status, such as the player voluntarily exiting the game, the snake hitting itself or hitting the wall causing the game to end. Afterwards, it releases the snake body node and cleans up the allocated memory

Guess you like

Origin blog.csdn.net/kingxzq/article/details/134340672