Win/Loss Logic for Snake

Add game-ending logic to our Snake game with custom events, state management, and visual cues for player feedback.
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Get Started for Free
Digital art showing a retro snake game
Ryan McCombe
Ryan McCombe
Posted

Now that we have a working Snake game with basic movement and apple collection, it's time to add win-and-loss conditions to create a complete gaming experience.

In this lesson, we'll implement collision detection for game over states, add a winning condition when the snake reaches maximum length, and provide visual feedback for both outcomes.

GAME_WON and GAME_LOST User Events

Let’s begin by creating new event types that we can dispatch to notify our components that a game has been won or lost:

// GameConfig.h
// ...

namespace UserEvents{
  inline Uint32 GAME_WON =
    SDL_RegisterEvents(1);
  inline Uint32 GAME_LOST =
    SDL_RegisterEvents(1);
}

// ...

Winning the Game

The only way to win in Snake is to get our snake to some target length. We’ll create a variable in our GameConfig.h to set what this should be. We’re free to set it however we like, and we can derive the value based on the grid size if we want:

// GameConfig.h
// ...

namespace Config{
  inline constexpr int MAX_LENGTH{
    GRID_COLUMNS * GRID_ROWS};
  // ...
}
// ...

Currently, the length of our snake is managed within the GameState class, where it is incremented every time an APPLE_EATEN event is encountered. Let’s expand that logic to dispatch a GAME_WON event if our snake’s length reaches the maximum length:

// GameState.h
// ...

class GameState {
public:
  // ...
  void HandleEvent(SDL_Event& E) {
    using namespace UserEvents;
    using namespace Config;
    if (E.type == SDL_KEYDOWN) {
      HandleKeyEvent(E.key);
    } else if (E.type == APPLE_EATEN) {
      ++Snake.Length;
      if (Snake.Length == MAX_LENGTH) {
        SDL_Event Event{GAME_WON};
        SDL_PushEvent(&Event);
      }
    } else if (E.type == RESTART_GAME) {
      RestartGame();
    }
  }
  // ...
};

Losing the Game

There are two failure conditions in our Snake game:

  1. Boundary Collision: When the snake hits the edge of the grid
  2. Self-Collision: When the snake runs into its own body

Let's implement both of these conditions.

Implementing Boundary Collision

We'll add boundary collision detection in the UpdateSnake() method of our GameState class. After calculating the new head position, we'll check if it lies outside the valid grid coordinates.

If it does, we’ll dispatch a GAME_LOST event, otherwise, we’ll dispatch an ADVANCE event as before:

// GameState.h
// ...

class GameState {
  // ...
private:
  void UpdateSnake() {
    Snake.Direction = NextDirection;
    switch (NextDirection) {
      case Up:
        Snake.HeadRow--;
        break;
      case Down:
        Snake.HeadRow++;
        break;
      case Left:
        Snake.HeadCol--;
        break;
      case Right:
        Snake.HeadCol++;
        break;
    }

    if (
      Snake.HeadRow < 0 ||
      Snake.HeadRow >= Config::GRID_ROWS ||
      Snake.HeadCol < 0 ||
      Snake.HeadCol >= Config::GRID_COLUMNS
    ) {
      SDL_Event Event{UserEvents::GAME_LOST};
      SDL_PushEvent(&Event);
    } else {
      // If we're not going outside the bounds
      // of the grid, advance as before
      SDL_Event Event{UserEvents::ADVANCE};
      Event.user.data1 = &Snake;
      SDL_PushEvent(&Event);
    }
  }
  // ...
};

Implementing Self Collision

For detecting when the snake collides with itself, we'll use our Cell class. Within the Cell::Advance() method, we'll check if the snake is moving into a cell that already contains a snake segment. If it is, we’ll dispatch a GAME_LOST event:

// Cell.h
// ...

class Cell {
  // ...
private:
  void Advance(SDL_UserEvent& E) {
    SnakeData* Data{
      static_cast<SnakeData*>(E.data1)};

    bool isThisCell{
      Data->HeadRow == Row &&
      Data->HeadCol == Column
    };

    if (isThisCell) {
      if (CellState == Snake) {
        SDL_Event Event{UserEvents::GAME_LOST};
        SDL_PushEvent(&Event);
        return;
      }
      if (CellState == Apple) {
        SDL_Event Event{
          UserEvents::APPLE_EATEN};
        SDL_PushEvent(&Event);
      }
      CellState = Snake;
      SnakeDuration = Data->Length;
    } else if (CellState == Snake) {
      SnakeDuration--;
      if (SnakeDuration <= 0) {
        CellState = Empty;
      }
    }
  }
  // ...
};

Reacting to Victory and Loss

Now that we’re dispatching GAME_WON and GAME_LOST events at the correct time, we need to update our components to react to these events. We’ll do three things when the game is over:

  • Our GameState will have a GameOver state which pauses the game and ignores keyboard input until the user presses the restart button.
  • The RestartButton will be highlighted, indicating that the user needs to press it.
  • The snake’s color will change to indicate whether the user won or lost.

Let’s update these components step by step.

1. Updating the Game State

We’ll add an IsGameOver boolean to our GameState class. We’ll initialize it to false, and reset it back to false every time we restart the game:

// GameState.h
// ...

class GameState {
  // ...
private:
  bool IsGameOver{false};
  
  void RestartGame() {
    IsPaused = true;
    IsGameOver = false;
    ElapsedTime = 0;
    Snake = {Config::GRID_ROWS / 2, 3, 2, Right};
    NextDirection = Right;
  }
  // ...
};

When our GameState object receives a GAME_LOST or GAME_WON event, we’ll update this IsGameOver variable to true:

// GameState.h
// ...

class GameState {
public:
  // ...
  void HandleEvent(SDL_Event& E) {
    using namespace UserEvents;
    using namespace Config;
    if (E.type == SDL_KEYDOWN) {
      HandleKeyEvent(E.key);
    } else if (E.type == APPLE_EATEN) {
      ++Snake.Length;
      if (Snake.Length == MAX_LENGTH) {
        SDL_Event Event{GAME_WON};
        SDL_PushEvent(&Event);
      }
    } else if (E.type == RESTART_GAME) {
      RestartGame();
    } else if (E.type == GAME_LOST ||
               E.type == GAME_WON) {
      IsGameOver = true;
    }
  }
  // ...
};

When the game is over, we’ll replicate a similar behavior to the initial IsPaused state. First, we’ll disable our ticking logic:

// GameState.h
// ...

class GameState {
public:
  // ...
  void Tick(Uint32 DeltaTime) {
    if (IsPaused || IsGameOver) return;

    ElapsedTime += DeltaTime;
    if (ElapsedTime >= Config::ADVANCE_INTERVAL) {
      ElapsedTime = 0;
      UpdateSnake();
    }
  }
  // ...
};

Additionally, when the game is over, we’ll ignore keyboard input until the game is restarted:

// GameState.h
// ...

class GameState {
  // ...
private:
  void HandleKeyEvent(SDL_KeyboardEvent& E) {
    if (IsGameOver) return;
} } };

If we run our game, we should now note that our snake stops moving and our keyboard input is ignored once we win or lose the game. Note that the IsGameOver and IsPaused states are subtly different.

Both of these states disable our GameState object’s Tick() behavior, but the logic in our HandleKeyEvent() function continues to be evaluated if the game is only paused. Players can press the right arrow or D key to unpause the game by setting IsPaused to false, but to set IsGameOver to false, they need to restart the game.

2. Highlighting the Restart Button

To indicate to the user that they need to press the restart button when the game is over, let’s highlight that button. We’ll first create a variable to control the highlighted color of our button:

// GameConfig.h
// ...

namespace Config{
  inline constexpr SDL_Color BUTTON_COLOR{
    73, 117, 46, 255};
  inline constexpr SDL_Color BUTTON_HIGHLIGHT_COLOR{
    67, 117, 234, 255};
}
// ...

In our RestartButton class, we’ll update our ButtonColor member based on GAME_LOST, GAME_WON, and RESTART_GAME events:

// RestartButton.h
// ...

class RestartButton {
public:
  // ...
  void HandleEvent(SDL_Event& E) {
    using namespace UserEvents;
    using namespace Config;
    if (E.type == SDL_MOUSEBUTTONDOWN) {
      HandleClick(E.button);
    } else if (
      E.type == GAME_LOST ||
      E.type == GAME_WON
    ) {
      CurrentColor = BUTTON_HIGHLIGHT_COLOR;
    } else if (E.type == RESTART_GAME) {
      CurrentColor = BUTTON_COLOR;
    }
  }
  // ...
};

Running our code, we should now see that the restart button uses a different color when the game is over:

Screenshot showing the restart button highlighting after the snake collides with the border

3. Changing Snake Color

Finally, let’s update the color of our snake when the user has won or lost the game. We’ll add new configuration variables for these colors:

// GameConfig.h
// ...

namespace Config{
  inline constexpr SDL_Color SNAKE_COLOR{
    67, 117, 234, 255};
  inline constexpr SDL_Color SNAKE_LOST_COLOR{
    227, 67, 97, 255};
  inline constexpr SDL_Color SNAKE_VICTORY_COLOR{
    255, 140, 0, 255};
  // ...
}

In our Cell class, we’ll add a SnakeColor variable to keep track of what color our snake segments should be. We’ll set this to the default SNAKE_COLOR variable every time our cell is initialized:

// Cell.h
// ...

class Cell {
  // ...
private:
  // ...
  SDL_Color SnakeColor{Config::SNAKE_COLOR};
  
  void Initialize() {
    CellState = Empty;
    SnakeColor = Config::SNAKE_COLOR;
    SnakeDuration = 0;

    int MiddleRow{Config::GRID_ROWS / 2};
    if (Row == MiddleRow && Column == 2) {
      CellState = Snake;
      SnakeDuration = 1;
    } else if (Row == MiddleRow && Column == 3) {
      CellState = Snake;
      SnakeDuration = 2;
    } else if (Row == MiddleRow && Column == 11) {
      CellState = Apple;
    }
  }
};

Within the HandleEvent() function, we’ll change this color depending on whether the player won or lost. Our Initialize() function is already being called when the game is restarted, so we’re already handling that scenario:

// Cell.h
// ...

class Cell {
public:
  // ...
  void HandleEvent(SDL_Event& E) {
    using namespace UserEvents;
    if (E.type == ADVANCE) {
      Advance(E.user);
    } else if (E.
      type == APPLE_EATEN) {
      if (CellState == Snake) {
        ++SnakeDuration;
      }
    } else if (E.type == GAME_LOST) {
      SnakeColor = Config::SNAKE_LOST_COLOR;
    } else if (E.type == GAME_WON) {
      SnakeColor = Config::SNAKE_VICTORY_COLOR;
    } else if (E.type == RESTART_GAME) {
      Initialize();
    }
  }
  // ...
};

Finally, let’s update our Render() method to use this new SnakeColor variable, rather than the static Config::SNAKE_COLOR value it was previously using:

// Cell.h
// ...

class Cell {
public:
  // ...
  void Render(SDL_Surface* Surface) {
    SDL_FillRect(Surface, &BackgroundRect,
      SDL_MapRGB(
        Surface->format,
        BackgroundColor.r,
        BackgroundColor.g,
        BackgroundColor.b
      )
    );

    if (CellState == Apple) {
      Assets.Apple.Render(
        Surface, &BackgroundRect);
    } else if (CellState == Snake) {
      SDL_FillRect(Surface, &BackgroundRect,
        SDL_MapRGB(
          Surface->format,
          SnakeColor.r,
          SnakeColor.g,
          SnakeColor.b
        )
      );
    }
  }
  // ...
};

Now, when we lose, our snake should be rendered in the SNAKE_LOSS_COLOR we defined:

Screenshot showing the snake colored red after colliding with the border wall

And, when we win, it should use the SNAKE_VICTORY_COLOR. To test this, it can be helpful to temporarily reduce the MAX_LENGTH value to make winning easier:

Screenshot showing the snake colored gold after winning the game

Complete Code

Complete versions of the files we changed in this part are available below

#pragma once
#define CHECK_ERRORS

#include <iostream>
#include <SDL.h>
#include <string>

namespace UserEvents{
  inline Uint32 ADVANCE =
    SDL_RegisterEvents(1);
  inline Uint32 APPLE_EATEN =
    SDL_RegisterEvents(1);
  inline Uint32 GAME_WON =
    SDL_RegisterEvents(1);
  inline Uint32 GAME_LOST =
    SDL_RegisterEvents(1);
  inline Uint32 RESTART_GAME =
    SDL_RegisterEvents(1);
}

namespace Config{
  // Game Settings
  inline const std::string GAME_NAME{
    "Snake"};
  inline constexpr int ADVANCE_INTERVAL{200};

  inline constexpr int GRID_COLUMNS{16};
  static_assert(
    GRID_COLUMNS >= 12,
    "Grid must be at least 12 columns wide");

  inline constexpr int GRID_ROWS{5};
  static_assert(
    GRID_ROWS >= 5,
    "Grid must be at least 5 rows tall");

  inline constexpr int MAX_LENGTH{
    GRID_COLUMNS * GRID_ROWS};

  // Size and Positioning
  inline constexpr int PADDING{5};
  inline constexpr int CELL_SIZE{36};
  inline constexpr int FOOTER_HEIGHT{60};

  inline constexpr int GRID_HEIGHT{
    CELL_SIZE * GRID_ROWS};
  inline constexpr int GRID_WIDTH{
    CELL_SIZE * GRID_COLUMNS};

  inline constexpr int WINDOW_HEIGHT{
    GRID_HEIGHT + FOOTER_HEIGHT
    + PADDING * 2};
  inline constexpr int WINDOW_WIDTH{
    GRID_WIDTH + PADDING * 2
  };

  // Colors
  inline constexpr SDL_Color BACKGROUND_COLOR{
    85, 138, 52, 255};
  inline constexpr SDL_Color CELL_COLOR_A{
    171, 214, 82, 255};
  inline constexpr SDL_Color CELL_COLOR_B{
    161, 208, 74, 255};
  inline constexpr SDL_Color SNAKE_COLOR{
    67, 117, 234, 255};
  inline constexpr SDL_Color SNAKE_LOST_COLOR{
    227, 67, 97, 255};
  inline constexpr SDL_Color SNAKE_VICTORY_COLOR{
    255, 140, 0, 255};
  inline constexpr SDL_Color BUTTON_COLOR{
    73, 117, 46, 255};
  inline constexpr SDL_Color BUTTON_HIGHLIGHT_COLOR{
    67, 117, 234, 255};
  inline constexpr SDL_Color FONT_COLOR{
    255, 255, 255, 255};

  // Asset Paths
  inline const std::string APPLE_IMAGE{
    "apple.png"};
  inline const std::string FONT{
    "Rubik-SemiBold.ttf"};
}

inline void CheckSDLError(
  const std::string& Msg){
#ifdef CHECK_ERRORS
  const char* error = SDL_GetError();
  if (*error != '\0') {
    std::cerr << Msg << " Error: "
      << error << '\n';
    SDL_ClearError();
  }
#endif
}
#pragma once
#include <SDL.h>
#include "GameConfig.h"
#include "SnakeData.h"

class GameState {
public:
  void HandleEvent(SDL_Event& E) {
    using namespace UserEvents;
    using namespace Config;
    if (E.type == SDL_KEYDOWN) {
      HandleKeyEvent(E.key);
    } else if (E.type == APPLE_EATEN) {
      ++Snake.Length;
      if (Snake.Length == MAX_LENGTH) {
        SDL_Event Event{GAME_WON};
        SDL_PushEvent(&Event);
      }
    } else if (E.type == RESTART_GAME) {
      RestartGame();
    } else if (E.type == GAME_LOST ||
               E.type == GAME_WON) {
      IsGameOver = true;
    }
  }

  void Tick(Uint32 DeltaTime) {
    if (IsPaused || IsGameOver) return;

    ElapsedTime += DeltaTime;
    if (ElapsedTime >= Config::ADVANCE_INTERVAL) {
      ElapsedTime = 0;
      UpdateSnake();
    }
  }

private:
  void HandleKeyEvent(SDL_KeyboardEvent& E) {
    if (IsGameOver) return;
    switch (E.keysym.sym) {
      case SDLK_UP:
      case SDLK_w:
        if (Snake.Direction != Down) {
          NextDirection = Up;
        }
        break;
      case SDLK_DOWN:
      case SDLK_s:
        if (Snake.Direction != Up) {
          NextDirection = Down;
        }
        break;
      case SDLK_LEFT:
      case SDLK_a:
        if (Snake.Direction != Right) {
          NextDirection = Left;
        }
        break;
      case SDLK_RIGHT:
      case SDLK_d:
        if (IsPaused) {
          NextDirection = Right;
          IsPaused = false;
          UpdateSnake();
        } else if (Snake.Direction != Left) {
          NextDirection = Right;
        }
        break;
    }
  }

  void UpdateSnake() {
    Snake.Direction = NextDirection;
    switch (NextDirection) {
      case Up:
        Snake.HeadRow--;
        break;
      case Down:
        Snake.HeadRow++;
        break;
      case Left:
        Snake.HeadCol--;
        break;
      case Right:
        Snake.HeadCol++;
        break;
    }

    if (
      Snake.HeadRow < 0 ||
      Snake.HeadRow >= Config::GRID_ROWS ||
      Snake.HeadCol < 0 ||
      Snake.HeadCol >= Config::GRID_COLUMNS
    ) {
      SDL_Event Event{UserEvents::GAME_LOST};
      SDL_PushEvent(&Event);
    } else {
      SDL_Event Event{UserEvents::ADVANCE};
      Event.user.data1 = &Snake;
      SDL_PushEvent(&Event);
    }
  }

  void RestartGame() {
    IsPaused = true;
    IsGameOver = false;
    ElapsedTime = 0;
    Snake = {Config::GRID_ROWS / 2, 3, 2, Right};
    NextDirection = Right;
  }

  bool IsPaused{true};
  bool IsGameOver{false};
  Uint32 ElapsedTime{0};
  SnakeData Snake{Config::GRID_ROWS / 2, 3, 2};
  MovementDirection NextDirection{Right};
};
#pragma once
#include <SDL.h>
#include "GameConfig.h"
#include "SnakeData.h"
#include "Assets.h"

enum CellState { Snake, Apple, Empty };

class Cell {
public:
  Cell(int Row, int Column, Assets& Assets)
    : Row(Row),
      Column(Column),
      Assets{Assets}
  {
    Initialize();
  }

  void HandleEvent(SDL_Event& E) {
    using namespace UserEvents;
    if (E.type == ADVANCE) {
      Advance(E.user);
    } else if (E.
      type == APPLE_EATEN) {
      if (CellState == Snake) {
        ++SnakeDuration;
      }
    } else if (E.type == GAME_LOST) {
      SnakeColor = Config::SNAKE_LOST_COLOR;
    } else if (E.type == GAME_WON) {
      SnakeColor = Config::SNAKE_VICTORY_COLOR;
    } else if (E.type == RESTART_GAME) {
      Initialize();
    }
  }

  void Tick(Uint32 DeltaTime) {}

  void Render(SDL_Surface* Surface) {
    SDL_FillRect(Surface, &BackgroundRect,
      SDL_MapRGB(
        Surface->format,
        BackgroundColor.r,
        BackgroundColor.g,
        BackgroundColor.b
      )
    );

    if (CellState == Apple) {
      Assets.Apple.Render(
        Surface, &BackgroundRect);
    } else if (CellState == Snake) {
      SDL_FillRect(Surface, &BackgroundRect,
        SDL_MapRGB(
          Surface->format,
          SnakeColor.r,
          SnakeColor.g,
          SnakeColor.b
        )
      );
    }
  }

  bool PlaceApple() {
    if (CellState != Empty) return false;

    CellState = Apple;
    return true;
  }

private:
  void Initialize() {
    CellState = Empty;
    SnakeColor = Config::SNAKE_COLOR;
    SnakeDuration = 0;

    int MiddleRow{Config::GRID_ROWS / 2};
    if (Row == MiddleRow && Column == 2) {
      CellState = Snake;
      SnakeDuration = 1;
    } else if (Row == MiddleRow && Column == 3) {
      CellState = Snake;
      SnakeDuration = 2;
    } else if (Row == MiddleRow && Column == 11) {
      CellState = Apple;
    }
  }

  void Advance(SDL_UserEvent& E) {
    SnakeData* Data{
      static_cast<SnakeData*>(E.data1)};

    bool isThisCell{
      Data->HeadRow == Row &&
      Data->HeadCol == Column
    };

    if (isThisCell) {
      if (CellState == Snake) {
        SDL_Event Event{UserEvents::GAME_LOST};
        SDL_PushEvent(&Event);
        return;
      }
      if (CellState == Apple) {
        SDL_Event Event{
          UserEvents::APPLE_EATEN};
        SDL_PushEvent(&Event);
      }
      CellState = Snake;
      SnakeDuration = Data->Length;
    } else if (CellState == Snake) {
      SnakeDuration--;
      if (SnakeDuration <= 0) {
        CellState = Empty;
      }
    }
  }

  int Row;
  int Column;
  CellState CellState;
  SDL_Color SnakeColor{Config::SNAKE_COLOR};
  int SnakeDuration;

  SDL_Rect BackgroundRect{
    Column * Config::CELL_SIZE + Config::PADDING,
    Row * Config::CELL_SIZE + Config::PADDING,
    Config::CELL_SIZE,
    Config::CELL_SIZE
  };

  SDL_Color BackgroundColor{
    (Row + Column) % 2 == 0
      ? Config::CELL_COLOR_A
      : Config::CELL_COLOR_B
  };
  
  Assets& Assets;
};
#pragma once
#include <SDL.h>
#include "GameConfig.h"
#include "Engine/Text.h"

class RestartButton {
public:
  void Render(SDL_Surface* Surface) {
    SDL_FillRect(Surface, &ButtonRect,
      SDL_MapRGB(
        Surface->format,
        CurrentColor.r,
        CurrentColor.g,
        CurrentColor.b
      )
    );
    Text.Render(Surface, &TextRect);
  }

  void HandleEvent(SDL_Event& E) {
    using namespace UserEvents;
    using namespace Config;
    if (E.type == SDL_MOUSEBUTTONDOWN) {
      HandleClick(E.button);
    } else if (
      E.type == GAME_LOST ||
      E.type == GAME_WON
    ) {
      CurrentColor = BUTTON_HIGHLIGHT_COLOR;
    } else if (E.type == RESTART_GAME) {
      CurrentColor = BUTTON_COLOR;
    }
  }

private:
  void HandleClick(SDL_MouseButtonEvent& E) {
    using namespace UserEvents;
    if (
      E.x >= ButtonRect.x &&
      E.x <= ButtonRect.x + ButtonRect.w &&
      E.y >= ButtonRect.y &&
      E.y <= ButtonRect.y + ButtonRect.h
    ) {
      SDL_Event RestartEvent{RESTART_GAME};
      SDL_PushEvent(&RestartEvent);
    }
  }

  Text Text{"RESTART", 20};

  SDL_Rect ButtonRect{
    Config::WINDOW_WIDTH - 150,
    Config::GRID_HEIGHT + 10,
    150 - Config::PADDING,
    Config::FOOTER_HEIGHT - Config::PADDING
  };

  SDL_Rect TextRect{
    ButtonRect.x + Config::PADDING * 5,
    ButtonRect.y + Config::PADDING * 3,
    ButtonRect.w - Config::PADDING,
    ButtonRect.h - Config::PADDING
  };

  SDL_Color CurrentColor{Config::BUTTON_COLOR};
};

Files not listed above have not been changed since the previous section.

Summary

In this lesson, we've implemented win-and-loss conditions. We've used the familiar event-based techniques to signal changes in one component, and react to those events throughout our application to keep our game state synchronised and provide visual feedback to players.

Next, we’ll update our UI to include a score tracker, keeping track of how many apples the player has collected.

Free and Unlimited Access

Professional C++

Unlock the true power of C++ by mastering complex features, optimizing performance, and learning expert workflows used in professional development

Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Ryan McCombe
Ryan McCombe
Posted
sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Get Started for Free
Project: Snake

    67.
    Win/Loss Logic for Snake

    Add game-ending logic to our Snake game with custom events, state management, and visual cues for player feedback.


sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

This course includes:

  • 84 Lessons
  • 92% Positive Reviews
  • Regularly Updated
  • Help and FAQs
Free and Unlimited Access

Professional C++

Unlock the true power of C++ by mastering complex features, optimizing performance, and learning expert workflows used in professional development

Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Contact|Privacy Policy|Terms of Use
Copyright © 2025 - All Rights Reserved