Tetris (Russian: Тетрис)[a] is a multimedia franchise originating from a puzzle video game created by Soviet software engineer Alexey Pajitnov in 1984.
Video games with Tetris’ core mechanics have been published by several companies for multiple platforms, most prominently during a dispute over the appropriation of the rights in the late 1980s. After a significant period of publication by Nintendo, the rights reverted to Pajitnov in 1996, who co-founded the Tetris Company with Henk Rogers to manage licensing. The franchise has since expanded into film, television, books, and music singles.
In this article we will create Tetris with C++ and SFML adding score and game over .
Basic start files:
tetris.hpp
tetris.cpp
main.cpp
Makefile
resources/
tetris.hpp
#pragma once
#include <SFML/Graphics.hpp>
#include <memory>
class Tetris {
std::shared_ptr<sf::RenderWindow> window;
sf::Texture tiles;
std::shared_ptr<sf::Sprite> sprite;
protected:
void events();
void draw();
public:
Tetris();
void run();
};
tetris.cpp
#include "tetris.hpp"
Tetris::Tetris(){
window = std::make_shared<sf::RenderWindow>(
sf::VideoMode(360, 720),
"Tetris (remix)",
sf::Style::Titlebar | sf::Style::Close
);
window->setPosition(sf::Vector2i(100, 100));
tiles.loadFromFile("./resources/img/squares.png");
sprite = std::make_shared<sf::Sprite>();
sprite->setTexture(tiles);
}
void Tetris::events(){
auto e = std::make_shared<sf::Event>();
while( window->pollEvent(*e)){
if( e->type == sf::Event::Closed){
window->close();
}
}
}
void Tetris::draw(){
window->clear(sf::Color::Black);
window->draw(*sprite);
window->display();
}
void Tetris::run(){
while( window->isOpen() ){
events();
draw();
}
}
main.cpp
#include "tetris.hpp"
int main( int argc , char **argv ){
auto tetris = std::make_shared<Tetris>();
tetris->run();
return 0;
}
Makefile
TARGET=a.out
CXX=g++
DEBUG=-g
OPT=-O0
WARN=-Wall
SFML=-lsfml-graphics -lsfml-window -lsfml-system
CXXFLAGS=$(DEBUG) $(OPT) $(WARN) $(SFML)
LD=g++
OBJS= main.o tetris.o
all: $(OBJS)
$(LD) -o $(TARGET) $(OBJS) $(CXXFLAGS)
@rm *.o
@./$(TARGET)
main.o: main.cpp
$(CXX) -c $(CXXFLAGS) main.cpp -o main.o
tetris.o: tetris.cpp
$(CXX) -c $(CXXFLAGS) tetris.cpp -o tetris.o
Compile and run:
make
Add to
tetris.hpp
(private
and first of all)
static const std::uint32_t lines {20};
static const std::uint32_t cols {10};
static const std::uint32_t squares {4};
static const std::uint32_t shapes {7};
struct Coords {
std::uint32_t x, y;
} z[squares], k[squares];
std::vector<std::vector<std::uint32_t>> area;
std::vector<std::vector<std::uint32_t>> forms;
Add to
tetris.hpp
the member function:void moveToDown();
Shapes:
]
{1,3,5,7}, // I
{2,4,5,7}, // Z
{3,5,4,6}, // S
{3,5,4,7}, // T
{2,3,5,7}, // L
{3,5,7,6}, // J
{2,3,4,5}, // O
Add
tetris.cpp
to the Constructor:
area.resize(lines);
for (std::size_t i {}; i < area.size(); ++i) {
area[i].resize(cols);
}
forms = {
{1,3,5,7}, // I
{2,4,5,7}, // Z
{3,5,4,6}, // S
{3,5,4,7}, // T
{2,3,5,7}, // L
{3,5,7,6}, // J
{2,3,4,5}, // O
};
Add to
tetris.hpp
the member functionvoid Tetris::moveToDown()
totetris.cpp
:
void Tetris::moveToDown(){
std::uint32_t number = {3};
for (std::size_t i {}; i < squares; ++i) {
z[i].x = forms[number][i] % 2;
z[i].y = forms[number][i] / 2;
}
}
Add to
tetris.cpp
thevoid Tetris::draw()
member function:
for (std::size_t i {}; i < squares; ++i) {
sprite->setPosition(z[i].x * 36, z[i].y * 36);
window->draw(*sprite);
}
Add to
tetris.cpp
thevoid Tetris::run()
member function:
...
events();
moveToDown(); // ADD
...
Add to
tetris.hpp
:
int dirx;
bool rotate;
void changePosition();
void setRotate();
void resetValues();
Add
tetris.cpp
to the constructor:
dirx = {0};
rotate = {false};
Add
tetris.cpp
to theevents()
loop
if( e->type == sf::Event::KeyPressed ){
if( e->key.code == sf::Keyboard::Up ){
rotate = true;
}else if( e->key.code == sf::Keyboard::Left ){
--dirx;
}else if( e->key.code == sf::Keyboard::Right ){
++dirx;
}
}
Add
tetris.cpp
contents of the 3 created member functions:
void Tetris::changePosition(){
for (std::size_t i {}; i < squares; ++i) {
z[i].x += dirx;
}
}
void Tetris::setRotate(){
if( rotate ){
Coords coord = z[1];
for (std::size_t i {}; i < squares; ++i) {
int x = z[i].y - coord.y; // INVERTIDOS
int y = z[i].x - coord.x; // INVERTIDOS
z[i].x = coord.x - x;
z[i].y = coord.y + y; // AQUI É SOMA
}
}
}
void Tetris::resetValues(){
dirx = 0;
rotate = false;
}
Change the
run()
member function intotetris.cpp
and add the new created member functions in that order:
void Tetris::run(){
while( window->isOpen() ){
events();
changePosition();
setRotate();
moveToDown();
resetValues();
draw();
}
}
TO MOVE YOU NEED TO CHANGE moveToDown()
and add the condition if( z[0].x == 0 ){}
and put the LOOP inside
if( z[0].x == 0 ){ // ADD
for (std::size_t i {}; i < squares; ++i) {
z[i].x = forms[number][i] % 2;
z[i].y = forms[number][i] / 2;
}
}
COMPILE AND DISPLAY ROTATION AND SIDES MOVEMENT!
Add to
tetris.hpp
:
sf::Clock clock;
float timercount, delay;
Adicionar ao construtor:
timercount = {0.f};
delay = {0.2f};
Adicionar à
void Tetris::events()
antes deauto e = ...
:
float time = clock.getElapsedTime().asSeconds();
clock.restart();
timercount += time;
Adicionar à
void Tetris::moveToDown()
antes dostd::uint32_t number = {3}
:
if( timercount > delay ){
for (std::size_t i {}; i < squares; ++i) {
++z[i].y;
}
timercount = 0;
}
Add to builder:
timercount = {0.f};
delay = {0.2f};
Add to
void Tetris::events()
beforeauto and = ...
:
float time = clock.getElapsedTime().asSeconds();
clock.restart();
timercount += time;
Add to
void Tetris::moveToDown()
beforestd::uint32_t number = {3}
:
if( timercount > delay ){
for (std::size_t i {}; i < squares; ++i) {
++z[i].y;
}
timercount = 0;
}
Add to
tetris.hpp
:
bool maxLimit();
Add to
bool Tetris::maxLimit()
:
bool Tetris::maxLimit(){
for (std::size_t i {}; i < squares; ++i) {
if( z[i].x < 0 ||
z[i].x >= cols ||
z[i].y >= lines ||
area[ z[i].y][ z[i].x ] ){
return true;
}
}
return false;
}
Add the seeder to
main.cpp
:
std::srand( std::time( 0 ));
Change
changePosition()
to store the value of the moving shape(z[i]
) to fix it(k[i]
) and check withmaxLimit()
:
void Tetris::changePosition(){
for (std::size_t i {}; i < squares; ++i) {
k[i] = z[i];
z[i].x += dirx;
if(maxLimit()){
for (std::size_t i {}; i < squares; ++i) {
z[i] = k[i];
}
}
}
}
Copy the condition from
if( maxLimit() )...
and paste inside theif( rotate )...
, but outside thefor
loop invoid Tetris::setRotate()
if( maxLimit() ){
for (std::size_t i {}; i < squares; ++i) {
z[i] = k[i];
}
}
Add to
int color
tetris.hpp
and initialize it to1
:color = {1};
Same thing also in
moveToDown()
, but creating random pieces and changing the position of thenumber
line into theif( maxLimit() )
condition, REMOVE CONDITION:if( z[0 ].x == 0 ){ ...
:IMPORTANT: create the loop to set the color to a fixed position and make it random;
void Tetris::moveToDown(){
if( timercount > delay ){
for (std::size_t i {}; i < squares; ++i) {
k[i] = z[i];
z[i].y += 1;
}
if( maxLimit() ){
for (std::size_t i {}; i < squares; ++i) { // ADD
area[ k[i].y ][ k[i].x ] = color;
}
color = 1 + std::rand() % shapes;
std::uint32_t number = std::rand() % shapes;
for (std::size_t i {}; i < squares; ++i) {
z[i].x = forms[number][i] % 2;
z[i].y = forms[number][i] / 2;
}
}
timercount = 0;
}
}
Add loop in
void Tetris::draw()
:
for (std::size_t i {}; i < lines; ++i) {
for (std::size_t j {}; j < cols; ++j) {
if( area[i][j] != 0 ){
sprite->setPosition( j * 36, i * 36 );
window->draw( *sprite );
}
}
}
Add to
events()
outside the loop:
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down)){ // ADD
delay = 0.05f;
}
TO MAKE THE FIRST PIECE ALSO RANDOM, add to the constructor:
Copy from
moveToDown()
std::uint32_t number = std::rand() % shapes; // ADD
for (std::size_t i {}; i < squares; ++i) {
z[i].x = forms[number][i] % 2;
z[i].y = forms[number][i] / 2;
}
Add
sprite->setTextureRect(sf::IntRect(color * 36, 0, 36, 36));
to thesquares
loop indraw()
for (std::size_t i {}; i < squares; ++i) {
sprite->setTextureRect(sf::IntRect(color * 36, 0, 36, 36));
sprite->setPosition(z[i].x * 36, z[i].y * 36);
window->draw(*sprite);
}
Note that the fixed part will also change color, so to solve this, just add sprite->setTextureRect(sf::IntRect(area[i][j] * 36, 0, 36, 36));
(COPY the already existing and modify) to the lines cols
loop:
for (std::size_t i {}; i < lines; ++i) {
for (std::size_t j {}; j < cols; ++j) {
if( area[i][j] == 0 ){
continue;
}
sprite->setTextureRect(sf::IntRect(area[i][j] * 36, 0, 36, 36));
sprite->setPosition( j * 36, i * 36 );
window->draw( *sprite );
}
}
Create a new member function in
tetris.hpp
also create score and add as zero to the constructor:
void setScore();
Add to
tetris.cpp
the execution ofsetScore()
:
void Tetris::setScore(){
int match = {lines - 1};
for (std::size_t i = match; i >= 1; --i) {
std::uint32_t sum {};
for (std::size_t j {}; j < cols; ++j) {
if( area[i][j] ){
++sum;
}
area[match][j] = area[i][j];
}
if( sum < cols ){
--match;
}else{
std::cout << "Score: " << ++score << '\n';
}
}
}
GIMP → 360x720 → Filters → Render → Textures → Grid → 36 | 1 | COLOR |
sf::Texture tiles, bg;
and std::shared_ptr<sf::Sprite> sprite, background;
bg.loadFromFile("./resources/img/background.png");
background = std::make_shared<sf::Sprite>();
background->setTexture(bg);
draw()
→ window->draw(*background);
Add to
tetris.hpp
sf::Font font;
sf::Text txtScore, txtGameOver;
bool rotate, gameover;
Add to
tetris.cpp
score = {0};
gameover = {false};
font.loadFromFile("./font.ttf");
txtScore.setFont(font);
txtScore.setPosition(100.f, 10.f);
txtScore.setString( "SCORE: " + std::to_string( score ));
txtScore.setStyle(sf::Text::Bold);
txtScore.setCharacterSize(30);
txtScore.setOutlineThickness(3);
txtGameOver.setFont(font);
txtGameOver.setPosition( 30.f , 330.f);
txtGameOver.setString("GAME OVER");
txtGameOver.setStyle(sf::Text::Bold);
txtGameOver.setCharacterSize(50);
txtGameOver.setOutlineThickness(3);
Tetris::draw()
:
window->draw( txtScore );
if( gameover ){
window->draw( txtGameOver );
}
Tetris::run()
if( !gameover ){
changePosition();
setRotate();
moveToDown();
setScore();
resetValues();
}
Tetris::setScore
:
void Tetris::setScore(){
int match = {lines - 1};
for (std::size_t i = match; i >= 1; --i) {
std::uint32_t sum {};
for (std::size_t j {}; j < cols; ++j) {
//if( area[i][j] != 0 ){
if( area[i][j] ){
if( i == 1 ){
gameover = true;
}
++sum;
}
area[match][j] = area[i][j]; // FIXA AO CHAO
}
if( sum < cols ){
--match;
}else{
txtScore.setString( "SCORE: " + std::to_string( ++score ));
}
}
}
The video is in Portuguese, but you can follow the steps even if you don’t understand the language. You can also enable subtitles and use Youtube’s automatic translation.