Make a simple snake game

Two preparatory knowledge

1. ncurses

What is ncurses:

  1. ncurses is a library that provides an API that allows programmers to write terminal-independent text-based user interfaces. It is a "GUI-like" application software toolbox in a virtual terminal. It also optimizes the screen refresh method to reduce delays encountered when using remote shells.

    Excerpted from:What is ncurses

  2. ncurses (new curses) is a programming library that provides a series of functions that users can call to generate text-based user interfaces. The n` in the name ncurses means "new" because it is a free software version of curses. Due to AT&T's "notorious" copyright policy, people had to replace it with ncurses later.

    Excerpted from:Introduction and installation of ncurses library

    • curses is a graphics function library widely used under Linux/Unix. Its function is to draw a simple graphical user interface in the terminal.

    • Curses allows us to create beautiful graphics under Linux.

    • The name curses originates from "cursor optimization", which means cursor optimization. Almost all Linux systems now come with the curses function library, and curses has also added mouse support and some menu and panel processing. It can be said that curses is the best choice for Linux terminal graphical interface programming (for example, the famous vi is compiled based on curses)

    Excerpted from:C language curses

Why is it necessary to make the Snake mini game this time?ncurse: It is faster to obtain keyboard input and the response is faster. If you don't use ncurse and use other input functions (such as scanf(), get(), etc.) to control the snake's steering, it would be too much if you need to enter the direction and then press the Enter key. Slow

ncurses small example: get the keys pressed on the keyboard
#include <curses.h>
int main(void)
{
    
    
        char c;
        initscr();
        while(1)
        {
    
    
                c = getch();
                printw("\nwhat you input:%c\n",c);
        }
        endwin();
        return 0;
}
  • To use ncurses, you need to add header filescurses.h
  • initscr(): Initialize the window object and ncurses code, and return the window object representing the entire screen (simple understanding: initialize the screen to start entering the curses graphical working mode)
  • endwin(): used to end curses and restore the original screen

After the code is written, it needs to be compiled with the ncurses library, so you need to enter it when using gcc to compile the file.gcc test.c -lcurses

Reference:C curses

The running program is as follows:
Insert image description here

Run the program compiled with the above code. If you press , the following will be displayed:
Insert image description here

Now makes it work properly to output display function keys

Viewcurses.h, enter:vi /usr/include/curses.h in the terminal, and press Enter to enter the view. Then you can enter /KEY_UP and press Enter to view these functional keys (such as F1, F2, , etc.),

In order for the above small example to be displayed after pressing , a function keypad() must be used. This function can be set in Accept keyboard function keys in stdscr

What is stdscr:
  curses is a graphics function library widely used under Linux/Unix. Its function is to draw a simple graphical user interface in the terminal.
  curses uses two data structures to map the terminal screen: stdscr and curscr. stdscr is the "standard screen" (logical screen), which is refreshed when the curses function library generates output. It is the default output window (the user will not see the content). Curscr is the "current screen" (physical screen). When the refresh function is called, the function library will refresh curscr to stdscr.

Excerpted from:Detailed introduction to curses function library under Linux

My simple understanding: It is an output screen that is more powerful than a terminal, like a canvas, and can be used to draw various things.

The above code should be as follows after addingkeypad():

#include <curses.h>
int main(void)
{
    
    
        char c;
        initscr();
        keypad(stdscr,1);
        while(1)
        {
    
    
                c = getch();
                printw("\nwhat you input:%c\n",c);
        }
        endwin();
        return 0;
}

keypad(stdscr,1): The first parameter means to receive function keys from stdscr, the second parameter indicates whether to receive, 1 means to receive


Insert image description here

is still displayed abnormally. This is because the variable c is of type char, which only occupies 1 byte. It is 8 bits. In the unsigned case, it can only express up to 128. The "value" of may exceed this value, so the information stored in the variable c is incomplete

Useint type variable and continue to modify the code as follows:

#include <curses.h>
int main(void)
{
    
    
        int key;
        initscr();
        keypad(stdscr,1);
        while(1)
        {
    
    
                key = getch();
                printw("\nwhat you input:%d\n",key);
        }
        endwin();
        return 0;
}

editing, downloading
Insert image description here

It seems that some content can be output normally, but this value is different from the value of the function key defined in the header file curses.h:
Insert image description here

This is because 0403 here is in octal, which is exactly equal to 259 in decimal.

You can use the name defined by the curses.h macro to modify it as follows:

#include <curses.h>
int main(void)
{
    
    
        int key;
        initscr();
        keypad(stdscr,1);
        while(1)
        {
    
    
                key = getch();
                switch(key)
                {
    
    
                        case KEY_UP:
                                printw("UP\n");
                                break;
                        case KEY_DOWN:
                                printw("DOWN\n");
                                break;
                        case KEY_LEFT:
                                printw("LEFT\n");
                                break;
                        case KEY_RIGHT:
                                printw("RIGHT\n");
                                break;
                        default:
                                printw("\n%c\n",key);
                }
        }
        endwin();
        return 0;
}

After compiling and running, press and it will output: UP

2. Thread

The origin of threads:"What is a thread"

Programs, processes and threads:Programs, processes and threads

Other articles about threads:

  1. "[Linux] Threads"ps. This article includes the concept of threads, programming using the pthread thread library, and supplements to Linux threads. Suitable to read when learning threads for the first time
  2. "Threads in Linux"ps. This article includes the concept of threads, explanations of various terms, various comparisons, and the purpose of using threads. It is also suitable for reading when learning threads for the first time
  3. [Linux] Threadsps. This article has a lot of content and a lot of proper nouns, which is a bit difficult to understand. You can read it later in the thread learning process

Personal understanding: When a program has two threads, it is like having two channels. Channel one has the code of channel one, and channel two has the code of channel two. Both channels will run "simultaneously"

Excerpts from some of the more important things in the article mentioned above:

  1. Linux does not have real threads, it is simulated by processes.
  2. Process is the basic entity responsible for allocating system resources. Threads are the basic unit of CPU scheduling.
  3. Threads exist in a process and share the resources of the process
Create thread

Create a new thread to usepthread_create(), add the header file before using itpthread.h

prototype:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);

Parameters:
thread: Thread identifier address.
attr: Manually specify the properties of the new thread. Generally, it is set to NULL, which means that the new thread follows the default properties.
start_routine: Indicate which function the new thread needs to execute in the form of a function pointer.
arg: Pass data to the formal parameters of the start_routinue() function. Generally, it is set to NULL, which means no data is passed.

Thread small example

Run bothwhile(1)in the program

#include <stdio.h>
#include <pthread.h>
void* func1()
{
    
    
        while(1)
        {
    
    
                printf("This is func1!\n");
                sleep(1);
        }
}
void* func2()
{
    
    
        while(1)
        {
    
    
                printf("This is func2!\n");
                sleep(1);
        }
}
int main(void)
{
    
    
        pthread_t th1;
        pthread_create(&th1, NULL, func1, NULL);
        func2();
        return 0;
}

sleep(1);Used to delay the current action for a period of time to prevent printed things from flooding the screen.

Output result:
Insert image description here

  • Threads do not necessarily run alternately
  • In multi-core, multiple threads run simultaneously. In single-core, threads compete for hardware resources such as CPU.

A simple snake game

Draw map border

The vertical boundary of the map:|

Horizontal boundaries of the map:--

Map size: 20x20

#include <curses.h>
void gamePic()
{
    
    
	int row = 0;
	int col = 0;
	for(row=0; row<20; row++)
	{
    
    
		if(row==0)//绘制上边框
			for(col=0; col<20; col++)
				printw("--");
		printw("\n");
		for(col=0; col<20; col++)//绘制左右边框
		{
    
    
			if(col==0 || col==19)
				printw("|");
			else
				printw("  ");//两个空格
		}
		if(row==19)//绘制下边框
		{
    
    
			printw("\n");
			for(col=0; col<20; col++)
				printw("--");
		}
	}
}
int main(void)
{
    
    
	initscr();
	keypad(stdscr,1);
	gamePic();
	getch();//让程序停一下,方便显示画面
	endwin();
	return 0;
}

After compiling and running, the screen is as follows:
Insert image description here

It is found that the upper and lower dotted boxes will protrude a little. At this time, you need to move the right border a little more to the right. You only need to modify the code for drawing the left and right borders:

for(col=0; col<=20; col++)
{
    
    
	if(col==0 || col==20)
		printw("|");
	else
		printw("  ");
}

After compiling and running again, the screen is as follows:
Insert image description here

Draw a motionless snake

Next, draw the body of the greedy snake that cannot move. The idea is as follows:

The body of the greedy snake is composed of nodes[], each[] is a structure with its own coordinates. row, col, and the address of the next [] is also stored. Each[] is strung into a linked list.

Set two global pointer variableshead and tail, pointing to the head node and tail node of the snake respectively to facilitate the processing of the snake body. Operation

struct Snake
{
    
    
        int row;
        int col;
        struct Snake *next;
}
struct Snake *head = NULL;
struct Snake *tail = NULL;

Considering that the snake will move later and increase in length after eating food, each [] is dynamically generated by malloc(), To facilitate the management of node deletion and addition

Customize a function initSnake() to initialize the snake body. At the beginning of the game, the snake body has 3 nodes, all in the second row. The coordinates of the first, second, and third nodes are in the seventeenth, eighteenth, and nineteenth columns respectively.

void initSnake()
{
    
    
    head = (struct Snake *)malloc(sizeof(struct Snake));
    head->row = 2;
    head->col = 19;
    head->next = NULL;
    tail = head;
    addNode();
    addNode();
}

Among them,addNode() is used to add a body node. The direction in which to add it is not considered here. Just use this function to draw the above three nodes. The way to add a node is to add a new node to the left of the head node:

void addNode()
{
    
    
    struct Snake *new = (struct Snake*)malloc(sizeof(struct Snake));
    new->row = head->row;
    new->col = head->col-1;
    new->next = head;
    head = new;
}

Drawing the greedy snake is completed ingamePic(). Just modify the code in the drawing part to be as follows:

for(col=0; col<=20; col++)
{
    
    
	if(col==0 || col==20)
		printw("|");
	else if(hasSnakeBody(row,col))
		printw("[]");
	else
		printw("  ");
}

Among them,hasSnakeBody(row, col) is used to determine whether there is a snake body at row and col. If it exists, 1 is returned:

int hasSnakeBody(int y, int x)
{
    
    
	struct Snake *p = head;
	while(p!=NULL)
	{
    
    
		if(y==p->row && x==p->col)
			return 1;
		p = p->next;
	}
	return 0;
}

At this point, the complete code of the file is as follows:

#include <curses.h>
#include <stdlib.h>
struct Snake
{
    
    
	int row;
	int col;
	struct Snake *next;
};
struct Snake *head = NULL;
struct Snake *tail = NULL;
void addNode()//增加一个蛇节点
{
    
    
	struct Snake *new = (struct Snake*)malloc(sizeof(struct Snake));
	new->row = head->row;
	new->col = head->col-1;
	new->next = head;
	head = new;
}
void initSnake()//初始化蛇
{
    
    
	head = (struct Snake *)malloc(sizeof(struct Snake));
	head->row = 1;
	head->col = 19;
	head->next = NULL;
	tail = head;
	
	addNode();
	addNode();
}
int hasSnakeBody(int y, int x)//判断当前位置是否存在蛇身
{
    
    
	struct Snake *p = head;
	while(p!=NULL)
	{
    
    
		if(y==p->row && x==p->col)
			return 1;
		p = p->next;
	}
	return 0;
}
void gamePic()//绘制画面
{
    
    
	int row = 0;
	int col = 0;
	for(row=0; row<20; row++)
	{
    
    
		if(row==0)
			for(col=0; col<20; col++)
				printw("--");
		printw("\n");
		for(col=0; col<=20; col++)
		{
    
    
			if(col==0 || col==20)
				printw("|");
			else if(hasSnakeBody(row,col))
				printw("[]");
			else
				printw("  ");
		}
		if(row==19)
		{
    
    
			printw("\n");
			for(col=0; col<20; col++)
				printw("--");
		}
	}
}
int main(void)
{
    
    
	initscr();
	keypad(stdscr,1);	
	initSnake();
	gamePic();
	getch();//防止程序直接退出
	endwin();
	return 0;
}

The compilation and running results are as follows:
Insert image description here

Make the greedy snake press it and move it

First press only once, and the snake will move to the left

In the main function, set a variablecon to store the value of the key pressed on the keyboard. Usewhile(1) to constantly monitor the keyboard. When it detects that the keyboard is pressed, make the snake take a step to the left. Then draw the pattern at this time

The code in the main function is as follows:

int main(void)
{
    
    
    int con;
    initscr();
    keypad(stdscr,1);

    initSnake();
    gamePic();

    while(1)
    {
    
    
        con = getch();
        if(con == KEY_LEFT)
        {
    
    
            moveSnake();
            gamePic();
        }
    }

    getch();//防止程序直接退出
    endwin();
    return 0;
}

Among them, moveSnake() moves the snake body one square to the left. Taking the body with three nodes as an example, the idea of ​​moving the snake body one square is as follows:

  1. Counting from left to right, add a new node before the first node
  2. The original second and original third nodes remain unchanged.
  3. Delete the last node

code show as below:

void moveSnake()
{
    
    
    addNode();
    deleteNode();
}

deleteNode() is used to delete the last node of the snake body. First set a pointer variable so that it points to the penultimate node of the linked list after passing through the while loop, and then use free(tail) to delete the last node of the snake body:

void deleteNode()
{
    
    
    struct Snake *p = head;
    while(p->next != tail)
        p = p->next;
    free(tail);
    tail = p;
    tail->next = NULL;
}

Compile and run, press and you will find the following screen:
Insert image description here

I found that the interface was a bit confusing, because before pressing , the cursor was at the end of the bottom frame. When drawing the picture of the snake body moving one space to the left, it was right next to the cursor. The position continues to be drawn. In order to make the newly generated picture overwrite the original picture, you need to add to the gamePic() function of the drawing, so that the cursor moves to the function before each drawing. (0,0):move(0,0);

void gamePic()
{
    
    
    int row = 0;
    int col = 0;
    move(0,0);
    ......
}

Compile and run after adding, press to move the snake body one space to the left

At this point, the complete code is as follows:

#include <curses.h>
#include <stdlib.h>
struct Snake
{
    
    
	int row;
	int col;
	struct Snake *next;
};
struct Snake *head = NULL;
struct Snake *tail = NULL;
void addNode()
{
    
    
	struct Snake *new = (struct Snake*)malloc(sizeof(struct Snake));
	new->row = head->row;
	new->col = head->col-1;
	new->next = head;
	head = new;
}
void initSnake()
{
    
    
	head = (struct Snake *)malloc(sizeof(struct Snake));
	head->row = 1;
	head->col = 19;
	head->next = NULL;
	tail = head;
	
	addNode();
	addNode();
}
int hasSnakeBody(int y, int x)
{
    
    
	struct Snake *p = head;
	while(p!=NULL)
	{
    
    
		if(y==p->row && x==p->col)
			return 1;
		p = p->next;
	}
	return 0;
}
void gamePic()
{
    
    
	int row = 0;
	int col = 0;
	move(0,0);
	for(row=0; row<20; row++)
	{
    
    
		if(row==0)
			for(col=0; col<20; col++)
				printw("--");
		printw("\n");
		for(col=0; col<=20; col++)
		{
    
    
			if(col==0 || col==20)
				printw("|");
			else if(hasSnakeBody(row,col))
				printw("[]");
			else
				printw("  ");
		}
		if(row==19)
		{
    
    
			printw("\n");
			for(col=0; col<20; col++)
				printw("--");
		}
	}
	printw("\n");
}

void deleteNode()
{
    
    
	struct Snake *p;
	p = head;
	while(p->next != tail)
		p = p->next;
	free(tail);
	tail = p;
	tail->next = NULL;
	
}

void moveSnake()
{
    
    
	addNode();
	deleteNode();
}

int main(void)
{
    
    
	int con;
	initscr();
	keypad(stdscr,1);
	
	initSnake();
	gamePic();

	while(1)
	{
    
    
		con = getch();
		if(con == KEY_LEFT)
		{
    
    	
			moveSnake();
			gamePic();
		}
	}
	endwin();
	return 0;
}

Snake hits the wall and starts again

Implementation method: Judge the snake head (head node) after each movement of the snake. When the row value of the snake head is equal to 0 or 20, or the col value of the snake head is equal to 0 or 20, it is considered to have hit the wall. If you hit a wall, use initSnake() to start over. Therefore, themoveSnake() function is modified as follows:

void moveSnake()
{
    
    
    addNode();
    deleteNode();
    if(head->row==0 || head->col==0 || head->row==20 || head->col==20)
        initSnake();
}

Then compile and run directly, and keep pressing to make the snake move to the left. When the snake's head hits the wall on the left, it will start again

Although it is now possible to restart after hitting a wall, there is a problem. When initSnake() is called, a new space will be opened up to form the snake body (that is, again Get a new linked list). The snake after hitting the wall (that is, the old linked list) still occupies space in the system memory. If you directly use initSnake() every time it hits the wall without releasing the memory space occupied by the snake that hits the wall, the system memory will be continuously occupied, resulting in insufficient memory space, and the program may crash. .

Before starting over (or before recreating a snake), you need to release the memory space occupied by the old snake. You only need to judge the global variable head first to see whether there is already a snake (that is, whether there is already one). linked list)

initSnake()Add the following code in front of the function:

void initSnake()
{
    
    
    struct Snake *p;
    while(head != NULL)
    {
    
    
        p = head;
        head = head->next;
        free(p);
    }
    ......
}

At this point, the complete code is as follows:

#include <curses.h>
#include <stdlib.h>
struct Snake
{
    
    
	int row;
	int col;
	struct Snake *next;
};
struct Snake *head = NULL;
struct Snake *tail = NULL;
void addNode()
{
    
    
	struct Snake *new = (struct Snake*)malloc(sizeof(struct Snake));
	new->row = head->row;
	new->col = head->col-1;
	new->next = head;
	head = new;
}
void initSnake()
{
    
    
	struct Snake *p;
	while(head != NULL)
	{
    
    
		p = head;
		head = head->next;
		free(p);
	}
	
	head = (struct Snake *)malloc(sizeof(struct Snake));
	head->row = 1;
	head->col = 19;
	head->next = NULL;
	tail = head;
	
	addNode();
	addNode();
}
int hasSnakeBody(int y, int x)
{
    
    
	struct Snake *p = head;
	while(p!=NULL)
	{
    
    
		if(y==p->row && x==p->col)
			return 1;
		p = p->next;
	}
	return 0;
}
void gamePic()
{
    
    
	int row = 0;
	int col = 0;
	move(0,0);
	for(row=0; row<20; row++)
	{
    
    
		if(row==0)
			for(col=0; col<20; col++)
				printw("--");
		printw("\n");
		for(col=0; col<=20; col++)
		{
    
    
			if(col==0 || col==20)
				printw("|");
			else if(hasSnakeBody(row,col))
				printw("[]");
			else
				printw("  ");
		}
		if(row==19)
		{
    
    
			printw("\n");
			for(col=0; col<20; col++)
				printw("--");
		}
	}
	printw("\n");
}
void deleteNode()
{
    
    
	struct Snake *p;
	p = head;
	while(p->next != tail)
		p = p->next;
	free(tail);
	tail = p;
	tail->next = NULL;
	
}
void moveSnake()
{
    
    
	addNode();
	deleteNode();
	if(head->row==0 || head->col==0 || head->row==20 || head->col==20)
		initSnake();
}
int main(void)
{
    
    
	int con;
	initscr();
	keypad(stdscr,1);
	initSnake();
	gamePic();
	while(1)
	{
    
    
		con = getch();
		if(con == KEY_LEFT)
		{
    
    	
			moveSnake();
			gamePic();
		}
	}
	endwin();
	return 0;
}

Make the snake automatically move to the left

In the main functionwhile(1), modify the code to the following:

int main(void)
{
    
    
    initscr();
    keypad(stdscr,1);

    initSnake();
    gamePic();

    while(1)
    {
    
    
        moveSnake();
        gamePic();
        refresh();
        usleep(100000);//=0.1s (1000000μs=1s)
    }

    endwin();
    return 0;
}

Among them, refresh() and usleep(100000) combined together refresh the screen every 0.1 seconds. Compile and run to make the snake automatically move to the left

The snake body automatically moves to the left while monitoring the buttons.

Monitoring keyboard keys and making the snake move by itself require one while(1) each. If you want to make the snake move continuously and monitor the keyboard continuously, you need two /span> run at the same time in the program, you need to use threadswhile(1), to make bothwhile(1)

To use thread-related operations, you need to first add the header file pthread.h

The main function code is modified as follows:

int main(void)
{
    
    
    pthread_t t1;//创建线程变量t1
    pthread_t t2;//创建线程变量t1

    initscr();
    keypad(stdscr,1);

    initSnake();
    gamePic();

    pthread_create(&t1, NULL, refreshScr, NULL);
    pthread_create(&t2, NULL, changeDir, NULL);

    while(1);//防止主线程退出

    endwin();
    return 0;
}

pthread_create() has four parameters:
thread: Thread identifier address.
attr: Manually specify the properties of the new thread. Generally, it is set to NULL, which means that the new thread follows the default properties.
start_routine: Use the function pointer to indicate which function the new thread needs to execute.
arg: Pass data to the formal parameters of the start_routinue() function. Generally, it is set to NULL, which means no data is passed.

The statementpthread_create(&t1, NULL, refreshScr, NULL); creates a thread that executes therefreshScr() function. refreshScr()The function is used to make the snake move to the left on its own.

refreshScr()The function code is as follows:

void* refreshScr()
{
    
    
    while(1)
    {
    
    
        moveSnake();
        gamePic();
        refresh();
        usleep(100000);//=0.1s  (1000000μs = 1s)
    }
}

The statementpthread_create(&t2, NULL, changeDir, NULL); creates another thread that executes thechangeDir() function. changeDir()The function is used here to monitor the keyboard and output the direction keys pressed.

changeDir()The function code is as follows:

void* changeDir()
{
    
    
    while(1)
    {
    
    
        key = getch();
        switch(key)
        {
    
    
            case KEY_DOWN:
                printw("DOWN\n");
                break;
            case KEY_UP:
                printw("UP\n");
                break;
            case KEY_LEFT:
                printw("LEFT\n");
                break;
            case KEY_RIGHT:
                printw("RIGHT\n");
                break;
        }
    }
}

Then,keyThe total amount of change

At this point, the complete code is as follows:

#include <curses.h>
#include <stdlib.h>
#include <pthread.h>
struct Snake
{
    
    
	int row;
	int col;
	struct Snake *next;
};
struct Snake *head = NULL;
struct Snake *tail = NULL;
int key;
void addNode()
{
    
    
	struct Snake *new = (struct Snake*)malloc(sizeof(struct Snake));
	new->row = head->row;
	new->col = head->col-1;
	new->next = head;
	head = new;
}
void initSnake()
{
    
    
	struct Snake *p;
	while(head != NULL)
	{
    
    
		p = head;
		head = head->next;
		free(p);
	}
	
	head = (struct Snake *)malloc(sizeof(struct Snake));
	head->row = 1;
	head->col = 19;
	head->next = NULL;
	tail = head;
	
	addNode();
	addNode();
}
int hasSnakeBody(int y, int x)
{
    
    
	struct Snake *p = head;
	while(p!=NULL)
	{
    
    
		if(y==p->row && x==p->col)
			return 1;
		p = p->next;
	}
	return 0;
}
void gamePic()
{
    
    
	int row = 0;
	int col = 0;
	move(0,0);
	for(row=0; row<20; row++)
	{
    
    
		if(row==0)
			for(col=0; col<20; col++)
				printw("--");
		printw("\n");
		for(col=0; col<=20; col++)
		{
    
    
			if(col==0 || col==20)
				printw("|");
			else if(hasSnakeBody(row,col))
				printw("[]");
			else
				printw("  ");
		}
		if(row==19)
		{
    
    
			printw("\n");
			for(col=0; col<20; col++)
				printw("--");
		}
	}
	printw("\n");
}

void deleteNode()
{
    
    
	struct Snake *p;
	p = head;
	while(p->next != tail)
		p = p->next;
	free(tail);
	tail = p;
	tail->next = NULL;
	
}

void moveSnake()
{
    
    
	addNode();
	deleteNode();
	if(head->row==0 || head->col==0 || head->row==20 || head->col==20)
		initSnake();
}

void* refreshScr()
{
    
    	
	while(1)
	{
    
    
		moveSnake();
		gamePic();
		refresh();
		usleep(100000);//=0.1s  (1000000μs = 1s)
	}
}

void* changeDir()
{
    
    
	while(1)
	{
    
    
		key = getch();
		switch(key)
		{
    
    
			case KEY_DOWN:
				printw("DOWN\n");
				break;
			case KEY_UP:
				printw("UP\n");
				break;
			case KEY_LEFT:
				printw("LEFT\n");
				break;
			case KEY_RIGHT:
				printw("RIGHT\n");
				break;
		}
	}
}

int main(void)
{
    
    
	pthread_t t1;
	pthread_t t2;

	initscr();
	keypad(stdscr,1);
	
	initSnake();
	gamePic();
	
	pthread_create(&t1, NULL, refreshScr, NULL);
	pthread_create(&t2, NULL, changeDir, NULL);

	while(1);

	endwin();
	return 0;
}

After compiling and running, press, the interface is as follows:
Insert image description here

At this time, the snake's body can automatically move to the left, and press the direction keys, , , can all be recognized and output on the screen

Use the keyboard to change the direction the snake moves

Add a global variable dir to store the current moving direction of the snake.

IninitSnake(), initialize dir to RIGHT and put this statement at the front

Detects keyboard arrow keys inchangeDir() to change the value of dir. changeDir()The code is modified as follows:

void* changeDir()
{
    
    
    while(1)
    {
    
    
        key = getch();
        switch(key)
        {
    
    
            case KEY_DOWN:
                    dir = DOWN;
                    break;
            case KEY_UP:
                    dir  = UP;
                    break;
            case KEY_LEFT:
                    dir = LEFT;
                    break;
            case KEY_RIGHT:
                    dir = RIGHT;
                    break;
        }
    }
}

in that, DOWN, UP, LEFT, RIGHT as follows:

#define UP 1
#define DOWN 2
#define LEFT 3
#define RIGHT 4

Then create a new node inaddNode() based on the value of dir

void addNode()
{
    
    
    struct  Snake *new = (struct Snake*)malloc(sizeof(struct Snake));
    new->next = NULL;
    switch(dir)
    {
    
    
        case UP:
            new->row = head->row-1;
            new->col = head->col;
            break;
        case DOWN:
            new->row = head->row+1;
            new->col = head->col;
            break;
        case LEFT:
            new->row = head->row;
            new->col = head->col-1;
            break;
        case RIGHT:
            new->row = head->row;
            new->col = head->col+1;
            break;

    }
    new->next = head;
    head = new;
}

At this point, the complete code is as follows:

#include <curses.h>
#include <stdlib.h>
#include <pthread.h>
#define UP 1
#define DOWN 2
#define LEFT 3
#define RIGHT 4
struct Snake
{
    
    
	int row;
	int col;
	struct Snake *next;
};
struct Snake *head = NULL;
struct Snake *tail = NULL;
int key;
int dir;

void addNode()
{
    
    
	struct  Snake *new = (struct Snake*)malloc(sizeof(struct Snake));
	new->next = NULL;

	switch(dir)
	{
    
    
		case UP:
			new->row = head->row-1;
			new->col = head->col;
			break;
		case DOWN:
			new->row = head->row+1;
			new->col = head->col;
			break;
		case LEFT:
			new->row = head->row;
			new->col = head->col-1;
			break;
		case RIGHT:
			new->row = head->row;
			new->col = head->col+1;
			break;
			
	}

	new->next = head;
	head = new;
}

void initSnake()
{
    
    
	struct Snake *p;
	dir = LEFT;
	while(head != NULL)
	{
    
    
		p = head;
		head = head->next;
		free(p);
	}
	
	head = (struct Snake *)malloc(sizeof(struct Snake));
	head->row = 1;
	head->col = 19;
	head->next = NULL;
	tail = head;
	
	addNode();
	addNode();
}
int hasSnakeBody(int y, int x)
{
    
    
	struct Snake *p = head;
	while(p!=NULL)
	{
    
    
		if(y==p->row && x==p->col)
			return 1;
		p = p->next;
	}
	return 0;
}
void gamePic()
{
    
    
	int row = 0;
	int col = 0;
	move(0,0);
	for(row=0; row<20; row++)
	{
    
    
		if(row==0)
			for(col=0; col<20; col++)
				printw("--");
		printw("\n");
		for(col=0; col<=20; col++)
		{
    
    
			if(col==0 || col==20)
				printw("|");
			else if(hasSnakeBody(row,col))
				printw("[]");
			else
				printw("  ");
		}
		if(row==19)
		{
    
    
			printw("\n");
			for(col=0; col<20; col++)
				printw("--");
		}
	}
	printw("\n");
}

void deleteNode()
{
    
    
	struct Snake *p;
	p = head;
	while(p->next != tail)
		p = p->next;
	free(tail);
	tail = p;
	tail->next = NULL;
	
}

void moveSnake()
{
    
    
	addNode();
	deleteNode();
	if(head->row==0 || head->col==0 || head->row==20 || head->col==20)
		initSnake();
}

void* refreshScr()
{
    
    	
	while(1)
	{
    
    
		moveSnake();
		gamePic();
		refresh();
		usleep(100000);//=0.1s  (1000000μs = 1s)
	}
}

void* changeDir()
{
    
    
	while(1)
	{
    
    
		key = getch();
		switch(key)
		{
    
    
			case KEY_DOWN:
				dir = DOWN;
				break;
			case KEY_UP:
				dir  = UP;
				break;
			case KEY_LEFT:
				dir = LEFT;
				break;
			case KEY_RIGHT:
				dir = RIGHT;
				break;
		}
	}
}

int main(void)
{
    
    
	pthread_t t1;
	pthread_t t2;

	initscr();
	keypad(stdscr,1);
	
	initSnake();
	gamePic();
	
	pthread_create(&t1, NULL, refreshScr, NULL);
	pthread_create(&t2, NULL, changeDir, NULL);

	while(1);

	endwin();
	return 0;
}

Compile and run, press the arrow keys to control the movement of the snake body

If you find a mess like the picture below after compilation and running, you can refer to the last supplementary column to solve the problem.
Insert image description here

So far, there are still some problems regarding the movement of snake bodies:

  1. Snakes can turn around and walk away
  2. The top row of the map cannot be walked, there is an "invisible wall"

Prevent the snake from moving directly in the opposite direction

Change the values ​​ofUP and DOWN to their opposites, LEFT and RIGHT The value of is the opposite number

#define UP 1
#define DOWN -1
#define LEFT -2
#define RIGHT 2

When pressing the arrow keys to change the value of dir, you should make a judgment first.changeDir()Modify the code to the following:

void* changeDir()
{
    
    
    while(1)
    {
    
    
        key = getch();
        switch(key)
        {
    
    
            case KEY_DOWN:
                turn(DOWN);
                break;
            case KEY_UP:
                turn(UP);
                break;
            case KEY_LEFT:
                turn(LEFT);
                break;
            case KEY_RIGHT:
                turn(RIGHT);
                break;
        }
    }
}

Among them, turn() function is used to determine whether the global variable dir is allowed to be changed. turn()The function code is as follows:

void turn(int direction)
{
    
    
    if(abs(dir) != abs(direction))
        dir = direction;
}

abs()is to take the absolute value of the number in the brackets.

  • If the absolute value of the parameter passed in (that is, the direction key pressed on the keyboard is monitored) is not equal to the absolute value of the current dir, the value of dir can be changed to the value of the direction key pressed on the keyboard.

  • If the absolute value of the parameter passed in (that is, the direction key pressed by the keyboard is monitored) is equal to the absolute value of the current dir, it means that the value pressed by the keyboard is either the same as the direction in which the snake is currently moving, or the direction in which the snake is currently moving. If the current direction is opposite, the value of dir will not be changed.

Solve the problem of being unable to walk on the top row of the map:

moveSnake(), the if statement used to determine whether the snake head has hit the wall can be modified to the following:

if(head->row<0 || head->col==0 || head->row==20 || head->col==20)

Tadazeshohead->row==0Repair and reformhead->row<0

Reason: When drawing the bordergamePic(), the red box part in the picture below is drawn when row=0 (the dotted line above plus the two vertical bars on the left and right):
Insert image description here

So the row in the red box belongs to row=0, and the original judgment conditionhead->row==0 makes this row unable to walk

Add more food, and the snake will grow one square after eating it.

Add a structure variable food of type struct snake, and define a initFood() to initialize the position of the food

struct Snake food;
void initFood()
{
    
    
    static int x = 2;
    static int y = 2;
    food.row = x;
    food.col = y;
    x+=2;
    y+=2;
}

Here, the initial position of the food is set at (2,2), and the x and y coordinates of the food each increase by 2 after each meal.

Add a judgment when drawing the interface. If it is the coordinate of food, print "##". The code for drawing food in gamePic() is modified as follows:

for(col=0; col<=20; col++)
{
    
    
    if(col==0 || col==20)
        printw("|");
    else if(hasSnakeBody(row,col))
        printw("[]");
    else if(hasFood(row,col))
        printw("##");
    else
        printw("  ");
}

WherehasFood(row, col) is used to determine whether there is food at the current row and col. If it exists, it returns 1, if it does not exist, it returns 0. hasFood()The code is as follows:

int hasFood(int i, int j)
{
    
    
    if(foo.row==i && food.col==j)
        return 1;
    return 0;
}

initFood()Call ininitSnake() and place it in the front position

To realize that the snake's body becomes longer after eating food:

Judge the snake head inmoveSnake(). If the row value and col value of the current snake head node are equal to the row value and col value of the food, it means that the food has been eaten and the snake body has moved. When , there is no need to delete the last node; otherwise, the tail node is deleted. Just use hasFood() to determine whether there is food at the current head node position. moveSnake()Modify the code to the following:

void moveSnake()
{
    
    
    addNode();
    if(hasFood(head->row, head->col))
        initFood();
    else
        deleteNode();
    if(head->row<0 || head->col==0 || head->row==20 || head->col==20)
        initSnake();
}

At this point, the complete code is as follows:

#include <curses.h>
#include <stdlib.h>
#include <pthread.h>
#define UP 1
#define DOWN -1
#define LEFT -2
#define RIGHT 2
struct Snake
{
    
    
	int row;
	int col;
	struct Snake *next;
};
struct Snake *head = NULL;
struct Snake *tail = NULL;
int key;
int dir;
struct Snake food;
void initFood()
{
    
    
	static int x = 2;
	static int y = 2;
	food.row = x;
	food.col = y;
	x+=2;
	y+=2;
}
void addNode()
{
    
    
	struct  Snake *new = (struct Snake*)malloc(sizeof(struct Snake));
	new->next = NULL;
	switch(dir)
	{
    
    
		case UP:
			new->row = head->row-1;
			new->col = head->col;
			break;
		case DOWN:
			new->row = head->row+1;
			new->col = head->col;
			break;
		case LEFT:
			new->row = head->row;
			new->col = head->col-1;
			break;
		case RIGHT:
			new->row = head->row;
			new->col = head->col+1;
			break;			
	}
	new->next = head;
	head = new;
}

void initSnake()
{
    
    
	struct Snake *p;
	dir = LEFT;
	while(head != NULL)
	{
    
    
		p = head;
		head = head->next;
		free(p);
	}
	
	initFood();
	head = (struct Snake *)malloc(sizeof(struct Snake));
	head->row = 1;
	head->col = 19;
	head->next = NULL;
	tail = head;
	
	addNode();
	addNode();
}
int hasSnakeBody(int y, int x)
{
    
    
	struct Snake *p = head;
	while(p!=NULL)
	{
    
    
		if(y==p->row && x==p->col)
			return 1;
		p = p->next;
	}
	return 0;
}
int hasFood(int i, int j)
{
    
    
	if(food.row==i && food.col==j)
		return 1;
	return 0;
}
void gamePic()
{
    
    
	int row = 0;
	int col = 0;
	move(0,0);
	for(row=0; row<20; row++)
	{
    
    
		if(row==0)
			for(col=0; col<20; col++)
				printw("--");
		printw("\n");
		for(col=0; col<=20; col++)
		{
    
    
			if(col==0 || col==20)
				printw("|");
			else if(hasSnakeBody(row,col))
				printw("[]");
			else if(hasFood(row,col))
				printw("##");
			else
				printw("  ");
		}
		if(row==19)
		{
    
    
			printw("\n");
			for(col=0; col<20; col++)
				printw("--");
		}
	}
	printw("\n");
}
void deleteNode()
{
    
    
	struct Snake *p;
	p = head;
	while(p->next != tail)
		p = p->next;
	free(tail);
	tail = p;
	tail->next = NULL;
	
}
void moveSnake()
{
    
    
	addNode();
	if(hasFood(head->row, head->col))
		initFood();
	else
		deleteNode();

	if(head->row<0 || head->col==0 || head->row==20 || head->col==20)
		initSnake();
}
void* refreshScr()
{
    
    	
	while(1)
	{
    
    
		moveSnake();
		gamePic();
		refresh();
		usleep(100000);//=0.1s  (1000000μs = 1s)
	}
}
void turn(int direction)
{
    
    
	if(abs(dir) != abs(direction))
		dir = direction;
}
void* changeDir()
{
    
    
	while(1)
	{
    
    
		key = getch();
		switch(key)
		{
    
    
			case KEY_DOWN:
				turn(DOWN);
				break;
			case KEY_UP:
				turn(UP);
				break;
			case KEY_LEFT:
				turn(LEFT);
				break;
			case KEY_RIGHT:
				turn(RIGHT);
				break;
		}
	}
}
int main(void)
{
    
    
	pthread_t t1;
	pthread_t t2;

	initscr();
	keypad(stdscr,1);
	
	initSnake();
	gamePic();
	
	pthread_create(&t1, NULL, refreshScr, NULL);
	pthread_create(&t2, NULL, changeDir, NULL);

	while(1);

	endwin();
	return 0;
}

After running the compilation, when the snake eats food, it will grow one grid, and the food will appear at (2,2), (4,4), (6,6)... until it disappears (the coordinates exceed the boundary)

At this time, there will be one more problem: after the snake grows longer, it will not die if it bites itself.

Randomize where food appears

To make the coordinates of food random, userand() to generate a random number

The width of the map is 20. To get a number 0≤x<20, you can userand()%20

RepairinitFood()Yogo Shijoshi:

void initFood()
{
    
    
    int x = rand()%20;
    int y = rand()%20;
    food.row = y;
    food.col = x;
}

Compile and run to make food appear at random locations

But there will be another problem. After running it a few times, sometimes you will find that the food disappears and no food appears on the interface.

This is because according to the above rand()%20 writing method, the col value of food, that is, the x value, can be obtained 0.

Look at the code when drawing the interface:

for(row=0;row<20;row++)
	{
    
    
		if(row == 0)
			for(col=0;col<20;col++)
				printw("--");
		printw("\n");
		for(col=0;col<=20;col++)
			if(col==0 || col==20)
				printw("|");
			else if(hasSnakeBody(row,col))
				printw("[]");
			else if(hasFood(row,col))
				printw("##");
			else
				printw("  ");	
		if(row == 19)
		{
    
    
			printw("\n");
			for(col=0;col<20;col++)
				printw("--");
		}
	}

When drawing the left vertical linecol is equal to 0, and when drawing the right vertical linecol is equal to 20, so the left border may be covered food. Therefore, the value range of foodcol should be [1,19]

Generate the integer of [0,19]:a = rand()%20;
Generate the integer of [1,20]:a = 1 + rand()%20;
Generate the integer of [n, m]: n + rand()%(m-n+1);

Continue to modifyinitFood()the code to the following:

void initFood()
{
    
    
    int x = 1 + (rand()%19);
    int y = rand()%20;
    food.row = y;
    food.col = x;
}

Compile and run, and the food will appear normally.

If a snake bites you, you will die

Whether the snake bites you can be judged inmoveSnake(). moveSnake()The code is modified as follows:

void moveSnake()
{
    
    
    addNode();
    if(hasFood(head->row, head->col))
        initFood();
    else
        deleteNode();
    if(ifSnakeDie())
        initSnake();
}

Among them,ifSnakeDie() is used to determine whether the snake hits the boundary or bites itself. If it hits the boundary or bites itself, it returns 1. The code is as follows:

int ifSnakeDie()
{
    
    
    struct Snake *p;
    p = head->next;
    if(head->row<0 || head->col==0 || head->row==20 || head->col==20)//判断是否撞界
        return 1;
    while(p != NULL)//判断是否咬到自己
    {
    
    
        if(p->row == head->row && p->col == head->col)
            return 1;
        p = p->next;
    }
    return 0;
}

At this point, the simple snake game is completed.

Complete code

#include <curses.h>
#include <stdlib.h>
#include <pthread.h>
#define UP 1
#define DOWN -1
#define LEFT -2
#define RIGHT 2
struct Snake
{
    
    
	int row;
	int col;
	struct Snake *next;
};
struct Snake *head = NULL;
struct Snake *tail = NULL;
int key;
int dir;
struct Snake food;
void initFood()
{
    
    
	int x = 1 + (rand()%19);
	int y = rand()%20;
	food.row = y;
	food.col = x;
}
void addNode()
{
    
    
	struct  Snake *new = (struct Snake*)malloc(sizeof(struct Snake));
	new->next = NULL;

	switch(dir)
	{
    
    
		case UP:
			new->row = head->row-1;
			new->col = head->col;
			break;
		case DOWN:
			new->row = head->row+1;
			new->col = head->col;
			break;
		case LEFT:
			new->row = head->row;
			new->col = head->col-1;
			break;
		case RIGHT:
			new->row = head->row;
			new->col = head->col+1;
			break;
			
	}

	new->next = head;
	head = new;
}

void initSnake()
{
    
    
	struct Snake *p;
	dir = LEFT;
	while(head != NULL)
	{
    
    
		p = head;
		head = head->next;
		free(p);
	}
	
	initFood();
	head = (struct Snake *)malloc(sizeof(struct Snake));
	head->row = 1;
	head->col = 19;
	head->next = NULL;
	tail = head;
	
	addNode();
	addNode();
}
int hasSnakeBody(int y, int x)
{
    
    
	struct Snake *p = head;
	while(p!=NULL)
	{
    
    
		if(y==p->row && x==p->col)
			return 1;
		p = p->next;
	}
	return 0;
}
int hasFood(int i, int j)
{
    
    
	if(food.row==i && food.col==j)
		return 1;
	return 0;
}
void gamePic()
{
    
    
	int row = 0;
	int col = 0;
	move(0,0);
	for(row=0; row<20; row++)
	{
    
    
		if(row==0)
			for(col=0; col<20; col++)
				printw("--");
		printw("\n");
		for(col=0; col<=20; col++)
		{
    
    
			if(col==0 || col==20)
				printw("|");
			else if(hasSnakeBody(row,col))
				printw("[]");
			else if(hasFood(row,col))
				printw("##");
			else
				printw("  ");
		}
		if(row==19)
		{
    
    
			printw("\n");
			for(col=0; col<20; col++)
				printw("--");
		}
	}
	printw("\n");
}

void deleteNode()
{
    
    
	struct Snake *p;
	p = head;
	while(p->next != tail)
		p = p->next;
	free(tail);
	tail = p;
	tail->next = NULL;
	
}

int ifSnakeDie()
{
    
    
	struct Snake *p;
	p = head->next;
	if(head->row<0 || head->col==0 || head->row==20 || head->col==20)
		return 1;
	while(p != NULL)
	{
    
    
		if(p->row == head->row && p->col == head->col)
			return 1;
		p = p->next;
	}
	return 0;
}

void moveSnake()
{
    
    
	addNode();
	if(hasFood(head->row, head->col))
		initFood();
	else
		deleteNode();

	if(ifSnakeDie())
		initSnake();
}

void* refreshScr()
{
    
    	
	while(1)
	{
    
    
		moveSnake();
		gamePic();
		refresh();
		usleep(100000);//=0.1s  (1000000μs = 1s)
	}
}
void turn(int direction)
{
    
    
	if(abs(dir) != abs(direction))
		dir = direction;
}

void* changeDir()
{
    
    
	while(1)
	{
    
    
		key = getch();
		switch(key)
		{
    
    
			case KEY_DOWN:
				turn(DOWN);
				break;
			case KEY_UP:
				turn(UP);
				break;
			case KEY_LEFT:
				turn(LEFT);
				break;
			case KEY_RIGHT:
				turn(RIGHT);
				break;
		}
	}
}

int main(void)
{
    
    
	pthread_t t1;
	pthread_t t2;

	initscr();
	keypad(stdscr,1);
	
	initSnake();
	gamePic();
	
	pthread_create(&t1, NULL, refreshScr, NULL);
	pthread_create(&t2, NULL, changeDir, NULL);

	while(1);

	endwin();
	return 0;
}

Replenish

Sometimes after compiling and running, the code has no problem, but the interface will be a mess, such as the following picture:
Insert image description here

This is a pitfall of Ncurse. After ensuring that there are no problems with the code, you can compile and run it again. If it doesn't work, compile and run it again...

Or you can add in the line next to the statement in refreshScr(). It means not to print out some irrelevant information (such as function keys). moveSnake()noecho()noecho()

If it doesn't work after adding this, you can only try to compile it a few more times...

Guess you like

Origin blog.csdn.net/YuanApple/article/details/132572243