Stacking Success: Introduction to Game Development Using Tetris

You can find this code's C++ implementation in my GitHub:

Fueled by my passion for C++ and the urge to learn more, I've stepped into the vibrant world of video game development. What intrigues me the most is the diversity of problems we can tackle due to the virtual nature of video games. This field truly offers a playground for anyone looking to deepen their understanding of software development, since various approaches at different levels stand out more here than in other domains.

With a surge of enthusiasm, I took on my first game project, which, as you can discern, is Tetris!

And my aim is to bring you along on my journey 😀 and convey the approachability I experienced during this introduction.

Tetris

Coming soon.

Lets get started

In this tutorial, I'll be walking you through the process of creating an interactive application using Visual Studio in object-oriented programming. To facilitate the development, we'll use the Simple DirectMedia Layer (SDL), a library to give developers intimate, low-level access to elements such as audio, keyboard, mouse, joystick, and graphics. But what's development without robust testing? Hence, we'll adopt a debugging-oriented approach throughout. Dive in and let's begin this exciting journey!

I won't be talking about how to install SLD, but here is the video where I learned!

Implementation design

Designing our game is the foundational step. It sets the stage because all the programming will revolve around it. Given that Tetris is a widely-analyzed challenge, many solutions have been proposed over the years. One of the most straightforward methods to tackle it is by writing the code in-line, leading to a single file for our entire project.

We won't be taking that route. Instead, we'll prepare for an industry that's predominantly Object-Oriented. It's a complex programming subject; however, I believe this application does a good job of keeping it simple.

And, once you get the hang of it, you'll find there's a comprehensive methodology underlying Object-Oriented-Programming (OOP) that can truly make our life easier as programmers.

Steps

The following list outlines some of the most basic tasks a game can entail. Organized sequentially, it ensures that our progression through each task remains independent of subsequent ones. This structure guarantees that even if we pause at any stage, we'll have a somewhat complete exercise to reflect upon and showcase 🙂

  1. Set up your development
  2. Game Initialization.
  3. Basic Gameboard
  4. Tetromino implementation
  5. Game logic
  6. User input
  7. Game flow

There are also some steps that could lead to a more polished result, but I haven't explored them myself. Some examples:

  • Polishing
  • User interface
  • Testing iteration

Implementation

1. Set up your environment.

After we install SDL as seen here, we can confirm with the following snippet that we can use it.



	////////////////// Main.cpp /////////////////////
	#include <SDL.h>

	int main(int argc, char* argv[]) {
		// Initialize SDL
		if (SDL_Init(SDL_INIT_VIDEO) < 0) {
			printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError());
			return 1;
		}

		return 0;
	}

2. Game Initialization

SDL Usage

At this point, SDL functions should be completely accesible 🙂, but as SDL usage depends on other components, first we need to initialize it. This way, we make sure every subsystem (video, audio, events, etc.) is operationg properly. If anything turns out, you can handle errors gracefully instead of encountering unpredictable behavior later. Also, we set up the necessary ressources for the library to work properly. It's a crucial step in using the library correctly and efficiently.

Please note, we are also creating and initializing the window where we will see tangible proof of our advancements.



	////////////////// Main.cpp /////////////////////
	bool initializeSDL(SDL_Window* &window, SDL_Renderer* &renderer) {
		// Initialize SDL
		if (SDL_Init(SDL_INIT_VIDEO) < 0) {
			printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError());
			return false;
		}

		// Create window
		window = SDL_CreateWindow("Tetris",
		SDL_WINDOWPOS_UNDEFINED,
		SDL_WINDOWPOS_UNDEFINED,
		SCREEN_WIDTH, SCREEN_HEIGHT,
		SDL_WINDOW_SHOWN);

		if (!window) {
			printf("Window could not be created! SDL_Error: %s\n", SDL_GetError());
			SDL_Quit();
			return false;
		}

		// Initialize renderer
		renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
		if (!renderer) {
			printf("Renderer could not be created! SDL_Error: %s\n", SDL_GetError());
			SDL_DestroyWindow(window);
			SDL_Quit();
			return false;
		}

		//For opacity
		SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
		return true;
	}

				

To mirror this, cleaning up after using SDL is equally crucial.


	////////////////// Main.cpp /////////////////////

	bool cleanupSDL(SDL_Window*& window, SDL_Renderer*& renderer) {
		SDL_DestroyRenderer(renderer);
		SDL_DestroyWindow(window);
		SDL_Quit();

		return true;
	}

Game Class

Here, we introduce the concept of the "main loop" or "game loop." Essentially, it's a continuous loop responsible for calling the core mechanics of our Tetris game. Some comon mechanics are:

  • Event handling: This method is responsible for handling user input or other external events
  • Update: Updates the game state. This can include moving characters, checking for collisions, updating timers, etc.
  • Render phases: This method is responsible for drawing the game's content on the screen.

As I mentioned before, we will introduce this mechanichs as characteristics of an object "Game".



	////////////////// Game.h /////////////////////
	class Game {
	public:
		Game(SDL_Renderer* sdlRenderer); // This makes the renderer accesible to the game mechanics
		const bool isRunning() const;  //  The main loop continues as long as the game is "running".
		void handleEvents();
		void update();
		void render();

	private:
		SDL_Renderer* mSdlRenderer;
		bool mIsRunning;

		void renderWindow(); //specialized rendering function
	};

After defining our "Game"" class, we've implemented it's member functions and initialization. The code is quite extensive, so I encourage you to review the "Game.cpp" file. Please focus on the functions introduced so far and disregard the rest for now.

Then we can call our new code to manage correctly our game setup.


	////////////////// Main.cpp /////////////////////
	#include <SDL.h>
	#include "Game.h"

	int main(int argc, char* argv[]) {
		SDL_Window* window = nullptr;
		SDL_Renderer* renderer = nullptr;

		if (!initializeSDL(window, renderer)) {
			return 1;
		}

		//Initialize game renderer
		Game game(renderer);

		//Game loop
		while (game.isRunning()) {
			game.handleEvents();
			game.update();
			game.render();
		}

		//clean up and free resources.
		cleanupSDL(window, renderer);

		return 0;
	}

Now, we have some logic going on. So far, your window should look like this: Picture coming soon! TODO: Check if this code would actually not end uo in an infinite loop

3. Basic Gameboard

Coming soon!

4. Tetromino Implementation

Coming soon!

5. Game Logic

Coming soon!

6. User Input

Coming soon!

7. Game Flow

Coming soon!

Next Steps

Coming soon!

Aknowledgements

Coming soon!