Tetris in C++ with the SFML2 library

Introduction

This page holds a complete Tetris game in C++ with the SFML 2 library, written in one source file. The whole game is a single program: a class describes one falling tetromino, and the main loop draws the field, moves the piece down and handles the keyboard.

The playing field is an array of 15 by 25 integers, where zero marks an empty cell and any other value is the color of a filled block. Seven tetromino shapes, I, S, Z, T, L, J and O, are stored as four block positions each. A piece moves down, left and right and rotates around its second block; a move that would push it off the field or into an occupied cell is cancelled, and a piece that can no longer move down is fixed in the field.

A clock drives the fall: the piece steps down after 0.3 seconds, and the delay shrinks to 0.05 seconds while the down arrow is held. Each fully filled line is cleared by shifting the rows above it down and adds one point to the score drawn above the field. The right and left arrows move the piece sideways, the up arrow rotates it and the down arrow speeds up the fall.

Code

#include <SFML/Graphics.hpp> #include <iostream> #include <time.h> #include <string> const int field_height = 25; // field height const int field_width = 15; // field width int field[field_height][field_width] { }; // the playing field // Array of tetromino shapes int figures[7][4] = { 1,3,5,7, // I 2,4,5,7, // S 3,5,4,6, // Z 3,5,4,7, // T 2,3,5,7, // L 3,5,7,6, // J 2,3,4,5, // O }; int checkLine(int& userPoints) { //----LINE CHECK----// int k = field_height - 1; // row k is the lowest one for (int i = field_height - 1; i > 0; i--) // i runs through every row from bottom to top. // at the start k and i are equal { int count = 0; for (int j = 0; j < field_width; j++) { if (field[i][j]) { count++; } field[k][j] = field[i][j]; } if (count < field_width) { k--; } else { userPoints += 1; } // k decreases only if the line is not completely filled // if the line is completely filled, the next i // already corresponds to a higher row // while k is still on the full row // row k will be overwritten by the next row i } return userPoints; } class Tetramino { public: int color { }; int type { }; int blockPositions[4] { }; struct Coordinates { int x; int y; }; Coordinates coordinates[4]{ }; int figures[7][4] = { 1,3,5,7, // I 2,4,5,7, // S 3,5,4,6, // Z 3,5,4,7, // T 2,3,5,7, // L 3,5,7,6, // J 2,3,4,5, // O }; void create() { std::cout << "new tetramino created" << std::endl; color = 1 + rand() % 7; type = rand() % 7; for (int i = 0; i < 4; i++) { blockPositions[i] = figures[type][i]; } for (int i = 0; i < 4; i++) { coordinates[i].x = blockPositions[i] % 2 + 7; // shift the starting position closer to the center with + 7 coordinates[i].y = blockPositions[i] / 2; } }; void draw(sf::RenderWindow& thatWindow, sf::Sprite& thatSprite, int marginLeft, int marginTop) { // Draw the tetromino from the Tetramino class for (int i = 0; i < 4; i++) { // Color the tetromino thatSprite.setTextureRect(sf::IntRect(color * 18, 0, 18, 18)); // Set the position of every block of the tetromino thatSprite.setPosition(coordinates[i].x * 18, coordinates[i].y * 18); thatSprite.move(marginLeft, marginTop); // offset // Drawing the sprite thatWindow.draw(thatSprite); } }; bool check() { for (int i = 0; i < 4; i++) { if (coordinates[i].x < 0 || coordinates[i].x >= field_width || coordinates[i].y >= field_height) { std::cout << std::endl << "field X" << coordinates[i].x << "Y " << coordinates[i].y << "is out of board" << std::endl; return false; } else if (field[coordinates[i].y][coordinates[i].x]) { std::cout << std::endl << "field X" << coordinates[i].x << "Y " << coordinates[i].y << "is occupied" << std::endl; return false; } } return true; } void moveDown(sf::RenderWindow& thatWindow) { int x[4]{}; int y[4]{}; for (int i = 0; i < 4; i++) { y[i] = coordinates[i].y; x[i] = coordinates[i].x; } for (int i = 0; i < 4; i++) { coordinates[i].y += 1; } if (check() == false) { std::cout << "stop here" << std::endl; for (int i = 0; i < 4; i++) { coordinates[i].y = y[i]; std::cout << "coloring the field" << std::endl; field[y[i]][x[i]] = color; } create(); if (check() == false) { std::cout << "GAME OVER " << std::endl; thatWindow.close(); } } else { ; } } void moveHoriz(sf::RenderWindow& thatWindow, int direction) { int x[4]{}; int y[4]{}; for (int i = 0; i < 4; i++) { y[i] = coordinates[i].y; x[i] = coordinates[i].x; } for (int i = 0; i < 4; i++) { coordinates[i].x += direction; } if (check() == false) { for (int i = 0; i < 4; i++) { coordinates[i].x = x[i]; } } else { ; } } void rotate() { int x[4]{}; int y[4]{}; for (int i = 0; i < 4; i++) { y[i] = coordinates[i].y; x[i] = coordinates[i].x; } int originX = coordinates[1].x; // set the X of the rotation center int originY = coordinates[1].y; // set the Y of the rotation center for (int i = 0; i < 4; i++) { int dy = coordinates[i].y - originY; // y - y0 int dx = coordinates[i].x - originX; // x - x0 coordinates[i].x = originX - dy; // x_2 = x_0 - dy coordinates[i].y = originY + dx; // y_2 = y_0 + dx } if (check() == false) { for (int i = 0; i < 4; i++) { coordinates[i].x = x[i]; } } else { ; } } }; int main() { srand(time(0)); int window_width{ 640 }; int window_height{ 960 }; int userPoints{ 0 }; Tetramino tetramino; tetramino.create(); sf::RenderWindow window(sf::VideoMode(window_width, window_height), "HH Tetris!"); // Creating and loading the texture sf::Texture texture, texture_background, texture_frame; texture.loadFromFile("C:\Users\andrei\source\repos\tetris\images\corona_tiles.png"); texture_background.loadFromFile("C:\Users\andrei\source\repos\tetris\images\background.png"); texture_frame.loadFromFile("C:\Users\andrei\source\repos\tetris\images\frame.png"); // Creating the sprite sf::Sprite sprite(texture), sprite_background(texture_background), sprite_frame(texture_frame); // Cut a single square of 18x18 pixels out of the sprite sprite.setTextureRect(sf::IntRect(0, 0, 18, 18)); // Variables for the timer and the delay float timer = 0; float delay = 0.3; // Clock (timer) sf::Clock clock; // Main application loop: runs while the window is open while (window.isOpen()) { // Get the time elapsed since the last restart and convert it to seconds float time = clock.getElapsedTime().asSeconds(); clock.restart(); timer += time; // Handle events in the loop sf::Event event; while (window.pollEvent(event)) { // Did the user click the cross and want to close the window? if (event.type == sf::Event::Closed) // then close it window.close(); // Was a key pressed on the keyboard? if (event.type == sf::Event::KeyPressed) { // Is this key the left arrow? if (event.key.code == sf::Keyboard::Left) { tetramino.moveHoriz(window, -1); } // Or the right arrow? else if (event.key.code == sf::Keyboard::Right) { tetramino.moveHoriz(window, 1); } // Or maybe the up arrow? else if (event.key.code == sf::Keyboard::Up) { tetramino.rotate(); } } // Is the "Down" key held? Speed up the falling tetromino if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) delay = 0.05; } // Move the tetromino down (a timer tick) if (timer > delay) { tetramino.moveDown(window); timer = 0; } checkLine(userPoints); delay = 0.3; //----DRAWING----// // Set the background color to white window.clear(sf::Color::White); // Drawing the background //window.draw(sprite_background); int tetrisFeildTop{ 150 }; int tetrisFeildLeft{ 185 }; for (int varHeight = 0; varHeight < field_height; varHeight++) { for (int j = 0; j < field_width; j++) { if (field[varHeight][j] == 0) { //std::cout << "empty field" << std::endl; sprite.setTextureRect(sf::IntRect(8 * 18, 0, 18, 18)); sprite.setPosition(tetrisFeildLeft + j * 18, tetrisFeildTop + varHeight * 18); // Drawing the sprite window.draw(sprite); } else { sprite.setTextureRect(sf::IntRect(field[varHeight][j] * 18, 0, 18, 18)); // the value of every field[varHeight][j] cell is a color, we use it // to pick a colored square from the sprite } sprite.setPosition(tetrisFeildLeft + j * 18, tetrisFeildTop + varHeight * 18); //sprite.move(28, 31); // offset window.draw(sprite); } } // Draw the tetromino with the draw method of the Tetramino class tetramino.draw(window, sprite, tetrisFeildLeft, tetrisFeildTop); // Drawing the frame //window.draw(sprite_frame); sf::Font font; if (!font.loadFromFile("C:\Development\Fonts\Roboto\Roboto-Regular.ttf")) { // error... } sf::Text text; // select the font text.setFont(font); // font is a sf::Font std::string userPointsString = "Score: " + std::to_string(userPoints); // set the string to display text.setString(userPointsString); // set the character size text.setCharacterSize(24); // in pixels, not points! // set the color text.setFillColor(sf::Color::Blue); // set the text style text.setStyle(sf::Text::Bold | sf::Text::Underlined); text.setPosition(190.f, 10.f); window.draw(text); // Drawing the window window.display(); } return 0; }

Article author: Andrei Olegovich