The Ice Blog
Nov 19, 2021

Snake Game in C++

Snake game in C++If you’re reading this you’ve likely heard of ‘Snake’, and perhaps played it. In this tutorial I will show how to make a simple snake game with C++. There will be detailed explanations in case you want to implement the project in a different language (which I suggest for non-C++ coders). Before we begin, the completed project is available here.Necessary Game InformationThe snake game has many variations and implementations, but they all stem from a basic idea: there is a form of snake (1), that is capable of eating a form of fruit (2), gets a length extension when said fruit is eaten (3), and a score is incremented based on fruit eaten (4).In this tutorial we will implement these four features, and I will leave some details on how to add extra features.Platform InformationThis project was built for a windows terminal host, and as such contains some windows specific header files. If you are running a Linux distribution and would want to instead build for Linux I will indicate all windows specific headers so you can change those in your implementation.RequirementsA text editor for C++, VS code works well. A C++ compiler, mingw.Note: Knowledge of C++ is not required for this project but at the end of the project you’re bound to have at least a basic understanding of C++ 😉.With your editor ready and compiler installed, let’s get into it!Libraries:iostream – for essential I/O (input and output) operations. Input for keyboard strokes and output to display on the terminal.random – to generate random numbers (necessary for fruit placement).cstdlib – standard library functions from C.conio – useful for performing console input and output.string – easy use and manipulation of text strings.windows – contains functions for the Windows API. Windows specific.time – functions to get and manipulate date and time information.ctype – functions for handling characters.fstream – contains istream and ostream for read and write operations respectively with files.namespace std – specifies the scope of the program, allowing code to use all entities under the std namespace without having to prefix std::.Global variablesGlobal variables are variables declared outside the scope of all functions and hence, are available to all functions to access. They hold their values throughout the lifetime of the program. Anything containing x and y here refers to location coordinates.width – this variable defines the size of the playing area. Can be altered to preference.height – specifies the height of the playing area. Can also be altered.x and y – these are location coordinates for the head of the snake.fx and fy – f refers to ‘fruit’. These variables specify the location of the fruit.score – You guessed it, the player's score, starts from zero.nt n implies number, hence number of t (tails). This indicates how many tail elements or pieces the snake has.tx[100] and ty[100] – these are arrays for storing the location of each individual tail element. In this case they are given a size of 100 but this can be increased or reduced. Choose a value based on the size of your playing field. 100 here implies that the snake is not expected at any point within the game to have more than 100 tail elements. If it does, the game will crash so ensure the value is large enough.diff – this represents the game difficulty.names[100] and scores[100] – for storing the names and respective scores of players; a leaderboard.Direction – an enumeration containing all possible directions for the snake: up, down, left and right.dir and ndir – current direction and next/new direction.gameover – a Boolean indicating whether the game is over or not.For simplicity and modularity of the program, all major parts of the game are divided into functions accessible by function calls. The following are the functions:MainMenu – This function contains code for displaying the main menu.Help – To display the help menu.Difficulty – Menu for changing game difficulty.SpawnF – spawns a fruit at a random location.Setup – Initializes a new game.DrawGame – Perhaps the most important game function. This function draws the game borders, the fruit, and the snake itself.CheckInput – checks the keyboard input for the new direction of the snake.Update – Another vital game function. Calculates the location of essentially everything after any changes.Play – Simplifies the calling of essential game functions.SaveScore – Saves a user’s score after a game.GetScores – Gets previously saved scores.DisplayScores – Shows saved scores.main – the main function in a C++ program: where everything happens.DrawGameThis function draws the actual game. Drawing is done based on the terminal window text spaces, every space can be accessed using coordinates, starting from the top left. The enclosing top border is drawn first, as it is the first part of the game that can be seen starting from the top. After drawing the top border, we can now begin drawing the game elements based on their location.Now we’re going to run two loops, first one represents every row in the playing field, second represents every text column of each row.If we’re in the first column of each row, we want to print a pipe to represent the border, same if we’re in the last column. If we’re not in first or last, then we want to perform some checks:
  • Check if the coordinates of the head align to this space;
  • If the coordinates of the fruit align to this space;
  • Or if there is a tail element in this space;
  • Performing these checks involve comparing every row-column coordinate with the known coordinates of game elements. With the snake head, we compare against x and y. For the fruit, we compare against fx and fy. With the tail elements it’s a little different here: the tail elements are stored in an array hence a single comparison would not be sufficient, we would have to compare every array element with the current space. Before we begin looping through the tail array, we set a space Boolean to true. This Boolean tells us if there should be a blank space or not (if nothing goes in the current place, then it gets filled by a blank space 🎵).After initializing space to true, we can now begin looping through the tail array. If the current coordinate matches a tail element coordinate then we place an ‘o’. But that’s not all, we also have to set space to false to indicate that something is here and hence no need for blank space. Now we check if a space is needed (if space is true), and if it is, we draw a blank space. Now we can draw the end border, end the line, and move on to the next line/row.As it is a loop, on the next row, we do all the above again (that’s why we used a loop, for repeated logic based on a condition).After all this, we still need to draw one more thing: the bottom border. Now we can print the current score and pretty much any other information we want for debugging purposes. If you feel something isn’t working as should be, you can check its values by printing them out to get a better idea of what’s going on.CheckInputThis is a very straightforward function, it checks the keyboard input to determine which way the snake should go next or if the player wants to quit the game. Input collection is performed by the _kbhit and _getch functions. These are buffered functions, in other words, any keys pressed while the process thread is asleep will still be recorded in the input buffer. These functions are provided to us by the conio (Console I/O) library.The first step is to check if a key has actually been hit using _kbhit. If that returns true it means a key has been hit, and we can move to the next step.Second step involves collecting and checking if a key meaningful to us has been hit, and a switch construct is used here. A switch is similar to if/else but is the preferred option if you’re checking the different possible states of a variable, and here we’re checking against the possible states (or cases) of the collected keystroke.In summary, presence of a key in the input buffer is detected using _kbhit, detected key is collected using _getch (and converted to uppercase: toupper), and collected key is checked for meaning. If it’s an ‘A’, that means the snake’s next direction should be LEFT, ‘S’ for DOWN, ‘D’ for RIGHT and ‘W’ for UP. If the entered key is an ‘X’, then we quit the game by setting gameover to true.UpdateIn this function we see the introduction of some more variables: curX, curY, prevX and prevY. curX and curY store the current coordinates of the first tail element, whereas prevX and prevY store previous coordinates.
  • The first step of the update process is to assign the location of the first tail element to the snake’s head location. This follows the logic that the only part of the snake which is actually controlled is the head, and the tail simply follows in the same path the head took. By said logic, the location of subsequent tail elements will be set to the location of the preceding tail element. With this understanding in place, we can continue.
  • Next, we want to loop through the tail; every element except the first, since the location of the first element was already changed in step 1.
  • For every tail element, its location is stored in the prevX and prevY variables, and then set to curX and curY.
  • Since we’re in a loop, the current location in one iteration of the loop becomes the previous location in the next iteration, hence, curX and curY are set to prevX and prevY.
  • Now we update the direction of the snake’s head. This is done by comparing the new direction ndir with its current direction dir to determine if a direction change is needed. I suggest you take some time to think about this portion of code, you might realize something interesting about it (If it isn’t coming to you, try removing it and see how the snake reacts).
  • Using a switch on the new direction, we change the location of the head by incrementing and decrementing its location coordinates based on the new direction.
  • In this snake game, when the border is hit, the game ends (in others the snake simply pops out at the opposite side). To implement this, we have to compare the snake’s location with the borders. But we do not need to check the entire snake, only the head is required as it is impossible for a tail piece to pass where the head hasn’t. If a border has been hit then the game is over and we return to the main menu.
  • Here we implement the logic for eating fruit. This is done in four simple steps: we check if the snake is at the location of the fruit, then we increase the score, increase the number of tail elements, and spawn a new fruit (fx is passed as a parameter in the SpawnF function, don’t worry it’ll be explained later).
  • Finally, we set the current direction of the snake to the new direction since from steps 4 to 6 we changed the direction to the new direction.
  • PlayThis function doesn’t implement any major logic but simply calls functions which do, to make the program easier to read and prevent code repetition. First it draws the game, then it looks for input, then it updates game elements based on the input, then it sleeps the thread for some time given as diff (the value of diff is changed using the Difficulty function, sleeping for shorter makes the game appear faster whereas sleeping for longer makes it appear slower).int mainThe main function in a C or C++ program is the function which is called when the program is run. In other words, even if there are a hundred functions in your code, unless they are called within the scope of the main function, they will have no effect when the code is run. Thus, all the code we’ve written so far will not actually be of any effect lol.New construct: do ... while. This simply says, “do …a…, as long as …b… is true.” In this case, as long as the ‘close’ variable is false, we want to do some things, and these are as follows:
  • Show the main menu. When a game is launched you expect to see a main menu, so we display it immediately.
  • Right after, we have a familiar block of code. If you’re wondering why we didn’t just make this a function, that’s because you can do it on your own, just look at the CheckInput function, this works in a similar way but you’ll just have to make a few changes in your new function.
  • If the user decides to quit the game, we give a final message and return 0. The main function has a return type of int meaning, the function has to return an integer (a whole number from negative infinity to positive infinity). If the game exits properly we return a zero, this is a programming convention, it doesn’t have to be zero.At this point you can go ahead to compile and run the game.Leave any comments or questions down below. Thanks for reading!
    Max Otuteye

    Max Otuteye

    Mobile, Web and Desktop app developer.

    Leave A Comment

    Related Posts

    Categories