C language to achieve piano block games (low imitation crotch version)

Posted by rhasce on Thu, 20 Jan 2022 16:23:00 +0100

Statement: the code in this article is not a word count, and those irrelevant to the narrative content have been replaced by words. Please read it patiently

(that's right. I'm lazy. I just changed my homework report (actually I didn't change much) and sent it out.)

(although it's a small game, I don't know why it exceeds 50MB after compression 555)

First, go to the source code link (including eating methods)

Link: https://pan.baidu.com/s/1UDjxsmzEnmURTg8a9TuxPQ?pwd=abbb  
Extraction code: abbb

(oh, yes, the decompression password of the compressed package is my qq number. Please personally stamp me (* ´∀` *) for experience)

(and the total code is in the link, which is basically semi pseudo code)

(go to the Tuzhen building first, but no one will notice that I got a double strike line)

1, Development environment

  1. Basic software: vs2022 and easy_x plug-in

  2. Use existing libraries

#include<windows.h>
#include<stdio.h>
#include <stdlib.h>
#include<time.h>
#include <conio.h>
#include <graphics.h>
#include <time.h>
#include <math.h>

 

2, Basic principles

  1. Menu interface display: use < windows h> The system function in the library performs window format and display content operations (such as clearing "cls" and pausing "pause"), and the COORD structure is used to move the cursor

  2. Real time reading of input: using < coino h> Library_ getch() function to get the input key value_ kbhit() to determine whether there is a key value input

  3. Drawing of game interface: using < graphics h> Library related drawing operations, drawing (here mainly uses line and rectangle drawing) functions to form basic pictures and dynamic images

  4. Storage of game elements and variables: the core element of the game is the piano block, and the order of its generation and elimination is FIFO, which is in line with the characteristics of queue, so it is realized with this data structure; Game data is recorded by reading the local txt file, but the display may face the problem of insufficient one page interface, so the one-way linked list is used to store the data of each page

  5. Game dynamic display thinking: the picture of the game is also similar to the "frame by frame play" of video. It needs to constantly clear the screen and draw. There are a series of batch drawing functions in the library function of easy_x, which can realize double buffering and prevent the game picture from flickering

3, Requirement description

As a lightweight simple game, it has corresponding characteristics in the following aspects

  1. The interface design is simple and the function is clear

  2. In terms of interaction with users, try to achieve timely and accurate feedback, smooth and comfortable picture, etc

  3. In terms of game content and rules, the relevant settings and game playing methods are presented in a concise way, which can enable the experimenter to get started quickly

  4. In terms of game conditions, it should be as simple as possible, and the game can be opened and run without too many configurations

4, Achievement display

(1) Basic functions

  1. Interface display

    • Menu interface

      First use system("mode con cols=120 lines=40"); Set the command line window to 120 characters wide and 40 lines high

      In cooperation with gotoxy() function, move the cursor freely to the specified position for output, such as star marking

      //The star mark is printed. n indicates the line in which it is printed, and num indicates the total number of options
      void menu_select(int num, int n)
      {
          int i;
          for (i = 0; i < num; i++)
          {
              if (i == n)
              {
                  gotoxy(35, 18 + i * 5);
                  printf("-★-");
                  gotoxy(64, 18 + i * 5);
                  printf("-★-");
              }
              else
              {
                  gotoxy(35, 18 + i * 5);
                  printf("    ");
                  gotoxy(64, 18 + i * 5);
                  printf("    ");
              }
          }
      }

    • Game interface

      With initgraph(100 * key_num + 120, 650); At first, the width is affected by the number of strike keys, and the height is constant at 650; Close graph(); End, close the drawing (game) window

      To draw background and graphics, set first_ sth_ color(RGB()),set_ sth_ Style (), and then draw corresponding figures, such as straight line (left endpoint x, left endpoint y, right endpoint x, right endpoint y), rectangular fillrctangle (L, t, R, d)

  2. Piano block movement

    Each time the y value of blocks structure is increased, and it can be realized in combination with screen clearing and drawing

    while (tmp)
    {
        display_b(tmp);
        tmp->y += 3 * speed;
        tmp = tmp->next;
    }
  3. Hit determination and phonation

    When the external input is detected, the corresponding processing is given according to the relationship between the y of the blocks structure (i.e. the lower end of the piano block) and the strike area. It is regarded as miss outside the interval, and different scores are given according to its distance from the strike center of gravity within the interval; Phonation is a mode corresponding to the version of bgm, which is realized by a simple beep function Beep(fre,time). The hitting processing is to make the current node out of the queue (the specific structure application will be described in detail later)

  4. Setting of game properties

    Some resizable parameters are set to change the attributes of the game, so as to increase the diversity of the game

  5. Storage of game data

    It mainly uses the file reading function fopen(),fgets(),fscanf() of C language to read and write the file. It is determined by fscanf()== EOF at the end of reading

(2) Game mode

There are three game modes:

  1. With bgm version / without bgm original

The former mainly has an additional sound effect of hitting some specific notes, and the interface is the same

  1. Two way magic revision

Here, the game interface is mainly divided into two parts, moving down on the left and up on the right. The position of the strike line is symmetrical to ensure that only one of the two sides will hit the piano block each time

The specific interface can be viewed in the later running result display and the attached game experience video

(3) Core code / thought analysis

[1] Overall programming process

  1. Parametric setting is convenient for continuous debugging and modification

    For some quantities that are often used but do not need to change the value, they are defined as macros and placed at the top of the file to facilitate centralized modification, because the size of the window, the position and size of text output are very much in need of actual operation, and the output can be adjusted step by step, which provides great convenience for parametric setting

  2. Modular programming (multi file)

    (i) Content: the project contains three source files (main.cpp,menu.cpp,game.cpp), two header files (menu.h,game.h), that is, a main program and two modules (one is menu and the other is game)

    (ii) process:

    • Start from the menu interface display, by learning the articles on the network, in main C, first write the corresponding function, reference the corresponding library function, compile it, run it through, and then start the code transfer;

    • //main.c in the document
      #include <windows.h>
      //#include ....
      ​
      //Clear screen
      void clr(void)
      {
          system("cls");
      }
      ​
      //int main()....

    • Create / find the corresponding module you want to put in cpp and h file and supplement with reference to the following format

    • //menu.h
      /*
      Put the following contents into the header file (header files are generally not referenced here)
      1.Macro definition (with value) #define
      2.Structure declaration typedef struct;
      3.Constant definition (with value) const data_ type <x> = ...;
      4.Variable declaration ▲ is easy to be redefined by error reporting. If you want to use a variable that can be accessed by the whole project, it is recommended to use extern ▲
                extern data_type <x>;
      5.Function declaration: void fun(void);
      */
      #pragma once
      #define main_menu_choice 4
      typedef struct _page//Data per page
      {
          int n;//This indicates how many lines have been saved
          int data[9][7];
          char time[9][20];
          struct _page* last;
          struct _page* next;
      }p, * pp;
      extern pp head;//At this point, it has no value, but is declared
      const int choose[3][3] = { {1,2,3},{1,2,3},{4,5,6} };
      void clr(void);//Function declaration

    • //menu.cpp
      /*
      1.Header file reference #include required under this file
      (Special reminder, must be in non - main program The cpp file refers to a file with the same name h file)
      2.Variable definition (if initial value is required, it can be set here) data_ type <x> = value;
      3.Function definition void fun()
                 {
                  ...
                 }
      */
      #include "menu.h"
      //#include <stdio.h> ...
      ​
      //The screen is cleared and the function definition is moved here
      void clr(void)
      {
          system("cls");
      }

    • Finally, remember that the main program should also refer to its own definition h header file, in #include "head.h" format, is in double quotation marks, otherwise the reference may fail

    • Even, for some complex and highly related functions that need to be written out and debugged at one time, you can restart a new project, debug them separately, and transplant them after success

    • In short, for a more complex project with more functions, it should be divided and ruled, and then integrated after level by level debugging

  3. Game operation mode processing

    (i) Circularity: because the game needs to interact with the user in real time, monitor the input information at any time and give the output in time, the main body of the whole program is in the while(1) cycle, and most functional modules are also broken in the while(1) cycle and jump to other code blocks only under specific conditions. From this experience, the overall code has the following framework:

    //Main menu
    int main()
    {
        while(1)
        {
            Clear screen
            Adjust the screen display according to the input value and output the main menu
            while(1)
            {
                Monitor for external inputs
                if(have)
                    break;            
            }
            Get external input
            if(input == value_1)
            {
                Clear screen         
                Give corresponding display
                while(1)
                {
                    Clear screen
                    Get external input
                    if(input == ord_1)
                        Corresponding display..
                    else if(...)
                        break;
                }
            }
            else if(input == value_2)
                ....                            
        }           
        return 0;
    }

    The main part of the game is even more so

    //Game part
    void game()
    {
        Initialization interface, memory
        while(1)
        {
            while(The limit of strike area is not exceeded)
            {
                Move piano block
                Clear screen, display
                Check whether the external input is
                if(have)
                    break;
            }
            Get input
            if(Indicates pause)
            {
                while(1)
                    Exit in case of corresponding conditions
            }
            else if(Reach the strike area)  
                Judge and deal with whether it is hit or not
            else if(Beyond the strike area)
                Corresponding processing    
        } 
        Output the result of the end of the game
    }

    (ii) interface dynamics: in fact, it is combined with looping, because the screen will change due to the player's operation, which involves the repeatability of screen clearing and redrawing, but the drawing will always have priority. If you just clear the screen in the while loop and draw it one by one, it is easy to make the screen flash. Many solutions seen on the Internet are to use the double buffer mechanism and powerful easy_ The X library provides three powerful tools that can directly achieve this effect, namely

    void BeginBatchDraw();
    // End the batch drawing and execute the unfinished drawing task in the specified area
    void EndBatchDraw(
        int left,
        int top,
        int right,
        int bottom
    );
    // Perform unfinished drawing tasks in the specified area
    void FlushBatchDraw(
        int left,
        int top,
        int right,
        int bottom
    ); 

    There is no big difference between the latter two. The method of use is

    BeginBatchDraw();
    Draw operation
    FlushBatchDraw();

    In this way, the drawn content can be output on the screen at one time, regardless of order, so as to solve the flicker problem.

[2] Applied data structure knowledge

  1. Storage of piano block elements - > chained queue

    • (1) Background: since the game mode is to continuously generate new elements and then eliminate the hit elements, which is in line with the characteristics of FIFO, the queue structure is used to store blocks. The implementation process is as follows

    • (2) Process: first define the piano block structure, store its related information, and then define the queue

    • typedef struct _block
      {
      	int x;    //This is the coordinate of the lower left corner
      	int y;
      	int fre;  //For the sound box, save its frequency
      	int time; //For the sound box, save its sound duration
      	struct _block* next; //Using the chain queue, you don't have to worry about overflow
      }b, * pb;
      typedef struct _queue
      {
      	pb head;
      	pb tail;
      	int num;  //How many elements are stored in the current queue
      }que, * qq;

    • Then define the operation functions of entering and leaving the queue, including the change of pointer and the increase / decrease of the number of queue elements. In particular, it should be noted that free(p) should be released in time when leaving the queue

    • void in_queue(pb nb)
      {
      	q->tail->next->next = nb;
      	q->tail->next = nb;
      	q->num++;
      }
      //Out of the team operation, here is to disappear the box, can no longer be displayed, and remember to release the space in time
      void out_queue()
      {
      	pb tmp = q->head->next;
      	q->head->next = tmp->next;
      	free(tmp);
      	q->num--;
      }

    • Combined with the game background, set the judgment conditions, that is, when to join the team (when the number of elements in the queue is less than 6, in the bgm free mode, as long as one piano block is eliminated, it will leave the team immediately and add new elements; in the bgm mode, it is more troublesome, because the total number may exceed 6 at one time), When to get out of the queue (it is uniformly stipulated that as long as you hit in the hitting range, you will get out of the team. For the mode with bgm, it is also allowed to go beyond the hitting range at the lower end of the box to determine the team when miss). Here, take the simple mode without bgm as an example. At the same time, in order to facilitate reading, focus on the content of queue operation, omit some irrelevant functions and switch to some pseudo code

    • void init_block2(int n)
      {
          //First initialize the queue, the head and tail nodes and a node
      	q = (qq)malloc(sizeof(que));
          Initialize queue
      	pb nb;
      	nb = (pb)malloc(sizeof(b));
      	Initialization of queue head and tail nodes
      	q->head->next = q->tail->next = nb;
      	q->num = 1;
      	//Then initialize the remaining piano blocks
      	int i = 5;
      	while (i)
      	{
      		nb = (pb)malloc(sizeof(b));
      		nb->y = (4 - q->num) * 110;
      		nb->x = (rand() % n) * 100;
      		nb->next = NULL;
      		in_queue(nb);
      		i--;
      	}
      }
      //Successfully hit the box
      void hit_b23()
      {
      	out_queue();
      }
      //Continue to acquire new blocks. When there are blocks out of the team and remaining, keep 6 blocks. When the number of blocks is < = 6, continue to acquire blocks
      void get_block2(int n)
      {
      	pb nb = (pb)malloc(sizeof(b));
      	nb->y = q->tail->next->y - 110;
      	nb->x = (rand() % n) * 100;
      	nb->next = NULL;
      	in_queue(nb);
      }
      void game2(int n, int ac)
      {
      	//First, complete the initialization interface
      	init_block2(n);
      	pb tmp = q->head->next;
      	int now = 1;
      	while (tmp && now <= 6)
      	{
      		display_b(tmp);//Display the piano block on the interface
      		tmp = tmp->next;
      		now++;
      	}
          Set pause interface prompt
       	system("pause");
       	char in = 0;
       	int c, flag = 0;
       	//Start the official game
       	while (1)
       	{
       		Output prompt
       		if (The first node is empty)
       			break;
       		while (The first node does not exceed the attack area)
       		{
       			in = 0;
       			if (q->head->next->y >= 550 - ac * ac * ac_unit)
       				in = _kbhit();
       			if (in != 0)
       				break;
                  Clear the screen and start batch drawing
       			pb tmp = q->head->next;
       			Create screen
       			while (tmp)
       			{
       				display_b(tmp);//Show each piano block
       				tmp->y += 3 * speed;
       				tmp = tmp->next;
       			}
       			Output prompt
       			Batch drawing, display screen
       			Sleep(20);//The system pauses for 20ms to control the moving speed of the piano block
       		}
       		in = _getch();
       		if (Enter a space to pause)
       		{
       			while (1)
       			{
       				in = _getch();
       				if (Enter the space again, exit the pause and continue the game)
       					break;
       				else
       				{
       					End and exit the game and record the data
       				}
       			}
       		}
       		if (Beyond the strike area)
       		{//If the limit of strike area is exceeded, it is judged as miss
       			Record data, end and exit the game
       		}
       		else if (in && q->head->next->y >= 550 - ac * ac * ac_unit)
       		{//In the strike area, judge the strike correctness
       			c = q->head->next->x / 100;
       			if (in == key[c])
       			{
       				scoring
       				hit_b23();
       			}
       			else
       			{
       				int j = 0;
       				for (j = 0; j < n; j++)
       				{
       					if (j != c)
       					{
       						if (key[j] == in)
       						{
       							Record data and exit the game
       						}
       					}
       				}
       			}
       		}
       		if (q->num < 6)
       			get_block2(n);
       		else if (q->num == 0)
       			break;
       	}
       	End batch drawing
          Show game end information
          Record the game information of the Bureau
      }

  2. Storage of local data - > bidirectional linked list

    • (1) Usage background: players will play games many times, and the corresponding amount of recorded data will increase. When querying local game records, they will want to browse the information of any number of pages and any line, involving the needs of forward and backward access. Therefore, a two-way linked list is used to store the information of each page (after grouping the data into 9 lines / page)

      • (2) Process: define page information

  • typedef struct _page   //Data per page
       {
    	int n;             //This indicates how many lines have been saved
       	int data[9][7];    //There are up to 9 rows on a page, and each row has 7 data, scores, combos, etc
    	char time[9][20];  //This saves game time
    	struct _page* last;
    	struct _page* next;
    }p, * pp;

  • The basic operation of two-way linked list is to add a new node at the end of creating a new linked list (involving the judgment of whether to create a new page to continue saving data), and read the node information

  • //Add a new linked list from the tail
       void in_link(pp link, int i, int* list, char* tt)
    {
       	int j;
    	for (j = 0; j < 7; j++)
    		link->data[i][j] = list[j];
    	strcpy(link->time[i], tt);
    }
    
    //Read the txt file to form a linked list
    void store_data(void)
    {
    	rewind(data_fp);//Let the file be reread
              	int i, j;
    	pp tmp = head;
    	if (End of file not read)
    		Store the temporarily read value into the node
    	while (End of file unread)
    	{
    		Save temporary read value
    		if (i < 9)//That is, when the number of rows read is less than 9
    		{
    			The value is stored in the current page node
    			i++;
    		}
    		else
    		{
                //Create a new page node and build a front and back Association
    			pp new_page = (pp)malloc(sizeof(p));
    			new_page->n = i = 0;
    			in_link(new_page, i, info, ti);
    			new_page->last = tmp;
    			new_page->next = NULL;
    			tmp->next = new_page;
    			tmp = new_page;
    			new_page->n = i = 1;
    		}
    	}
    }
    
    //Read a page of information and display it in the window
    void read_data_new(pp pa)
    {
    	int i = 0, j;
         	Clear screen
        output
    	while (i < pa->n)
         	{
    		output pa The first i Row data
    		i++;
    	}
    }

  • When applied to the main program, some pseudo code is still used. Since pointers are involved, remember to free up space after use

  • else if (Select browse local game records)
         		{
    			Header pointer initialization
       			store_data();
    			read_data_new(tmp);		
    			while (1)
    			{
    				in = _getch();
    				if (in == 13)//Use double pointers to iterate and free up space one by one
    				{
    					tmp = head;
    					pp l = head->next;
    					while (l || tmp)
    					{
    						free(tmp);
    						tmp = l;
    						if(tmp)
    							l = tmp->next;
    					}
    					break;
       				}
    				else if (in == 'w')//Read the previous page
       				{
    					if (tmp->last)
    					{
    						tmp = tmp->last;
    						read_data_new(tmp);
    					}
    				}
    				else if (in == 's')//Read the next page
    				{
    					if (tmp->next)
    					{
    						tmp = tmp->next;
    						read_data_new(tmp);
    					}
    				}
    			}
    		}

(4) Functions not realized / problems to be solved

  1. About versions with bgm

    The original intention is to make a sound when hitting a specific piano block, but because the beep function itself occupies the CPU main process, the interface will have to be updated after the sound is made, so that the game process is stuck. You can try using threads H library functions perform process branching and multithreading operations, but due to configuration problems, the library was not successfully installed, and there is still room for improvement

  2. About input reading

    The game simply uses the method of reading input_ getch() function, but when the user inputs a large amount of content in a short time, the buffer characters will accumulate, affecting the subsequent timely experience of user input. At present, a better solution has not been found

5, Operation results

 

 

6, Summary

The first time I tried to play a game, I could feel the programming style different from the previous C language programming and problem-solving in the process of writing functions, and I could also use my learned data structure knowledge to solve some practical problems. I gained a lot. I also felt the difficulty of making software and the large amount of work. There are still many deficiencies in the interface and game experience, And the space for improvement. I hope I can learn more in the future and go farther and farther on the road of programming!

7, Reference articles

[1] How does C + + define multiple files to use global variables_ hugongda123's column - CSDN blog_ How to define multi file global variables

[2] Console cursor (I): hide cursor_ nocomment_84 blog - CSDN blog_ Hide console cursor

[3] Getch() function and usage_ YuJar's column - CSDN blog_ Function of getch()

[4] What does the function kbhit() do_ Baidu Knows

I would like to thank the UUS who helped the children with the internal test, as well as the excellent csdn bloggers (and the soft Editing Assistant 23333 next door. The whole Chinese chess board is very referential)

Topics: C