An article teaches you how to write a snake game
1. Game display
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 爱学习的鱼佬
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
GetStdHandle
Functions 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
GetConsoleCursorInfo
function 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_INFO
Is 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 anddwSize
.bVisible
indicates the visibility of the cursor,TRUE
indicates that the cursor is visible, andFALSE
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
SetConsoleCursorInfo
function 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
SetConsoleCursorPosition
function 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
GetAsyncKeyState
Is 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
GetAsyncKeyState
The 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.
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 (binary10000010
128--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 to256 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.printf
Formatting flags in the andscanf
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 asLC_ALL
,LC_COLLATE
,LC_CTYPE
, One of the predefined constants such asLC_MONETARY
,LC_NUMERIC
,LC_TIME
, etc.locale
Is 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_t
Is 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
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.
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
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);
}
- 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".
- Hide console cursor:
- Use
GetStdHandle
to get the handle to standard outputhOutput
, and then useGetConsoleCursorInfo
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.
- Use
- Display welcome screen:
- Calls the
WelcomeToGame()
function. This function may print some welcome information and game introduction.
- Calls the
- 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);
}
- Constant function SetPos:
void SetPos(short x, short y)
: Specifies that the function accepts two parameters, namely x and y coordinates.
- Set cursor position:
COORD pos = { x, y };
: Creates a structure variable of typeCOORD
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 theSetConsoleCursorPosition
function to set the position of the console cursor to the coordinates specified bypos
.
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");
}
- 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".
- Wait for user to press any key to continue:
SetPos(40, 25);
sets the cursor position so that the nextsystem("pause");
will be displayed in a beautiful position.system("pause");
Pauses program execution and waits for the user to press any key to continue.
- 清屏:
system("cls");
Clear the console screen.
- 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.
- Wait again for the user to press any key to continue:
SetPos(40, 25);
Set the cursor position again to ensure that the next timesystem("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
Prompt interface
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) through
for
loop andwprintf
function, given byWALL
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.
- Information about stamped seal:
PrintHelpInfo()
Called to display game help information.
- 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.
- 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.
- 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.
- 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("爱学习的鱼佬");
}
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);
}
- Create the next node: Use
malloc()
to allocate memory to create a new snake body nodepNextNode
. - 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.
- 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.
- If the next position is food, call the
- Check game ending conditions:
- Calls the
KillByWall(ps)
andKillBySelf(ps)
functions to check whether it hits the wall or bites itself. If the game ends, the corresponding game state will be set.
- Calls the
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;
}
NextIsFood
: This function checks whether there is food at the next location. Returns true if the coordinates match the food location; false otherwise.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.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;
}
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 toKILL_BY_WALL
and returns 1 (indicating that the snake hit the wall), otherwise it returns 0.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 toKILL_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