<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>Искусство разработки игр</title><author><name>Искусство разработки игр</name></author><id>https://teletype.in/atom/gamedeveloper</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/gamedeveloper?offset=0"></link><link rel="alternate" type="text/html" href="https://teletype.in/@gamedeveloper?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=gamedeveloper"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/gamedeveloper?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-04-15T23:21:47.053Z</updated><entry><id>gamedeveloper:1qgQI3T2-7A</id><link rel="alternate" type="text/html" href="https://teletype.in/@gamedeveloper/1qgQI3T2-7A?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=gamedeveloper"></link><title>Разработка игры &quot;Пятнашки&quot; на SFML C++</title><published>2025-06-05T14:48:49.656Z</published><updated>2025-06-05T14:48:49.656Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/83/95/839596a4-1d23-406e-970e-9c61c78b0e49.png"></media:thumbnail><category term="mul-timedijnaya-biblioteka-sfml-c" label="Мультимедийная библиотека  SFML C++"></category><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/53/d9/53d9953b-85d9-43d8-ab6a-f8d9e11d1c66.png&quot;&gt;Привет! Сегодня хочу поделиться опытом создания классической головоломки — игры &quot;Пятнашки&quot;, на C++ с использованием фреймворка SFML (Simple and Fast Multimedia Library).</summary><content type="html">
  &lt;figure id=&quot;OTNK&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/53/d9/53d9953b-85d9-43d8-ab6a-f8d9e11d1c66.png&quot; width=&quot;1241&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;LYDt&quot;&gt;Привет! Сегодня хочу поделиться опытом создания классической головоломки — игры &amp;quot;Пятнашки&amp;quot;, на C++ с использованием фреймворка SFML (Simple and Fast Multimedia Library).&lt;/p&gt;
  &lt;p id=&quot;lffC&quot;&gt;Если вы начинающий разработчик или просто любите писать игры своими руками, то эта статья может вам понравиться. &lt;br /&gt;Я расскажу не только о том, как отобразить окно и кнопки, но и о том, как организовать логику игры, обработать взаимодействие пользователя, а также немного поговорим о структуре проекта и возможностях SFML.&lt;/p&gt;
  &lt;p id=&quot;2RyM&quot;&gt;Игра &amp;quot;Пятнашки&amp;quot; — отличный способ попрактиковаться в программировании: здесь есть и логика, и работа с интерфейсом, и даже простая анимация. А SFML — это отличный выбор для тех, кто хочет создавать 2D-игры без излишней сложности и с минимальным порогом входа.&lt;/p&gt;
  &lt;p id=&quot;DyQK&quot;&gt;Давайте начнём!&lt;/p&gt;
  &lt;h2 id=&quot;ROYB&quot;&gt;Подготовка среды разработки&lt;/h2&gt;
  &lt;p id=&quot;66oV&quot;&gt;Перед тем как писать код, нужно подготовить рабочую среду:&lt;/p&gt;
  &lt;p id=&quot;AuPl&quot;&gt;Установите компилятор C++.&lt;/p&gt;
  &lt;h3 id=&quot;zvWP&quot;&gt;&lt;u&gt;Например:&lt;/u&gt;&lt;/h3&gt;
  &lt;p id=&quot;GTMR&quot;&gt;GCC (Linux)&lt;/p&gt;
  &lt;p id=&quot;53nN&quot;&gt;MinGW (Windows)&lt;/p&gt;
  &lt;p id=&quot;4BVp&quot;&gt;Visual Studio (Windows)&lt;/p&gt;
  &lt;h3 id=&quot;Ql9A&quot;&gt;&lt;u&gt;Скачайте и установите SFML:&lt;/u&gt;&lt;/h3&gt;
  &lt;p id=&quot;FDw2&quot;&gt;Официальный сайт: https://www.sfml-dev.org/download.php&lt;/p&gt;
  &lt;p id=&quot;0Yz6&quot;&gt;Выберите версию под ваш компилятор и систему.&lt;/p&gt;
  &lt;p id=&quot;Hf93&quot;&gt;Настройте свой проект:&lt;/p&gt;
  &lt;p id=&quot;FkYO&quot;&gt;Добавьте пути к заголовочным файлам SFML.&lt;/p&gt;
  &lt;p id=&quot;l1i8&quot;&gt;Слинкуйте нужные библиотеки (sfml-graphics, sfml-window, sfml-system).&lt;/p&gt;
  &lt;p id=&quot;1Xpo&quot;&gt;Проверьте, что всё работает — откройте простое окно SFML.&lt;/p&gt;
  &lt;h2 id=&quot;0APa&quot;&gt;Общая структура проекта&lt;/h2&gt;
  &lt;p id=&quot;iK2u&quot;&gt;Вот как выглядел мой проект:&lt;/p&gt;
  &lt;figure id=&quot;1qrE&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/5d/7c/5d7c9b8c-7f8e-4eab-bbe8-f8baca91739a.png&quot; width=&quot;281&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;MKm2&quot;&gt;&lt;strong&gt;Базовый класс Screen — основа всех экранов&lt;/strong&gt;&lt;/h2&gt;
  &lt;p id=&quot;gUtu&quot;&gt;В процессе создания игры важно было организовать удобную архитектуру, особенно потому, что у нас есть несколько экранов: главное меню, игровой экран и экран с правилами. Чтобы не повторять один и тот же код в каждом из них, я создал абстрактный базовый класс Screen.&lt;/p&gt;
  &lt;p id=&quot;wpXz&quot;&gt;&lt;u&gt;Зачем он нужен?&lt;/u&gt;&lt;/p&gt;
  &lt;p id=&quot;DjuU&quot;&gt;Класс Screen служит общей основой для всех экранов приложения:&lt;/p&gt;
  &lt;p id=&quot;24tw&quot;&gt;он предоставляет доступ к окну (sf::RenderWindow);&lt;/p&gt;
  &lt;p id=&quot;tCTl&quot;&gt;хранит ссылки на глобальное состояние игры (GameState) и переходы между экранами (Transition);&lt;/p&gt;
  &lt;p id=&quot;oQb3&quot;&gt;реализует общие элементы интерфейса (например, фон);&lt;/p&gt;
  &lt;p id=&quot;0v44&quot;&gt;упрощает расширяемость проекта — добавление нового экрана становится простым делом.&lt;/p&gt;
  &lt;h3 id=&quot;QqdI&quot;&gt;Основные компоненты класса&lt;/h3&gt;
  &lt;p id=&quot;fBC1&quot;&gt;&lt;strong&gt;&lt;u&gt;Screen.h&lt;/u&gt;&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;EzGn&quot; data-lang=&quot;cpp&quot;&gt;#pragma once
#include &amp;lt;SFML/Graphics.hpp&amp;gt;
#include &amp;quot;GameState.h&amp;quot;
#include &amp;quot;Transition.h&amp;quot;
#include &amp;quot;ResourceManager.h&amp;quot;
#include &amp;lt;memory&amp;gt;

class Screen : public sf::Drawable {
protected:
    sf::RenderWindow&amp;amp; window;
    GameState&amp;amp; state;
    Transition&amp;amp; transition;

    // Общие элементы интерфейса
    sf::Sprite backgroundSprite;
    bool useTexture;

    Screen(sf::RenderWindow&amp;amp; window, G
    ameState&amp;amp; state, Transition&amp;amp; transition);

    // Виртуальные методы для настройки ресурсов и элементов интерфейса
    virtual void initialize() = 0;

    // Вспомогательные методы
    void setupBackground(const std::string&amp;amp; textureId);

public:
    virtual ~Screen() = default;

    // Общие методы, которые будут переопределяться
    virtual void handleEvent(const sf::Event&amp;amp; event) = 0;
    virtual void update(float deltaTime) = 0;
    virtual void 
    draw(sf::RenderTarget&amp;amp; target, sf::RenderStates states) const = 0;
};

&lt;/pre&gt;
  &lt;p id=&quot;1hku&quot;&gt;&lt;strong&gt;&lt;u&gt;Screen.cpp&lt;/u&gt;&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;hfhR&quot; data-lang=&quot;cpp&quot;&gt;#include &amp;quot;Screen.h&amp;quot;
#include &amp;lt;iostream&amp;gt;

Screen::Screen(sf::RenderWindow&amp;amp; window, GameState&amp;amp; state,
 Transition&amp;amp; transition)
    : window(window), state(state), transition(transition), 
    useTexture(false) {
}

void Screen::setupBackground(const std::string&amp;amp; textureId) {
    ResourceManager&amp;amp; rm = ResourceManager::getInstance();
    sf::Texture&amp;amp; bgTexture = rm.getTexture(textureId);
    if (bgTexture.getSize().x == 0) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Ошибка: Не удалось загрузить текстуру фона &amp;#x27;&amp;quot; 
        &amp;lt;&amp;lt; textureId &amp;lt;&amp;lt; &amp;quot;&amp;#x27; из ResourceManager.\n&amp;quot;;
        useTexture = false;
    }
    else {
        useTexture = true;
        backgroundSprite.setTexture(bgTexture);
        backgroundSprite.setScale(
            static_cast&amp;lt;float&amp;gt;(window.getSize().x) / bgTexture.getSize().x,
            static_cast&amp;lt;float&amp;gt;(window.getSize().y) / bgTexture.getSize().y
        );
    }
}
&lt;/pre&gt;
  &lt;p id=&quot;AWtH&quot;&gt;&lt;u&gt;Что здесь происходит?&lt;/u&gt;&lt;/p&gt;
  &lt;p id=&quot;ixSE&quot;&gt;Конструктор принимает ссылки на важнейшие объекты: окно, состояние игры и переходы.&lt;/p&gt;
  &lt;p id=&quot;2Eey&quot;&gt;initialize() — чисто виртуальный метод, который должен быть реализован в каждом дочернем классе. Здесь можно загрузить специфичные для экрана ресурсы или создать кнопки.&lt;/p&gt;
  &lt;p id=&quot;nB8I&quot;&gt;setupBackground() — универсальный метод для настройки фона. Он использует ResourceManager, чтобы получить текстуру по ID и корректно её от масштабировать под размер окна.&lt;/p&gt;
  &lt;p id=&quot;T4Me&quot;&gt;Все экраны обязаны реализовать три ключевых метода:&lt;/p&gt;
  &lt;p id=&quot;9Zem&quot;&gt;handleEvent() — обработка событий (нажатия мыши, клавиш и т.д.)&lt;/p&gt;
  &lt;p id=&quot;ay1C&quot;&gt;update() — логика обновления состояния экрана (анимации, проверка условий и пр.)&lt;/p&gt;
  &lt;p id=&quot;80zq&quot;&gt;draw() — отрисовка всех элементов экрана.&lt;/p&gt;
  &lt;h2 id=&quot;FKq7&quot;&gt;Реализация класса ResourceManager  с поддержкой ресурсов Windows&lt;/h2&gt;
  &lt;p id=&quot;5uLE&quot;&gt;Одним из важнейших компонентов в любой игре или мультимедийном приложении является централизованное управление ресурсами. В этой главе мы рассмотрим реализацию класса &lt;code&gt;ResourceManager&lt;/code&gt; на C++ с использованием SFML и Windows API. &lt;/p&gt;
  &lt;p id=&quot;S6kZ&quot;&gt;Класс ResourceManager отвечает за загрузку и хранение следующих типов ресурсов:&lt;/p&gt;
  &lt;p id=&quot;i6uJ&quot;&gt;·         sf::Font — шрифты;&lt;/p&gt;
  &lt;p id=&quot;DX8c&quot;&gt;·         sf::SoundBuffer — звуковые эффекты;&lt;/p&gt;
  &lt;p id=&quot;vWY0&quot;&gt;·         sf::Texture — текстуры;&lt;/p&gt;
  &lt;p id=&quot;2Z9I&quot;&gt;·         sf::Music — музыкальные потоки (streaming).&lt;/p&gt;
  &lt;p id=&quot;TY4T&quot;&gt;Все ресурсы хранятся в памяти, загружаются из ресурсов Windows &lt;code&gt;.exe&lt;/code&gt; файла (раздел &lt;code&gt;.rc&lt;/code&gt;) и доступны по строковому идентификатору.&lt;/p&gt;
  &lt;p id=&quot;3wUu&quot;&gt;&lt;strong&gt;resource.h&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;5FAG&quot; data-lang=&quot;cpp&quot;&gt;#pragma once

#define IDR_TEXTURE1   102  // Изображение PNG
#define IDR_TEXTURE2   103  // Изображение PNG
#define IDR_TEXTURE3   104  // Изображение PNG
#define IDR_TEXTURE4   105  // Изображение PNG
#define IDR_TEXTURE5   106  // Изображение PNG

#define IDR_FONT1      201  // Шрифт
#define IDR_FONT2      202  // Шрифт

#define IDR_SOUND1     301  // Звук

#define IDR_ICON1      401  // Иконка
&lt;/pre&gt;
  &lt;p id=&quot;hAVM&quot;&gt;&lt;strong&gt;fifteen_puzzle.rc&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;ImfY&quot; data-lang=&quot;cpp&quot;&gt;#include &amp;quot;resource.h&amp;quot;

IDR_TEXTURE1   RCDATA  &amp;quot;assets/puzzle.png&amp;quot;
IDR_TEXTURE2   RCDATA  &amp;quot;assets/background.png&amp;quot;
IDR_TEXTURE3   RCDATA  &amp;quot;assets/background1.png&amp;quot;
IDR_TEXTURE4   RCDATA  &amp;quot;assets/background2.png&amp;quot;
IDR_TEXTURE5   RCDATA  &amp;quot;assets/numbers.png&amp;quot;

IDR_SOUND1     RCDATA &amp;quot;assets/music.wav&amp;quot;

IDR_FONT1      RCDATA &amp;quot;assets/arial.ttf&amp;quot;
IDR_FONT2      RCDATA &amp;quot;assets/glv.ttf&amp;quot;

IDR_ICON1      ICON &amp;quot;assets/puzzle.ico&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;t1Yq&quot;&gt;&lt;strong&gt;ResourceManager.h&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;j8Ng&quot; data-lang=&quot;cpp&quot;&gt;#pragma once

#include &amp;lt;SFML/Graphics.hpp&amp;gt;
#include &amp;lt;SFML/Audio.hpp&amp;gt;
#include &amp;lt;windows.h&amp;gt;
#include &amp;lt;map&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;memory&amp;gt;

class ResourceManager {
private:
	// Хранилища для ресурсов
    // Хранилище для шрифтов
    std::map&amp;lt;std::string, sf::Font&amp;gt; fonts;
    // Хранилище для звуковых буферов
    std::map&amp;lt;std::string, sf::SoundBuffer&amp;gt; buffers;
    // Хранилище для текстур
    std::map&amp;lt;std::string, sf::Texture&amp;gt; textures;
    // Хранилище для музыкальных потоков
    std::map&amp;lt;std::string, std::unique_ptr&amp;lt;sf::Music&amp;gt;&amp;gt; musicStreams; 

    // Приватный конструктор для реализации синглтона
    ResourceManager();

    // Приватный деструктор
    ~ResourceManager() {}

    // Вспомогательный метод для загрузки ресурса
    template&amp;lt;typename T&amp;gt;
    bool loadResource(HRSRC resource, HGLOBAL handle, T&amp;amp; target);

public:
    // Метод получения единственного экземпляра класса
    static ResourceManager&amp;amp; getInstance();

    // Запрет копирования
    ResourceManager(const ResourceManager&amp;amp;) = delete;
    ResourceManager&amp;amp; operator=(const ResourceManager&amp;amp;) = delete;

    // Методы загрузки конкретных ресурсов
    bool loadFont(const std::string&amp;amp; id, int resourceId);
    bool loadSound(const std::string&amp;amp; id, int resourceId);
    bool loadTexture(const std::string&amp;amp; id, int resourceId);
    bool loadMusic(const std::string&amp;amp; id, int resourceId); 

    // Геттеры для получения ресурсов по идентификатору
    sf::Font&amp;amp; getFont(const std::string&amp;amp; id);
    sf::SoundBuffer&amp;amp; getSoundBuffer(const std::string&amp;amp; id);
    sf::Texture&amp;amp; getTexture(const std::string&amp;amp; id);
    sf::Image getImage(const std::string&amp;amp; id);
    sf::Music&amp;amp; getMusic(const std::string&amp;amp; id); 
};&lt;/pre&gt;
  &lt;p id=&quot;lMw7&quot;&gt;&lt;strong&gt;ResourceManager.cpp&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;22Sc&quot; data-lang=&quot;cpp&quot;&gt;#include &amp;quot;ResourceManager.h&amp;quot;
#include &amp;lt;iostream&amp;gt;

// Конструктор по умолчанию
ResourceManager::ResourceManager() {}

// Шаблонная функция для загрузки ресурса из памяти в целевой объект (например, шрифт, текстура и т.д.)
template&amp;lt;typename T&amp;gt;
bool ResourceManager::loadResource(HRSRC resource, HGLOBAL handle, T&amp;amp; target) {
    if (!resource) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Не удалось найти ресурс&amp;quot; &amp;lt;&amp;lt; std::endl;
        return false;
    }

    // Загружаем ресурс в память
    handle = LoadResource(NULL, resource);
    if (!handle) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Не удалось загрузить ресурс&amp;quot; &amp;lt;&amp;lt; std::endl;
        return false;
    }

    // Получаем указатель на данные ресурса
    const void* data = LockResource(handle);

    // Получаем размер ресурса
    DWORD size = SizeofResource(NULL, resource);

    // Пытаемся загрузить ресурс в целевой объект (например, sf::Font или sf::Texture)
    return target.loadFromMemory(data, size);
}

// Возвращает единственный экземпляр ResourceManager (паттерн Singleton)
ResourceManager&amp;amp; ResourceManager::getInstance() {
    static ResourceManager instance;
    return instance;
}

// Загружает шрифт из ресурса Windows с заданным ID и сохраняет его под указанным идентификатором
bool ResourceManager::loadFont(const std::string&amp;amp; id, int resourceId) {
    // Находим ресурс шрифта в исполняемом файле
    HRSRC fontResource = FindResource(NULL, MAKEINTRESOURCE(resourceId), RT_RCDATA);
    HGLOBAL fontHandle = NULL;

    sf::Font font;
    if (!loadResource(fontResource, fontHandle, font)) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Не удалось загрузить шрифт &amp;quot; &amp;lt;&amp;lt; id &amp;lt;&amp;lt; std::endl;
        return false;
    }

    // Сохраняем загруженный шрифт в хранилище
    fonts[id] = std::move(font);
    return true;
}

// Загружает звуковой буфер из ресурса Windows с заданным ID и сохраняет его под указанным идентификатором
bool ResourceManager::loadSound(const std::string&amp;amp; id, int resourceId) {
    HRSRC soundResource = FindResource(NULL, MAKEINTRESOURCE(resourceId), RT_RCDATA);
    HGLOBAL soundHandle = NULL;

    sf::SoundBuffer buffer;
    if (!loadResource(soundResource, soundHandle, buffer)) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Не удалось загрузить звук &amp;quot; &amp;lt;&amp;lt; id &amp;lt;&amp;lt; std::endl;
        return false;
    }

    // Сохраняем загруженный звуковой буфер в хранилище
    buffers[id] = std::move(buffer);
    return true;
}

// Загружает текстуру из ресурса Windows с заданным ID и сохраняет её под указанным идентификатором
bool ResourceManager::loadTexture(const std::string&amp;amp; id, int resourceId) {
    HRSRC textureResource = FindResource(NULL, MAKEINTRESOURCE(resourceId), RT_RCDATA);
    HGLOBAL textureHandle = NULL;

    sf::Texture texture;
    if (!loadResource(textureResource, textureHandle, texture)) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Не удалось загрузить текстуру &amp;quot; &amp;lt;&amp;lt; id &amp;lt;&amp;lt; std::endl;
        return false;
    }

    // Сохраняем загруженную текстуру в хранилище
    textures[id] = std::move(texture);
    return true;
}

// Загружает музыку из ресурса Windows с заданным ID и сохраняет её под указанным идентификатором
bool ResourceManager::loadMusic(const std::string&amp;amp; id, int resourceId) {
    HRSRC musicResource = FindResource(NULL, MAKEINTRESOURCE(resourceId), RT_RCDATA);
    HGLOBAL musicHandle = NULL;

    if (!musicResource) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Не удалось найти музыкальный ресурс &amp;quot; &amp;lt;&amp;lt; id &amp;lt;&amp;lt; std::endl;
        return false;
    }

    musicHandle = LoadResource(NULL, musicResource);
    if (!musicHandle) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Не удалось загрузить музыкальный ресурс &amp;quot; &amp;lt;&amp;lt; id &amp;lt;&amp;lt; std::endl;
        return false;
    }

    // Получаем указатель на данные музыки и её размер
    const void* data = LockResource(musicHandle);
    DWORD size = SizeofResource(NULL, musicResource);

    // Создаём объект музыки и загружаем его из памяти
    auto music = std::make_unique&amp;lt;sf::Music&amp;gt;();
    if (!music-&amp;gt;openFromMemory(data, size)) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Не удалось открыть музыкальный поток &amp;quot; &amp;lt;&amp;lt; id &amp;lt;&amp;lt; &amp;quot; из памяти&amp;quot; &amp;lt;&amp;lt; std::endl;
        return false;
    }

    // Сохраняем музыкальный поток в хранилище
    musicStreams[id] = std::move(music);
    return true;
}

// Возвращает ссылку на шрифт по указанному ID
sf::Font&amp;amp; ResourceManager::getFont(const std::string&amp;amp; id) {
    auto it = fonts.find(id);
    if (it == fonts.end()) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Шрифт &amp;quot; &amp;lt;&amp;lt; id &amp;lt;&amp;lt; &amp;quot; не найден&amp;quot; &amp;lt;&amp;lt; std::endl;
        static sf::Font emptyFont;  // Статический пустой шрифт на случай ошибки
        return emptyFont;
    }
    return it-&amp;gt;second;
}

// Возвращает ссылку на звуковой буфер по указанному ID
sf::SoundBuffer&amp;amp; ResourceManager::getSoundBuffer(const std::string&amp;amp; id) {
    auto it = buffers.find(id);
    if (it == buffers.end()) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Звуковой буфер &amp;quot; &amp;lt;&amp;lt; id &amp;lt;&amp;lt; &amp;quot; не найден&amp;quot; &amp;lt;&amp;lt; std::endl;
        static sf::SoundBuffer emptyBuffer;
        return emptyBuffer;
    }
    return it-&amp;gt;second;
}

// Возвращает ссылку на текстуру по указанному ID
sf::Texture&amp;amp; ResourceManager::getTexture(const std::string&amp;amp; id) {
    auto it = textures.find(id);
    if (it == textures.end()) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Текстура &amp;quot; &amp;lt;&amp;lt; id &amp;lt;&amp;lt; &amp;quot; не найдена&amp;quot; &amp;lt;&amp;lt; std::endl;
        static sf::Texture emptyTexture;
        return emptyTexture;
    }
    return it-&amp;gt;second;
}

// Возвращает копию изображения из текстуры по указанному ID
sf::Image ResourceManager::getImage(const std::string&amp;amp; id) {
    auto it = textures.find(id);
    if (it != textures.end()) {
        return it-&amp;gt;second.copyToImage();  // Копируем текстуру в изображение
    }
    std::cerr &amp;lt;&amp;lt; &amp;quot;Текстура для изображения &amp;quot; &amp;lt;&amp;lt; id &amp;lt;&amp;lt; &amp;quot; не найдена&amp;quot; &amp;lt;&amp;lt; std::endl;
    return sf::Image();
}

// Возвращает ссылку на музыкальный поток по указанному ID
sf::Music&amp;amp; ResourceManager::getMusic(const std::string&amp;amp; id) {
    auto it = musicStreams.find(id);
    if (it == musicStreams.end()) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Музыкальный поток &amp;quot; &amp;lt;&amp;lt; id &amp;lt;&amp;lt; &amp;quot; не найден&amp;quot; &amp;lt;&amp;lt; std::endl;
        static sf::Music emptyMusic;
        return emptyMusic;
    }
    return *(it-&amp;gt;second);  // Разыменовываем уникальный указатель, чтобы вернуть ссылку
}&lt;/pre&gt;
  &lt;h2 id=&quot;60sN&quot;&gt;Кастомный класс Button для интерфейса&lt;/h2&gt;
  &lt;p id=&quot;BbSP&quot;&gt;Интерфейс — важная часть любой игры. В этой главе мы реализуем &lt;strong&gt;гибкий класс &lt;code&gt;Button&lt;/code&gt;&lt;/strong&gt;, полностью совместимый с SFML 2.x и современными возможностями C++20. Он предоставляет визуальные эффекты нажатия, возможность настройки цветов, шрифтов и размеров, а также безопасную обработку кликов.&lt;/p&gt;
  &lt;p id=&quot;TnKd&quot;&gt;&lt;strong&gt;Возможности данного класса&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;sL2U&quot;&gt;Поддержка нажатия с визуальной анимацией.&lt;/p&gt;
  &lt;p id=&quot;WPvr&quot;&gt;Гибкая настройка цветов: фона, обводки, текста.&lt;/p&gt;
  &lt;p id=&quot;iQYc&quot;&gt;Обработка клика через sf::Vector2f (позиция мыши).&lt;/p&gt;
  &lt;p id=&quot;VB3i&quot;&gt;Использование concepts из C++20 для безопасных шаблонов.&lt;/p&gt;
  &lt;p id=&quot;x8hg&quot;&gt;Центрирование текста по кнопке.&lt;/p&gt;
  &lt;p id=&quot;deoO&quot;&gt;Поддержка перемещения и сравнения (&amp;lt;=&amp;gt;).&lt;/p&gt;
  &lt;p id=&quot;QlLD&quot;&gt;&lt;strong&gt;Button.h&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;Jy2Q&quot; data-lang=&quot;cpp&quot;&gt;#pragma once

#include &amp;lt;SFML/Graphics.hpp&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;concepts&amp;gt;

// Класс кнопки для SFML. Наследуется от sf::Drawable 
// и sf::Transformable для отрисовки и трансформаций.
class Button : public sf::Drawable, public sf::Transformable {

public:
    // Конструктор кнопки с полным набором параметров
    Button(std::wstring text, sf::Vector2f position, sf::Font&amp;amp; font,
        sf::Color buttonColor, sf::Color pressedColor,
        sf::Color textColor, sf::Color pressedTextColor,
        float width, float height, unsigned int fontSize,
        sf::Color outlineColor, float outlineThickness);

    // Конструкторы и операторы копирования/перемещения
    Button(const Button&amp;amp;) = default;
    Button&amp;amp; operator=(const Button&amp;amp;) = default;
    Button(Button&amp;amp;&amp;amp;) noexcept;
    Button&amp;amp; operator=(Button&amp;amp;&amp;amp;) noexcept;

    // Отрисовка кнопки на экране
    void draw(sf::RenderTarget&amp;amp; target, sf::RenderStates states) const override;

    // Проверка, был ли клик по кнопке
    [[nodiscard]] bool isClicked(sf::Vector2f mousePos) const noexcept;

    // Получение глобальных границ кнопки
    [[nodiscard]] sf::FloatRect getGlobalBounds() const noexcept;

    // Установка текста кнопки
    void setText(std::wstring text) noexcept;

    // Анимация при нажатии
    void pressAnimation() noexcept;

    // Анимация при отпускании
    void releaseAnimation() noexcept;

    // Установка цвета кнопки (с использованием concepts C++20)
    template&amp;lt;typename T&amp;gt; requires std::same_as&amp;lt;T, sf::Color&amp;gt;
    void setButtonColor(T color) noexcept;

    // Остальные настройки кнопки
    void setPressedColor(sf::Color color) noexcept;
    void setTextColor(sf::Color color) noexcept;
    void setPressedTextColor(sf::Color color) noexcept;
    void setSize(float width, float height) noexcept;
    void setFontSize(unsigned int size) noexcept;
    void setBackgroundTransparency(unsigned int alpha) noexcept;
    void setOutlineColor(sf::Color color) noexcept;
    void setOutlineThickness(float thickness) noexcept;

    // Трёхстороннее сравнение (spaceship operator)
    auto operator&amp;lt;=&amp;gt;(const Button&amp;amp; other) const = default;

private:
    // Прямоугольная форма кнопки и текст
    sf::RectangleShape shape;
    sf::Text label;

    // Цвета: обычный, при нажатии, для текста и при нажатии текста
    sf::Color buttonColor;
    sf::Color pressedColor;
    sf::Color textColor;
    sf::Color pressedTextColor;

    // Оригинальные параметры: толщина границы и размер шрифта
    float originalOutlineThickness;
    unsigned int originalFontSize;

    // Обновление позиции текста по центру кнопки
    void updateTextPosition() noexcept;
};&lt;/pre&gt;
  &lt;p id=&quot;AJMq&quot;&gt;&lt;strong&gt;Button.cpp&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;i31D&quot; data-lang=&quot;cpp&quot;&gt;#include &amp;quot;Button.h&amp;quot;
#include &amp;lt;ranges&amp;gt;

// Конструктор с параметрами
Button::Button(std::wstring text, sf::Vector2f position, sf::Font&amp;amp; font,
    sf::Color buttonColor, sf::Color pressedColor, sf::Color textColor,
    sf::Color pressedTextColor, float width, float height, unsigned int fontSize,
    sf::Color outlineColor, float outlineThickness)
    : buttonColor{ buttonColor },
    pressedColor{ pressedColor },
    textColor{ textColor },
    pressedTextColor{ pressedTextColor },
    originalOutlineThickness{ outlineThickness },
    originalFontSize{ fontSize }
{
    shape.setSize(sf::Vector2f{ width, height });
    shape.setFillColor(buttonColor);
    shape.setOutlineColor(outlineColor);
    shape.setOutlineThickness(outlineThickness);
    shape.setPosition(position);

    label.setFont(font);
    label.setString(std::move(text));
    label.setCharacterSize(fontSize);
    label.setFillColor(textColor);

    updateTextPosition();
}

// Центрирует текст по центру кнопки
void Button::updateTextPosition() noexcept {
    const auto textBounds = label.getLocalBounds();
    label.setOrigin({
        textBounds.left + textBounds.width / 2.0f,
        textBounds.top + textBounds.height / 2.0f
        });

    const auto shapePos = shape.getPosition();
    const auto shapeSize = shape.getSize();
    label.setPosition({
        shapePos.x + shapeSize.x / 2.0f,
        shapePos.y + shapeSize.y / 2.0f
        });
}

// Проверка попадания курсора мыши в границы кнопки
[[nodiscard]] bool Button::isClicked(sf::Vector2f mousePos) const noexcept {
    return shape.getGlobalBounds().contains(mousePos);
}

// Устанавливает новый текст
void Button::setText(std::wstring text) noexcept {
    label.setString(std::move(text));
    updateTextPosition();
}

// Визуальная анимация при нажатии на кнопку
void Button::pressAnimation() noexcept {
    shape.setFillColor(pressedColor);
    label.setFillColor(pressedTextColor);
    shape.setOutlineThickness(1.0f); // уменьшаем толщину обводки
    label.setCharacterSize(originalFontSize - 2); // уменьшаем размер текста
    updateTextPosition();
}

// Визуальная анимация при отпускании кнопки
void Button::releaseAnimation() noexcept {
    shape.setFillColor(buttonColor);
    label.setFillColor(textColor);
    shape.setOutlineThickness(originalOutlineThickness);
    label.setCharacterSize(originalFontSize);
    updateTextPosition();
}

// Установка цвета кнопки (через шаблон с concept)
template&amp;lt;typename T&amp;gt;
    requires std::same_as&amp;lt;T, sf::Color&amp;gt;
void Button::setButtonColor(T color) noexcept {
    this-&amp;gt;buttonColor = color;
    shape.setFillColor(color);
}

// Установка цвета при нажатии
void Button::setPressedColor(sf::Color color) noexcept {
    this-&amp;gt;pressedColor = color;
}

// Установка цвета текста
void Button::setTextColor(sf::Color color) noexcept {
    this-&amp;gt;textColor = color;
    label.setFillColor(color);
}

// Установка цвета текста при нажатии
void Button::setPressedTextColor(sf::Color color) noexcept {
    this-&amp;gt;pressedTextColor = color;
}

// Изменение размера кнопки
void Button::setSize(float width, float height) noexcept {
    shape.setSize({ width, height });
    updateTextPosition();
}

// Изменение размера шрифта
void Button::setFontSize(unsigned int size) noexcept {
    originalFontSize = size;
    label.setCharacterSize(size);
    updateTextPosition();
}

// Установка прозрачности фона кнопки
void Button::setBackgroundTransparency(unsigned int alpha) noexcept {
    buttonColor.a = static_cast&amp;lt;std::uint8_t&amp;gt;(std::clamp(alpha, 0u, 255u));
    shape.setFillColor(buttonColor);
}

// Установка цвета обводки
void Button::setOutlineColor(sf::Color color) noexcept {
    shape.setOutlineColor(color);
}

// Установка толщины обводки
void Button::setOutlineThickness(float thickness) noexcept {
    originalOutlineThickness = thickness;
    shape.setOutlineThickness(thickness);
}

// Отрисовка кнопки и текста
void Button::draw(sf::RenderTarget&amp;amp; target, sf::RenderStates states) const {
    states.transform.combine(getTransform());
    target.draw(shape, states);
    target.draw(label, states);
}

// Получение границ кнопки (для коллизий/кликов)
[[nodiscard]] sf::FloatRect Button::getGlobalBounds() const noexcept {
    return shape.getGlobalBounds();
}

// Конструктор перемещения
Button::Button(Button&amp;amp;&amp;amp; other) noexcept = default;

// Оператор перемещения
Button&amp;amp; Button::operator=(Button&amp;amp;&amp;amp; other) noexcept = default;
&lt;/pre&gt;
  &lt;h2 id=&quot;TBV9&quot;&gt;Анимированный разноцветный текст, класс ColorfulText&lt;/h2&gt;
  &lt;p id=&quot;sx8Q&quot;&gt;В этом разделе блога я хочу показать вам, как создать красивый, динамически меняющийся текст с цветной анимацией на библиотеке SFML. Такой текст отлично подходит для заставок, титулов, экранов загрузки или игровых меню. Мы создадим собственный класс ColorfulText, где каждая буква будет представлять собой отдельный объект sf::Text, меняющий цвет через определённый интервал времени.&lt;/p&gt;
  &lt;h3 id=&quot;Jbqv&quot;&gt;Возможности данного класса&lt;/h3&gt;
  &lt;p id=&quot;Y5BF&quot;&gt;- отображает строку текста;&lt;/p&gt;
  &lt;p id=&quot;1pEk&quot;&gt;- окрашивает каждую букву в случайный цвет;&lt;/p&gt;
  &lt;p id=&quot;kVC0&quot;&gt;- периодически обновляет цвета;&lt;/p&gt;
  &lt;p id=&quot;r54T&quot;&gt;- поддерживает позиционирование с центрированием.&lt;/p&gt;
  &lt;p id=&quot;ziI1&quot;&gt;&lt;strong&gt;ColorfulText.h&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;sAFT&quot; data-lang=&quot;cpp&quot;&gt;#pragma once

#include &amp;lt;SFML/Graphics.hpp&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;random&amp;gt;
#include &amp;lt;memory&amp;gt;

// Класс ColorfulText — отображает анимированный разноцветный текст
class ColorfulText {
public:
    // Конструктор
    ColorfulText(const std::string&amp;amp; fontId, std::wstring_view text, unsigned int charSize,
        const sf::Vector2f&amp;amp; position, const sf::RenderWindow&amp;amp; window,
        bool centerHorizontally = false, bool centerVertically = false);

    // Обновление цвета текста (вызывается в цикле игры)
    void update(float deltaTime);

    // Отрисовка текста на экране
    void draw(sf::RenderTarget&amp;amp; target, sf::RenderStates states = {}) const;

    // Установка новой позиции текста с возможностью центрирования
    void setPosition(const sf::Vector2f&amp;amp; position,
        bool centerHorizontally = false,
        bool centerVertically = false);

private:
    // Обновление цветов букв на случайные
    void updateColors();

    std::shared_ptr&amp;lt;sf::Font&amp;gt; font;       // Шрифт
    std::vector&amp;lt;sf::Text&amp;gt; letters;        // Вектор отдельных букв (sf::Text)
    float colorTimer;                     // Таймер для смены цвета
    static constexpr float COLOR_CHANGE_INTERVAL = 0.5f; // Интервал смены цвета
};
&lt;/pre&gt;
  &lt;p id=&quot;o1DG&quot;&gt;Вместо одного объекта sf::Text, как обычно, мы создаём вектор letters, в котором каждая буква строки является отдельным объектом sf::Text. Это даёт нам гибкость при анимации — можно изменять цвет каждой буквы по отдельности.&lt;/p&gt;
  &lt;p id=&quot;sdF9&quot;&gt;&lt;strong&gt;ColorfulText.cpp&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;OkTG&quot; data-lang=&quot;cpp&quot;&gt;#include &amp;quot;ColorfulText.h&amp;quot;
#include &amp;quot;ResourceManager.h&amp;quot;
#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;random&amp;gt;

// Конструктор
ColorfulText::ColorfulText(const std::string&amp;amp; fontId, std::wstring_view text, unsigned int charSize,
    const sf::Vector2f&amp;amp; position, const sf::RenderWindow&amp;amp; window,
    bool centerHorizontally, bool centerVertically)
    : colorTimer(0.0f) {

    // Получаем ссылку на синглтон ResourceManager
    ResourceManager&amp;amp; rm = ResourceManager::getInstance();

    // Загружаем шрифт
    font = std::make_shared&amp;lt;sf::Font&amp;gt;(rm.getFont(fontId));

    // Проверяем, загрузился ли шрифт
    if (font-&amp;gt;getInfo().family.empty()) {
        throw std::runtime_error(&amp;quot;Не удалось загрузить шрифт с ID: &amp;quot; + std::string(fontId) + &amp;quot; из ResourceManager&amp;quot;);
    }

    // Вычисляем общую ширину текста для центрирования
    float totalWidth = text.size() * charSize * 0.8f;
    float xPos = position.x;
    float yPos = position.y;

    if (centerHorizontally) {
        xPos = (window.getSize().x - totalWidth) / 2.0f;
    }
    if (centerVertically) {
        yPos = (window.getSize().y - charSize) / 2.0f;
    }

    // Создаем отдельные буквы и задаем им позицию и цвет
    letters.reserve(text.size());
    for (size_t i = 0; i &amp;lt; text.size(); ++i) {
        sf::Text letter;
        letter.setFont(*font);
        letter.setString(text[i]); // Каждая буква отдельно
        letter.setCharacterSize(charSize);
        letter.setFillColor(sf::Color::Magenta); // Начальный цвет
        letter.setPosition(xPos + i * charSize * 0.8f, yPos);
        letters.push_back(letter);
    }
}

// Обновление — вызывается каждый кадр, меняет цвет по таймеру
void ColorfulText::update(float deltaTime) {
    colorTimer += deltaTime;
    if (colorTimer &amp;gt;= COLOR_CHANGE_INTERVAL) {
        colorTimer = 0.0f;
        updateColors();
    }
}

// Функция случайной смены цвета каждой буквы
void ColorfulText::updateColors() {
    static std::random_device rd;
    static std::mt19937 gen(rd());
    static std::uniform_int_distribution&amp;lt;int&amp;gt; dis(0, 255);

    for (auto&amp;amp; letter : letters) {
        letter.setFillColor(sf::Color(
            static_cast&amp;lt;sf::Uint8&amp;gt;(dis(gen)),  // R
            static_cast&amp;lt;sf::Uint8&amp;gt;(dis(gen)),  // G
            static_cast&amp;lt;sf::Uint8&amp;gt;(dis(gen))   // B
        ));
    }
}

// Отрисовка всех букв текста
void ColorfulText::draw(sf::RenderTarget&amp;amp; target, sf::RenderStates states) const {
    for (const auto&amp;amp; letter : letters) {
        target.draw(letter, states);
    }
}

// Установка новой позиции текста с опциональным центрированием
void ColorfulText::setPosition(const sf::Vector2f&amp;amp; position, bool centerHorizontally, bool centerVertically) {
    float totalWidth = letters.size() * letters[0].getCharacterSize() * 0.8f;
    float xPos = position.x;
    float yPos = position.y;

    if (centerHorizontally) {
        xPos = position.x - totalWidth / 2.0f;
    }
    if (centerVertically) {
        yPos = position.y - letters[0].getCharacterSize() / 2.0f;
    }

    // Обновляем позицию каждой буквы
    for (size_t i = 0; i &amp;lt; letters.size(); ++i) {
        letters[i].setPosition(xPos + i * letters[0].getCharacterSize() * 0.8f, yPos);
    }
}
&lt;/pre&gt;
  &lt;h2 id=&quot;kx7F&quot;&gt;Плавный переход между экранами в игре, класс Transition&lt;/h2&gt;
  &lt;p id=&quot;NtxP&quot;&gt;Переходы между игровыми экранами — важная часть UX. Они делают игру визуально цельной и приятной. В этом разделе мы реализуем &lt;strong&gt;двухфазный эффект затемнения и осветления&lt;/strong&gt; — популярный способ перехода между состояниями игры с помощью чёрного оверлея.&lt;/p&gt;
  &lt;h3 id=&quot;6nm8&quot;&gt;&lt;strong&gt;Возможности данного класса:&lt;/strong&gt;&lt;/h3&gt;
  &lt;ul id=&quot;4wqU&quot;&gt;
    &lt;li id=&quot;thQR&quot;&gt;затухание экрана до чёрного (fade-out),&lt;/li&gt;
    &lt;li id=&quot;v8al&quot;&gt;смену состояния игры (например, меню → игра),&lt;/li&gt;
    &lt;li id=&quot;QMMU&quot;&gt;проявление нового состояния (fade-in).&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;EWLu&quot;&gt;&lt;strong&gt;Transition.h&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;zBmc&quot; data-lang=&quot;cpp&quot;&gt;#pragma once

#include &amp;lt;SFML/Graphics.hpp&amp;gt;
#include &amp;quot;GameState.h&amp;quot;

class Transition : public sf::Drawable, public sf::Transformable {
public:
    Transition(sf::RenderWindow&amp;amp; window); // Конструктор с окном для динамического размера
    void startTransition(GameState newState);
    bool update(GameState&amp;amp; currentState, float deltaTime); // Добавляем deltaTime

private:
    virtual void draw(sf::RenderTarget&amp;amp; target, sf::RenderStates states) const override;

    sf::RectangleShape overlay;
    sf::RenderWindow&amp;amp; window; // Ссылка на окно для получения размера
    GameState targetState;
    float alpha; // Используем float для плавности
    bool transitioning;
    bool fadingIn; // Флаг для фазы fade-in
    float transitionDuration; // Длительность одной фазы
};&lt;/pre&gt;
  &lt;p id=&quot;qOOd&quot;&gt;&lt;strong&gt;Transition.cpp&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;Lino&quot; data-lang=&quot;cpp&quot;&gt;#include &amp;quot;Transition.h&amp;quot;

// Конструктор: инициализирует переход с заданным окном отрисовки
Transition::Transition(sf::RenderWindow&amp;amp; window)
    : window(window), alpha(255.f), transitioning(false), fadingIn(false), transitionDuration(0.5f) {

    // Устанавливаем размер затемняющего прямоугольника равным размеру окна
    overlay.setSize(sf::Vector2f(static_cast&amp;lt;float&amp;gt;(window.getSize().x),
        static_cast&amp;lt;float&amp;gt;(window.getSize().y)));

    // Цвет прямоугольника — чёрный с максимальной непрозрачностью (начальное состояние — экран полностью закрыт)
    overlay.setFillColor(sf::Color(0, 0, 0, static_cast&amp;lt;sf::Uint8&amp;gt;(alpha)));
}

// Запускает переход к новому состоянию игры (например, из меню в игру)
void Transition::startTransition(GameState newState) {
    transitioning = true;   // Начинаем переход
    fadingIn = false;       // Сначала будет фейд-аут (затемнение)
    targetState = newState; // Сохраняем целевое состояние игры
    alpha = 255.f;          // Сбрасываем прозрачность

    // Обновляем цвет оверлея, чтобы он был полностью непрозрачным
    overlay.setFillColor(sf::Color(0, 0, 0, static_cast&amp;lt;sf::Uint8&amp;gt;(alpha)));
}

// Обновляет состояние перехода (анимация затухания / появления)
// Возвращает true, если переход всё ещё активен
bool Transition::update(GameState&amp;amp; currentState, float deltaTime) {
    if (transitioning) {
        if (!fadingIn) {
            // Фаза затухания (экран темнеет)
            alpha -= (255.f / transitionDuration) * deltaTime;
            if (alpha &amp;lt;= 0.f) {
                alpha = 0.f;
                fadingIn = true;           // Переходим к фазе проявления
                currentState = targetState; // Меняем текущее состояние игры
            }
        }
        else {
            // Фаза проявления (экран светлеет)
            alpha += (255.f / transitionDuration) * deltaTime;
            if (alpha &amp;gt;= 255.f) {
                alpha = 255.f;
                transitioning = false;     // Переход завершён
            }
        }

        // Обновляем прозрачность оверлея
        // Используется 255.f - alpha, чтобы получить эффект &amp;quot;появления&amp;quot; после затемнения
        overlay.setFillColor(sf::Color(0, 0, 0, static_cast&amp;lt;sf::Uint8&amp;gt;(255.f - alpha)));

        return true; // Переход всё ещё активен
    }
    return false; // Переход завершён
}

// Отрисовывает оверлей (черный прямоугольник) поверх текущего содержимого окна
void Transition::draw(sf::RenderTarget&amp;amp; target, sf::RenderStates states) const {
    if (transitioning) {
        target.draw(overlay, states); // Рисуем затемняющий слой, если переход активен
    }
}&lt;/pre&gt;
  &lt;h2 id=&quot;TXEL&quot;&gt;Реализация игры “Пятнашки” в классе FifteenPuzzle, который наследуется от sf::Drawable для отрисовки.&lt;/h2&gt;
  &lt;p id=&quot;29Ez&quot;&gt;&lt;strong&gt;FifteenPuzzle.h&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;fGZh&quot; data-lang=&quot;cpp&quot;&gt;#pragma once

#include &amp;lt;SFML/Graphics.hpp&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;memory&amp;gt;
#include &amp;lt;string&amp;gt;

// Класс, реализующий игру &amp;quot;Пятнашки&amp;quot;, наследуется от sf::Drawable для отрисовки
class FifteenPuzzle : public sf::Drawable {
public:
    // Константы по умолчанию для размера плитки и сетки
    static constexpr int DEFAULT_TILE_SIZE = 100;
    static constexpr int DEFAULT_GRID_SIZE = 4;

    // Конструктор с параметрами размера плитки и сетки
    explicit FifteenPuzzle(int tileSize = DEFAULT_TILE_SIZE,
        int gridSize = DEFAULT_GRID_SIZE);
    // Деструктор по умолчанию
    ~FifteenPuzzle() = default;

    // Запрет копирования для предотвращения дублирования ресурсов
    FifteenPuzzle(const FifteenPuzzle&amp;amp;) = delete;
    FifteenPuzzle&amp;amp; operator=(const FifteenPuzzle&amp;amp;) = delete;

    // Разрешено перемещение для поддержки семантики перемещения
    FifteenPuzzle(FifteenPuzzle&amp;amp;&amp;amp;) = default;
    FifteenPuzzle&amp;amp; operator=(FifteenPuzzle&amp;amp;&amp;amp;) = default;

    // Установка позиции пазла на экране
    void setPosition(float x, float y);
    // Получение текущей позиции пазла
    sf::Vector2f getPosition() const;
    // Получение границ пазла (для обработки событий)
    sf::FloatRect getBounds() const;

    // Обработка событий (например, кликов мыши)
    void handleEvent(const sf::Event&amp;amp; event, const sf::RenderWindow&amp;amp; window);
    // Обновление состояния игры (заглушка для возможных анимаций)
    void update();
    // Проверка, решён ли пазл
    bool isSolved() const;
    // Перезапуск игры (перемешивание плиток)
    void restart();

private:
    const int m_tileSize; // Размер одной плитки (в пикселях)
    const int m_gridSize; // Размер сетки (например, 4x4)
    std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; m_board; // Двумерный массив, представляющий игровое поле
    std::shared_ptr&amp;lt;sf::Font&amp;gt; m_font; // Шрифт для отображения номеров плиток
    int m_emptyX, m_emptyY; // Координаты пустой плитки
    sf::Vector2f m_position; // Позиция пазла на экране
    bool m_isSolved; // Флаг, указывающий, решён ли пазл

    // Перемешивание плиток для создания новой игры
    void shuffleBoard();
    // Проверка, можно ли переместить плитку на указанные координаты
    bool canMove(int x, int y) const;
    // Перемещение плитки на пустое место
    void moveTile(int x, int y);
    // Проверка, является ли текущая конфигурация решаемой
    bool isSolvable(const std::vector&amp;lt;int&amp;gt;&amp;amp; numbers) const;
    // Метод отрисовки пазла (переопределение sf::Drawable)
    virtual void draw(sf::RenderTarget&amp;amp; target, sf::RenderStates states) const override;
};&lt;/pre&gt;
  &lt;p id=&quot;dpZt&quot;&gt;Основные моменты:&lt;/p&gt;
  &lt;ul id=&quot;QxXS&quot;&gt;
    &lt;li id=&quot;wPth&quot;&gt;&lt;strong&gt;Константы&lt;/strong&gt;: DEFAULT_TILE_SIZE (100 пикселей) и DEFAULT_GRID_SIZE (4x4) задают размер плиток и сетки по умолчанию.&lt;/li&gt;
    &lt;li id=&quot;AyRv&quot;&gt;&lt;strong&gt;Запрет копирования&lt;/strong&gt;: Конструктор копирования и оператор присваивания отключены, чтобы избежать дублирования ресурсов.&lt;/li&gt;
    &lt;li id=&quot;pp2m&quot;&gt;&lt;strong&gt;Поддержка перемещения&lt;/strong&gt;: Конструктор перемещения и оператор присваивания включены для эффективного управления ресурсами.&lt;/li&gt;
    &lt;li id=&quot;zr1R&quot;&gt;&lt;strong&gt;Основные методы&lt;/strong&gt;: Методы вроде &lt;code&gt;handleEvent&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, &lt;code&gt;isSolved&lt;/code&gt; и &lt;code&gt;restart&lt;/code&gt; управляют игровым процессом, а &lt;code&gt;draw&lt;/code&gt; отвечает за отрисовку.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;y3av&quot;&gt;&lt;strong&gt;FifteenPuzzle.cpp&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;OZA2&quot; data-lang=&quot;cpp&quot;&gt;#include &amp;quot;FifteenPuzzle.h&amp;quot;
#include &amp;lt;numeric&amp;gt;
#include &amp;lt;random&amp;gt;
#include &amp;lt;stdexcept&amp;gt;
#include &amp;quot;ResourceManager.h&amp;quot;

// Конструктор: инициализация игры с заданными параметрами
FifteenPuzzle::FifteenPuzzle(int tileSize, int gridSize)
    : m_tileSize(tileSize)
    , m_gridSize(gridSize)
    , m_board(gridSize, std::vector&amp;lt;int&amp;gt;(gridSize, 0))
    , m_font(std::make_shared&amp;lt;sf::Font&amp;gt;())
    , m_emptyX(0)
    , m_emptyY(0)
    , m_position(0.f, 0.f)
    , m_isSolved(false)
{
    // Получение экземпляра менеджера ресурсов (паттерн Singleton)
    ResourceManager&amp;amp; rm = ResourceManager::getInstance();
    // Загрузка шрифта из менеджера ресурсов
    *m_font = rm.getFont(&amp;quot;font&amp;quot;);

    // Проверка успешной загрузки шрифта
    if (m_font-&amp;gt;getInfo().family.empty()) {
        throw std::runtime_error(&amp;quot;Не удалось загрузить шрифт &amp;#x27;font&amp;#x27; из ResourceManager&amp;quot;);
    }

    // Инициализация игрового поля
    shuffleBoard();
}

// Установка позиции пазла на экране
void FifteenPuzzle::setPosition(float x, float y) {
    m_position.x = x;
    m_position.y = y;
}

// Получение текущей позиции пазла
sf::Vector2f FifteenPuzzle::getPosition() const {
    return m_position;
}

// Получение прямоугольника, ограничивающего пазл
sf::FloatRect FifteenPuzzle::getBounds() const {
    return sf::FloatRect(m_position.x, m_position.y,
        m_tileSize * m_gridSize,
        m_tileSize * m_gridSize);
}

// Проверка, является ли текущая конфигурация пазла решаемой
bool FifteenPuzzle::isSolvable(const std::vector&amp;lt;int&amp;gt;&amp;amp; numbers) const {
    // Подсчёт инверсий в последовательности чисел
    int inversions = 0;
    for (size_t i = 0; i &amp;lt; numbers.size(); ++i) {
        for (size_t j = i + 1; j &amp;lt; numbers.size(); ++j) {
            if (numbers[i] &amp;amp;&amp;amp; numbers[j] &amp;amp;&amp;amp; numbers[i] &amp;gt; numbers[j]) {
                ++inversions;
            }
        }
    }

    // Для нечётного размера сетки: чётное число инверсий =&amp;gt; решаемо
    if (m_gridSize % 2 == 1) {
        return inversions % 2 == 0;
    }
    // Для чётного размера сетки: учитываем позицию пустой плитки
    else {
        int emptyRowFromBottom = m_gridSize - m_emptyY;
        if (emptyRowFromBottom % 2 == 0) {
            return inversions % 2 == 1;
        }
        else {
            return inversions % 2 == 0;
        }
    }
}

// Перемешивание игрового поля для новой игры
void FifteenPuzzle::shuffleBoard() {
    // Создание последовательности чисел от 1 до gridSize*gridSize-1, с 0 в конце
    std::vector&amp;lt;int&amp;gt; numbers(m_gridSize * m_gridSize);
    std::iota(numbers.begin(), numbers.end(), 1);
    numbers.back() = 0;

    // Инициализация генератора случайных чисел
    std::random_device rd;
    std::mt19937 g(rd());

    // Перемешивание до получения решаемой конфигурации
    do {
        std::shuffle(numbers.begin(), numbers.end(), g);

        // Заполнение игрового поля
        for (int i = 0; i &amp;lt; m_gridSize; ++i) {
            for (int j = 0; j &amp;lt; m_gridSize; ++j) {
                m_board[i][j] = numbers[i * m_gridSize + j];
                if (m_board[i][j] == 0) {
                    m_emptyX = j;
                    m_emptyY = i;
                }
            }
        }
    } while (!isSolvable(numbers));

    // Сброс флага решения
    m_isSolved = false;
}

// Проверка, можно ли переместить плитку на указанные координаты
bool FifteenPuzzle::canMove(int x, int y) const {
    // Плитка должна быть соседней с пустой и находиться в пределах поля
    return (std::abs(x - m_emptyX) + std::abs(y - m_emptyY) == 1) &amp;amp;&amp;amp;
        (x &amp;gt;= 0 &amp;amp;&amp;amp; x &amp;lt; m_gridSize &amp;amp;&amp;amp; y &amp;gt;= 0 &amp;amp;&amp;amp; y &amp;lt; m_gridSize);
}

// Перемещение плитки на пустое место
void FifteenPuzzle::moveTile(int x, int y) {
    if (canMove(x, y)) {
        // Обмен плитки с пустым местом
        std::swap(m_board[m_emptyY][m_emptyX], m_board[y][x]);
        m_emptyX = x;
        m_emptyY = y;
        // Проверка, решён ли пазл после хода
        m_isSolved = isSolved();
    }
}

// Проверка, решён ли пазл
bool FifteenPuzzle::isSolved() const {
    int expected = 1;
    for (int i = 0; i &amp;lt; m_gridSize; ++i) {
        for (int j = 0; j &amp;lt; m_gridSize; ++j) {
            if (i == m_gridSize - 1 &amp;amp;&amp;amp; j == m_gridSize - 1) {
                return m_board[i][j] == 0;
            }
            if (m_board[i][j] != expected++) {
                return false;
            }
        }
    }
    return true;
}

// Обработка событий (например, кликов мыши)
void FifteenPuzzle::handleEvent(const sf::Event&amp;amp; event, const sf::RenderWindow&amp;amp; window) {
    if (event.type == sf::Event::MouseButtonPressed &amp;amp;&amp;amp;
        event.mouseButton.button == sf::Mouse::Left) {
        // Преобразование координат мыши в мировые координаты
        sf::Vector2f mousePos = window.mapPixelToCoords(
            sf::Vector2i(event.mouseButton.x, event.mouseButton.y));

        // Проверка, находится ли клик внутри пазла
        if (getBounds().contains(mousePos)) {
            // Вычисление координат плитки
            int x = static_cast&amp;lt;int&amp;gt;((mousePos.x - m_position.x) / m_tileSize);
            int y = static_cast&amp;lt;int&amp;gt;((mousePos.y - m_position.y) / m_tileSize);
            moveTile(x, y);
        }
    }
}

// Обновление состояния игры (пока не используется)
void FifteenPuzzle::update() {
    // Здесь можно добавить анимации или другие обновления
}

// Отрисовка пазла
void FifteenPuzzle::draw(sf::RenderTarget&amp;amp; target, sf::RenderStates states) const {
    // Смещение координат для учёта позиции пазла
    states.transform.translate(m_position);

    // Отрисовка каждой плитки
    for (int i = 0; i &amp;lt; m_gridSize; ++i) {
        for (int j = 0; j &amp;lt; m_gridSize; ++j) {
            if (m_board[i][j] != 0) {
                // Отри Clay плитки
                sf::RectangleShape rect(sf::Vector2f(m_tileSize - 2, m_tileSize - 2));
                rect.setPosition(j * m_tileSize + 1, i * m_tileSize + 1);
                rect.setFillColor(sf::Color(64, 224, 208)); // Бирюзовый цвет
                target.draw(rect, states);

                // Отрисовка номера плитки
                if (m_font) {
                    sf::Text text;
                    text.setFont(*m_font);
                    text.setString(std::to_string(m_board[i][j]));
                    text.setCharacterSize(m_tileSize / 2);
                    text.setFillColor(sf::Color::Black);

                    // Центрирование текста
                    auto textRect = text.getLocalBounds();
                    text.setOrigin(textRect.width / 2.0f, textRect.height / 2.0f);
                    text.setPosition(j * m_tileSize + m_tileSize / 2.0f,
                        i * m_tileSize + m_tileSize / 2.5f);
                    target.draw(text, states);
                }
            }
        }
    }
}

// Перезапуск игры (перемешивание плиток)
void FifteenPuzzle::restart() {
    shuffleBoard();
}&lt;/pre&gt;
  &lt;h3 id=&quot;U65b&quot;&gt;Инициализация&lt;/h3&gt;
  &lt;p id=&quot;88nI&quot;&gt;В конструкторе создаётся игровое поле (m_board) как двумерный вектор размером gridSize x gridSize. Шрифт для номеров плиток загружается через ResourceManager (паттерн Singleton). Затем вызывается shuffleBoard() для создания начальной конфигурации.&lt;/p&gt;
  &lt;h3 id=&quot;r5it&quot;&gt;Перемешивание&lt;/h3&gt;
  &lt;p id=&quot;OdzA&quot;&gt;Метод shuffleBoard() генерирует случайную последовательность чисел от 1 до gridSize*gridSize-1 с 0 в конце (пустая плитка). Используется std::shuffle с генератором случайных чисел std::mt19937. Важно, что метод isSolvable проверяет, является ли конфигурация решаемой, подсчитывая инверсии и учитывая позицию пустой плитки.&lt;/p&gt;
  &lt;h3 id=&quot;nwTZ&quot;&gt;Обработка кликов&lt;/h3&gt;
  &lt;p id=&quot;PzE2&quot;&gt;Метод handleEvent обрабатывает клики мыши. Координаты клика преобразуются в индексы плитки, и если плитка соседствует с пустой, вызывается moveTile для её перемещения.&lt;/p&gt;
  &lt;h3 id=&quot;73kd&quot;&gt;Отрисовка&lt;/h3&gt;
  &lt;p id=&quot;9SLy&quot;&gt;Метод draw отрисовывает каждую плитку как прямоугольник (sf::RectangleShape) бирюзового цвета с номером, отцентрированным с помощью sf::Text. Пустая плитка (0) не отрисовывается.&lt;/p&gt;
  &lt;h3 id=&quot;HTeD&quot;&gt;Проверка решения&lt;/h3&gt;
  &lt;p id=&quot;Mzj6&quot;&gt;Метод isSolved проверяет, расположены ли плитки в порядке от 1 до 15 с пустой плиткой в правом нижнем углу.&lt;/p&gt;
  &lt;h1 id=&quot;NQWr&quot;&gt;Создаём главное меню для игры &amp;quot;Пятнашки&amp;quot;, класс MainMenu&lt;/h1&gt;
  &lt;p id=&quot;vmfQ&quot;&gt;Класс MainMenu отвечает за отображение и обработку главного меню игры. Оно включает заголовок &amp;quot;Пятнашки&amp;quot; и пять кнопок: &amp;quot;Играть&amp;quot;, &amp;quot;Правила&amp;quot;, &amp;quot;Музыка: Вкл/Выкл&amp;quot;, &amp;quot;Рестарт&amp;quot; и &amp;quot;Выход&amp;quot;. Меню использует SFML для отрисовки, поддерживает анимации кнопок и фоновую музыку. Код организован так, чтобы быть понятным и легко расширяемым.&lt;/p&gt;
  &lt;p id=&quot;6gqX&quot;&gt;Класс MainMenu наследуется от базового класса Screen, что позволяет интегрировать его в систему управления состояниями игры. &lt;/p&gt;
  &lt;p id=&quot;4oY3&quot;&gt;&lt;strong&gt;MainMenu.h&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;MQHk&quot; data-lang=&quot;cpp&quot;&gt;
#pragma once  

#include &amp;lt;SFML/Graphics.hpp&amp;gt;
#include &amp;lt;SFML/Audio.hpp&amp;gt;
#include &amp;quot;Button.h&amp;quot;
#include &amp;quot;GameState.h&amp;quot;
#include &amp;quot;Transition.h&amp;quot;
#include &amp;quot;ColorfulText.h&amp;quot;
#include &amp;lt;memory&amp;gt;
#include &amp;quot;GameScreen.h&amp;quot;
#include &amp;quot;Screen.h&amp;quot;

class MainMenu : public Screen {
private:
    // Элементы интерфейса главного меню — все они уникальные указатели для удобного управления памятью
    std::unique_ptr&amp;lt;ColorfulText&amp;gt; titleText;   // Заголовок меню с цветной анимацией
    std::unique_ptr&amp;lt;Button&amp;gt; playButton;        // Кнопка &amp;quot;Играть&amp;quot;
    std::unique_ptr&amp;lt;Button&amp;gt; rulesButton;       // Кнопка &amp;quot;Правила&amp;quot;
    std::unique_ptr&amp;lt;Button&amp;gt; musicButton;       // Кнопка включения/выключения музыки
    std::unique_ptr&amp;lt;Button&amp;gt; restartButton;     // Кнопка &amp;quot;Перезапустить&amp;quot; (может использоваться в других состояниях)
    std::unique_ptr&amp;lt;Button&amp;gt; exitButton;        // Кнопка &amp;quot;Выход&amp;quot;

    sf::Music* music;            // Указатель на объект музыки (не владеем им, просто ссылка)
    GameScreen&amp;amp; gameScreen;      // Ссылка на игровой экран для перехода к игре
    bool isMusicOn;              // Флаг: включена ли музыка

    // Метод для инициализации элементов интерфейса меню
    void initialize() override;

public:
    // Конструктор: принимает окно отрисовки, текущее состояние игры, переход и игровой экран
    MainMenu(sf::RenderWindow&amp;amp; window,
        GameState&amp;amp; state,
        Transition&amp;amp; transition,
        GameScreen&amp;amp; gameScreen);

    // Обработка событий (нажатия мыши, клавиш и т.д.)
    void handleEvent(const sf::Event&amp;amp; event) override;

    // Обновление логики меню (анимация, проверка нажатий и т.д.)
    void update(float deltaTime) override;

    // Отрисовка всех элементов меню
    void draw(sf::RenderTarget&amp;amp; target, sf::RenderStates states) const override;
};&lt;/pre&gt;
  &lt;p id=&quot;mzXg&quot;&gt;Основные моменты:&lt;/p&gt;
  &lt;ul id=&quot;ag6o&quot;&gt;
    &lt;li id=&quot;UrgP&quot;&gt;&lt;strong&gt;Поля&lt;/strong&gt;: Указатели std::unique_ptr на заголовок (ColorfulText) и кнопки (Button) обеспечивают автоматическое управление памятью. Переменная isMusicOn отслеживает состояние музыки.&lt;/li&gt;
    &lt;li id=&quot;6tYq&quot;&gt;&lt;strong&gt;Зависимости&lt;/strong&gt;: Класс использует sf::Music для фоновой музыки, GameScreen для перезапуска игры и Transition для переключения между экранами.&lt;/li&gt;
    &lt;li id=&quot;4mW8&quot;&gt;&lt;strong&gt;Методы&lt;/strong&gt;: initialize настраивает элементы меню, handleEvent обрабатывает клики, update обновляет анимации, а draw отрисовывает меню.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;qFWB&quot;&gt;&lt;strong&gt;MainMenu.cpp&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;D3j4&quot; data-lang=&quot;cpp&quot;&gt;#include &amp;quot;MainMenu.h&amp;quot;
#include &amp;lt;stdexcept&amp;gt;

// Константы для расположения и размеров элементов интерфейса
constexpr float BUTTON_WIDTH = 500.0f;       // Ширина кнопок
constexpr float BUTTON_HEIGHT = 50.0f;       // Высота кнопок
constexpr float SPACING = 30.0f;             // Расстояние между кнопками
constexpr float TITLE_Y_POS = 50.0f;         // Вертикальная позиция заголовка

// Конструктор: инициализирует главное меню и вызывает метод initialize()
MainMenu::MainMenu(sf::RenderWindow&amp;amp; window, GameState&amp;amp; state, Transition&amp;amp; transition, GameScreen&amp;amp; gameScreen)
    : Screen(window, state, transition), gameScreen(gameScreen), isMusicOn(true) {
    initialize();  // Инициализация всех элементов интерфейса
}

// Метод инициализации главного меню
void MainMenu::initialize() {
    ResourceManager&amp;amp; rm = ResourceManager::getInstance();

    // Получаем шрифты из менеджера ресурсов
    sf::Font&amp;amp; font = rm.getFont(&amp;quot;font&amp;quot;);
    sf::Font&amp;amp; font2 = rm.getFont(&amp;quot;font1&amp;quot;);

    // Проверяем, успешно ли загружены шрифты
    if (font.getInfo().family.empty() || font2.getInfo().family.empty()) {
        throw std::runtime_error(&amp;quot;Не удалось получить шрифты из ResourceManager&amp;quot;);
    }

    // Устанавливаем фоновое изображение
    setupBackground(&amp;quot;background&amp;quot;);

    // Создаем анимированный цветной заголовок &amp;quot;Пятнашки&amp;quot;
    titleText = std::make_unique&amp;lt;ColorfulText&amp;gt;(
        &amp;quot;font1&amp;quot;,
        L&amp;quot;Пятнашки&amp;quot;,
        100,
        sf::Vector2f(0, TITLE_Y_POS),
        window,
        true,
        false);

    // Рассчитываем начальную Y-координату для размещения кнопок по центру экрана
    float startY = window.getSize().y / 2.0f - (5 * BUTTON_HEIGHT + 4 * SPACING) / 2.0f + 50.0f;
    float centerX = (window.getSize().x - BUTTON_WIDTH) / 2.0f;

    // Создаем все кнопки меню с заданными параметрами стиля и расположения

    playButton = std::make_unique&amp;lt;Button&amp;gt;(
        L&amp;quot;Играть&amp;quot;,
        sf::Vector2f(centerX, startY),
        font,
        sf::Color(255, 192, 203, 150),
        sf::Color(255, 192, 203, 200),
        sf::Color::Blue,
        sf::Color(128, 128, 128),
        BUTTON_WIDTH,
        BUTTON_HEIGHT,
        30,
        sf::Color::Magenta,
        5.0f);

    rulesButton = std::make_unique&amp;lt;Button&amp;gt;(
        L&amp;quot;Правила&amp;quot;,
        sf::Vector2f(centerX, startY + BUTTON_HEIGHT + SPACING),
        font,
        sf::Color(255, 192, 203, 150),
        sf::Color(255, 192, 203, 200),
        sf::Color::Blue,
        sf::Color(128, 128, 128),
        BUTTON_WIDTH,
        BUTTON_HEIGHT,
        30,
        sf::Color::Magenta,
        5.0f);

    musicButton = std::make_unique&amp;lt;Button&amp;gt;(
        L&amp;quot;Музыка: Вкл&amp;quot;,
        sf::Vector2f(centerX, startY + 2 * (BUTTON_HEIGHT + SPACING)),
        font,
        sf::Color(255, 192, 203, 150),
        sf::Color(255, 192, 203, 200),
        sf::Color::Blue,
        sf::Color(128, 128, 128),
        BUTTON_WIDTH,
        BUTTON_HEIGHT,
        30,
        sf::Color::Magenta,
        5.0f);

    restartButton = std::make_unique&amp;lt;Button&amp;gt;(
        L&amp;quot;Рестарт&amp;quot;,
        sf::Vector2f(centerX, startY + 3 * (BUTTON_HEIGHT + SPACING)),
        font,
        sf::Color(255, 192, 203, 150),
        sf::Color(255, 192, 203, 200),
        sf::Color::Blue,
        sf::Color(128, 128, 128),
        BUTTON_WIDTH,
        BUTTON_HEIGHT,
        30,
        sf::Color::Magenta,
        5.0f);

    exitButton = std::make_unique&amp;lt;Button&amp;gt;(
        L&amp;quot;Выход&amp;quot;,
        sf::Vector2f(centerX, startY + 4 * (BUTTON_HEIGHT + SPACING)),
        font,
        sf::Color(255, 192, 203, 150),
        sf::Color(255, 192, 203, 200),
        sf::Color::Blue,
        sf::Color(128, 128, 128),
        BUTTON_WIDTH,
        BUTTON_HEIGHT,
        30,
        sf::Color::Magenta,
        5.0f);

    // Получаем ссылку на музыку из менеджера ресурсов
    music = &amp;amp;rm.getMusic(&amp;quot;music&amp;quot;);

    // Проверяем, что музыка загружена и готова к воспроизведению
    if (music-&amp;gt;getStatus() == sf::Music::Stopped &amp;amp;&amp;amp; music-&amp;gt;getDuration() == sf::Time::Zero) {
        throw std::runtime_error(&amp;quot;Музыкальный поток не инициализирован в ResourceManager&amp;quot;);
    }

    // Настраиваем зацикленное воспроизведение и запускаем музыку
    music-&amp;gt;setLoop(true);
    music-&amp;gt;play();
}

// Обновляет логику меню — в данном случае только анимацию заголовка
void MainMenu::update(float deltaTime) {
    titleText-&amp;gt;update(deltaTime);  // Анимация цвета заголовка
}

// Обрабатывает события от пользователя (например, нажатие мыши)
void MainMenu::handleEvent(const sf::Event&amp;amp; event) {
    // Получаем текущую позицию курсора
    sf::Vector2f mousePos(static_cast&amp;lt;float&amp;gt;(sf::Mouse::getPosition(window).x),
        static_cast&amp;lt;float&amp;gt;(sf::Mouse::getPosition(window).y));

    // Если произошло нажатие мыши
    if (event.type == sf::Event::MouseButtonPressed) {
        if (playButton-&amp;gt;isClicked(mousePos)) playButton-&amp;gt;pressAnimation();
        if (rulesButton-&amp;gt;isClicked(mousePos)) rulesButton-&amp;gt;pressAnimation();
        if (musicButton-&amp;gt;isClicked(mousePos)) musicButton-&amp;gt;pressAnimation();
        if (restartButton-&amp;gt;isClicked(mousePos)) restartButton-&amp;gt;pressAnimation();
        if (exitButton-&amp;gt;isClicked(mousePos)) exitButton-&amp;gt;pressAnimation();
    }

    // Если кнопка мыши отпущена
    if (event.type == sf::Event::MouseButtonReleased) {
        if (playButton-&amp;gt;isClicked(mousePos)) {
            playButton-&amp;gt;releaseAnimation();
            transition.startTransition(GameState::GAME);  // Переход к игре
        }
        if (rulesButton-&amp;gt;isClicked(mousePos)) {
            rulesButton-&amp;gt;releaseAnimation();
            transition.startTransition(GameState::RULES);  // Переход к правилам
        }
        if (musicButton-&amp;gt;isClicked(mousePos)) {
            musicButton-&amp;gt;releaseAnimation();
            isMusicOn = !isMusicOn;  // Переключаем состояние музыки
            musicButton-&amp;gt;setText(isMusicOn ? L&amp;quot;Музыка: Вкл&amp;quot; : L&amp;quot;Музыка: Выкл&amp;quot;);  // Обновляем текст кнопки
            if (isMusicOn &amp;amp;&amp;amp; music-&amp;gt;getStatus() != sf::Music::Playing) {
                music-&amp;gt;play();  // Включаем музыку
            }
            else if (!isMusicOn) {
                music-&amp;gt;stop();  // Выключаем музыку
            }
        }
        if (restartButton-&amp;gt;isClicked(mousePos)) {
            restartButton-&amp;gt;releaseAnimation();
            gameScreen.restartGame();  // Перезапускаем игру
            transition.startTransition(GameState::GAME);  // Переходим в игру
        }
        if (exitButton-&amp;gt;isClicked(mousePos)) {
            exitButton-&amp;gt;releaseAnimation();
            window.close();  // Закрываем окно приложения
        }
    }
}

// Отрисовывает всё содержимое главного меню
void MainMenu::draw(sf::RenderTarget&amp;amp; target, sf::RenderStates states) const {
    // Если фоновая текстура установлена — рисуем её
    if (useTexture) {
        target.draw(backgroundSprite, states);
    }
    else {
        // Иначе рисуем однотонный фон (в данном случае голубой)
        sf::RectangleShape background(sf::Vector2f(
            static_cast&amp;lt;float&amp;gt;(window.getSize().x),
            static_cast&amp;lt;float&amp;gt;(window.getSize().y)));
        background.setFillColor(sf::Color::Cyan);
        target.draw(background, states);
    }

    // Отрисовываем заголовок
    titleText-&amp;gt;draw(target, states);

    // Отрисовываем все кнопки
    target.draw(*playButton, states);
    target.draw(*rulesButton, states);
    target.draw(*musicButton, states);
    target.draw(*restartButton, states);
    target.draw(*exitButton, states);
}&lt;/pre&gt;
  &lt;h2 id=&quot;cRlU&quot;&gt;&lt;strong&gt;Создание игрового окна, класс  GameScreen&lt;/strong&gt;&lt;/h2&gt;
  &lt;p id=&quot;eJHF&quot;&gt;Игровое окно включает в себя:&lt;/p&gt;
  &lt;ul id=&quot;lD53&quot;&gt;
    &lt;li id=&quot;4WxW&quot;&gt;отрисовку поля игры,&lt;/li&gt;
    &lt;li id=&quot;PEiw&quot;&gt;анимации и интерфейс,&lt;/li&gt;
    &lt;li id=&quot;hfqg&quot;&gt;таймер прохождения,&lt;/li&gt;
    &lt;li id=&quot;9g8d&quot;&gt;обработку событий,&lt;/li&gt;
    &lt;li id=&quot;xBKR&quot;&gt;и даже отображение сообщения о победе.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;y99u&quot;&gt;Класс GameScreen — это наследник базового класса Screen, который управляет визуальными и логическими элементами экрана. Он включает следующие компоненты:&lt;/p&gt;
  &lt;ul id=&quot;Cvd4&quot;&gt;
    &lt;li id=&quot;zNqe&quot;&gt;FifteenPuzzle — основная игровая логика и поле;&lt;/li&gt;
    &lt;li id=&quot;txrt&quot;&gt;Button — кнопка возврата в меню;&lt;/li&gt;
    &lt;li id=&quot;yM8X&quot;&gt;ColorfulText — анимированный заголовок &amp;quot;Пятнашки&amp;quot;;&lt;/li&gt;
    &lt;li id=&quot;L19Q&quot;&gt;sf::Text, sf::RectangleShape — для уведомлений и таймера;&lt;/li&gt;
    &lt;li id=&quot;dL7a&quot;&gt;sf::Clock — измерение времени прохождения;&lt;/li&gt;
    &lt;li id=&quot;kNnv&quot;&gt;флаг isGameStarted — определяет, запущена ли игра.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;5HLD&quot;&gt;&lt;strong&gt;GameScreen.h&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;Ch5y&quot; data-lang=&quot;cpp&quot;&gt;#pragma once


#include &amp;quot;Screen.h&amp;quot;           
#include &amp;quot;FifteenPuzzle.h&amp;quot;    
#include &amp;quot;Button.h&amp;quot;           
#include &amp;quot;ColorfulText.h&amp;quot;     


class GameScreen : public Screen {
private:
    // Указатель на объект головоломки &amp;quot;Пятнашки&amp;quot;
    std::unique_ptr&amp;lt;FifteenPuzzle&amp;gt; puzzle;

    // Кнопка выхода в меню
    std::unique_ptr&amp;lt;Button&amp;gt; exitButton;

    // Цветной анимированный заголовок &amp;quot;Пятнашки&amp;quot;
    std::unique_ptr&amp;lt;ColorfulText&amp;gt; titleText;

    // Текстовое поле для отображения сообщений (например, победы)
    sf::Text text;

    // Прямоугольник-фон под текстовые уведомления
    sf::RectangleShape textBackground;

    // Текст таймера, показывающий время игры
    sf::Text timerText;

    // Фон под таймер
    sf::RectangleShape timerBackground;

    // Часы для измерения времени игры
    sf::Clock gameClock;

    // Флаг, указывающий, начал ли пользователь игру
    bool isGameStarted;

    // Метод инициализации элементов интерфейса
    void initialize() override;

public:
    /*
     Конструктор.
     Инициализирует игровой экран, связывает его с окном, состоянием и системой переходов.
     */
    GameScreen(sf::RenderWindow&amp;amp; window, GameState&amp;amp; state, Transition&amp;amp; transition);

    /*
     Обработка событий (нажатия клавиш, клики мыши и др.).
     */
    void handleEvent(const sf::Event&amp;amp; event) override;

    /*
      Отрисовка всех элементов экрана: головоломки, кнопок, текста и фона.
     */
    void draw(sf::RenderTarget&amp;amp; target, sf::RenderStates states) const override;

    /*
     Обновление состояния экрана (анимация заголовка, обновление таймера и т.д.).
     */
    void update(float deltaTime) override;

    /*
     Перезапуск игры: сброс позиций плиток, таймера, очистка сообщений.
     */
    void restartGame();
};&lt;/pre&gt;
  &lt;p id=&quot;cN9m&quot;&gt;&lt;strong&gt;GameScreen.cpp&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;Co2o&quot; data-lang=&quot;cpp&quot;&gt;#include &amp;quot;GameScreen.h&amp;quot;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;string&amp;gt;

// Константа для позиции Y заголовка 
constexpr float TITLE_Y_POS = 15.0f;

/*
 Конструктор класса GameScreen.
 Инициализирует игровой экран, связывает его с окном и системой состояний.
 */
GameScreen::GameScreen(sf::RenderWindow&amp;amp; window, GameState&amp;amp; state, Transition&amp;amp; transition)
    : Screen(window, state, transition),
    puzzle(std::make_unique&amp;lt;FifteenPuzzle&amp;gt;(100, 4)), // Создаем головоломку 4x4 с размером плитки 100
    isGameStarted(false) { // Игра ещё не начата
    initialize(); // Вызываем метод инициализации графических элементов
}

/*
 Метод инициализации всех графических элементов интерфейса:
 - Кнопка выхода в меню
 - Текстовые поля: сообщение о победе, таймер
 - Заголовок игры
 */
void GameScreen::initialize() {
    ResourceManager&amp;amp; rm = ResourceManager::getInstance();
    sf::Font&amp;amp; font = rm.getFont(&amp;quot;font&amp;quot;);

    if (font.getInfo().family.empty()) {
        throw std::runtime_error(&amp;quot;Не удалось загрузить шрифт &amp;#x27;font&amp;#x27; из ResourceManager&amp;quot;);
    }

    setupBackground(&amp;quot;background1&amp;quot;); // Устанавливаем фоновое изображение

    // Создаем кнопку &amp;quot;меню&amp;quot; с заданными стилями и размерами
    exitButton = std::make_unique&amp;lt;Button&amp;gt;(
        std::wstring(L&amp;quot;м е н ю&amp;quot;),
        sf::Vector2f(445, 600),
        font,
        sf::Color(64, 224, 208, 255),
        sf::Color(64, 224, 208, 255),
        sf::Color::Black,
        sf::Color(128, 128, 128),
        395.0f,
        50.0f,
        30u,
        sf::Color::Black,
        4.0f
    );

    // Устанавливаем шрифт для текстового уведомления (например, победы)
    text.setFont(font);

    // Центрируем головоломку по горизонтали, вертикальная позиция — фиксированная
    puzzle-&amp;gt;setPosition(
        (window.getSize().x - puzzle-&amp;gt;getBounds().width) / 2.0f,
        150.0f
    );

    // Цветной анимированный заголовок &amp;quot;Пятнашки&amp;quot;
    titleText = std::make_unique&amp;lt;ColorfulText&amp;gt;(&amp;quot;font1&amp;quot;, L&amp;quot;Пятнашки&amp;quot;, 100,
        sf::Vector2f(0, TITLE_Y_POS), window, true, false);

    // Фон под текстовые сообщения (полупрозрачный белый прямоугольник)
    textBackground.setFillColor(sf::Color(255, 255, 255, 200));

    // Настройка текста таймера
    timerText.setFont(font);
    timerText.setCharacterSize(45);
    timerText.setFillColor(sf::Color::Red);
    timerText.setPosition(
        puzzle-&amp;gt;getBounds().left + puzzle-&amp;gt;getBounds().width + 50.0f,
        puzzle-&amp;gt;getBounds().top + 200.0f
    );

    // Подложка под таймер (с полупрозрачным фоном)
    timerBackground.setFillColor(sf::Color(255, 255, 255, 128));
    sf::FloatRect timerRect = timerText.getLocalBounds();
    timerBackground.setSize(sf::Vector2f(timerRect.width + 20.0f, timerRect.height + 40.0f));
    timerBackground.setPosition(
        timerText.getPosition().x - 10.0f,
        timerText.getPosition().y
    );
}

/*
 Обработка событий от пользователя:
 - Нажатие Esc
 - Клик мыши на кнопке выхода
 - Перемещение плиток
 - Начало игры
 - Перезапуск
 */
void GameScreen::handleEvent(const sf::Event&amp;amp; event) {
    // Выход в меню при нажатии Esc
    if (event.type == sf::Event::KeyPressed &amp;amp;&amp;amp; event.key.code == sf::Keyboard::Escape) {
        transition.startTransition(GameState::MENU);
    }

    // Обработка нажатия на кнопку &amp;quot;меню&amp;quot;
    if (event.type == sf::Event::MouseButtonPressed) {
        sf::Vector2f mousePos = window.mapPixelToCoords(sf::Mouse::getPosition(window));
        if (exitButton-&amp;gt;isClicked(mousePos)) {
            exitButton-&amp;gt;pressAnimation(); // Анимация нажатия
        }
    }

    // Обработка отпускания кнопки мыши
    if (event.type == sf::Event::MouseButtonReleased) {
        sf::Vector2f mousePos = window.mapPixelToCoords(sf::Mouse::getPosition(window));
        if (exitButton-&amp;gt;isClicked(mousePos)) {
            exitButton-&amp;gt;releaseAnimation(); // Анимация отпускания
            transition.startTransition(GameState::MENU); // Переход в меню
        }
    }

    // Старт игры при первом взаимодействии (нажатие клавиши или клик)
    if (!isGameStarted &amp;amp;&amp;amp; (event.type == sf::Event::MouseButtonPressed || event.type == sf::Event::KeyPressed)) {
        isGameStarted = true;
        gameClock.restart(); // Запускаем таймер
    }

    // Передаем событие головоломке (например, клик на плитку)
    puzzle-&amp;gt;handleEvent(event, window);

    // Проверяем, решена ли головоломка
    if (puzzle-&amp;gt;isSolved() &amp;amp;&amp;amp; isGameStarted) {
        isGameStarted = false;

        // Получаем время прохождения
        sf::Time elapsed = gameClock.getElapsedTime();
        int minutes = static_cast&amp;lt;int&amp;gt;(elapsed.asSeconds()) / 60;
        int seconds = static_cast&amp;lt;int&amp;gt;(elapsed.asSeconds()) % 60;
        int milliseconds = static_cast&amp;lt;int&amp;gt;(elapsed.asMilliseconds()) % 1000;

        // Форматируем строку времени
        std::wstring timeString = std::wstring(L&amp;quot;Время решения: &amp;quot;) +
            (minutes &amp;lt; 10 ? L&amp;quot;0&amp;quot; : L&amp;quot;&amp;quot;) + std::to_wstring(minutes) + L&amp;quot;:&amp;quot; +
            (seconds &amp;lt; 10 ? L&amp;quot;0&amp;quot; : L&amp;quot;&amp;quot;) + std::to_wstring(seconds) + L&amp;quot;:&amp;quot; +
            (milliseconds &amp;lt; 100 ? L&amp;quot;0&amp;quot; : (milliseconds &amp;lt; 10 ? L&amp;quot;00&amp;quot; : L&amp;quot;&amp;quot;)) + std::to_wstring(milliseconds);

        // Отображаем сообщение о победе
        text.setString(L&amp;quot;                   Победа!!!\n&amp;quot; +
            timeString + L&amp;quot;\n&amp;quot; +
            L&amp;quot;Нажмите Esc или кнопку меню!&amp;quot;);
        text.setCharacterSize(50);
        text.setFillColor(sf::Color::Red);

        // Центрируем текст по окну
        sf::FloatRect textRect = text.getLocalBounds();
        text.setOrigin(textRect.left + textRect.width / 2.0f, textRect.top + textRect.height / 2.0f);
        text.setPosition(window.getSize().x / 2.0f, window.getSize().y / 2.0f);

        // Настраиваем фон под текст
        textBackground.setSize(sf::Vector2f(textRect.width + 40.0f, textRect.height + 40.0f));
        textBackground.setOrigin(textBackground.getSize().x / 2.0f, textBackground.getSize().y / 2.0f);
        textBackground.setPosition(window.getSize().x / 2.0f, window.getSize().y / 2.0f);

        // Скрываем таймер после победы
        timerText.setString(L&amp;quot;&amp;quot;);
        timerBackground.setSize(sf::Vector2f(0, 0));
    }

    // Перезапуск игры при нажатии R
    if (event.type == sf::Event::KeyPressed &amp;amp;&amp;amp; event.key.code == sf::Keyboard::R) {
        restartGame();
    }
}

/*
 Отрисовка всех элементов экрана:
 - Фон
 - Головоломка
 - Сообщения (победа)
 - Таймер
 - Кнопки
 - Заголовок
 */
void GameScreen::draw(sf::RenderTarget&amp;amp; target, sf::RenderStates states) const {
    target.clear(sf::Color::White); // Очищаем экран

    // Рисуем фон, если он есть
    if (useTexture) {
        target.draw(backgroundSprite, states);
    }

    target.draw(*puzzle, states);             // Рисуем головоломку
    target.draw(textBackground, states);      // Фон под текстом
    target.draw(text, states);                // Текст сообщений
    target.draw(timerBackground, states);     // Фон под таймером
    target.draw(timerText, states);           // Текст таймера
    target.draw(*exitButton, states);         // Кнопка выхода
    titleText-&amp;gt;draw(target, states);          // Анимированный заголовок
}

/*
 Перезапуск игры:
 - Сброс плиток в случайное положение
 - Сброс таймера
 - Очистка текстовых сообщений
 - Восстановление таймера
 */
void GameScreen::restartGame() {
    puzzle-&amp;gt;restart(); // Перемешиваем головоломку
    isGameStarted = false; // Сбрасываем флаг начала игры
    gameClock.restart(); // Сбрасываем таймер

    text.setString(L&amp;quot;&amp;quot;); // Очищаем текст победы
    text.setCharacterSize(30); // Сбрасываем размер текста

    textBackground.setSize(sf::Vector2f(0, 0)); // Скрываем фон под текстом

    // Восстанавливаем начальное значение таймера
    timerText.setString(L&amp;quot;Время: 00:00:000&amp;quot;);
    sf::FloatRect timerRect = timerText.getLocalBounds();
    timerBackground.setSize(sf::Vector2f(timerRect.width + 20.0f, timerRect.height + 40.0f));
    timerBackground.setPosition(
        timerText.getPosition().x - 10.0f,
        timerText.getPosition().y
    );
}

/*
 Обновление состояния экрана:
 - Анимация заголовка
 - Обновление таймера, если игра активна
 */
void GameScreen::update(float deltaTime) {
    titleText-&amp;gt;update(deltaTime); // Обновляем анимацию цветного заголовка

    if (isGameStarted) {
        sf::Time elapsed = gameClock.getElapsedTime();
        int minutes = static_cast&amp;lt;int&amp;gt;(elapsed.asSeconds()) / 60;
        int seconds = static_cast&amp;lt;int&amp;gt;(elapsed.asSeconds()) % 60;
        int milliseconds = static_cast&amp;lt;int&amp;gt;(elapsed.asMilliseconds()) % 1000;

        // Форматируем строку времени
        std::wstring timerString = std::wstring(L&amp;quot;Время: &amp;quot;) +
            (minutes &amp;lt; 10 ? L&amp;quot;0&amp;quot; : L&amp;quot;&amp;quot;) + std::to_wstring(minutes) + L&amp;quot;:&amp;quot; +
            (seconds &amp;lt; 10 ? L&amp;quot;0&amp;quot; : L&amp;quot;&amp;quot;) + std::to_wstring(seconds) + L&amp;quot;:&amp;quot; +
            (milliseconds &amp;lt; 100 ? L&amp;quot;0&amp;quot; : (milliseconds &amp;lt; 10 ? L&amp;quot;00&amp;quot; : L&amp;quot;&amp;quot;)) + std::to_wstring(milliseconds);
        timerText.setString(timerString);

        // Обновляем размер фона под таймер
        sf::FloatRect timerRect = timerText.getLocalBounds();
        timerBackground.setSize(sf::Vector2f(timerRect.width + 20.0f, timerRect.height + 20.0f));
        timerBackground.setPosition(
            timerText.getPosition().x - 10.0f,
            timerText.getPosition().y
        );
    }
}&lt;/pre&gt;
  &lt;h2 id=&quot;TnAu&quot;&gt;&lt;strong&gt;Перечисление GameState&lt;/strong&gt;&lt;/h2&gt;
  &lt;p id=&quot;JxLZ&quot;&gt;&lt;strong&gt;GameState.h&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;tijB&quot; data-lang=&quot;cpp&quot;&gt;#pragma once

enum class GameState { MENU, GAME, RULES };&lt;/pre&gt;
  &lt;p id=&quot;hJaW&quot;&gt;Это перечисление описывает &lt;strong&gt;три возможных состояния&lt;/strong&gt; игры:&lt;/p&gt;
  &lt;ul id=&quot;nV8O&quot;&gt;
    &lt;li id=&quot;sRAQ&quot;&gt;&lt;code&gt;MENU&lt;/code&gt; – главное меню.&lt;/li&gt;
    &lt;li id=&quot;KmlO&quot;&gt;&lt;code&gt;GAME&lt;/code&gt; – активный экран игры.&lt;/li&gt;
    &lt;li id=&quot;CqY5&quot;&gt;&lt;code&gt;RULES&lt;/code&gt; – экран с правилами.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;rx5K&quot;&gt;Оно используется для управления переходами между экранами и состояниями.&lt;/p&gt;
  &lt;h2 id=&quot;slrl&quot;&gt;Окно &amp;quot;Правила игры&amp;quot;, класс RulesScreen&lt;/h2&gt;
  &lt;p id=&quot;VBl5&quot;&gt;Класс RulesScreen — это экран с правилами игры. Он помогает игроку быстро понять суть и цель головоломки &lt;strong&gt;перед началом игры&lt;/strong&gt;. &lt;/p&gt;
  &lt;p id=&quot;9BW5&quot;&gt;Включает:&lt;/p&gt;
  &lt;ul id=&quot;4knn&quot;&gt;
    &lt;li id=&quot;6L5n&quot;&gt;анимированный заголовок;&lt;/li&gt;
    &lt;li id=&quot;pD5W&quot;&gt;текст с пошаговыми правилами;&lt;/li&gt;
    &lt;li id=&quot;fv6g&quot;&gt;пример изображения правильно собранного поля;&lt;/li&gt;
    &lt;li id=&quot;Tnnc&quot;&gt;кнопку возврата в главное меню.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;7SKS&quot;&gt;&lt;strong&gt;RulesScreen.h&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;aQTP&quot; data-lang=&quot;cpp&quot;&gt;#pragma once

#include &amp;quot;Screen.h&amp;quot;

#include &amp;quot;Button.h&amp;quot;

#include &amp;quot;ColorfulText.h&amp;quot;

/*
 Класс RulesScreen представляет экран с правилами игры.
 Отображает описание правил игры &amp;quot;Пятнашки&amp;quot;, содержит анимированный заголовок,
 изображение игрового поля и кнопку возврата в главное меню.
 */
class RulesScreen : public Screen {
private:
    // Анимированный цветной заголовок &amp;quot;Правила&amp;quot;
    std::unique_ptr&amp;lt;ColorfulText&amp;gt; titleText;

    // Текстовое поле для отображения правил игры
    sf::Text text;

    // Прямоугольник-фон под текст, чтобы он был лучше виден на фоне
    sf::RectangleShape textBackground;

    // Спрайт с изображением правильно собранного поля (пример)
    sf::Sprite numbersSprite;

    // Кнопка &amp;quot;Назад&amp;quot; для возврата в главное меню
    std::unique_ptr&amp;lt;Button&amp;gt; backButton;

    /**
     Инициализирует графические элементы экрана.
     Метод вызывается один раз при создании экрана.
     Настраивает: текст, фон, спрайт, кнопку и заголовок.
     */
    void initialize() override;

public:
    /**
     Конструктор экрана правил.
     window Ссылка на SFML окно
     state Ссылка на текущее состояние приложения
     transition Объект для управления переходами между экранами
     */
    RulesScreen(sf::RenderWindow&amp;amp; window, GameState&amp;amp; state, Transition&amp;amp; transition);

    /*
     Обрабатывает события пользователя (нажатия клавиш, клики мыши).
     event Обработанное событие SFML
     */
    void handleEvent(const sf::Event&amp;amp; event) override;

    /*
     Обновляет логику экрана (анимации, состояние кнопок и т.д.).
     deltaTime Время, прошедшее с последнего кадра (в секундах)
     */
    void update(float deltaTime) override;

    /*
     Отрисовывает все элементы экрана.
     target Цель отрисовки (обычно SFML окно)
     states Рендер-состояния (не используются по умолчанию)
     */
    void draw(sf::RenderTarget&amp;amp; target, sf::RenderStates states) const override;
};&lt;/pre&gt;
  &lt;p id=&quot;UFFo&quot;&gt;&lt;strong&gt;RulesScreen.cpp&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;iUoF&quot; data-lang=&quot;cpp&quot;&gt;#include &amp;quot;RulesScreen.h&amp;quot;
#include &amp;lt;stdexcept&amp;gt;
#include &amp;lt;iostream&amp;gt;

// Константа для позиции Y заголовка (чтобы избежать magic numbers)
constexpr float TITLE_Y_POS = 15.0f;

/*
 Конструктор класса RulesScreen.
 Инициализирует экран правил, связывает его с окном и системой состояний.
 */
RulesScreen::RulesScreen(sf::RenderWindow&amp;amp; window, GameState&amp;amp; state, Transition&amp;amp; transition)
    : Screen(window, state, transition) {
    initialize(); // Вызываем метод инициализации графических элементов
}

/*
 Метод инициализации всех графических элементов интерфейса:
 - Заголовок
 - Текст с правилами игры
 - Фон под текст
 - Изображение игрового поля
 - Кнопка возврата в меню
 */
void RulesScreen::initialize() {
    ResourceManager&amp;amp; rm = ResourceManager::getInstance();
    sf::Font&amp;amp; font = rm.getFont(&amp;quot;font&amp;quot;);

    if (font.getInfo().family.empty()) {
        throw std::runtime_error(&amp;quot;Не удалось загрузить шрифт &amp;#x27;font&amp;#x27; из ResourceManager&amp;quot;);
    }

    setupBackground(&amp;quot;background1&amp;quot;); // Устанавливаем фоновое изображение

    // Анимированный цветной заголовок &amp;quot;Пятнашки&amp;quot;
    titleText = std::make_unique&amp;lt;ColorfulText&amp;gt;(&amp;quot;font1&amp;quot;, L&amp;quot;Пятнашки&amp;quot;, 100,
        sf::Vector2f(0, TITLE_Y_POS), window, true, false);

    // Настройка текстового поля с правилами игры
    text.setFont(font);
    text.setString(
        L&amp;quot;   1. Игровое поле состоит из 15 пронумерованных\n&amp;quot;
        L&amp;quot;   плиток и одного пустого места.\n&amp;quot;
        L&amp;quot;   2. Цель игры перемещать плитки, расположить\n&amp;quot;
        L&amp;quot;   их в правильном порядке: (см. изображение справа)\n&amp;quot;
        L&amp;quot;   3. Перемещать можно только плитки,\n&amp;quot;
        L&amp;quot;   соседние с пустым местом.\n&amp;quot;
        L&amp;quot;   4. Используйте для перемещения плиток левую кнопку\n&amp;quot;
        L&amp;quot;   мыши.\n\n&amp;quot;
        L&amp;quot;   Удачи!&amp;quot;
    );
    text.setCharacterSize(30); // Размер шрифта
    text.setFillColor(sf::Color::Black); // Цвет текста — чёрный

    // Позиционируем текст по центру слева
    sf::FloatRect textRect = text.getLocalBounds();
    text.setOrigin(0, text.getLocalBounds().height / 2.0f);
    text.setPosition(30.0f, window.getSize().y / 2.0f);

    // Создаём фоновый прямоугольник под текстом для улучшения читаемости
    textBackground.setSize(sf::Vector2f(textRect.width + 40, textRect.height + 60));
    textBackground.setFillColor(sf::Color(255, 255, 255, 200)); // Полупрозрачный белый
    textBackground.setOrigin(0, textBackground.getSize().y / 2.0f);
    textBackground.setPosition(20.0f, window.getSize().y / 2.0f + 10.0f);

    // Загружаем изображение правильно собранного игрового поля
    sf::Texture&amp;amp; numTexture = rm.getTexture(&amp;quot;numbers&amp;quot;);
    if (numTexture.getSize().x == 0) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Ошибка: Не удалось загрузить изображение &amp;#x27;numbers&amp;#x27; из ResourceManager.\n&amp;quot;;
    }
    numbersSprite.setTexture(numTexture);
    sf::FloatRect numbersRect = numbersSprite.getLocalBounds();
    numbersSprite.setOrigin(numbersRect.width / 2.0f, numbersRect.height / 2.0f);
    numbersSprite.setScale(0.9f, 0.9f); // Уменьшаем немного изображение
    numbersSprite.setPosition(window.getSize().x * 0.75f + 100, window.getSize().y / 2.0f);

    // Создаём кнопку &amp;quot;Назад&amp;quot; внизу экрана
    backButton = std::make_unique&amp;lt;Button&amp;gt;(
        std::wstring(L&amp;quot;м е н ю&amp;quot;),
        sf::Vector2f(445, 600),
        font,
        sf::Color(64, 224, 208, 255),
        sf::Color(64, 224, 208, 255),
        sf::Color::Black,
        sf::Color(128, 128, 128),
        395.0f,
        50.0f,
        30u,
        sf::Color::Black,
        4.0f
    );
}

/*
 Обработка событий от пользователя:
 - Нажатие Esc — возврат в меню
 - Нажатие/отпускание кнопки &amp;quot;Назад&amp;quot;
 */
void RulesScreen::handleEvent(const sf::Event&amp;amp; event) {
    // Выход в меню при нажатии Esc
    if (event.type == sf::Event::KeyPressed &amp;amp;&amp;amp; event.key.code == sf::Keyboard::Escape) {
        transition.startTransition(GameState::MENU);
    }

    // Обработка нажатия на кнопку &amp;quot;Назад&amp;quot;
    if (event.type == sf::Event::MouseButtonPressed) {
        sf::Vector2f mousePos = window.mapPixelToCoords(sf::Mouse::getPosition(window));
        if (backButton-&amp;gt;isClicked(mousePos)) {
            backButton-&amp;gt;pressAnimation(); // Анимация нажатия
        }
    }

    // Обработка отпускания кнопки мыши
    if (event.type == sf::Event::MouseButtonReleased) {
        sf::Vector2f mousePos = window.mapPixelToCoords(sf::Mouse::getPosition(window));
        if (backButton-&amp;gt;isClicked(mousePos)) {
            backButton-&amp;gt;releaseAnimation(); // Анимация отпускания
            transition.startTransition(GameState::MENU); // Переход в меню
        }
    }
}

/*
 Отрисовка всех элементов экрана:
 - Фон
 - Текст с правилами
 - Фон под текстом
 - Изображение игрового поля
 - Кнопка &amp;quot;Назад&amp;quot;
 - Анимированный заголовок
 */
void RulesScreen::draw(sf::RenderTarget&amp;amp; target, sf::RenderStates states) const {
    target.clear(sf::Color::White); // Очищаем экран

    // Рисуем фон, если он есть
    if (useTexture) {
        target.draw(backgroundSprite, states);
    }

    target.draw(textBackground, states);     // Рисуем фон под текстом
    target.draw(numbersSprite, states);      // Рисуем изображение игрового поля
    target.draw(text, states);               // Рисуем текст с правилами
    target.draw(*backButton, states);        // Рисуем кнопку &amp;quot;Назад&amp;quot;
    titleText-&amp;gt;draw(target, states);         // Рисуем анимированный заголовок
}

/*
 Обновление состояния экрана:
 - Обновление анимации заголовка
 */
void RulesScreen::update(float deltaTime) {
    titleText-&amp;gt;update(deltaTime); // Обновляем анимацию цветного заголовка
}&lt;/pre&gt;
  &lt;h2 id=&quot;riTd&quot;&gt;main — Сердце игры &amp;quot;Пятнашки&amp;quot;&lt;/h2&gt;
  &lt;p id=&quot;zdvO&quot;&gt;Файл main.cpp — это отправная точка всей игры. Он выполняет ключевые задачи: загружает ресурсы, создаёт окно, переключает экраны, обрабатывает события и управляет рендерингом.&lt;/p&gt;
  &lt;p id=&quot;Yuup&quot;&gt;&lt;strong&gt;main.cpp&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;qLK8&quot; data-lang=&quot;cpp&quot;&gt;#include &amp;lt;SFML/Graphics.hpp&amp;gt;
#include &amp;lt;SFML/Audio.hpp&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;quot;MainMenu.h&amp;quot;
#include &amp;quot;GameScreen.h&amp;quot;
#include &amp;quot;RulesScreen.h&amp;quot;
#include &amp;quot;ResourceManager.h&amp;quot;
#include &amp;quot;Screen.h&amp;quot;
#include &amp;quot;resource.h&amp;quot; // Подключаем ресурсы из .rc файла (например, иконки, текстуры)

// Константы для размера окна
constexpr unsigned int WINDOW_WIDTH = 1280;
constexpr unsigned int WINDOW_HEIGHT = 720;

// Ограничение дельта-времени, чтобы предотвратить большие скачки времени между кадрами
constexpr float MAX_DELTA_TIME = 1.0f / 30.0f;

/*
 Инициализация всех игровых ресурсов.
 Метод загружает текстуры, шрифты, музыку и другие ресурсы из файла ресурсов (.rc).
 Если какой-то ресурс не загружен — выбрасывается исключение.
 */
void initializeResources() {
    ResourceManager&amp;amp; rm = ResourceManager::getInstance();

    if (!rm.loadTexture(&amp;quot;puzzle&amp;quot;, IDR_TEXTURE1)) {
        throw std::runtime_error(&amp;quot;Не удалось загрузить текстуру &amp;#x27;puzzle&amp;#x27;&amp;quot;);
    }
    if (!rm.loadTexture(&amp;quot;background&amp;quot;, IDR_TEXTURE2)) {
        throw std::runtime_error(&amp;quot;Не удалось загрузить текстуру &amp;#x27;background&amp;#x27;&amp;quot;);
    }
    if (!rm.loadTexture(&amp;quot;background1&amp;quot;, IDR_TEXTURE3)) {
        throw std::runtime_error(&amp;quot;Не удалось загрузить текстуру &amp;#x27;background1&amp;#x27;&amp;quot;);
    }
    if (!rm.loadTexture(&amp;quot;background2&amp;quot;, IDR_TEXTURE4)) {
        throw std::runtime_error(&amp;quot;Не удалось загрузить текстуру &amp;#x27;background2&amp;#x27;&amp;quot;);
    }
    if (!rm.loadTexture(&amp;quot;numbers&amp;quot;, IDR_TEXTURE5)) {
        throw std::runtime_error(&amp;quot;Не удалось загрузить текстуру &amp;#x27;numbers&amp;#x27;&amp;quot;);
    }
    if (!rm.loadMusic(&amp;quot;music&amp;quot;, IDR_SOUND1)) {
        throw std::runtime_error(&amp;quot;Не удалось загрузить звук &amp;#x27;music&amp;#x27;&amp;quot;);
    }
    if (!rm.loadFont(&amp;quot;font&amp;quot;, IDR_FONT1)) {
        throw std::runtime_error(&amp;quot;Не удалось загрузить шрифт &amp;#x27;font&amp;#x27;&amp;quot;);
    }
    if (!rm.loadFont(&amp;quot;font1&amp;quot;, IDR_FONT2)) {
        throw std::runtime_error(&amp;quot;Не удалось загрузить шрифт &amp;#x27;font1&amp;#x27;&amp;quot;);
    }
}

/*
 Точка входа в приложение.
 Создаёт окно игры, инициализирует ресурсы, создаёт экраны (меню, игра, правила),
 запускает основной цикл отрисовки и обработки событий.
 */
int main() {
    system(&amp;quot;chcp 1251&amp;quot;); // Устанавливаем кодовую страницу Windows-1251 для корректного вывода в консоль

    try {
        // Загружаем все игровые ресурсы
        initializeResources();

        // Создаем главное окно приложения
        sf::RenderWindow window(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), L&amp;quot;Пятнашки&amp;quot;);
        window.setFramerateLimit(60); // Ограничиваем частоту кадров до 60 FPS

        // Загружаем и устанавливаем иконку окна
        ResourceManager&amp;amp; rm = ResourceManager::getInstance();
        sf::Image icon = rm.getImage(&amp;quot;puzzle&amp;quot;);

        if (icon.getSize().x == 0 || icon.getSize().y == 0) {
            throw std::runtime_error(&amp;quot;Ошибка: не удалось загрузить иконку &amp;#x27;puzzle&amp;#x27; из ресурсов!&amp;quot;);
        }

        window.setIcon(icon.getSize().x, icon.getSize().y, icon.getPixelsPtr());

        // Создаем игровые экраны
        GameState state = GameState::MENU; // Начальное состояние — главное меню
        Transition transition(window); // Объект для анимации перехода между экранами

        GameScreen gameScreen(window, state, transition);   // Игровой экран
        MainMenu mainMenu(window, state, transition, gameScreen); // Главное меню
        RulesScreen rulesScreen(window, state, transition); // Экран с правилами

        // Указатель на текущий активный экран
        Screen* currentScreen = &amp;amp;mainMenu;

        // Часы для измерения времени между кадрами
        sf::Clock clock;

        while (window.isOpen()) {
            // Вычисляем время с последнего кадра (ограничиваем его)
            float deltaTime = clock.restart().asSeconds();
            if (deltaTime &amp;gt; MAX_DELTA_TIME) deltaTime = MAX_DELTA_TIME;

            // Обработка событий SFML
            sf::Event event;
            while (window.pollEvent(event)) {
                if (event.type == sf::Event::Closed) {
                    window.close(); // Закрытие окна по нажатию крестика
                }
                currentScreen-&amp;gt;handleEvent(event); // Передаем событие текущему экрану
            }

            // Обновляем состояние перехода между экранами
            bool isTransitioning = transition.update(state, deltaTime);

            // Обновляем текущий экран
            currentScreen-&amp;gt;update(deltaTime);

            // Отрисовка
            window.clear(); // Очищаем окно
            window.draw(*currentScreen); // Рисуем текущий экран
            if (isTransitioning) {
                window.draw(transition); // Рисуем анимацию перехода поверх экрана
            }
            window.display(); // Отображаем всё на экране

            // Переключение экранов в зависимости от состояния
            switch (state) {
            case GameState::MENU:
                currentScreen = &amp;amp;mainMenu;
                break;
            case GameState::GAME:
                currentScreen = &amp;amp;gameScreen;
                break;
            case GameState::RULES:
                currentScreen = &amp;amp;rulesScreen;
                break;
            default:
                break;
            }
        }
    }
    catch (const std::exception&amp;amp; e) {
        // Ловим и выводим ошибки
        std::cerr &amp;lt;&amp;lt; e.what() &amp;lt;&amp;lt; std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}&lt;/pre&gt;
  &lt;p id=&quot;dnOV&quot;&gt;main.cpp — это диспетчер игры. Он не содержит логику самих экранов, а только управляет их жизненным циклом и переключениями. Благодаря такой структуре код легко поддерживать и расширять — можно добавлять новые экраны или менять ресурсы, не затрагивая остальное.&lt;/p&gt;
  &lt;figure id=&quot;jbFJ&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/1a/83/1a831b8d-0eb2-43e0-9733-7052ecd7de3e.gif&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;4lCo&quot;&gt;&lt;a href=&quot;https://github.com/cnet-sudo/fifteen_puzzle&quot; target=&quot;_blank&quot;&gt;Клонировать репозиторий &lt;/a&gt;&lt;/p&gt;
  &lt;p id=&quot;brRM&quot;&gt;&lt;a href=&quot;https://t.me/ue4_dev_soft/191&quot; target=&quot;_blank&quot;&gt;Скачать файлы&lt;/a&gt;&lt;/p&gt;
  &lt;p id=&quot;h3h3&quot;&gt;&lt;a href=&quot;https://t.me/C_Verhovcevo_NVK&quot; target=&quot;_blank&quot;&gt;Наш телеграмм&lt;/a&gt;&lt;/p&gt;
  &lt;p id=&quot;vvNL&quot;&gt;&lt;a href=&quot;https://www.youtube.com/@%D0%98%D1%81%D0%BA%D1%83%D1%81%D1%81%D1%82%D0%B2%D0%BE%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%D0%BD%D0%B0%D1%8F%D0%B7%D1%8B&quot; target=&quot;_blank&quot;&gt;Наш ютюб &lt;/a&gt;&lt;/p&gt;

</content></entry><entry><id>gamedeveloper:CKUfS-Q-mGT</id><link rel="alternate" type="text/html" href="https://teletype.in/@gamedeveloper/CKUfS-Q-mGT?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=gamedeveloper"></link><title>Менеджер ресурсов и динамический текст на SFML</title><published>2025-04-19T12:37:02.146Z</published><updated>2025-04-19T12:37:02.146Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/90/53/90536cf3-3b26-4433-afcd-3c9347319717.png"></media:thumbnail><category term="mul-timedijnaya-biblioteka-sfml-c" label="Мультимедийная библиотека  SFML C++"></category><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/d9/cd/d9cdf266-65c4-4689-bb18-a75f575d97cf.jpeg&quot;&gt;Когда Вы только начинаете изучать игровую разработку на C++ с использованием SFML, перед многими из Вас встаёт вопрос: как правильно организовать хранение и загрузку игровых ресурсов? Сначала всё кажется просто - загружаешь текстуру здесь, шрифт там, звук где-то ещё. Но когда Ваш проект начинает расти, Вы сталкиваетесь с реальными проблемами.</summary><content type="html">
  &lt;figure id=&quot;peqr&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d9/cd/d9cdf266-65c4-4689-bb18-a75f575d97cf.jpeg&quot; width=&quot;1344&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;g4NA&quot;&gt;Когда Вы только начинаете изучать игровую разработку на C++ с использованием SFML, перед многими из Вас встаёт вопрос: как правильно организовать хранение и загрузку игровых ресурсов? Сначала всё кажется просто - загружаешь текстуру здесь, шрифт там, звук где-то ещё. Но когда Ваш проект начинает расти, Вы сталкиваетесь с реальными проблемами.&lt;/p&gt;
  &lt;p id=&quot;Q27P&quot;&gt;Одна и та же текстурная карта может загружаться в память трижды - для главного меню, для уровня и для редактора. В этом случае память расходуется неэффективно, а код превращается  в спагетти из вызовов loadFromFile, разбросанных по всей кодовой базе. Тогда Вы начинаете понимать, что нужен системный подход.&lt;/p&gt;
  &lt;p id=&quot;jp5J&quot;&gt;Какие варианты есть, это: хранение ресурсов в отдельных классах, глобальные переменные (да, я знаю, что это плохо), система с ручным управлением жизненного цикла.&lt;/p&gt;
  &lt;p id=&quot;eYry&quot;&gt;Каждый из этих подходов имеет свои недостатки. Либо ресурсы живут слишком долго, либо их освобождение происходит не в тот момент, либо доступ к ним неудобный.&lt;/p&gt;
  &lt;p id=&quot;7V7d&quot;&gt;Самый оптимальный вариант, это создание класса сингелтона, для управления ресурсами.&lt;/p&gt;
  &lt;p id=&quot;WBkO&quot;&gt;Но почему бы не упаковать все ресурсы прямо в исполняемый файл?&lt;/p&gt;
  &lt;p id=&quot;ijzh&quot;&gt;Мы создадим менеджер ресурсов, который использует встроенные возможности Windows.&lt;/p&gt;
  &lt;p id=&quot;Hn0r&quot;&gt;Стоит сразу оговориться — это решение работает исключительно под Windows. Оно использует системные API-функции для доступа к ресурсам, встроенным в EXE-файл.&lt;/p&gt;
  &lt;p id=&quot;O3nf&quot;&gt;Главное преимущество этой системы — все ресурсы действительно хранятся внутри EXE-файла. Вы не представляете, как приятно, когда твоя игра представляет собой всего один файл, который можно скопировать куда угодно, и он просто работает. Никаких &amp;quot;где-то потерялась текстура&amp;quot;, &amp;quot;не найден шрифт&amp;quot; и прочих проблем с путями.&lt;/p&gt;
  &lt;p id=&quot;vMG3&quot;&gt;Конечно, у этого подхода есть свои ограничения:&lt;/p&gt;
  &lt;p id=&quot;jXH4&quot;&gt;1.    Размер EXE-файла увеличивается (но современные компьютеры с этим легко справляются)&lt;/p&gt;
  &lt;p id=&quot;Yap2&quot;&gt;2.    Невозможно изменить ресурсы без перекомпиляции&lt;/p&gt;
  &lt;p id=&quot;CrMS&quot;&gt;3.    Работает только под Windows&lt;/p&gt;
  &lt;p id=&quot;b1eJ&quot;&gt;Но для многих сценариев разработки игр эти ограничения не критичны. В моём случае плюсы перевешивают:&lt;/p&gt;
  &lt;p id=&quot;JS19&quot;&gt;1.     Простота распространения (один файл!)&lt;/p&gt;
  &lt;p id=&quot;1y6b&quot;&gt;2.     Защита ресурсов от случайного изменения&lt;/p&gt;
  &lt;p id=&quot;rBOY&quot;&gt;3.     Гарантия, что все нужные файлы на месте&lt;/p&gt;
  &lt;p id=&quot;vjXZ&quot;&gt;Если вам нужна кроссплатформенность, этот подход не подойдёт. Но если вы, как и я, разрабатываете игры исключительно под Windows и цените простоту распространения, то встроенные ресурсы Windows — отличное решение. В следующих разделах я подробно расскажу, как реализовать эту систему с нуля.&lt;/p&gt;
  &lt;h3 id=&quot;BFuG&quot;&gt;&lt;strong&gt;Структура проекта в Visual Studio 2022&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;lpMc&quot;&gt;📁 Проект/&lt;/p&gt;
  &lt;p id=&quot;JgeD&quot;&gt;├── 📄 main.cpp                        # Главный файл с основным циклом&lt;/p&gt;
  &lt;p id=&quot;WuY9&quot;&gt;├── 📄 ResourceManager.h      # Заголовочный файл менеджера ресурсов&lt;/p&gt;
  &lt;p id=&quot;pTTZ&quot;&gt;├── 📄 ResourceManager.cpp # Реализация менеджера ресурсов&lt;/p&gt;
  &lt;p id=&quot;Sk5f&quot;&gt;├── 📄 ColorfulText.h               # Заголовочный файл анимированного текста&lt;/p&gt;
  &lt;p id=&quot;x72v&quot;&gt;├── 📄 ColorfulText.cpp          # Реализация анимированного текста&lt;/p&gt;
  &lt;p id=&quot;9J49&quot;&gt;├── 📄 resource.h                    # Идентификаторы ресурсов&lt;/p&gt;
  &lt;p id=&quot;ajUT&quot;&gt;└── 📄 resources.rc                 # Скрипт компиляции ресурсов&lt;/p&gt;
  &lt;h3 id=&quot;Ym3p&quot;&gt;Файлы ресурсов&lt;/h3&gt;
  &lt;figure id=&quot;FeQk&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a2/2f/a22fb23a-e6e8-4790-8e7f-841342837c93.png&quot; width=&quot;480&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;SPXv&quot;&gt;Ожидаемый результат от программы&lt;/h3&gt;
  &lt;figure id=&quot;pV48&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/0e/55/0e5567a0-6160-409b-b2dc-684bb75f1eb8.gif&quot; width=&quot;800&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;dYgq&quot;&gt;&lt;strong&gt;Начинаем творить чудеса &lt;/strong&gt;&lt;/h2&gt;
  &lt;p id=&quot;iKK3&quot;&gt;Сначала создаём в ручную файлы ресурсов используя редактор кода С++ нашей IDE. Надеюсь Вы уже справились с установкой библиотеки SFML, если нет почитайте статьи выше. &lt;/p&gt;
  &lt;h3 id=&quot;DM1z&quot;&gt;resource.h&lt;/h3&gt;
  &lt;pre id=&quot;ZQQa&quot; data-lang=&quot;cpp&quot;&gt;#pragma once

#define IDR_FONT1      201  // Шрифт
#define IDR_FONT2      202  // Шрифт

#define IDR_TEXTURE1   301  // Текстура&lt;/pre&gt;
  &lt;h3 id=&quot;UW48&quot;&gt;resource.rc&lt;/h3&gt;
  &lt;pre id=&quot;TItM&quot; data-lang=&quot;cpp&quot;&gt;#include &amp;quot;resource.h&amp;quot;

// ресурсы по умолчанию
IDR_TEXTURE1   RCDATA  &amp;quot;assets/puzzle.png&amp;quot;
IDR_FONT1      RCDATA  &amp;quot;assets/arial.ttf&amp;quot;
IDR_FONT2      RCDATA  &amp;quot;assets/glv.ttf&amp;quot;
IDR_ICON1      ICON    &amp;quot;assets/puzzle.ico&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;FtJd&quot;&gt;В нашей программе все ресурсы находятся в отдельной папке  assets, которая в свою очередь находится в корневом каталоге проекта. &lt;/p&gt;
  &lt;p id=&quot;lJo8&quot;&gt;Теперь создадим класс управления ресурсами.&lt;/p&gt;
  &lt;h3 id=&quot;hvX1&quot;&gt;ResourceManager.h&lt;/h3&gt;
  &lt;pre id=&quot;ZtHm&quot; data-lang=&quot;cpp&quot;&gt;#pragma once

#include &amp;lt;SFML/Graphics.hpp&amp;gt;
#include &amp;lt;SFML/Audio.hpp&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;unordered_map&amp;gt;
#include &amp;lt;memory&amp;gt;
#include &amp;lt;optional&amp;gt;
#include &amp;lt;source_location&amp;gt;
#include &amp;lt;windows.h&amp;gt;

// Класс для управления ресурсами (шрифты, текстуры, звуки, музыка)
class ResourceManager {
public:
    // Получение единственного экземпляра (паттерн Singleton)
    static ResourceManager&amp;amp; getInstance() noexcept;

    // Загрузка шрифта по идентификатору ресурса
    [[nodiscard]] bool loadFont(std::string_view id, int resourceId,
        const std::source_location&amp;amp; loc = std::source_l
        ocation::current()) noexcept;

    // Загрузка звукового буфера по идентификатору ресурса
    [[nodiscard]] bool loadSound(std::string_view id, int resourceId,
        const std::source_location&amp;amp; loc = std::sour
        ce_location::current()) noexcept;

    // Загрузка текстуры по идентификатору ресурса
    [[nodiscard]] bool loadTexture(std::string_view id, int resourceId,
        const std::source_location&amp;amp; loc = std::s
        ource_location::current()) noexcept;

    // Загрузка музыки по идентификатору ресурса
    [[nodiscard]] bool loadMusic(std::string_view id, int resourceId,
        const std::source_location&amp;amp; loc = s
        td::source_location::current()) noexcept;

    // Получение шрифта по идентификатору (возвращаем указатель)
    [[nodiscard]] std::optional&amp;lt;sf::Font*&amp;gt; getFont(std::string_view id,
        const std::source_location&amp;amp; loc
         = std::source_location::current()) noexcept;

    // Получение звукового буфера по идентификатору (возвращаем указатель)
    [[nodiscard]] std::opti
    onal&amp;lt;sf::SoundBuffer*&amp;gt; getSoundBuffer(std::string_view id,
        const std::source_location&amp;amp; 
        loc = std::source_location::current()) noexcept;

    // Получение текстуры по идентификатору (возвращаем указатель)
    [[nodiscard]] std::optional&amp;lt;
    sf::Texture*&amp;gt; getTexture(std::string_view id,
        const std::source_locat
        ion&amp;amp; loc = std::source_location::current()) noexcept;

    // Получение изображения из текстуры по идентификатору
    [[nodiscard]] std::optional&amp;lt;sf::Image&amp;gt; getImage(std::string_view id,
        const std::source_l
        ocation&amp;amp; loc = std::source_location::current()) noexcept;

    //Получение музыкального потока по идентификатору (возвращаем указатель)
    [[nodiscard]] std::optional&amp;lt;sf::Music*&amp;gt; getMusic(std::string_view id,
        const std::sourc
        e_location&amp;amp; loc = std::source_location::current()) noexcept;

    // Запрещаем копирование и перемещение
    ResourceManager(const ResourceManager&amp;amp;) = delete;
    ResourceManager&amp;amp; operator=(const ResourceManager&amp;amp;) = delete;
    ResourceManager(ResourceManager&amp;amp;&amp;amp;) = delete;
    ResourceManager&amp;amp; operator=(ResourceManager&amp;amp;&amp;amp;) = delete;

private:
    ResourceManager() = default; // Приватный конструктор для Singleton

    // Вспомогательный класс для RAII-управления ресурсами Windows API
    class ResourceHandle {
    public:
        ResourceHandle(HRSRC resource, HGLOBAL handle) noexcept : 
        resource_(resource), handle_(handle) {}
        ~ResourceHandle() { if (handle_) FreeResource(handle_); }
        [[nodiscard]] const void* data() const noexcept { 
        return LockResource(handle_); }
        [[nodiscard]] DWORD size() const noexcept { 
        return SizeofResource(NULL, resource_); }
    private:
        HRSRC resource_;
        HGLOBAL handle_;
    };

    // Шаблонный метод для загрузки ресурса
    template&amp;lt;typename T&amp;gt;
    [[nodiscard]] bool loadResource(int resourceId, T&amp;amp; target,
        const std::source_location&amp;amp; loc = std::source_location::current()) 
        noexcept;

    // Хранилища ресурсов
    std::unordered_map&amp;lt;std::string, sf::Font&amp;gt; fonts_; // Шрифты
    // Звуковые буферы
    std::unordered_map&amp;lt;std::string, sf::SoundBuffer&amp;gt; buffers_; 
    std::unordered_map&amp;lt;std::string, sf::Texture&amp;gt; textures_; // Текстуры
    // Музыкальные потоки
    std::unordered_map&amp;lt;std::string, std::unique_ptr&amp;lt;sf::Music&amp;gt;&amp;gt; 
    musicStreams_; 
    // Кэш изображений
    std::unordered_map&amp;lt;std::string, sf::Image&amp;gt; imageCache_;
};&lt;/pre&gt;
  &lt;h3 id=&quot;rdSd&quot;&gt;ResourceManager.cpp&lt;/h3&gt;
  &lt;pre id=&quot;kafi&quot; data-lang=&quot;cpp&quot;&gt;#include &amp;quot;ResourceManager.h&amp;quot;
#include &amp;lt;iostream&amp;gt;

// Получение единственного экземпляра менеджера ресурсов (синглтон)
ResourceManager&amp;amp; ResourceManager::getInstance() noexcept {
    static ResourceManager instance;
    return instance;
}

// Шаблонный метод для загрузки ресурса любого типа
template&amp;lt;typename T&amp;gt;
bool ResourceManager::loadResource(int resourceId, 
T&amp;amp; target, const std::source_location&amp;amp; loc) noexcept {
    // Поиск ресурса в исполняемом файле
    HRSRC resource = FindResource(NULL, MAKEINTRESOURCE(
    resourceId), RT_RCDATA);
    if (!resource) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Ошибка в &amp;quot; &amp;lt;&amp;lt; loc.function_name() 
        &amp;lt;&amp;lt; &amp;quot; (строка &amp;quot; &amp;lt;&amp;lt; loc.line()
            &amp;lt;&amp;lt; &amp;quot;): Не удалось найти ресурс с ID &amp;quot; &amp;lt;&amp;lt; resourceId &amp;lt;&amp;lt; &amp;#x27;\n&amp;#x27;;
        return false;
    }

    // Загрузка ресурса с использованием RAII
    HGLOBAL handle = LoadResource(NULL, resource);
    if (!handle) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Ошибка в &amp;quot; &amp;lt;&amp;lt; loc.function
        _name() &amp;lt;&amp;lt; &amp;quot; (строка &amp;quot; &amp;lt;&amp;lt; loc.line()
            &amp;lt;&amp;lt; &amp;quot;): Не удалось загрузить ресу
            рс с ID &amp;quot; &amp;lt;&amp;lt; resourceId &amp;lt;&amp;lt; &amp;#x27;\n&amp;#x27;;
        return false;
    }

    // Использование RAII-обертки для управления ресурсом
    ResourceHandle resourceHandle(resource, handle);
    const void* data = resourceHandle.data();
    if (!data) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Ошибка в &amp;quot; &amp;lt;&amp;lt; loc
        .function_name() &amp;lt;&amp;lt; &amp;quot; (строка &amp;quot; &amp;lt;&amp;lt; loc.line()
            &amp;lt;&amp;lt; &amp;quot;): Не удалось заблокиро
            вать ресурс с ID &amp;quot; &amp;lt;&amp;lt; resourceId &amp;lt;&amp;lt; &amp;#x27;\n&amp;#x27;;
        return false;
    }

    // Получение размера ресурса и загрузка в целевой объект
    DWORD size = resourceHandle.size();
    return target.loadFromMemory(data, size);
}

// Загрузка шрифта из ресурсов
bool ResourceManager::loa
dFont(std::string_view id, int resourceId, 
const std::source_location&amp;amp; loc) noexcept {
    sf::Font font;
    if (!loadResource(resourceId, font, loc)) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Оши
        бка в &amp;quot; &amp;lt;&amp;lt; loc.function_name() &amp;lt;&amp;lt; &amp;quot; (строка &amp;quot; &amp;lt;&amp;lt; loc.line()
            &amp;lt;&amp;lt; &amp;quot;): Не удалось загрузить шрифт &amp;#x27;&amp;quot; &amp;lt;&amp;lt; id &amp;lt;&amp;lt; &amp;quot;&amp;#x27;\n&amp;quot;;
        return false;
    }
    // Сохранение шрифта в словаре под указанным идентификатором
    fonts_[std::string(id)] = std::move(font);
    return true;
}

// Загрузка звукового буфера из ресурсов
bool Resource
Manager::loadSound(std::string_view id, int resourceId, const 
std::source_location&amp;amp; loc) noexcept {
    sf::SoundBuffer buffer;
    if (!loadResource(resourceId, buffer, loc)) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Ошибка в &amp;quot; &amp;lt;&amp;lt; loc.function_name() &amp;lt;&amp;lt; &amp;quot; (строка &amp;quot; 
        &amp;lt;&amp;lt; loc.line()
            &amp;lt;&amp;lt; &amp;quot;): Не удалось загрузить звук &amp;#x27;&amp;quot; &amp;lt;&amp;lt; id &amp;lt;&amp;lt; &amp;quot;&amp;#x27;\n&amp;quot;;
        return false;
    }
    // Сохранение звукового буфера в словаре
    buffers_[std::string(id)] = std::move(buffer);
    return true;
}

// Загрузка текстуры из ресурсов
bool ResourceManager::loadTexture(std::string_view id, int resourceId, 
const std::source_location&amp;amp; loc) noexcept {
    sf::Texture texture;
    if (!loadResource(resourceId, texture, loc)) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Ошибка в &amp;quot; &amp;lt;&amp;lt; loc.function_name() &amp;lt;&amp;lt; &amp;quot; (строка &amp;quot; 
        &amp;lt;&amp;lt; loc.line()
            &amp;lt;&amp;lt; &amp;quot;): Не удалось загрузить текстуру &amp;#x27;&amp;quot; &amp;lt;&amp;lt; id &amp;lt;&amp;lt; &amp;quot;&amp;#x27;\n&amp;quot;;
        return false;
    }
    // Сохранение текстуры в словаре
    textures_[std::string(id)] = std::move(texture);
    return true;
}

// Загрузка музыки из ресурсов (особый случай, так как Music в SFML 
//работает с потоками)
bool ResourceManager::loadMusic(std::string_view id, int resourceId, 
const std::source_location&amp;amp; loc) noexcept {
    // Поиск музыкального ресурса
    HRSRC resource = FindResource(NULL, MAKEINTRESOURCE(resourceId), 
    RT_RCDATA);
    if (!resource) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Ошибка в &amp;quot; &amp;lt;&amp;lt; loc.function_name() &amp;lt;&amp;lt; &amp;quot; (строка &amp;quot; 
        &amp;lt;&amp;lt; loc.line()
            &amp;lt;&amp;lt; &amp;quot;): Не удалось найти музыкальный ресурс &amp;#x27;&amp;quot; &amp;lt;&amp;lt; id &amp;lt;&amp;lt; &amp;quot;&amp;#x27;\n&amp;quot;;
        return false;
    }

    // Загрузка ресурса
    HGLOBAL handle = LoadResource(NULL, resource);
    if (!handle) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Ошибка в &amp;quot; &amp;lt;&amp;lt; loc.function_name() &amp;lt;&amp;lt; &amp;quot; (строка &amp;quot; 
        &amp;lt;&amp;lt; loc.line()
            &amp;lt;&amp;lt; &amp;quot;): Не удалось загрузить музыкальный ресурс &amp;#x27;&amp;quot; &amp;lt;&amp;lt; id 
            &amp;lt;&amp;lt; &amp;quot;&amp;#x27;\n&amp;quot;;
        return false;
    }

    // Использование RAII-обертки
    ResourceHandle resourceHandle(resource, handle);
    const void* data = resourceHandle.data();
    if (!data) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Ошибка в &amp;quot; &amp;lt;&amp;lt; loc.function_name() &amp;lt;&amp;lt; &amp;quot; (строка &amp;quot; 
        &amp;lt;&amp;lt; loc.line()
            &amp;lt;&amp;lt; &amp;quot;): Не удалось заблокировать музыкальный ресурс &amp;#x27;&amp;quot; &amp;lt;&amp;lt; id 
            &amp;lt;&amp;lt; &amp;quot;&amp;#x27;\n&amp;quot;;
        return false;
    }

    // Создание музыкального потока из данных в памяти
    DWORD size = resourceHandle.size();
    auto music = std::make_unique&amp;lt;sf::Music&amp;gt;();
    if (!music-&amp;gt;openFromMemory(data, size)) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Ошибка в &amp;quot; &amp;lt;&amp;lt; loc.function_name() &amp;lt;&amp;lt; &amp;quot; (строка &amp;quot; 
        &amp;lt;&amp;lt; loc.line()
            &amp;lt;&amp;lt; &amp;quot;): Не удалось открыть музыкальный поток &amp;#x27;&amp;quot; &amp;lt;&amp;lt; id 
            &amp;lt;&amp;lt; &amp;quot;&amp;#x27; из памяти\n&amp;quot;;
        return false;
    }

    // Сохранение музыкального потока в словаре
    musicStreams_[std::string(id)] = std::move(music);
    return true;
}

// Получение шрифта по идентификатору
std::optional&amp;lt;sf::Font*&amp;gt; ResourceManager::getFont(std::string_view id, 
const std::source_location&amp;amp; loc) noexcept {
    auto it = fonts_.find(std::string(id));
    if (it == fonts_.end()) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Ошибка в &amp;quot; &amp;lt;&amp;lt; loc.function_name() &amp;lt;&amp;lt; &amp;quot; (строка &amp;quot; 
        &amp;lt;&amp;lt; loc.line()
            &amp;lt;&amp;lt; &amp;quot;): Шрифт &amp;#x27;&amp;quot; &amp;lt;&amp;lt; id &amp;lt;&amp;lt; &amp;quot;&amp;#x27; не найден\n&amp;quot;;
        return std::nullopt;
    }
    return &amp;amp;(it-&amp;gt;second);
}

// Получение звукового буфера по идентификатору
std::optional&amp;lt;sf::SoundBuffer*&amp;gt; ResourceManager::getSoundBuffer(
std::string_view id, const std::source_location&amp;amp; loc) noexcept {
    auto it = buffers_.find(std::string(id));
    if (it == buffers_.end()) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Ошибка в &amp;quot; &amp;lt;&amp;lt; loc.function_name() &amp;lt;&amp;lt; &amp;quot; (строка &amp;quot; 
        &amp;lt;&amp;lt; loc.line()
            &amp;lt;&amp;lt; &amp;quot;): Звуковой буфер &amp;#x27;&amp;quot; &amp;lt;&amp;lt; id &amp;lt;&amp;lt; &amp;quot;&amp;#x27; не найден\n&amp;quot;;
        return std::nullopt;
    }
    return &amp;amp;(it-&amp;gt;second);
}

// Получение текстуры по идентификатору
std::optional&amp;lt;sf::Texture*&amp;gt; ResourceManager::getTexture(
std::string_view id, const std::source_location&amp;amp; loc) noexcept {
    auto it = textures_.find(std::string(id));
    if (it == textures_.end()) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Ошибка в &amp;quot; &amp;lt;&amp;lt; loc.function_name() &amp;lt;&amp;lt; &amp;quot; (строка &amp;quot; 
        &amp;lt;&amp;lt; loc.line()
            &amp;lt;&amp;lt; &amp;quot;): Текстура &amp;#x27;&amp;quot; &amp;lt;&amp;lt; id &amp;lt;&amp;lt; &amp;quot;&amp;#x27; не найдена\n&amp;quot;;
        return std::nullopt;
    }
    return &amp;amp;(it-&amp;gt;second);
}

// Получение изображения по идентификатору (с кэшированием)
std::optional&amp;lt;sf::Image&amp;gt; ResourceManager::getImage(std::string_view id, 
const std::source_location&amp;amp; loc) noexcept {
    // Проверяем, есть ли изображение в кэше
    auto cacheIt = imageCache_.find(std::string(id));
    if (cacheIt != imageCache_.end()) {
        return cacheIt-&amp;gt;second;
    }

    // Если нет в кэше, загружаем из текстуры
    auto texIt = textures_.find(std::string(id));
    if (texIt == textures_.end()) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Ошибка в &amp;quot; &amp;lt;&amp;lt; loc.function_name() &amp;lt;&amp;lt; &amp;quot; (строка &amp;quot; 
        &amp;lt;&amp;lt; loc.line()
            &amp;lt;&amp;lt; &amp;quot;): Текстура для изображения &amp;#x27;&amp;quot; &amp;lt;&amp;lt; id &amp;lt;&amp;lt; &amp;quot;&amp;#x27; не найдена\n&amp;quot;;
        return std::nullopt;
    }

    // Конвертируем текстуру в изображение и кэшируем результат
    sf::Image image = texIt-&amp;gt;second.copyToImage();
    imageCache_[std::string(id)] = image; // Кэшируем изображение
    return image;
}

// Получение музыкального потока по идентификатору
std::optional&amp;lt;sf::Music*&amp;gt; ResourceManager::getMusic(std::string_view id, 
const std::source_location&amp;amp; loc) noexcept {
    auto it = musicStreams_.find(std::string(id));
    if (it == musicStreams_.end()) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Ошибка в &amp;quot; &amp;lt;&amp;lt; loc.function_name() &amp;lt;&amp;lt; &amp;quot; (строка &amp;quot; 
        &amp;lt;&amp;lt; loc.line()
            &amp;lt;&amp;lt; &amp;quot;): Музыкальный поток &amp;#x27;&amp;quot; &amp;lt;&amp;lt; id &amp;lt;&amp;lt; &amp;quot;&amp;#x27; не найден\n&amp;quot;;
        return std::nullopt;
    }
    return it-&amp;gt;second.get();
}&lt;/pre&gt;
  &lt;p id=&quot;R92U&quot;&gt;Теперь мы можем создать класс для создания феерического текста&lt;/p&gt;
  &lt;h3 id=&quot;kpIt&quot;&gt;ColorfulText.h&lt;/h3&gt;
  &lt;pre id=&quot;hayU&quot; data-lang=&quot;cpp&quot;&gt;#pragma once

#include &amp;lt;SFML/Graphics.hpp&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;string_view&amp;gt;
#include &amp;lt;random&amp;gt;
#include &amp;lt;memory&amp;gt;
#include &amp;lt;optional&amp;gt;
#include &amp;lt;ranges&amp;gt;
#include &amp;lt;concepts&amp;gt;
#include &amp;lt;source_location&amp;gt;

class ResourceManager; // Предполагаем, что определен где-то еще

class ColorfulText : public sf::Drawable {
public:
    // Перечисление эффектов с scoped enum
    enum class Effect {
        RandomColors, // Случайные цвета для каждой буквы
        Wave,         // Волна цветов, бегущая по тексту
        Blink,        // Мигание букв с изменением яркости
        Rainbow,      // Радужный переход цветов
        Pulse,        // Пульсация размера букв
        Fade,         // Затухание и появление цветов
        Twinkle       // Мерцание случайных букв
    };

    // Статический метод создания с поддержкой юникода
    [[nodiscard]] static std::optional&amp;lt;ColorfulText&amp;gt; create(
        std::string_view fon
        tId, std::wstring_view text, unsigned int charSize,
        const sf::Vector2f&amp;amp; position, const sf::RenderWindow&amp;amp; window,
        bool centerHorizontally = false, bool centerVertically = false,
        Effect effect = Effect::RandomColors,
        const std::source_locat
        ion&amp;amp; loc = std::source_location::current());

    // Обновление состояния текста
    void update(float deltaTime) noexcept;

    // Установка эффекта с использованием концепций C++20
    template&amp;lt;typename T&amp;gt;
        requires std::same_as&amp;lt;T, Effect&amp;gt;
    void setEffect(T newEffect) noexcept { setEffectImpl(newEffect); }

    // Установка позиции текста
    void setPosition(const sf::Vector2f&amp;amp; position, 
    bool centerHorizontally = false, bool centerVertically = false) 
    noexcept;

    // Запрещаем копирование, разрешаем перемещение
    ColorfulText(const ColorfulText&amp;amp;) = delete;
    ColorfulText&amp;amp; operator=(const ColorfulText&amp;amp;) = delete;
    ColorfulText(ColorfulText&amp;amp;&amp;amp;) = default;
    ColorfulText&amp;amp; operator=(ColorfulText&amp;amp;&amp;amp;) = default;

private:
    // Приватный конструктор для внутреннего использования в create
    ColorfulText() = default;

    // Переопределение метода отрисовки из sf::Drawable
    void draw(sf::RenderTarget&amp;amp; target, sf::RenderStates states) 
    const override;

    // Методы обновления эффектов
    void updateRandomColors();        // Случайные цвета
    void updateWave(float deltaTime); // Волна цветов
    void updateBlink(float deltaTime); // Мигание букв
    void updateRainbow(float deltaTime); // Радужный переход
    void updatePulse(float deltaTime);   // Пульсация размера
    void updateFade(float deltaTime);    // Затухание цветов
    void updateTwinkle(float deltaTime); // Мерцание случайных букв

    // Внутренняя реализия смены эффекта
    void setEffectImpl(Effect newEffect) noexcept;

    // Данные класса
    // Указатель на шрифт (хранится в ResourceManager)
    sf::Font* font_ = nullptr; 
    std::vector&amp;lt;sf::Text&amp;gt; letters_;          // Вектор букв текста
    float colorTimer_ = 0.0f;                // Таймер для эффектов
    Effect currentEffect_;                   // Текущий эффект
    float waveOffset_ = 0.0f;           // Смещение для плавных эффектов
    std::vector&amp;lt;float&amp;gt; twinkleTimers_;  // Таймеры для эффекта мерцания

    // Константы
    // Интервал смены эффектов
    static constexpr float COLOR_CHANGE_INTERVAL = 0.5f; 
    static constexpr float WAVE_SPEED = 1.0f;       // Скорость волны
    static constexpr float PULSE_AMPLITUDE = 0.2f;  // Амплитуда пульсации
    static constexpr float TWINKLE_CHANCE = 0.1f;   // Шанс мерцания
};
&lt;/pre&gt;
  &lt;h3 id=&quot;4Qn2&quot;&gt;ColorfulText.cpp&lt;/h3&gt;
  &lt;pre id=&quot;IaYZ&quot; data-lang=&quot;cpp&quot;&gt;#include &amp;quot;ColorfulText.h&amp;quot;
#include &amp;quot;ResourceManager.h&amp;quot;
#include &amp;lt;cmath&amp;gt;
#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;iostream&amp;gt;

std::optional&amp;lt;ColorfulText&amp;gt; ColorfulText::create(
    std::string_view fontId, std::wstring_view
     text, unsigned int charSize,
    const sf::Vector2f&amp;amp; position, const sf::RenderWindow&amp;amp; window,
    bool centerHorizontally, bool centerVertically, Effect effect,
    const std::source_location&amp;amp; loc) {

    // Создаем объект с помощью приватного конструктора
    ColorfulText result;
    ResourceManager&amp;amp; rm = ResourceManager::getInstance();
    auto fontOpt = rm.getFont(fontId, loc);
    if (!fontOpt.has_value()) {
        return std::nullopt; // Ошибка уже выведена в getFont
    }
    result.font_ = fontOpt.value(); // Извлекаем указатель на шрифт

    // Расчет позиции текста
    float totalWidth = text.size() * charSize * 0.8f;
    float xPos = centerHorizontally ? (window.get
    Size().x - totalWidth) / 2.0f : position.x;
    float yPos = centerVertically ? (window.
    getSize().y - charSize) / 2.0f : position.y;

    // Резервируем память
    result.letters_.reserve(text.size());
    result.twinkleTimers_.resize(text.size(), 0.0f);

    // Инициализация букв с использованием std::ranges (C++20)
    std::ranges::for_each(std::views::i
    ota(size_t{ 0 }, text.size()), [&amp;amp;](size_t i) {
        sf::Text letter;
        letter.setFont(*result.font_);
        // Устанавливаем символ юникода
        letter.setCharacterSize(charSize);
        letter.setString(std::wstring(1, text[i])); 
        letter.setFillColor(sf::Color::Magenta);
        letter.setPosition(xPos + i * charSize * 0.8f, yPos);
        result.letters_.push_back(std::move(letter));
        });

    // Инициализация остальных полей
    result.currentEffect_ = effect;
    result.colorTimer_ = 0.0f;
    result.waveOffset_ = 0.0f;

    return result;
}

void ColorfulText::update(float deltaTime) noexcept {
    colorTimer_ += deltaTime;
    waveOffset_ += deltaTime * WAVE_SPEED;

    // Обновление в зависимости от текущего эффекта
    switch (currentEffect_) {
    case Effect::RandomColors:
        if (colorTimer_ &amp;gt;= COLOR_CHANGE_INTERVAL) {
            colorTimer_ = 0.0f;
            updateRandomColors();
        }
        break;
    case Effect::Wave: updateWave(deltaTime); break;
    case Effect::Blink: updateBlink(deltaTime); break;
    case Effect::Rainbow: updateRainbow(deltaTime); break;
    case Effect::Pulse: updatePulse(deltaTime); break;
    case Effect::Fade: updateFade(deltaTime); break;
    case Effect::Twinkle: updateTwinkle(deltaTime); break;
    }
}

void ColorfulText::setEffectImpl(Effect newEffect) noexcept {
    currentEffect_ = newEffect;
    colorTimer_ = 0.0f;
    waveOffset_ = 0.0f;
    std::ranges::fill(twinkleTimers_, 0.0f); // Сброс таймеров мерцания
    update(0.0f); // Немедленное обновление состояния
}

void ColorfulText::updateRandomColors() {
    // Генерация случайных цветов для каждой буквы
    thread_local std::mt19937 gen{ std::random_device{}() };
    std::uniform_int_distribution&amp;lt;int&amp;gt; dis(0, 255);

    std::ranges::for_each(letters_, [&amp;amp;](auto&amp;amp; letter) {
        letter.setFillColor(sf::Color(
            static_cast&amp;lt;sf::Uint8&amp;gt;(dis(gen)),
            static_cast&amp;lt;sf::Uint8&amp;gt;(dis(gen)),
            static_cast&amp;lt;sf::Uint8&amp;gt;(dis(gen))));
        });
}

void ColorfulText::updateWave(float deltaTime) {
    // Эффект волны цветов
    std::ranges::for_each(std::views::iota(size_t{ 0 }, 
    letters_.size()), [&amp;amp;](size_t i) {
        float phase = waveOffset_ + i * 0.2f;
        sf::Uint8 value = static_cast&amp;lt;sf::Uint8&amp;gt;(127.5f + 
        127.5f * std::sin(phase));
        letters_[i].setFillColor(sf::Color(value, 255 - 
        value, value / 2));
        });
}

void ColorfulText::updateBlink(float deltaTime) {
    // Мигание букв через изменение прозрачности
    float alpha = 127.5f + 127.5f * std::sin(colorTimer_ * 5.0f);
    std::ranges::for_each(letters_, [alpha](auto&amp;amp; letter) {
        sf::Color color = letter.getFillColor();
        color.a = static_cast&amp;lt;sf::Uint8&amp;gt;(alpha);
        letter.setFillColor(color);
        });
}

void ColorfulText::updateRainbow(float deltaTime) {
    // Радужный эффект с плавным переходом
    std::ranges::for_each(std::views::iota(size_t{ 0 }, 
    letters_.size()), [&amp;amp;](size_t i) {
        float phase = waveOffset_ + i * 0.1f;
        sf::Uint8 r = static_cast&amp;lt;sf::Uint8&amp;gt;(127.5f + 
        127.5f * std::sin(phase));
        sf::Uint8 g = static_cast&amp;lt;sf::Uint8&amp;gt;(127.5f + 
        127.5f * std::sin(phase + 2.0f));
        sf::Uint8 b = static_cast&amp;lt;sf::Uint8&amp;gt;(127.5f + 
        127.5f * std::sin(phase + 4.0f));
        letters_[i].setFillColor(sf::Color(r, g, b));
        });
}

void ColorfulText::updatePulse(float deltaTime) {
    // Пульсация размера букв
    float scale = 1.0f + PULSE_AMPLITUDE * std::sin(colorTimer_ * 3.0f);
    std::ranges::for_each(letters_, [scale](auto&amp;amp; letter) {
        letter.setScale(scale, scale);
        });
}

void ColorfulText::updateFade(float deltaTime) {
    // Затухание и появление цветов
    float alpha = 127.5f + 127.5f * std::sin(colorTimer_ * 2.0f);
    std::ranges::for_each(letters_, [alpha](auto&amp;amp; letter) {
        letter.setFillColor(sf::Color(255, 255, 255, 
        static_cast&amp;lt;sf::Uint8&amp;gt;(alpha)));
        });
}

void ColorfulText::updateTwinkle(float deltaTime) {
    // Мерцание случайных букв
    thread_local std::mt19937 gen{ std::random_device{}() };
    std::uniform_real_distribution&amp;lt;float&amp;gt; dis(0.0f, 1.0f);

    std::ranges::for_each(std::views::iota(size_t{ 0 }, 
    letters_.size()), [&amp;amp;](size_t i) {
        twinkleTimers_[i] += deltaTime;
        if (twinkleTimers_[i] &amp;gt;= COLOR_CHANGE_INTERVAL) {
            if (dis(gen) &amp;lt; TWINKLE_CHANCE) {
                letters_[i].setFillColor(sf::Color::White);
                twinkleTimers_[i] = 0.0f;
            }
            else {
                letters_[i].setFillColor(sf::Color(100, 100, 100));
            }
        }
        });
}

void ColorfulText::draw(sf::RenderTarget&amp;amp; target, 
sf::RenderStates states) const {
    // Отрисовка всех букв
    for (const auto&amp;amp; letter : letters_) {
        target.draw(letter, states);
    }
}

void ColorfulText::setPosition(const sf::Vector2f&amp;amp; position, 
bool centerHorizontally, bool centerVertically) noexcept {
    // Установка новой позиции текста
    float totalWidth = letters_.size() * letters_[0].getCharacterSize() 
    * 0.8f;
    float xPos = centerHorizontally ? position.x - totalWidth 
    / 2.0f : position.x;
    float yPos = centerVertically ? position.y - 
    letters_[0].getCharacterSize() / 2.0f : position.y;

    std::ranges::for_each(std::views::iota(size_t{ 0 }, 
    letters_.size()), [&amp;amp;](size_t i) {
        letters_[i].setPosition(xPos + i * 
        letters_[0].getCharacterSize() * 0.8f, yPos);
        });
}&lt;/pre&gt;
  &lt;p id=&quot;il5Z&quot;&gt;А теперь все объединим в исполняем файле.&lt;/p&gt;
  &lt;h3 id=&quot;xN3j&quot;&gt;Мигающие_буквы.cpp&lt;/h3&gt;
  &lt;pre id=&quot;5d0F&quot; data-lang=&quot;cpp&quot;&gt;#include &amp;lt;SFML/Graphics.hpp&amp;gt;
#include &amp;quot;ColorfulText.h&amp;quot;
#include &amp;quot;ResourceManager.h&amp;quot;
#include &amp;quot;resource.h&amp;quot;
#include &amp;lt;iostream&amp;gt;

// Инициализация ресурсов
void initializeResources() {
    ResourceManager&amp;amp; rm = ResourceManager::getInstance();
    if (!rm.loadTexture(&amp;quot;puzzle&amp;quot;, IDR_TEXTURE1) || 
    !rm.loadFont(&amp;quot;font&amp;quot;, IDR_FONT1)) {
        throw std::runtime_error(&amp;quot;Не удалось загрузить ресурсы&amp;quot;);
    }
}

int main() {
    initializeResources();
    sf::RenderWindow window(sf::VideoMode(1280, 720), 
    L&amp;quot;Весёлый текст&amp;quot;, sf::Style::Default);
    window.setFramerateLimit(60);

    // Установка иконки окна
    ResourceManager&amp;amp; rm = ResourceManager::getInstance();
    auto iconOpt = rm.getImage(&amp;quot;puzzle&amp;quot;);
    if (!iconOpt) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Не удалось загрузить иконку &amp;#x27;puzzle&amp;#x27;\n&amp;quot;;
        return 1;
    }
    sf::Image icon = *iconOpt; // Извлекаем значение из std::optional
    if (icon.getSize().x == 0 || icon.getSize().y == 0) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Иконка &amp;#x27;puzzle&amp;#x27; пуста\n&amp;quot;;
        return 1;
    }
    window.setIcon(icon.getSize().x, icon.getSize().y, 
    icon.getPixelsPtr());

    // Создание текста с юникодом
    auto text = ColorfulText::create(&amp;quot;font&amp;quot;, L&amp;quot;Привет, мир!&amp;quot;, 100,
        sf::Vector2f(0, 0), window, true, true, 
        ColorfulText::Effect::Pulse);
    if (!text) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Не удалось создать ColorfulText\n&amp;quot;;
        return 1;
    }

    sf::Clock clock;
    while (window.isOpen()) {
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed) {
                window.close();
            }
            // Переключение эффектов по нажатию клавиш (только 7 эффектов)
            if (event.type == sf::Event::KeyPressed) {
                switch (event.key.code) {
                case sf::Keyboard::F1: text-&amp;gt;setEffect(
                ColorfulText::Effect::RandomColors); break;
                case sf::Keyboard::F2: text-&amp;gt;setEffect(
                ColorfulText::Effect::Wave); break;
                case sf::Keyboard::F3: text-&amp;gt;setEffect(
                ColorfulText::Effect::Blink); break;
                case sf::Keyboard::F4: text-&amp;gt;setEffect(
                ColorfulText::Effect::Rainbow); break;
                case sf::Keyboard::F5: text-&amp;gt;setEffect(
                ColorfulText::Effect::Pulse); break;
                case sf::Keyboard::F6: text-&amp;gt;setEffect(
                ColorfulText::Effect::Fade); break;
                case sf::Keyboard::F7: text-&amp;gt;setEffect(
                ColorfulText::Effect::Twinkle); break;
                }
            }
        }

        // Обновление и отрисовка текста
        float deltaTime = clock.restart().asSeconds();
        text-&amp;gt;update(deltaTime);

        window.clear(sf::Color::Blue);
        window.draw(*text);
        window.display();
    }
    return 0;
}
&lt;/pre&gt;
  &lt;p id=&quot;T0Gn&quot;&gt;Надеюсь у Вас всё получилось и до новых встреч !!!&lt;/p&gt;
  &lt;p id=&quot;8sFF&quot;&gt;&lt;a href=&quot;https://t.me/C_Verhovcevo_NVK&quot; target=&quot;_blank&quot;&gt;Телеграмм канал - Программирование игр С++&lt;/a&gt;&lt;/p&gt;

</content></entry><entry><id>gamedeveloper:dpV_zrPwzWf</id><link rel="alternate" type="text/html" href="https://teletype.in/@gamedeveloper/dpV_zrPwzWf?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=gamedeveloper"></link><title>Пишем калькулятор на C++ с SFML </title><published>2025-03-16T15:03:31.841Z</published><updated>2025-03-16T18:44:28.641Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/88/66/8866a024-ba41-441e-88ed-f29334327952.png"></media:thumbnail><category term="mul-timedijnaya-biblioteka-sfml-c" label="Мультимедийная библиотека  SFML C++"></category><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/c7/0e/c70e7c09-3a8c-4448-8154-2ed51f44ece7.jpeg&quot;&gt;Привет, коллеги и доброжелательные критики! Сегодня я решил отвлечься от  своей грамозкой работы, чтобы написать что-то простое, но с изюминкой — калькулятор с графическим интерфейсом на C++20 и SFML.  Этот проект — не претензия на что-то грандиозное, а скорее лёгкий эксперимент, чтобы вспомнить, как приятно писать код, который сразу видно на экране. Заодно я поделюсь с вами своими мыслями, подходами и парой советов. Давайте разберём, как я это закрутил и почему выбрал именно SFML.</summary><content type="html">
  &lt;figure id=&quot;7eJt&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c7/0e/c70e7c09-3a8c-4448-8154-2ed51f44ece7.jpeg&quot; width=&quot;1344&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;JWwB&quot;&gt;Привет, коллеги и доброжелательные критики! Сегодня я решил отвлечься от  своей громоздкой работы, чтобы написать что-то простое, но с изюминкой — калькулятор с графическим интерфейсом на C++20 и SFML.  Этот проект — не претензия на что-то грандиозное, а скорее лёгкий эксперимент, чтобы вспомнить, как приятно писать код, который сразу видно на экране. Заодно я поделюсь с вами своими мыслями, подходами и парой советов. Давайте разберём, как я это закрутил и почему выбрал именно SFML.&lt;/p&gt;
  &lt;h3 id=&quot;WD8J&quot;&gt;Почему калькулятор? И почему SFML?&lt;/h3&gt;
  &lt;p id=&quot;kyIf&quot;&gt;Калькулятор — это классика программирования. Помню, как в начале карьеры, ещё в нулевых, писал такие на Pascal для курсовых, потом переделывал их на C с самописным парсером для зачётов. Это как &amp;quot;Hello, World&amp;quot;, только с кнопками и математикой — отличный способ проверить свои навыки. Сейчас, конечно, можно было бы взять что-то посерьёзнее: Qt для полноценного GUI, SDL для низкоуровневого контроля или даже Unreal Engine, если уж совсем размахнуться. Но я остановился на SFML, а конкретно на версии 2.6.1. Почему именно она? Это последняя стабильная версия на март 2025 года, и она отлично дружит с современными компиляторами — GCC 12, MSVC 2022 и даже Clang 17. В ней есть мелкие улучшения рендеринга, поддержка C++17/20 из коробки и никаких сюрпризов с совместимостью, что для меня как человека без дополнительного запаса времени очень важно — не люблю тратить своё драгоценное время на борьбу с зависимостями.&lt;/p&gt;
  &lt;p id=&quot;mYV6&quot;&gt;SFML я выбрал не просто так. Это лёгкая библиотека для 2D-графики, которая не заставляет тебя писать тонны boilerplate-кода, как Qt, или возиться с низкоуровневыми деталями, как SDL. Сравните с другими системами: Qt — это тяжеловес с кучей возможностей, но для калькулятора его функционал избыточен, как если бы вы использовали танк для поездки в магазин. SDL даёт больше контроля, но требует больше ручной работы — например, самому рисовать текстуры или управлять контекстом OpenGL. SFML же сразу предлагает готовые примитивы вроде RectangleShape, Text и Sprite, что идеально для простого GUI. Её плюсы: минимализм (быстро настраивается), производительность (для 2D почти не жрёт ресурсов), кроссплатформенность (Windows, Linux, macOS без лишних телодвижений). Почему не SFML 3.1, потому - что синтаксис поменялся и нужно потратить время, чтобы вникнуть, а времени увы очень мало. Может в будущем я и буду писать код на SFML 3.1, но точно не сейчас. Да и багов хватает всегда с выходом чего-то нового. &lt;/p&gt;
  &lt;p id=&quot;lVK5&quot;&gt;Я использую SFML для небольших экспериментов, где нужна быстрая визуализация: прототипы интерфейсов, простые инструменты для отладки, визуализации данных или даже мелкие игрушки для души. Например, пару лет назад я делал простой шутер - &amp;quot;Кощей&amp;quot;, рендерил тысячи клеток в реальном времени, и она справилась без лагов. Для больших проектов я бы взял Qt или Unity, но тут задача была другая — сделать что-то рабочее за пару часов, без оверхеда и с удовольствием.&lt;/p&gt;
  &lt;h3 id=&quot;g4DK&quot;&gt;Архитектура: как я это разложил&lt;/h3&gt;
  &lt;p id=&quot;ghx7&quot;&gt;Проект у меня получился из трёх основных классов плюс точка входа. Я старался держать всё просто, но с учётом современных практик — никаких сырых указателей, никаких C-style массивов. Вот что вышло:&lt;/p&gt;
  &lt;p id=&quot;Xox2&quot;&gt;&lt;strong&gt;Button&lt;/strong&gt; — класс для кнопок. Простая обёртка над прямоугольником и текстом, но с умными указателями для управления памятью.&lt;/p&gt;
  &lt;p id=&quot;BBGg&quot;&gt;&lt;strong&gt;Calculator&lt;/strong&gt; — главный класс, который собирает интерфейс, обрабатывает клики и связывает всё с вычислениями.&lt;/p&gt;
  &lt;p id=&quot;axUC&quot;&gt;&lt;strong&gt;ExpressionEvaluator&lt;/strong&gt; — рекурсивный парсер выражений. Без него это был бы просто красивый блокнот с кнопками.&lt;/p&gt;
  &lt;p id=&quot;AqF4&quot;&gt;&lt;strong&gt;main.cpp&lt;/strong&gt; — минимальный код для запуска окна и цикла событий.&lt;/p&gt;
  &lt;p id=&quot;JThC&quot;&gt;Я сразу решил использовать std::unique_ptr вместо сырых указателей — в 2025 году это уже стандарт, и возиться с new/delete нет никакого смысла. Также заменил все массивы на std::vector — меньше багов, больше читаемости. Давайте разберём каждый кусок подробнее.&lt;/p&gt;
  &lt;p id=&quot;ZdI9&quot;&gt;&lt;strong&gt;Кнопки (Button.h)&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;JWqo&quot; data-lang=&quot;cpp&quot;&gt;#pragma once
#include &amp;lt;string&amp;gt;
#include &amp;lt;memory&amp;gt;
#include &amp;lt;SFML/Graphics.hpp&amp;gt;

class Button : public sf::Drawable, public sf::Transformable {
public:
    Button(std::string text, sf::Font&amp;amp; font, unsigned int characterSize, 
           sf::Vector2f position, sf::Vector2f size)
        : m_rect{std::make_unique&amp;lt;sf::RectangleShape&amp;gt;
        (size)}, // Создаем прямоугольник кнопки
          m_text{std::make_unique&amp;lt;sf::Text&amp;gt;(text, font, charac
          terSize)} { // Создаем текст на кнопке
        
        m_rect-&amp;gt;setPosition(position); // Задаем позицию кнопки
        m_rect-&amp;gt;setFillColor(sf::Color(200, 200, 200)); // Серый фон
        m_rect-&amp;gt;setOutlineColor(sf::Color::Black); // Черная обводка
        m_rect-&amp;gt;setOutlineThickness(2); // Толщина обводки

        m_text-&amp;gt;setPosition(position.x + 20, p
        osition.y + 20); // Текст с отступом внутри кнопки
        m_text-&amp;gt;setFillColor(sf::Color::White); // Белый цвет текста
    }

    void pressEffect() { // Эффект нажатия — меняем цвет
        m_rect-&amp;gt;setFillColor(sf::Color(150, 150, 150));
        m_rect-&amp;gt;setOutlineColor(sf::Color(150, 150, 150));
    }

    void releaseEffect() { // Эффект отпускания — возвращаем исходный цвет
        m_rect-&amp;gt;setFillColor(sf::Color(200, 200, 200));
        m_rect-&amp;gt;setOutlineColor(sf::Color::Black);
    }

    std::string getText() const { // Получаем текст кнопки
        return m_text-&amp;gt;getString();
    }

    sf::FloatRect getGlobalBounds() const { 
    // Границы кнопки с учетом трансформаций
        return getTransform().transformRect(m_rect-&amp;gt;getGlobalBounds());
    }

private:
    void draw(sf::RenderTarget&amp;amp; target, sf::RenderStates states) 
    const override { // Отрисовка кнопки
        states.transform *= getTransform();
        target.draw(*m_rect, states); // Рисуем прямоугольник
        target.draw(*m_text, states); // Рисуем текст
    }

    std::unique_ptr&amp;lt;sf::RectangleShape&amp;gt; m_rect; 
    
    std::unique_ptr&amp;lt;sf::Text&amp;gt; m_text; // Умный указатель на текст
};f::Text&amp;gt; m_text; // Умный указатель на текст
};&lt;/pre&gt;
  &lt;p id=&quot;vWUM&quot;&gt;Класс кнопки — это мой первый шаг к интерфейсу. Он простой, но функциональный: прямоугольник с текстом, который реагирует на клики. Использовал std::unique_ptr, чтобы памятью управляла сама программа — никаких утечек, никакого ручного delete. Добавил визуальную обратную связь через pressEffect и releaseEffect — кнопка темнеет при нажатии, что делает UI живым. Цвета выбрал на глаз: серый фон и белый текст — классика, но в реальном проекте я бы вынес их в константы или конфиг-файл, чтобы дизайнеры могли играться. SFML тут хорош тем, что сразу даёт RectangleShape и Text — не надо самому писать шейдеры или возиться с текстурами, как в SDL. Ещё я подумал центрировать текст поумнее, но отступ в 20 пикселей для демки сгодился.&lt;/p&gt;
  &lt;p id=&quot;XQ3q&quot;&gt;&lt;strong&gt;Калькулятор (Calculator.h)&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;MlF8&quot; data-lang=&quot;cpp&quot;&gt;#pragma once
#include &amp;lt;string&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;memory&amp;gt;
#include &amp;lt;SFML/Graphics.hpp&amp;gt;
#include &amp;quot;Button.h&amp;quot;
#include &amp;quot;ExpressionEvaluator.h&amp;quot;

class Calculator : public sf::Drawable, public sf::Transformable {
public:
    explicit Calculator(sf::Font&amp;amp; font) { // Конструктор принимает шрифт
        display = std::make_unique&amp;lt;sf::Recta
        ngleShape&amp;gt;(sf::Vector2f(360, 50)); // Создаем дисплей
        display-&amp;gt;setPosition(20, 20); // Позиция дисплея
        display-&amp;gt;setFillColor(sf::Color(173, 216, 230)); // Голубой фон
        display-&amp;gt;setOutlineColor(sf::Color::Black); // Черная обводка
        display-&amp;gt;setOutlineThickness(2); // Толщина обводки

        displayText = std::make_unique&amp;lt;sf::Text&amp;gt;(&amp;quot;&amp;quot;, font, 30); 
        displayText-&amp;gt;setPosition(30, 30); // Позиция текста
        displayText-&amp;gt;setFillColor(sf::Color::Black); // Черный цвет текста

        windowBackground = std::make_unique&amp;lt;sf::RectangleShape&amp;gt;(
        sf::Vector2f(400, 600)); // Фон окна
        windowBackground-&amp;gt;setFillColor(sf::Color::White); 

        const std::vector&amp;lt;std::string&amp;gt; labels = { 
        // Список меток для кнопок
            &amp;quot;7&amp;quot;, &amp;quot;8&amp;quot;, &amp;quot;9&amp;quot;, &amp;quot;/&amp;quot;, &amp;quot;4&amp;quot;, &amp;quot;5&amp;quot;, &amp;quot;6&amp;quot;, &amp;quot;*&amp;quot;, &amp;quot;1&amp;quot;, &amp;quot;2&amp;quot;, &amp;quot;3&amp;quot;, &amp;quot;-&amp;quot;,
            &amp;quot;0&amp;quot;, &amp;quot;C&amp;quot;, &amp;quot;=&amp;quot;, &amp;quot;+&amp;quot;, &amp;quot;(&amp;quot;, &amp;quot;)&amp;quot;, &amp;quot;&amp;lt;&amp;lt;&amp;lt;&amp;quot;
        };

        buttons.reserve(labels.size()); // Резервируем место под кнопки
        for (size_t i = 0; i &amp;lt; labels.size(); ++i) { 
        // Создаем кнопки в цикле
            buttons.emplace_back(std::make_unique&amp;lt;Button&amp;gt;(
                labels[i], font, 24,
                sf::Vector2f(20 + (i % 4) * 90, 100 + (i / 4) * 90),
                sf::Vector2f(80, 80)
            ));
        }
    }

    void handleEvent(const sf::Event&amp;amp; event, sf::RenderWindow&amp;amp; window) { 
    // Обработка событий
        if (event.type == sf::Event::MouseButtonPressed &amp;amp;&amp;amp; 
            event.mouseButton.button == sf::Mouse::Left) {
            for (const auto&amp;amp; button : buttons) {
                if (button-&amp;gt;getGlobalBounds().contains(
                    static_cast&amp;lt;sf::Vector2f&amp;gt;(
                    sf::Mouse::getPosition(window)))) {
                    button-&amp;gt;pressEffect(); // Анимация нажатия
                    processInput(button-&amp;gt;getText()); // Обрабатываем ввод
                    displayText-&amp;gt;setString(input); // Обновляем дисплей
                }
            }
        }
        if (event.type == sf::Event::MouseButtonReleased &amp;amp;&amp;amp; 
            event.mouseButton.button == sf::Mouse::Left) {
            for (const auto&amp;amp; button : buttons) {
                button-&amp;gt;releaseEffect(); // Возвращаем цвет
            }
        }
    }

private:
    void draw(sf::RenderTarget&amp;amp; target, sf::RenderStates states) 
    const override { // Отрисовка калькулятора
        states.transform *= getTransform();
        target.draw(*windowBackground, states); // Рисуем фон
        target.draw(*display, states); // Рисуем дисплей
        target.draw(*displayText, states); // Рисуем текст
        for (const auto&amp;amp; button : buttons) {
            target.draw(*button, states); // Рисуем все кнопки
        }
    }

    void processInput(const std::string&amp;amp; text) { // Логика обработки ввода
        using namespace std::string_literals;
        if (text == &amp;quot;C&amp;quot;s) { // Очистка
            input.clear();
        }
        else if (text == &amp;quot;=&amp;quot;s) { // Вычисление результата
            try {
                double result = ExpressionEvaluator::evaluate(input);
                input = std::to_string(result);
                if (input.ends_with(&amp;quot;.000000&amp;quot;)) { // Убираем лишние нули
                    input = input.substr(0, input.find(&amp;#x27;.&amp;#x27;));
                }
            }
            catch (const std::exception&amp;amp;) {
                input = &amp;quot;Error&amp;quot;s; // Ошибка при вычислении
            }
        }
        else if (text == &amp;quot;&amp;lt;&amp;lt;&amp;lt;&amp;quot;s) { // Удаление последнего символа
            if (!input.empty()) {
                input.pop_back();
            }
        }
        else if (text == &amp;quot;+&amp;quot;s || text == &amp;quot;-&amp;quot;s || text == &amp;quot;*&amp;quot;s || text == 
        &amp;quot;/&amp;quot;s) { // Операторы
            std::string_view ops = &amp;quot;+-*/&amp;quot;;
            if (!input.empty() &amp;amp;&amp;amp; ops.find(input.back()) == 
            std::string_view::npos &amp;amp;&amp;amp; input.length() &amp;lt; 19) {
                input += text;
            }
        }
        else if (input.length() &amp;lt; 19) { 
        // Добавляем символ, если не превышен лимит
            input += text;
        }
        displayText-&amp;gt;setString(input); // Обновляем текст на дисплее
    }

    std::unique_ptr&amp;lt;sf::RectangleShape&amp;gt; windowBackground; // Фон окна
    std::unique_ptr&amp;lt;sf::RectangleShape&amp;gt; display; // Дисплей
    std::unique_ptr&amp;lt;sf::Text&amp;gt; displayText; // Текст дисплея
    std::vector&amp;lt;std::unique_ptr&amp;lt;Button&amp;gt;&amp;gt; buttons; // Вектор кнопок
    std::string input; // Текущий ввод
};&lt;/pre&gt;
  &lt;p id=&quot;5TLi&quot;&gt;Это ядро всего проекта. Я долго думал, как организовать кнопки, и решил остановиться на сетке 4x5 — это стандартная раскладка, как на старых калькуляторах Casio, только с парой дополнительных кнопок вроде скобок и &amp;quot;backspace&amp;quot;. Размеры окна (400x600) и кнопок (80x80) подбирал вручную, чтобы всё аккуратно влезло, а отступы в 20 пикселей между элементами добавил для читаемости. Дисплей сделал голубым — просто захотелось чего-то яркого на белом фоне. В processInput вся логика ввода: защита от двойных операторов (чтобы не вводились &amp;quot;++&amp;quot; или &amp;quot;*/&amp;quot;), лимит в 19 символов (SFML начинает обрезать текст, если больше), плюс обработка ошибок вроде деления на ноль. Использовал ends_with из C++20 — мелочь, но избавляет от ручной проверки концов строки. SFML  тут хорош своей простотой: метод draw сам рендерит всё в нужном порядке, и мне не пришлось писать сложную логику обновления.&lt;/p&gt;
  &lt;p id=&quot;nHPN&quot;&gt;Ещё я заметил, что при быстрых кликах SFML немного подтормаживает — это не баг самой библиотеки, а особенность того, как я обрабатываю события. В реальном проекте я бы добавил дебаунсинг или перерисовывал только изменённые элементы, но для демки оставил как есть.&lt;/p&gt;
  &lt;p id=&quot;tKiZ&quot;&gt;&lt;strong&gt;Парсер (ExpressionEvaluator.h)&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;fQLN&quot; data-lang=&quot;cpp&quot;&gt;#pragma once
#include &amp;lt;string&amp;gt;
#include &amp;lt;string_view&amp;gt;
#include &amp;lt;stdexcept&amp;gt;
#include &amp;lt;charconv&amp;gt;

class ExpressionEvaluator {
public:
    static double evaluate(std::string_view expres
    sion) { // Вычисляем выражение
        size_t pos = 0;
        return parseExpression(expression, pos);
    }

private:
    static double parseExpression(std::string_view expr,
     size_t&amp;amp; pos) { // Разбираем выражение (+, -)
        double result = parseTerm(expr, pos);
        while (pos &amp;lt; expr.length()) {
            char op = expr[pos];
            if (op != &amp;#x27;+&amp;#x27; &amp;amp;&amp;amp; op != &amp;#x27;-&amp;#x27;) break;
            pos++;
            double term = parseTerm(expr, pos);
            result = (op == &amp;#x27;+&amp;#x27;) ? result + term : result - term;
        }
        return result;
    }

    static double parseTerm(std::strin
    g_view expr, size_t&amp;amp; pos) { // Разбираем члены (*, /)
        double result = parseFactor(expr, pos);
        while (pos &amp;lt; expr.length()) {
            char op = expr[pos];
            if (op != &amp;#x27;*&amp;#x27; &amp;amp;&amp;amp; op != &amp;#x27;/&amp;#x27;) break;
            pos++;
            double factor = parseFactor(expr, pos);
            if (op == &amp;#x27;*&amp;#x27;) result *= factor;
            else if (factor
             == 0) throw std::invalid_argument(&amp;quot;Деление на ноль!&amp;quot;);
            else result /= factor;
        }
        return result;
    }

    static double parseFactor(std::string_view expr, size_t&amp;amp; pos) 
    { // Разбираем множители (числа, скобки)
        skipWhitespace(expr, pos);
        if (pos &amp;gt;= expr.length()) throw std::invalid_argument(
        &amp;quot;Некорректное выражение&amp;quot;);

        if (expr[pos] == &amp;#x27;(&amp;#x27;) { // Обработка скобок
            pos++;
            double result = parseExpression(expr, pos);
            skipWhitespace(expr, pos);
            if (pos &amp;gt;= expr.length() || expr[pos] != &amp;#x27;)&amp;#x27;) 
                throw std::invalid_argument(&amp;quot;Нет закрывающей скобки&amp;quot;);
            pos++;
            return result;
        }

        double result{};
        auto [ptr, ec] = std::from_chars(expr.data() + pos, 
        // Парсим число
                                       expr.data() + expr.length(), 
                                       result);
        if (ec != std::errc()) throw std::invalid_argument(
        &amp;quot;Некорректное число&amp;quot;);
        pos = ptr - expr.data();
        return result;
    }

    static void skipWhitespace(std::string_view expr, size_t&amp;amp; pos) { 
    // Пропускаем пробелы
        while (pos &amp;lt; expr.length() &amp;amp;&amp;amp; std::isspace(expr[pos])) pos++;
    }
};&lt;/pre&gt;
  &lt;p id=&quot;LnRn&quot;&gt;Парсер — это, пожалуй, самая интересная часть. Я решил сделать рекурсивный спуск. Алгоритм простой, но правильный: сначала парсим скобки и числа (через parseFactor), потом умножение и деление (в parseTerm), и только потом сложение с вычитанием (в parseExpression). Это автоматически учитывает приоритет операций, так что &amp;quot;2 + 3 * 4&amp;quot; даст 14, а не 20. Использовал std::from_chars вместо stringstream — это быстрее и меньше тянет за собой STL-оверхеда. Плюс, std::string_view экономит копирования строк, что для парсера мелочь, но приятная.&lt;/p&gt;
  &lt;p id=&quot;GIIf&quot;&gt;Я добавил поддержку пробелов через skipWhitespace, хотя в этом интерфейсе она не особо нужна — просто привычка писать код с запасом на будущее. Ещё парсер выбрасывает исключения при ошибках вроде деления на ноль или незакрытых скобок — в реальном проекте я бы добавил нормальное логирование, но тут просто вывожу &amp;quot;Error&amp;quot; на дисплей. SFML тут не участвует, но без парсера калькулятор был бы просто красивой оболочкой.&lt;/p&gt;
  &lt;p id=&quot;0i7s&quot;&gt;&lt;strong&gt;Точка входа (main.cpp)&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;8r8G&quot; data-lang=&quot;cpp&quot;&gt;#include &amp;lt;SFML/Graphics.hpp&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;quot;Calculator.h&amp;quot;

int main() {
    sf::RenderWindow window(sf::VideoMode(400, 600), L&amp;quot;SFML Калькулятор&amp;quot;); 
    // Создаем окно
    auto font = std::make_unique&amp;lt;sf::Font&amp;gt;(); // Загружаем шрифт
    if (!font-&amp;gt;loadFromFile(&amp;quot;arialmt.ttf&amp;quot;)) {
        std::cerr &amp;lt;&amp;lt; &amp;quot;Не удалось загрузить шрифт!\n&amp;quot;;
        return -1;
    }

    Calculator calculator(*font); // Создаем калькулятор

    while (window.isOpen()) { // Главный цикл
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed) window.close(); 
            // Закрытие окна
            calculator.handleEvent(event, window); // Обработка событий
        }

        window.clear(); // Очистка экрана
        window.draw(calculator); // Отрисовка калькулятора
        window.display(); // Показываем результат
    }
    return 0;
}&lt;/pre&gt;
  &lt;p id=&quot;9AHv&quot;&gt;Точка входа — это стандартный цикл SFML. Окно 400x600 выбрал как компромисс между компактностью и читаемостью. Шрифт взял Arial, потому что он универсален и не требует возни с лицензиями — в продакшене я бы добавил fallback на системный шрифт через sf::Font::loadFromMemory или проверку через std::filesystem. SFML  тут стабильно рендерит всё без сюрпризов, хотя я заметил, что при частых кликах FPS может проседать — это не баг библиотеки, а моя реализация без оптимизаций.&lt;/p&gt;
  &lt;figure id=&quot;dWa7&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e9/5f/e95f4f61-d58b-42bf-9c69-a97861786aa6.jpeg&quot; width=&quot;388&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;SqQX&quot;&gt;Рефлексия: что получилось и что можно лучше&lt;/h3&gt;
  &lt;p id=&quot;HvWC&quot;&gt;Проект занял у меня пару часов в субботу вечером, и результат меня приятно удивил. Калькулятор считает выражения вроде &amp;quot;2 + 3 * (4 - 1)&amp;quot; (правильно выдаёт 11), ловит ошибки вроде деления на ноль или незакрытых скобок, и выглядит аккуратно. SFML показала себя с лучшей стороны: рендеринг быстрый, API интуитивный, никаких глюков с памятью или шрифтами. C++20 тоже не подвёл: std::string_view экономит копирования, ends_with упрощает обработку строк, а std::from_chars делает парсинг чисел шустрым.&lt;/p&gt;
  &lt;p id=&quot;lYIj&quot;&gt;Нет предела совершенству, что можно улучшить:&lt;/p&gt;
  &lt;p id=&quot;Du76&quot;&gt;&lt;strong&gt;Десятичные числа&lt;/strong&gt;. Сейчас парсер понимает только целые — надо добавить поддержку точек и, возможно, научные форматы вроде &amp;quot;1.23e-4&amp;quot;.&lt;/p&gt;
  &lt;p id=&quot;tVS2&quot;&gt;&lt;strong&gt;Производительность&lt;/strong&gt;. Для коротких выражений мой рекурсивный спуск норм, но на длинных строках (например, 100 операторов) лучше взять стековый алгоритм или подключить Boost.Spirit. Я даже прикинул, как это сделать, но для демки оставил как есть.&lt;/p&gt;
  &lt;p id=&quot;Umxa&quot;&gt;&lt;strong&gt;UI/UX&lt;/strong&gt;. Клавиатурный ввод был бы логичным дополнением — сейчас только мышью тыкать. Ещё можно добавить масштабируемость окна, тёмную тему или анимации переходов между состояниями.&lt;/p&gt;
  &lt;p id=&quot;jJmS&quot;&gt;&lt;strong&gt;Тестирование&lt;/strong&gt;. Я писал на коленке, но в реальном проекте нужны юнит-тесты для парсера — хотя бы на базовые случаи вроде &amp;quot;2+2&amp;quot;, &amp;quot;1/0&amp;quot; и &amp;quot;(2+3)*4&amp;quot;.&lt;/p&gt;
  &lt;p id=&quot;XuN1&quot;&gt;&lt;strong&gt;Логирование&lt;/strong&gt;. Ошибки сейчас просто пишут &amp;quot;Error&amp;quot; на дисплей. В продакшене я бы вывел их в файл или консоль с деталями: где упало, что ввели.&lt;/p&gt;
  &lt;p id=&quot;I05a&quot;&gt;&lt;strong&gt;Оптимизация рендеринга&lt;/strong&gt;. SFML немного подтормаживает при частых кликах — можно добавить дебаунсинг на события или перерисовывать только изменённые элементы через dirty rectangles.&lt;/p&gt;
  &lt;p id=&quot;qGqR&quot;&gt;Ещё я подумал про локализацию — например, заменить точку на запятую для регионов, где так принято, но это уже избыточно для такого проекта. В целом, SFML 2.6.1 дала мне ровно то, что я хотел: быстрый старт и минимум головной боли.&lt;/p&gt;
  &lt;h3 id=&quot;90h9&quot;&gt;&lt;strong&gt;Рекомендации новичкам и не только&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;djlj&quot;&gt;Если вы только начинаете, вот что я бы посоветовал:&lt;/p&gt;
  &lt;ol id=&quot;HEWB&quot;&gt;
    &lt;li id=&quot;gq0c&quot;&gt;Не бойтесь библиотек вроде SFML — это не так страшно, как кажется. Она проще, чем Qt, и учит основам работы с графикой.&lt;/li&gt;
    &lt;li id=&quot;PfET&quot;&gt;Осваивайте современный C++ — умные указатели вроде unique_ptr и контейнеры вроде vector спасут вас от кучи багов с памятью.&lt;/li&gt;
    &lt;li id=&quot;Jqc1&quot;&gt;Попробуйте написать парсер вручную хотя бы раз — это отличное упражнение для понимания алгоритмов и структур данных.&lt;/li&gt;
    &lt;li id=&quot;Mtur&quot;&gt;Делайте визуальную обратную связь — даже простая смена цвета кнопки делает UI живым и дружелюбным.&lt;/li&gt;
    &lt;li id=&quot;qyMp&quot;&gt;Экспериментируйте с версиями — SFML 2.6.1 стабильна, но если вам нужны новые фичи, следите за веткой разработки на GitHub, уже доступна версия 3.1 .&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;3HfZ&quot;&gt;Для проффи  совет другой: возьмите такую простую задачу и доведите её до идеала.  Добавьте тесты, профилирование, конфиги, обработку граничных случаев. Это хороший способ не закиснуть на рабочих рутинах и вспомнить, почему мы вообще любим кодить. Я, например, после этого проекта задумался, как бы переписать парсер на концепты C++20 или прикрутить многопоточность для рендеринга — просто ради интереса. Спасибо за внимание и всем хорошего времени суток. &lt;/p&gt;
  &lt;p id=&quot;EJDK&quot;&gt;Творите и любите !!! Это то для чего создан человек !!!&lt;/p&gt;
  &lt;p id=&quot;fOhQ&quot;&gt;&lt;a href=&quot;https://t.me/C_Verhovcevo_NVK&quot; target=&quot;_blank&quot;&gt;Телеграмм канал - Программирование игр С++&lt;/a&gt;&lt;/p&gt;

</content></entry><entry><id>gamedeveloper:KO2svQewVZP</id><link rel="alternate" type="text/html" href="https://teletype.in/@gamedeveloper/KO2svQewVZP?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=gamedeveloper"></link><title>Чистая архитектура в играх на SFML С++: как разложить код по полочкам</title><published>2025-03-15T14:05:15.145Z</published><updated>2025-03-15T14:24:14.791Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/d2/c0/d2c0210a-c844-47cb-bee4-4b1e420089b0.png"></media:thumbnail><category term="mul-timedijnaya-biblioteka-sfml-c" label="Мультимедийная библиотека  SFML C++"></category><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/46/15/46158a2a-9cbc-491b-9ba2-61503b6265ce.png&quot;&gt;Привет, разработчики! Сегодня поговорим о том, как применить чистую архитектуру (Clean Architecture) в разработке игр с использованием библиотеки SFML. Если вы хотите, чтобы ваш код был модульным, тестируемым и не привязанным к конкретной библиотеке, этот подход для вас. Давайте разберем, как разделить код на слои и сделать вашу игру структурированной и гибкой.</summary><content type="html">
  &lt;figure id=&quot;LO0W&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/46/15/46158a2a-9cbc-491b-9ba2-61503b6265ce.png&quot; width=&quot;1197&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Ct6K&quot;&gt;Привет, разработчики! Сегодня поговорим о том, как применить &lt;strong&gt;чистую архитектуру&lt;/strong&gt; (Clean Architecture) в разработке игр с использованием библиотеки SFML. Если вы хотите, чтобы ваш код был модульным, тестируемым и не привязанным к конкретной библиотеке, этот подход для вас. Давайте разберем, как разделить код на слои и сделать вашу игру структурированной и гибкой.&lt;/p&gt;
  &lt;h2 id=&quot;RzFb&quot;&gt;Что такое чистая архитектура?&lt;/h2&gt;
  &lt;p id=&quot;hnro&quot;&gt;Чистая архитектура — это концепция, предложенная Робертом Мартином (дядей Бобом), которая помогает организовать код так, чтобы он был:&lt;/p&gt;
  &lt;ul id=&quot;DmqK&quot;&gt;
    &lt;li id=&quot;Wkjl&quot;&gt;&lt;strong&gt;Независимым&lt;/strong&gt; от фреймворков (в нашем случае — SFML).&lt;/li&gt;
    &lt;li id=&quot;x2Tl&quot;&gt;&lt;strong&gt;Тестируемым&lt;/strong&gt; без запуска всей игры.&lt;/li&gt;
    &lt;li id=&quot;XKWf&quot;&gt;&lt;strong&gt;Четко разделенным&lt;/strong&gt; по зонам ответственности.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;mk1T&quot;&gt;Идея в том, чтобы игровая логика не знала о том, как рисуются спрайты или как обрабатываются нажатия клавиш. Вместо этого мы разбиваем код на слои, где каждый слой делает свою работу.&lt;/p&gt;
  &lt;h2 id=&quot;W4iR&quot;&gt;Слои чистой архитектуры в игре&lt;/h2&gt;
  &lt;p id=&quot;uBCr&quot;&gt;Представьте игру как торт: каждый слой — это отдельный уровень со своей задачей. Вот как это может выглядеть в контексте SFML:&lt;/p&gt;
  &lt;p id=&quot;g6uG&quot;&gt;&lt;strong&gt;Структура проекта&lt;/strong&gt;&lt;/p&gt;
  &lt;figure id=&quot;yOWz&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/07/f2/07f2f545-dc28-41fe-b33e-bff65d990183.png&quot; width=&quot;820&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;yQAp&quot;&gt;1. Сущности (Entities)&lt;/h3&gt;
  &lt;p id=&quot;mVND&quot;&gt;Это сердце игры — основные объекты, такие как игрок, враги или пули. Они содержат данные (позицию, здоровье, скорость) и базовую логику, но не знают ничего о SFML.&lt;/p&gt;
  &lt;p id=&quot;41g1&quot;&gt;Пример:&lt;/p&gt;
  &lt;pre id=&quot;zLTN&quot; data-lang=&quot;cpp&quot;&gt;class Player {
public:
    float x, y;
    float speed;
    int health;

    void move(float dx, float dy) {
        x += dx * speed;
        y += dy * speed;
    }
};&lt;/pre&gt;
  &lt;h3 id=&quot;WZrQ&quot;&gt;2. Бизнес-логика (Use Cases / Interactors)&lt;/h3&gt;
  &lt;p id=&quot;WlEf&quot;&gt;Здесь живут правила игры. Это слой, который отвечает за &amp;quot;что происходит&amp;quot;. Например, движение игрока, проверка столкновений или условия победы.&lt;/p&gt;
  &lt;p id=&quot;eXXU&quot;&gt;Пример:&lt;/p&gt;
  &lt;pre id=&quot;RtqQ&quot; data-lang=&quot;cpp&quot;&gt;class MovePlayerUseCase {
public:
    void execute(Player&amp;amp; player, float dx, float dy) {
        player.move(dx, dy);
    }
};&lt;/pre&gt;
  &lt;h3 id=&quot;5zYX&quot;&gt;3. Интерфейсы (Controllers / Presenters)&lt;/h3&gt;
  &lt;p id=&quot;YTR7&quot;&gt;Этот слой связывает бизнес-логику с внешним миром. Например:&lt;/p&gt;
  &lt;ul id=&quot;jmol&quot;&gt;
    &lt;li id=&quot;emno&quot;&gt;InputController — превращает нажатия клавиш в команды для игры.&lt;/li&gt;
    &lt;li id=&quot;mCLt&quot;&gt;Renderer — отвечает за отрисовку.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;9f6w&quot;&gt;Пример обработки ввода:&lt;/p&gt;
  &lt;pre id=&quot;cFYw&quot; data-lang=&quot;cpp&quot;&gt;class InputController {
public:
    void handleInput(sf::RenderWindow&amp;amp; window, 
    MovePlayerUseCase&amp;amp; moveUseCase, Player&amp;amp; player) {
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::W)) {
            moveUseCase.execute(player, 0, -1);
        }
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::S)) {
            moveUseCase.execute(player, 0, 1);
        }
    }
};&lt;/pre&gt;
  &lt;h3 id=&quot;NlhB&quot;&gt;4. Инфраструктура (Frameworks &amp;amp; Drivers)&lt;/h3&gt;
  &lt;p id=&quot;bXqI&quot;&gt;Тут живет код, который зависит от SFML: рендеринг, обработка событий, создание окон. Этот слой изолирован от остальной логики.&lt;/p&gt;
  &lt;p id=&quot;2FPA&quot;&gt;Пример рендеринга:&lt;/p&gt;
  &lt;pre id=&quot;tmL3&quot; data-lang=&quot;cpp&quot;&gt;class SfmlRenderer {
public:
    void render(sf::RenderWindow&amp;amp; window, const Player&amp;amp; player) {
        sf::CircleShape shape(20.f);
        shape.setPosition(player.x, player.y);
        shape.setFillColor(sf::Color::Green);
        window.draw(shape);
    }
};&lt;/pre&gt;
  &lt;h3 id=&quot;9pze&quot;&gt;Собираем игру: главный цикл&lt;/h3&gt;
  &lt;p id=&quot;9XTF&quot;&gt;Теперь соединим всё в главном цикле игры. &lt;/p&gt;
  &lt;p id=&quot;Pmg8&quot;&gt;Вот пример:&lt;/p&gt;
  &lt;pre id=&quot;FUEi&quot; data-lang=&quot;cpp&quot;&gt;#include &amp;lt;SFML/Graphics.hpp&amp;gt;
#include &amp;quot;Entities/Player.hpp&amp;quot;
#include &amp;quot;UseCases/MovePlayerUseCase.hpp&amp;quot;
#include &amp;quot;Interfaces/InputController.hpp&amp;quot;
#include &amp;quot;Infrastructure/SfmlRenderer.hpp&amp;quot;

int main() {
    sf::RenderWindow window(sf::VideoMode(800, 600), &amp;quot;Clean SFML Game&amp;quot;);

    Player player{400.f, 300.f, 5.f, 100}; // Начальная позиция и параметры
    MovePlayerUseCase moveUseCase;
    InputController inputController;
    SfmlRenderer renderer;

    while (window.isOpen()) {
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed) {
                window.close();
            }
        }

        // Ввод
        inputController.handleInput(window, moveUseCase, player);

        // Рендеринг
        window.clear();
        renderer.render(window, player);
        window.display();
    }

    return 0;
}&lt;/pre&gt;
  &lt;h3 id=&quot;Rk26&quot;&gt;Почему это круто?&lt;/h3&gt;
  &lt;ol id=&quot;KGz8&quot;&gt;
    &lt;li id=&quot;AfvF&quot;&gt;&lt;strong&gt;Гибкость&lt;/strong&gt;: Хотите заменить SFML на SDL? Просто перепишите слой инфраструктуры — остальной код не тронется.&lt;/li&gt;
    &lt;li id=&quot;5KoJ&quot;&gt;&lt;strong&gt;Тестируемость&lt;/strong&gt;: Можно проверять логику движения или столкновений без запуска окна.&lt;/li&gt;
    &lt;li id=&quot;gqsU&quot;&gt;&lt;strong&gt;Порядок&lt;/strong&gt;: Каждый слой отвечает за своё, и код становится читаемым.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h3 id=&quot;vaF2&quot;&gt;Полезные советы&lt;/h3&gt;
  &lt;p id=&quot;wa7k&quot;&gt;Используйте абстрактные классы или интерфейсы, чтобы слои общались через четкие границы.&lt;/p&gt;
  &lt;p id=&quot;C3sP&quot;&gt;Не храните состояние игры (например, координаты) в объектах SFML — держите их в сущностях.&lt;/p&gt;
  &lt;p id=&quot;Bruf&quot;&gt;Избегайте соблазна смешивать логику с рендерингом — это путь к хаосу.&lt;/p&gt;
  &lt;p id=&quot;lBB3&quot;&gt;Чистая архитектура в играх на SFML — это не просто модный подход, а способ сделать ваш проект масштабируемым и удобным для поддержки. Да, на старте придется потратить чуть больше времени на разделение кода, но в долгосрочной перспективе это окупается.&lt;/p&gt;
  &lt;p id=&quot;VHNG&quot;&gt;Если у вас есть вопросы или вы хотите разобрать конкретный пример — пишите в комментариях! &lt;/p&gt;
  &lt;p id=&quot;lJMm&quot;&gt;А как вы организуете код в своих играх? Делитесь опытом!&lt;/p&gt;
  &lt;p id=&quot;DCYb&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;FygN&quot;&gt;&lt;a href=&quot;https://t.me/C_Verhovcevo_NVK&quot; target=&quot;_blank&quot;&gt;Телеграмм канал - Программирование игр С++&lt;/a&gt;&lt;/p&gt;

</content></entry><entry><id>gamedeveloper:eapvb5LgYef</id><link rel="alternate" type="text/html" href="https://teletype.in/@gamedeveloper/eapvb5LgYef?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=gamedeveloper"></link><title>Визуализация быстрой сортировки с SFML и C++20</title><published>2025-03-08T15:04:05.096Z</published><updated>2025-03-08T15:04:25.579Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/a8/8d/a88d6c97-e663-4a0d-9d47-42a7765616f3.png"></media:thumbnail><category term="mul-timedijnaya-biblioteka-sfml-c" label="Мультимедийная библиотека  SFML C++"></category><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/8e/ec/8eeca781-1042-4130-84ef-18bf7ca2693e.png&quot;&gt;В этой статье мы создадим интерактивную визуализацию алгоритма быстрой сортировки (QuickSort) с использованием библиотеки SFML и современных возможностей C++20. Этот проект поможет вам лучше понять, как работает один из самых популярных алгоритмов сортировки, и покажет, как можно комбинировать графику и алгоритмы для создания образовательных инструментов.</summary><content type="html">
  &lt;figure id=&quot;sfMs&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/8e/ec/8eeca781-1042-4130-84ef-18bf7ca2693e.png&quot; width=&quot;1186&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Y3GO&quot;&gt;В этой статье мы создадим интерактивную визуализацию алгоритма быстрой сортировки (QuickSort) с использованием библиотеки SFML и современных возможностей C++20. Этот проект поможет вам лучше понять, как работает один из самых популярных алгоритмов сортировки, и покажет, как можно комбинировать графику и алгоритмы для создания образовательных инструментов.&lt;/p&gt;
  &lt;h2 id=&quot;4Yky&quot;&gt;Зачем визуализировать алгоритмы?&lt;/h2&gt;
  &lt;p id=&quot;McPi&quot;&gt;Алгоритмы сортировки, такие как QuickSort, могут быть сложными для понимания, особенно для новичков. &lt;/p&gt;
  &lt;p id=&quot;GcwK&quot;&gt;Визуализация помогает:&lt;/p&gt;
  &lt;ul id=&quot;5Nu7&quot;&gt;
    &lt;li id=&quot;ZqZD&quot;&gt;Увидеть, как данные перемещаются и сравниваются на каждом шаге.&lt;/li&gt;
    &lt;li id=&quot;oFUH&quot;&gt;Понять концепции разбиения и рекурсии.&lt;/li&gt;
    &lt;li id=&quot;CL8k&quot;&gt;Сделать обучение более интерактивным и увлекательным.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;bZni&quot;&gt;Если вы хотите не только изучить алгоритмы, но и создать что-то наглядное и красивое, этот проект для вас!&lt;/p&gt;
  &lt;h2 id=&quot;ugdB&quot;&gt;Что нам понадобится?&lt;/h2&gt;
  &lt;p id=&quot;xH4Y&quot;&gt;&lt;strong&gt;SFML&lt;/strong&gt;: Библиотека для работы с графикой, событиями и окнами. &lt;a href=&quot;https://www.sfml-dev.org/&quot; target=&quot;_blank&quot;&gt;Скачать и установить&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;qVyn&quot;&gt;&lt;strong&gt;Компилятор с поддержкой C++20&lt;/strong&gt;: Например, GCC или Clang.&lt;/p&gt;
  &lt;p id=&quot;s66r&quot;&gt;&lt;strong&gt;Базовые знания C++&lt;/strong&gt;: Работа с векторами, лямбда-функциями и рекурсией.&lt;/p&gt;
  &lt;h2 id=&quot;5PAu&quot;&gt;Описание проекта&lt;/h2&gt;
  &lt;h3 id=&quot;OWzD&quot;&gt;Цель&lt;/h3&gt;
  &lt;p id=&quot;u42w&quot;&gt;Создать программу, которая:&lt;/p&gt;
  &lt;ul id=&quot;1hWH&quot;&gt;
    &lt;li id=&quot;pQwr&quot;&gt;Генерирует случайный массив чисел.&lt;/li&gt;
    &lt;li id=&quot;IfZt&quot;&gt;Визуализирует его как набор столбцов.&lt;/li&gt;
    &lt;li id=&quot;HmQb&quot;&gt;Анимирует процесс сортировки с помощью QuickSort.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;eF53&quot;&gt;Особенности&lt;/h3&gt;
  &lt;p id=&quot;BhmI&quot;&gt;Использование std::ranges из C++20 для удобной работы с диапазонами.&lt;/p&gt;
  &lt;p id=&quot;6kCd&quot;&gt;Рекурсивная реализация QuickSort с визуализацией на каждом шаге.&lt;/p&gt;
  &lt;p id=&quot;PSay&quot;&gt;Простая, но эффективная анимация с задержкой для наглядности.&lt;/p&gt;
  &lt;h2 id=&quot;eEcA&quot;&gt;Код проекта&lt;/h2&gt;
  &lt;h3 id=&quot;HuHz&quot;&gt;Подготовка&lt;/h3&gt;
  &lt;ol id=&quot;8YLp&quot;&gt;
    &lt;li id=&quot;yuZB&quot;&gt;Установите SFML и настройте ваш проект (например, через CMake или вручную подключите библиотеки).&lt;/li&gt;
    &lt;li id=&quot;iAWh&quot;&gt;Убедитесь, что ваш компилятор поддерживает C++20 (флаг -std=c++20).&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h3 id=&quot;rDhC&quot;&gt;&lt;strong&gt;Полный код&lt;/strong&gt;&lt;/h3&gt;
  &lt;pre id=&quot;7FtS&quot; data-lang=&quot;cpp&quot;&gt;#include &amp;lt;SFML/Graphics.hpp&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;ranges&amp;gt;
#include &amp;lt;random&amp;gt;
#include &amp;lt;chrono&amp;gt;
#include &amp;lt;thread&amp;gt;

int main() {
    // Настройки окна
    sf::RenderWindow window(sf::VideoMode(1280, 720), &amp;quot;QuickSort Visualization&amp;quot;);
    window.setFramerateLimit(60);

    // Генерация случайного массива чисел
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution&amp;lt;&amp;gt; dis(10, 700);

    std::vector&amp;lt;int&amp;gt; arr(150);
    for (auto&amp;amp; x : arr) x = dis(gen);

    // Функция быстрой сортировки с визуализацией процесса
    auto quickSort = [&amp;amp;](auto&amp;amp; range, int low, int high, auto&amp;amp; recur) mutable {
        if (low &amp;gt;= high) return; // Базовый случай рекурсии

        int pivot = range[high]; // Выбираем опорный элемент (последний в диапазоне)
        int i = low - 1;

        // Разделение элементов: меньшие идут влево, большие вправо
        for (int j = low; j &amp;lt; high; ++j) {
            if (range[j] &amp;lt;= pivot) {
                ++i;
                std::swap(range[i], range[j]);
            }
        }
        std::swap(range[i + 1], range[high]); // Устанавливаем опорный элемент на своё место
        int pi = i + 1; // Индекс опорного элемента

        // Рекурсивная сортировка левой и правой частей
        recur(range, low, pi - 1, recur);
        recur(range, pi + 1, high, recur);

        // Визуализация текущего состояния массива
        window.clear();
        float barWidth = static_cast&amp;lt;float&amp;gt;(window.getSize().x) / arr.size();
        for (int k = 0; k &amp;lt; arr.size(); ++k) {
            sf::RectangleShape bar(sf::Vector2f(barWidth - 1, arr[k]));
            bar.setPosition(k * barWidth, window.getSize().y - arr[k]);
            bar.setFillColor(sf::Color::White);
            window.draw(bar);
        }
        window.display();
        std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Задержка для анимации
        };

    // Используем std::ranges для удобной работы с массивом
    auto range = std::ranges::subrange(arr.begin(), arr.end());
    bool sorting = true; // Флаг, чтобы сортировка выполнялась только один раз

    // Основной цикл программы
    while (window.isOpen()) {
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed) window.close(); // Закрытие окна
        }

        if (sorting) {
            quickSort(range, 0, arr.size() - 1, quickSort); // Запуск сортировки
            sorting = false; // Отключаем повторную сортировку
        }

        // Отрисовка финального состояния массива
        window.clear();
        float barWidth = static_cast&amp;lt;float&amp;gt;(window.getSize().x) / arr.size();
        for (int i = 0; i &amp;lt; arr.size(); ++i) {
            sf::RectangleShape bar(sf::Vector2f(barWidth - 1, arr[i]));
            bar.setPosition(i * barWidth, window.getSize().y - arr[i]);
            bar.setFillColor(sf::Color::White);
            window.draw(bar);
        }
        window.display();
    }

    return 0;
}
&lt;/pre&gt;
  &lt;h3 id=&quot;AHDg&quot;&gt;Пояснения к коду&lt;/h3&gt;
  &lt;p id=&quot;v34i&quot;&gt;&lt;strong&gt;Генерация массива&lt;/strong&gt;: Создаем вектор из 150 случайных чисел от 10 до 700 с помощью std::random_device и std::mt19937.&lt;/p&gt;
  &lt;p id=&quot;Cd12&quot;&gt;&lt;strong&gt;QuickSort&lt;/strong&gt;: Реализован как лямбда-функция с рекурсией. На каждом шаге разбиения массива мы визуализируем текущее состояние.&lt;/p&gt;
  &lt;p id=&quot;3zbT&quot;&gt;&lt;strong&gt;Визуализация&lt;/strong&gt;: Каждый элемент массива отображается как прямоугольник (sf::RectangleShape), высота которого пропорциональна значению элемента.&lt;/p&gt;
  &lt;p id=&quot;nwGk&quot;&gt;&lt;strong&gt;Анимация&lt;/strong&gt;: Задержка в 50 миллисекунд (std::this_thread::sleep_for) между шагами сортировки создает эффект плавной анимации.&lt;/p&gt;
  &lt;h3 id=&quot;PVcA&quot;&gt;Как это работает?&lt;/h3&gt;
  &lt;ol id=&quot;HA0h&quot;&gt;
    &lt;li id=&quot;UQIw&quot;&gt;При запуске программы генерируется случайный массив, который отображается как набор столбцов в окне SFML.&lt;/li&gt;
    &lt;li id=&quot;RqZy&quot;&gt;Алгоритм QuickSort начинает сортировку, и на каждом шаге разбиения массива окно обновляется, показывая текущее состояние.&lt;/li&gt;
    &lt;li id=&quot;z8p5&quot;&gt;После завершения сортировки вы видите отсортированный массив.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;OwG1&quot;&gt;Попробуйте запустить код и понаблюдать за процессом — это действительно увлекательно!&lt;/p&gt;
  &lt;figure id=&quot;M5a4&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/bc/ad/bcadbba7-8495-40ff-8ba2-8e3a0eba132d.gif&quot; width=&quot;1280&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;ntLz&quot;&gt;Расширяемость проекта&lt;/h3&gt;
  &lt;p id=&quot;meql&quot;&gt;Хотите сделать проект еще интереснее? Вот несколько идей:&lt;/p&gt;
  &lt;ul id=&quot;IBcy&quot;&gt;
    &lt;li id=&quot;Y1io&quot;&gt;Добавьте другие алгоритмы сортировки (например, пузырьковую или сортировку слиянием) и сравните их визуально.&lt;/li&gt;
    &lt;li id=&quot;We7e&quot;&gt;Включите интерактивные элементы: кнопки для запуска сортировки или изменения скорости анимации.&lt;/li&gt;
    &lt;li id=&quot;IixR&quot;&gt;Экспериментируйте с цветами: выделяйте опорный элемент (pivot) другим цветом для наглядности.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;mxGh&quot;&gt;Заключение&lt;/h2&gt;
  &lt;p id=&quot;8LtC&quot;&gt;Визуализация алгоритмов — это мощный инструмент для обучения и отладки. С помощью SFML и C++20 вы можете создавать не только полезные, но и красивые образовательные проекты. Этот пример демонстрирует, как комбинировать графику, алгоритмы и современные возможности языка для создания интерактивных приложений.&lt;/p&gt;
  &lt;p id=&quot;xU9M&quot;&gt;Попробуйте улучшить этот проект: добавьте звуковые эффекты, измените стиль визуализации или реализуйте другой алгоритм. Делитесь своими идеями в комментариях — мне будет интересно узнать, что вы придумали!&lt;/p&gt;
  &lt;p id=&quot;yQ2f&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;0EWI&quot;&gt;&lt;a href=&quot;https://t.me/C_Verhovcevo_NVK&quot; target=&quot;_blank&quot;&gt;Телеграмм канал - Программирование игр С++&lt;/a&gt;&lt;/p&gt;

</content></entry><entry><id>gamedeveloper:BQuEWTLbgaU</id><link rel="alternate" type="text/html" href="https://teletype.in/@gamedeveloper/BQuEWTLbgaU?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=gamedeveloper"></link><title>CRTP в игровых механиках</title><published>2025-03-02T19:10:20.797Z</published><updated>2025-03-02T19:10:20.797Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/5d/7f/5d7f84df-442f-4a6b-9038-f8164b51f67c.png"></media:thumbnail><category term="prostye-prilozheniya-na-s" label="Простые приложения на С++"></category><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/04/09/040987a7-245e-4c93-9f8a-627113387944.jpeg&quot;&gt;В мире разработки игр часто возникает необходимость создавать гибкие и производительные системы. Одним из мощных инструментов в C++ для достижения этой цели является CRTP — Curiously Recurring Template Pattern. Сегодня мы разберём, как CRTP может помочь в создании игровых механик, и реализуем простой пример с использованием библиотеки SFML.</summary><content type="html">
  &lt;figure id=&quot;xuib&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/04/09/040987a7-245e-4c93-9f8a-627113387944.jpeg&quot; width=&quot;1280&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;nPXr&quot;&gt;В мире разработки игр часто возникает необходимость создавать гибкие и производительные системы. Одним из мощных инструментов в C++ для достижения этой цели является &lt;strong&gt;CRTP&lt;/strong&gt; — Curiously Recurring Template Pattern. Сегодня мы разберём, как CRTP может помочь в создании игровых механик, и реализуем простой пример с использованием библиотеки &lt;strong&gt;SFML&lt;/strong&gt;.&lt;/p&gt;
  &lt;h2 id=&quot;EHrw&quot;&gt;Что такое CRTP?&lt;/h2&gt;
  &lt;p id=&quot;Gsx9&quot;&gt;CRTP — это шаблонный паттерн в C++, где базовый класс является шаблоном, а производный класс передаётся в качестве параметра шаблона самому себе. Это позволяет базовому классу вызывать методы производного класса без использования виртуальных функций, что даёт выигрыш в производительности за счёт статической диспетчеризации.&lt;/p&gt;
  &lt;p id=&quot;1rG6&quot;&gt;Пример структуры CRTP:&lt;/p&gt;
  &lt;pre id=&quot;Vepp&quot; data-lang=&quot;cpp&quot;&gt;template &amp;lt;typename Derived&amp;gt;
class Base {
public:
    void interface() {
        static_cast&amp;lt;Derived*&amp;gt;(this)-&amp;gt;implementation();
    }
};

class Derived : public Base&amp;lt;Derived&amp;gt; {
public:
    void implementation() {
        // Реализация в производном классе
    }
};&lt;/pre&gt;
  &lt;h2 id=&quot;Xt7T&quot;&gt;Зачем использовать CRTP в играх?&lt;/h2&gt;
  &lt;ol id=&quot;cZqj&quot;&gt;
    &lt;li id=&quot;39DS&quot;&gt;&lt;strong&gt;Производительность&lt;/strong&gt;: Виртуальные функции добавляют накладные расходы из-за таблицы vtable. CRTP позволяет обойтись без них.&lt;/li&gt;
    &lt;li id=&quot;mQdC&quot;&gt;&lt;strong&gt;Гибкость&lt;/strong&gt;: Легко добавлять новые типы объектов с общей логикой.&lt;/li&gt;
    &lt;li id=&quot;RVlf&quot;&gt;&lt;strong&gt;Типобезопасность&lt;/strong&gt;: Компилятор проверяет корректность типов на этапе компиляции.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;tK7J&quot;&gt;В игровых механиках это особенно полезно для систем вроде управления объектами (игроки, враги, снаряды), где нужно минимизировать задержки и упростить расширение.&lt;/p&gt;
  &lt;h2 id=&quot;yMts&quot;&gt;Cистема игровых объектов с SFML&lt;/h2&gt;
  &lt;p id=&quot;VmvT&quot;&gt;Давайте создадим простую систему игровых сущностей с использованием CRTP и SFML. Мы реализуем базовый класс GameObject, от которого будут наследоваться Player и Enemy. Каждый объект будет обновляться и отрисовываться через SFML.&lt;/p&gt;
  &lt;p id=&quot;TCOW&quot;&gt;Перед началом убедитесь, что у вас установлена библиотека SFML. &lt;/p&gt;
  &lt;p id=&quot;rX1X&quot;&gt;Минимальные зависимости:&lt;/p&gt;
  &lt;ul id=&quot;fEho&quot;&gt;
    &lt;li id=&quot;XAjG&quot;&gt;sfml-graphics&lt;/li&gt;
    &lt;li id=&quot;Pyxz&quot;&gt;sfml-window&lt;/li&gt;
    &lt;li id=&quot;jXWB&quot;&gt;sfml-system&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;u5ni&quot;&gt;Создадим абстрактный базовый класс IGameObject и шаблонный класс с CRTP для реализации общей логики игровых объектов GameObject.&lt;/p&gt;
  &lt;pre id=&quot;7Vjh&quot; data-lang=&quot;cpp&quot;&gt;#pragma once
#include &amp;lt;SFML/Graphics.hpp&amp;gt;

// Абстрактный базовый класс для всех игровых объектов
class IGameObject {
public:
    // Виртуальный деструктор для корректного удаления объектов
    virtual ~IGameObject() = default; 
    // Чисто виртуальный метод для обновления состояния объекта
    virtual void update(float deltaTime) = 0; 
    // Чисто виртуальный метод для отрисовки объекта
    virtual void draw(sf::RenderWindow&amp;amp; window) = 0; 
    // Чисто виртуальный метод для получения позиции объекта
    virtual sf::Vector2f getPosition() const = 0; 
};&lt;/pre&gt;
  &lt;pre id=&quot;EIiV&quot; data-lang=&quot;cpp&quot;&gt;#pragma once

#include &amp;lt;SFML/Graphics.hpp&amp;gt;
// Шаблонный класс с CRTP для реализации общей логики игровых объектов
template &amp;lt;typename Derived&amp;gt;
class GameObject : public IGameObject {
public:
    // Переопределяем метод update, вызывая реализацию из производного класса
    void update(float deltaTime) override {
        static_cast&amp;lt;Derived*&amp;gt;(this)-&amp;gt;updateImpl(deltaTime);
    }

    // Переопределяем метод draw, вызывая реализацию из производного класса
    void draw(sf::RenderWindow&amp;amp; window) override {
        static_cast&amp;lt;Derived*&amp;gt;(this)-&amp;gt;drawImpl(window);
    }

    // Переопределяем метод getPosition, вызывая реализацию из производного класса
    sf::Vector2f getPosition() const override {
        return static_cast&amp;lt;const Derived*&amp;gt;(this)-&amp;gt;getPositionImpl();
    }
};&lt;/pre&gt;
  &lt;p id=&quot;wwgM&quot;&gt;В этом коде:&lt;/p&gt;
  &lt;ul id=&quot;JPDW&quot;&gt;
    &lt;li id=&quot;hWDS&quot;&gt;update обновляет состояние объекта.&lt;/li&gt;
    &lt;li id=&quot;cibD&quot;&gt;draw отвечает за отрисовку.&lt;/li&gt;
    &lt;li id=&quot;Irgz&quot;&gt;getPosition возвращает позицию объекта.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;JZ2I&quot;&gt;Методы вызывают соответствующие реализации в производных классах через static_cast.&lt;/p&gt;
  &lt;p id=&quot;44kx&quot;&gt;Теперь определим два игровых объекта: Player (игрок) и Enemy (враг).&lt;/p&gt;
  &lt;pre id=&quot;qhkK&quot; data-lang=&quot;cpp&quot;&gt;#pragma once
#include &amp;quot;GameObject.h&amp;quot;
// Класс игрока, наследующийся от GameObject с использованием CRTP
class Player : public GameObject&amp;lt;Player&amp;gt; {
private:
    // Графический объект — круг для представления игрока
    sf::CircleShape shape; 
    // Позиция игрока на экране
    sf::Vector2f position = sf::Vector2f(200.0f, 200.0f); 
    // Скорость перемещения игрока
    float speed = 200.0f;  

public:
    // Конструктор игрока
    Player() { 
    // Устанавливаем радиус круга
        shape.setRadius(20.0f); 
    // Задаём зелёный цвет
        shape.setFillColor(sf::Color::Green);
    // Устанавливаем позицию круга
        shape.setPosition(position);     
    }

    // Реализация обновления состояния игрока
    void updateImpl(float deltaTime) {
        // Движение влево
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) position.x -= speed * deltaTime; 
        // Движение вправо
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) position.x += speed * deltaTime; 
        // Движение вверх
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::W)) position.y -= speed * deltaTime;
        // Движение вниз
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::S)) position.y += speed * deltaTime; 
        // Обновляем позицию графического объекта
        shape.setPosition(position); 
    }

    // Реализация отрисовки игрока
    void drawImpl(sf::RenderWindow&amp;amp; window) const {
    // Отрисовываем круг в окне
        window.draw(shape); 
    }

    // Реализация получения позиции игрока
    sf::Vector2f getPositionImpl() const {
    // Возвращаем текущую позицию
        return position; 
    }
};&lt;/pre&gt;
  &lt;pre id=&quot;M80H&quot; data-lang=&quot;cpp&quot;&gt;#pragma once  
#include &amp;quot;GameObject.h&amp;quot;  
// Класс врага, наследующийся от GameObject с использованием CRTP  
class Enemy : public GameObject&amp;lt;Enemy&amp;gt; {  
private:  
// Графический объект — прямоугольник для врага
    sf::RectangleShape shape;
// Позиция врага на экране
   sf::Vector2f position = {400.0f, 300.0f}; 
// Скорость перемещения врага  
   float speed = 100.0f;     

public:
// Конструктор врага
   Enemy() { 
// Устанавливаем размер прямоугольника 
       shape.setSize({ 30.0f, 30.0f });
// Задаём красный цвет 
       shape.setFillColor(sf::Color::Red); 
// Устанавливаем позицию прямоугольника
       shape.setPosition(position);           
   }  

   // Реализация обновления состояния врага  
   void updateImpl(float deltaTime) {  
// Движение врага по горизонтали  
       position.x += speed * deltaTime;
// Разворот при достижении границ  
       if (position.x &amp;gt; 700 || position.x &amp;lt; 100) speed = -speed; 
// Обновляем позицию графического объекта 
       shape.setPosition(position);  
   }  

   // Реализация отрисовки врага  
   void drawImpl(sf::RenderWindow&amp;amp; window) const { 
// Отрисовываем прямоугольник в окне
       window.draw(shape);   
   }  

   // Реализация получения позиции врага  
   sf::Vector2f getPositionImpl() const { 
// Возвращаем текущую позицию 
       return position;  
   }  
};&lt;/pre&gt;
  &lt;p id=&quot;l5XL&quot;&gt;Теперь соберём всё вместе в главном файле.&lt;/p&gt;
  &lt;pre id=&quot;7zAx&quot; data-lang=&quot;cpp&quot;&gt;#include &amp;lt;SFML/Graphics.hpp&amp;gt; 
#include &amp;lt;vector&amp;gt;            
#include &amp;lt;memory&amp;gt;            
#include &amp;quot;IGameObject.h&amp;quot;     
#include &amp;quot;GameObject.h&amp;quot;      
#include &amp;quot;Player.h&amp;quot;          
#include &amp;quot;Enemy.h&amp;quot;           

// Основная функция программы
int main() {
// Создаём окно 800x600
    sf::RenderWindow window(sf::VideoMode(800, 600), &amp;quot;CRTP Game Example&amp;quot;); 
// Ограничиваем частоту кадров до 60 FPS
    window.setFramerateLimit(60); 

    // Вектор умных указателей для хранения всех игровых объектов
    std::vector&amp;lt;std::unique_ptr&amp;lt;IGameObject&amp;gt;&amp;gt; objects;
// Добавляем игрока в вектор
    objects.push_back(std::make_unique&amp;lt;Player&amp;gt;()); 
// Добавляем врага в вектор
    objects.push_back(std::make_unique&amp;lt;Enemy&amp;gt;());  
// Часы для расчёта времени между кадрами
    sf::Clock clock; 
// Главный игровой цикл
    while (window.isOpen()) { 
        sf::Event event;
// Обрабатываем события
        while (window.pollEvent(event)) { 
// Закрываем окно при нажатии на крестик
            if (event.type == sf::Event::Closed) window.close(); 
        }
// Вычисляем время с последнего кадра
        float deltaTime = clock.restart().asSeconds(); 

        // Обновляем состояние всех объектов
        for (auto const&amp;amp; obj : objects) {
            obj-&amp;gt;update(deltaTime);
        }
// Очищаем окно перед отрисовкой
        window.clear(); 
        // Отрисовываем все объекты
        for (auto const&amp;amp; obj : objects) {
            obj-&amp;gt;draw(window);
        }
        // Отображаем нарисованное на экране
        window.display(); 
    }

    return 0; 
}&lt;/pre&gt;
  &lt;h3 id=&quot;exkd&quot;&gt;Как это работает?&lt;/h3&gt;
  &lt;p id=&quot;K5J3&quot;&gt;&lt;strong&gt;Player&lt;/strong&gt; управляется клавишами W, A, S, D и отображается как зелёный круг.&lt;/p&gt;
  &lt;p id=&quot;7iOZ&quot;&gt;&lt;strong&gt;Enemy&lt;/strong&gt; движется автоматически влево-вправо и отображается как красный квадрат.&lt;/p&gt;
  &lt;p id=&quot;uupv&quot;&gt;Базовый класс GameObject через CRTP вызывает методы конкретных классов (Player или Enemy) без виртуальных функций.&lt;/p&gt;
  &lt;h3 id=&quot;aua5&quot;&gt;Преимущества подхода&lt;/h3&gt;
  &lt;p id=&quot;1vSf&quot;&gt;&lt;strong&gt;Скорость&lt;/strong&gt;: Отсутствие &lt;strong&gt;vtable&lt;/strong&gt; ускоряет вызовы методов.&lt;/p&gt;
  &lt;p id=&quot;XGt4&quot;&gt;&lt;strong&gt;Расширяемость&lt;/strong&gt;: Чтобы добавить новый тип объекта (например, Projectile), достаточно унаследовать его от GameObject&amp;lt;Projectile&amp;gt; и реализовать нужные методы.&lt;/p&gt;
  &lt;p id=&quot;t9Gw&quot;&gt;&lt;strong&gt;Простота&lt;/strong&gt;: Логика обновления и отрисовки сосредоточена в одном месте.&lt;/p&gt;
  &lt;h3 id=&quot;GNGR&quot;&gt;Итог&lt;/h3&gt;
  &lt;p id=&quot;ypoH&quot;&gt;CRTP — это мощный инструмент для игровых движков, где важны производительность и гибкость. В примере с SFML мы создали систему объектов, которую легко расширить без потери скорости. Попробуйте добавить новые сущности вроде снарядов или бонусов — это займёт всего несколько строк кода!&lt;/p&gt;
  &lt;p id=&quot;ZPiB&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;0Y2C&quot;&gt;&lt;strong&gt;vtable&lt;/strong&gt; (от англ. &lt;em&gt;virtual table&lt;/em&gt;) — это механизм в C++, который используется для реализации &lt;strong&gt;динамического полиморфизма&lt;/strong&gt; через виртуальные функции. Когда вы объявляете функцию в базовом классе как virtual, компилятор создаёт специальную таблицу указателей (vtable) для каждого класса, который содержит виртуальные функции. Эта таблица хранит адреса реализаций виртуальных методов для конкретного класса.&lt;/p&gt;
  &lt;p id=&quot;5Xwa&quot;&gt;&lt;a href=&quot;https://t.me/C_Verhovcevo_NVK&quot; target=&quot;_blank&quot;&gt;Телеграмм канал - Программирование игр С+&lt;/a&gt;+&lt;/p&gt;

</content></entry><entry><id>gamedeveloper:R118GOjqWi_</id><link rel="alternate" type="text/html" href="https://teletype.in/@gamedeveloper/R118GOjqWi_?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=gamedeveloper"></link><title>Разработка игр на C++</title><published>2025-02-26T22:33:55.612Z</published><updated>2025-02-26T22:33:55.612Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/05/6e/056efce9-d723-4ee4-a613-df0d57956f11.png"></media:thumbnail><category term="prostye-prilozheniya-na-s" label="Простые приложения на С++"></category><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/4a/f2/4af24d53-0567-494c-a0b1-23419c0abce3.jpeg&quot;&gt;Разработка игр на C++ — это увлекательный и сложный процесс, который требует понимания как самого языка программирования, так и основ игрового дизайна, математики и компьютерной графики. C++ является одним из самых популярных языков для создания игр благодаря своей производительности и гибкости. В этом введении мы рассмотрим основные этапы и концепции, которые помогут вам начать путь в разработке игр на C++.</summary><content type="html">
  &lt;figure id=&quot;67Df&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/4a/f2/4af24d53-0567-494c-a0b1-23419c0abce3.jpeg&quot; width=&quot;1344&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;m8V5&quot;&gt;Разработка игр на C++ — это увлекательный и сложный процесс, который требует понимания как самого языка программирования, так и основ игрового дизайна, математики и компьютерной графики. C++ является одним из самых популярных языков для создания игр благодаря своей производительности и гибкости. В этом введении мы рассмотрим основные этапы и концепции, которые помогут вам начать путь в разработке игр на C++.&lt;/p&gt;
  &lt;h3 id=&quot;bXG3&quot;&gt;1. &lt;strong&gt;Основы C++&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;sHXZ&quot;&gt;Прежде чем приступать к созданию игр, важно освоить базовые концепции языка C++:&lt;/p&gt;
  &lt;p id=&quot;C7u8&quot;&gt;&lt;strong&gt;Типы данных и переменные&lt;/strong&gt;: &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;float&lt;/code&gt;, &lt;code&gt;double&lt;/code&gt;, &lt;code&gt;char&lt;/code&gt;, &lt;code&gt;bool&lt;/code&gt; и т.д.&lt;/p&gt;
  &lt;p id=&quot;uBEU&quot;&gt;&lt;strong&gt;Управляющие конструкции&lt;/strong&gt;: &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;else&lt;/code&gt;, &lt;code&gt;switch&lt;/code&gt;, циклы (&lt;code&gt;for&lt;/code&gt;, &lt;code&gt;while&lt;/code&gt;, &lt;code&gt;do-while&lt;/code&gt;).&lt;/p&gt;
  &lt;p id=&quot;8i6T&quot;&gt;&lt;strong&gt;Функции&lt;/strong&gt;: создание и использование функций, передача параметров, возврат значений.&lt;/p&gt;
  &lt;p id=&quot;7Vjw&quot;&gt;&lt;strong&gt;Классы и объекты&lt;/strong&gt;: основы объектно-ориентированного программирования (ООП).&lt;/p&gt;
  &lt;p id=&quot;370j&quot;&gt;&lt;strong&gt;Указатели и ссылки&lt;/strong&gt;: работа с динамической памятью.&lt;/p&gt;
  &lt;p id=&quot;JBWm&quot;&gt;&lt;strong&gt;STL (Standard Template Library)&lt;/strong&gt;: использование контейнеров (&lt;code&gt;vector&lt;/code&gt;, &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;set&lt;/code&gt;) и алгоритмов.&lt;/p&gt;
  &lt;h3 id=&quot;QZgP&quot;&gt;2. &lt;strong&gt;Основы разработки игр&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;9NB2&quot;&gt;Разработка игр включает в себя несколько ключевых аспектов:&lt;/p&gt;
  &lt;p id=&quot;qixH&quot;&gt;&lt;strong&gt;Игровой цикл&lt;/strong&gt;: основной цикл игры, который обновляет состояние игры и отрисовывает кадры.&lt;/p&gt;
  &lt;p id=&quot;dX6V&quot;&gt;&lt;strong&gt;Графика&lt;/strong&gt;: работа с 2D и 3D графикой, использование библиотек для рендеринга.&lt;/p&gt;
  &lt;p id=&quot;FMyO&quot;&gt;&lt;strong&gt;Физика&lt;/strong&gt;: реализация физических взаимодействий (гравитация, столкновения и т.д.).&lt;/p&gt;
  &lt;p id=&quot;GawS&quot;&gt;&lt;strong&gt;Звук&lt;/strong&gt;: добавление звуковых эффектов и музыки.&lt;/p&gt;
  &lt;p id=&quot;oiSp&quot;&gt;&lt;strong&gt;Управление&lt;/strong&gt;: обработка ввода от пользователя (клавиатура, мышь, геймпад).&lt;/p&gt;
  &lt;p id=&quot;BppI&quot;&gt;&lt;strong&gt;Искусственный интеллект (ИИ)&lt;/strong&gt;: создание поведения для NPC (неигровых персонажей).&lt;/p&gt;
  &lt;h3 id=&quot;wgdG&quot;&gt;3. &lt;strong&gt;Библиотеки и фреймворки для разработки игр&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;aL1X&quot;&gt;Для упрощения разработки игр на C++ используются различные библиотеки и фреймворки. Вот некоторые из них:&lt;/p&gt;
  &lt;p id=&quot;brri&quot;&gt;&lt;strong&gt;SFML (Simple and Fast Multimedia Library)&lt;/strong&gt;: библиотека для работы с графикой, звуком и вводом. Отлично подходит для 2D-игр.&lt;/p&gt;
  &lt;p id=&quot;mNSj&quot;&gt;&lt;strong&gt;SDL (Simple DirectMedia Layer)&lt;/strong&gt;: кросс-платформенная библиотека для работы с графикой, звуком и вводом.&lt;/p&gt;
  &lt;p id=&quot;IQxM&quot;&gt;&lt;strong&gt;OpenGL&lt;/strong&gt;: низкоуровневая библиотека для работы с 3D-графикой.&lt;/p&gt;
  &lt;p id=&quot;6i0t&quot;&gt;&lt;strong&gt;Unreal Engine&lt;/strong&gt;: мощный игровой движок, который использует C++ для написания логики игр.&lt;/p&gt;
  &lt;p id=&quot;xzWm&quot;&gt;&lt;strong&gt;Unity (с использованием C++ через плагины)&lt;/strong&gt;: популярный игровой движок, который поддерживает C++ для расширения функциональности.&lt;/p&gt;
  &lt;p id=&quot;vbuV&quot;&gt;&lt;strong&gt;Godot&lt;/strong&gt;: открытый игровой движок, который также поддерживает C++.&lt;/p&gt;
  &lt;h3 id=&quot;0Awn&quot;&gt;4. &lt;strong&gt;Создание простой игры&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;s0E9&quot;&gt;Давайте рассмотрим основные шаги для создания простой 2D-игры на C++ с использованием SFML:&lt;/p&gt;
  &lt;h4 id=&quot;6Wzp&quot;&gt;Шаг 1: Установка SFML&lt;/h4&gt;
  &lt;ul id=&quot;31g2&quot;&gt;
    &lt;li id=&quot;t28K&quot;&gt;Скачайте и установите SFML с официального сайта: &lt;a href=&quot;https://www.sfml-dev.org/&quot; target=&quot;_blank&quot;&gt;https://www.sfml-dev.org/&lt;/a&gt;.&lt;/li&gt;
    &lt;li id=&quot;MfA3&quot;&gt;Настройте проект в вашей IDE (например, Visual Studio, Code::Blocks или CLion).&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h4 id=&quot;ecOC&quot;&gt;Шаг 2: Создание окна игры&lt;/h4&gt;
  &lt;pre id=&quot;uNqy&quot; data-lang=&quot;cpp&quot;&gt;#include &amp;lt;SFML/Graphics.hpp&amp;gt;

int main() {
    sf::RenderWindow window(sf::VideoMode(800, 600), &amp;quot;My Game&amp;quot;);

    while (window.isOpen()) {
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed)
                window.close();
        }

        window.clear();
        // Здесь будет отрисовка объектов
        window.display();
    }

    return 0;
}&lt;/pre&gt;
  &lt;h4 id=&quot;W2lH&quot;&gt;Шаг 3: Добавление спрайта&lt;/h4&gt;
  &lt;pre id=&quot;mYB0&quot; data-lang=&quot;cpp&quot;&gt;#include &amp;lt;SFML/Graphics.hpp&amp;gt;

int main() {
    sf::RenderWindow window(sf::VideoMode(800, 600), &amp;quot;My Game&amp;quot;);

    // Загрузка текстуры и создание спрайта
    sf::Texture texture;
    if (!texture.loadFromFile(&amp;quot;player.png&amp;quot;)) {
        return -1;
    }
    sf::Sprite player(texture);
    player.setPosition(400, 300);

    while (window.isOpen()) {
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed)
                window.close();
        }

        window.clear();
        window.draw(player); // Отрисовка спрайта
        window.display();
    }

    return 0;
}&lt;/pre&gt;
  &lt;h4 id=&quot;Scdp&quot;&gt;Шаг 4: Обработка ввода&lt;/h4&gt;
  &lt;pre id=&quot;HkIS&quot; data-lang=&quot;cpp&quot;&gt;if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) {
    player.move(-0.1f, 0.0f);
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) {
    player.move(0.1f, 0.0f);
}&lt;/pre&gt;
  &lt;h3 id=&quot;eLN4&quot;&gt;Заключение&lt;/h3&gt;
  &lt;p id=&quot;Wznm&quot;&gt;Разработка игр на C++ — это сложный, но очень интересный процесс. Начните с простых проектов, постепенно углубляя свои знания в области графики, физики и ИИ. Используйте доступные библиотеки и движки, чтобы ускорить разработку. Удачи в создании ваших игр!&lt;/p&gt;
  &lt;p id=&quot;WGIi&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;42tf&quot;&gt;&lt;a href=&quot;https://t.me/C_Verhovcevo_NVK&quot; target=&quot;_blank&quot;&gt;Телеграмм канал - Программирование игр С++&lt;/a&gt;&lt;/p&gt;

</content></entry><entry><id>gamedeveloper:dgq4fXaTw3O</id><link rel="alternate" type="text/html" href="https://teletype.in/@gamedeveloper/dgq4fXaTw3O?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=gamedeveloper"></link><title>Построение минимального дерева Штейнера в евклидовой плоскости</title><published>2025-02-02T17:38:43.736Z</published><updated>2025-02-02T17:38:43.736Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/5a/3c/5a3cbc99-28f1-43de-8f02-25c49361d72f.png"></media:thumbnail><category term="prostye-prilozheniya-na-s" label="Простые приложения на С++"></category><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/20/0f/200f6354-24e5-4fd0-989d-c7e695637521.jpeg&quot;&gt;Даны три точки в евклидовой плоскости с координатами A(x1,y1), B(x2,y2) и C(x3​,y3​). Необходимо построить минимальное дерево, соединяющее эти точки с минимальной общей длиной рёбер.</summary><content type="html">
  &lt;figure id=&quot;9KuY&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/20/0f/200f6354-24e5-4fd0-989d-c7e695637521.jpeg&quot; width=&quot;1280&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;o5Jt&quot;&gt;&lt;strong&gt;Условие:&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;s17Y&quot;&gt;Даны три точки в евклидовой плоскости с координатами A(x1,y1), B(x2,y2) и C(x3​,y3​). Необходимо построить минимальное дерево, соединяющее эти точки с минимальной общей длиной рёбер.&lt;/p&gt;
  &lt;p id=&quot;Iiss&quot;&gt;Разрешается добавление одной дополнительной точки S(xs,ys)(точки Штейнера), которая может уменьшить суммарную длину соединяющих отрезков.&lt;/p&gt;
  &lt;h3 id=&quot;aVkp&quot;&gt;&lt;strong&gt;Требования:&lt;/strong&gt;&lt;/h3&gt;
  &lt;ol id=&quot;41qu&quot;&gt;
    &lt;li id=&quot;AQqv&quot;&gt;Найти координаты точки S, которая минимизирует сумму расстояний от неё до заданных точек.&lt;/li&gt;
    &lt;li id=&quot;AawZ&quot;&gt;Визуализировать полученное дерево с помощью библиотеки &lt;strong&gt;SFML&lt;/strong&gt;.&lt;/li&gt;
    &lt;li id=&quot;55V0&quot;&gt;Вывести терминальные точки (красным), точку Штейнера (синим) и соединяющие рёбра (жёлтым).&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h3 id=&quot;BHTP&quot;&gt;&lt;strong&gt;Формат входных данных:&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;9mHw&quot;&gt;На вход подаются три пары вещественных чисел (x1,y1), (x2,y2), (x3,y3) — координаты заданных точек.&lt;/p&gt;
  &lt;h3 id=&quot;1RXX&quot;&gt;&lt;strong&gt;Формат выходных данных:&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;PrNW&quot;&gt;Программа должна:&lt;/p&gt;
  &lt;ul id=&quot;ZMjt&quot;&gt;
    &lt;li id=&quot;rDFp&quot;&gt;Вычислить координаты точки Штейнера S(xs,ys).&lt;/li&gt;
    &lt;li id=&quot;6qAD&quot;&gt;Вывести общую длину построенного дерева.&lt;/li&gt;
    &lt;li id=&quot;6TUX&quot;&gt;Открыть графическое окно с визуализацией результата.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;MaWR&quot;&gt;&lt;strong&gt;Пример работы программы:&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;3ZEL&quot;&gt;Входные данные:&lt;/p&gt;
  &lt;figure id=&quot;QCvz&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/0e/cd/0ecdd76b-a764-4cdd-b660-49681b48e5b9.png&quot; width=&quot;730&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;JkEX&quot;&gt;Выходные данные:&lt;/p&gt;
  &lt;figure id=&quot;3aNE&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/2b/18/2b180899-767e-4b6c-9f66-3e4277135995.png&quot; width=&quot;734&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;15v6&quot;&gt;&lt;strong&gt;Порядок решения задачи о минимальном дереве Штейнера&lt;/strong&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;aOO0&quot;&gt;&lt;strong&gt;Шаг 1: Определение входных данных&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;hzC4&quot;&gt;Заданы три точки A(x1,y1), B(x2,y2) и C(x3,y3) в евклидовой плоскости. Нужно найти &lt;strong&gt;минимальное дерево&lt;/strong&gt;, соединяющее их, с возможностью добавления &lt;strong&gt;одной точки Штейнера&lt;/strong&gt; S(xs,ys), которая минимизирует сумму расстояний.&lt;/p&gt;
  &lt;h3 id=&quot;KtPV&quot;&gt;&lt;strong&gt;Шаг 2: Поиск точки Штейнера&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;PK2k&quot;&gt;Метод &lt;strong&gt;градиентного спуска&lt;/strong&gt; применяется для нахождения оптимальной точки S.&lt;/p&gt;
  &lt;ol id=&quot;62Kj&quot;&gt;
    &lt;li id=&quot;jW30&quot;&gt;&lt;strong&gt;Начальная точка&lt;/strong&gt;&lt;/li&gt;
    &lt;ul id=&quot;n6aJ&quot;&gt;
      &lt;li id=&quot;niD9&quot;&gt;Используем &lt;strong&gt;центр масс&lt;/strong&gt; терминальных точек в качестве начальной точки&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/ol&gt;
  &lt;figure id=&quot;COq3&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/36/50/36508e09-c1fd-4230-9be5-5bce65042eb6.png&quot; width=&quot;506&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;zR2K&quot;&gt;    2.&lt;strong&gt; Минимизация суммы расстояний&lt;/strong&gt;&lt;/p&gt;
  &lt;ul id=&quot;yRcq&quot;&gt;
    &lt;li id=&quot;vFly&quot;&gt;Вычисляем сумму расстояний от текущего положения S до всех терминалов:&lt;/li&gt;
  &lt;/ul&gt;
  &lt;figure id=&quot;3EdF&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/48/7c/487ce0e7-302a-4c0a-86aa-50f893a6d77b.png&quot; width=&quot;448&quot; /&gt;
  &lt;/figure&gt;
  &lt;ul id=&quot;muMh&quot;&gt;
    &lt;li id=&quot;Vkt0&quot;&gt;Двигаемся в направлениях &lt;strong&gt;вверх, вниз, влево и вправо&lt;/strong&gt; с некоторым шагом Δ, уменьшая его, когда не находим лучшего положения. &lt;/li&gt;
    &lt;li id=&quot;jMI4&quot;&gt;Останавливаемся, когда шаг становится меньше заданной точности.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;UfbA&quot;&gt;&lt;strong&gt;Шаг 3: Визуализация графа (SFML)&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;41S1&quot;&gt;После нахождения S(xs,ys), строим дерево:&lt;/p&gt;
  &lt;ol id=&quot;qDJD&quot;&gt;
    &lt;li id=&quot;sPzc&quot;&gt;&lt;strong&gt;Рисуем точки:&lt;/strong&gt;&lt;/li&gt;
    &lt;ul id=&quot;QbmI&quot;&gt;
      &lt;li id=&quot;aOsO&quot;&gt;&lt;strong&gt;Красные круги&lt;/strong&gt; для терминальных точек A,B,C.&lt;/li&gt;
      &lt;li id=&quot;h6t5&quot;&gt;&lt;strong&gt;Синяя точка&lt;/strong&gt; для найденной точки Штейнера.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;li id=&quot;G35G&quot;&gt;&lt;strong&gt;Рисуем рёбра:&lt;/strong&gt;&lt;/li&gt;
    &lt;ul id=&quot;ADY3&quot;&gt;
      &lt;li id=&quot;kVYb&quot;&gt;&lt;strong&gt;Жёлтые линии&lt;/strong&gt; от точки S к каждой из терминальных точек.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;li id=&quot;20kU&quot;&gt;&lt;strong&gt;Отображение в окне SFML&lt;/strong&gt;&lt;/li&gt;
    &lt;ul id=&quot;1v2k&quot;&gt;
      &lt;li id=&quot;qX2I&quot;&gt;Создаём окно SFML.&lt;/li&gt;
      &lt;li id=&quot;pZYi&quot;&gt;Отображаем точки и соединяющие линии.&lt;/li&gt;
      &lt;li id=&quot;zxUB&quot;&gt;Обновляем экран.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/ol&gt;
  &lt;h3 id=&quot;NXeI&quot;&gt;&lt;strong&gt;Шаг 4: Вывод результатов&lt;/strong&gt;&lt;/h3&gt;
  &lt;ol id=&quot;X0wI&quot;&gt;
    &lt;li id=&quot;IivD&quot;&gt;Вывод координат точки Штейнера&lt;/li&gt;
    &lt;li id=&quot;vtkm&quot;&gt;Вывод длинны минимального дерева&lt;/li&gt;
    &lt;li id=&quot;IQH4&quot;&gt;Графическое представление : &lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;tKei&quot;&gt;- Красные точки — исходные точки&lt;/p&gt;
  &lt;p id=&quot;IlzL&quot;&gt;- Синяя точка — точка Штейнера&lt;/p&gt;
  &lt;p id=&quot;XxSK&quot;&gt;- Жёлтые линии — минимальное дерево&lt;/p&gt;
  &lt;h3 id=&quot;GeKO&quot;&gt;Код программы&lt;/h3&gt;
  &lt;pre id=&quot;e1r0&quot; data-lang=&quot;cpp&quot;&gt;#include &amp;lt;SFML/Graphics.hpp&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;cmath&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;limits&amp;gt;

using namespace std;
using namespace sf;

struct Point {
    double x, y;
};

// Вычисление расстояния между двумя точками
double distance(Point a, Point b) {
    return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}

// Сумма расстояний от точки p до всех остальных
double totalDistance(Point p, const vector&amp;lt;Point&amp;gt;&amp;amp; terminals) {
    double sum = 0;
    for (const auto&amp;amp; t : terminals) {
        sum += distance(p, t);
    }
    return sum;
}

// Метод градиентного спуска для поиска точки Штейнера
Point steinerPoint(const vector&amp;lt;Point&amp;gt;&amp;amp; terminals) {
    double step = 0.1;
    double precision = 1e-6;
    Point steiner = { 0, 0 };

    for (const auto&amp;amp; t : terminals) {
        steiner.x += t.x;
        steiner.y += t.y;
    }
    steiner.x /= terminals.size();
    steiner.y /= terminals.size();

    double prevDist = totalDistance(steiner, terminals);

    while (step &amp;gt; precision) {
        bool improved = false;

        for (double dx : {-step, step}) {
            for (double dy : {-step, step}) {
                Point newPoint = { steiner.x + dx, steiner.y + dy };
                double newDist = totalDistance(newPoint, terminals);

                if (newDist &amp;lt; prevDist) {
                    steiner = newPoint;
                    prevDist = newDist;
                    improved = true;
                }
            }
        }

        if (!improved) {
            step /= 2;
        }
    }

    return steiner;
}

// Основная функция
int main() {
    // Размер окна
    const int WIDTH = 600, HEIGHT = 600;

    // Исходные точки
    vector&amp;lt;Point&amp;gt; terminals = { {100, 500}, {500, 500}, {300, 100}};

    // Найдём точку Штейнера
    Point s = steinerPoint(terminals);

    // Создание окна SFML
    RenderWindow window(VideoMode(WIDTH, HEIGHT), &amp;quot;Steiner Tree Visualization&amp;quot;);

    while (window.isOpen()) {
        Event event;
        while (window.pollEvent(event)) {
            if (event.type == Event::Closed)
                window.close();
        }

        // Очистка экрана
        window.clear(Color::Black);

        // Рисуем рёбра (жёлтые линии)
        for (const auto&amp;amp; t : terminals) {
            Vertex line[] = {
                Vertex(Vector2f(s.x, s.y), Color::Yellow),
                Vertex(Vector2f(t.x, t.y), Color::Yellow)
            };
            window.draw(line, 2, Lines);
        }

        // Рисуем терминальные точки (красные)
        for (const auto&amp;amp; t : terminals) {
            CircleShape terminal(5);
            terminal.setPosition(t.x - 5, t.y - 5);
            terminal.setFillColor(Color::Red);
            window.draw(terminal);
        }

        // Рисуем точку Штейнера (синяя)
        CircleShape steiner(5);
        steiner.setPosition(s.x - 5, s.y - 5);
        steiner.setFillColor(Color::Blue);
        window.draw(steiner);

        // Отображение кадра
        window.display();
    }

    return 0;
}&lt;/pre&gt;
  &lt;p id=&quot;ggSv&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;PzBh&quot;&gt;&lt;strong&gt;Итог&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;NRFH&quot;&gt;Метод использует &lt;strong&gt;градиентный спуск&lt;/strong&gt; для поиска точки Штейнера и &lt;strong&gt;SFML&lt;/strong&gt; для визуализации. Это позволяет эффективно решать задачу и демонстрировать результат в графическом окне.&lt;/p&gt;
  &lt;p id=&quot;Wk7u&quot;&gt;&lt;a href=&quot;https://t.me/C_Verhovcevo_NVK&quot; target=&quot;_blank&quot;&gt;Телеграмм канал - Программирование игр С++/С#&lt;/a&gt;&lt;/p&gt;

</content></entry><entry><id>gamedeveloper:tjzqC6FUB-G</id><link rel="alternate" type="text/html" href="https://teletype.in/@gamedeveloper/tjzqC6FUB-G?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=gamedeveloper"></link><title>Паттерны состояния (State) и стратегии (Strategy) в  играх на C++</title><published>2025-02-02T13:51:28.104Z</published><updated>2025-02-02T14:12:22.878Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/62/a9/62a94516-8638-4592-a6b6-ae3e68833e39.png"></media:thumbnail><category term="prostye-prilozheniya-na-s" label="Простые приложения на С++"></category><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/e6/6e/e66e9756-82d5-4fd3-8c03-6d92ef05ce4f.jpeg&quot;&gt;В мире разработки игр существует множество способов улучшить код и сделать его более гибким и масштабируемым. Два популярных паттерна, которые часто используются в этой области, - это паттерны состояния (State) и стратегии (Strategy). В этой статье мы подробно рассмотрим каждый из них и объясним, как их можно применять в разработке игр.</summary><content type="html">
  &lt;figure id=&quot;9MSw&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e6/6e/e66e9756-82d5-4fd3-8c03-6d92ef05ce4f.jpeg&quot; width=&quot;1280&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;CUYX&quot;&gt;В мире разработки игр существует множество способов улучшить код и сделать его более гибким и масштабируемым. Два популярных паттерна, которые часто используются в этой области, - это паттерны состояния (State) и стратегии (Strategy). В этой статье мы подробно рассмотрим каждый из них и объясним, как их можно применять в разработке игр.&lt;/p&gt;
  &lt;h2 id=&quot;CjuW&quot;&gt;Паттерн состояния (State)&lt;/h2&gt;
  &lt;p id=&quot;L2aA&quot;&gt;Паттерн состояния - это поведенческий паттерн проектирования, который позволяет объекту изменять свое поведение в зависимости от своего внутреннего состояния. Вместо того чтобы использовать условные операторы для обработки различных состояний объекта, мы можем делегировать ответственность за каждое состояние отдельному классу. Представьте себе персонажа в игре, который может находиться в разных состояниях: &amp;quot;стоит&amp;quot;, &amp;quot;идет&amp;quot;, &amp;quot;прыгает&amp;quot;, &amp;quot;атакует&amp;quot; и т.д. С помощью паттерна состояния мы можем создать отдельный класс для каждого из этих состояний, и каждый класс будет отвечать за конкретное поведение персонажа.&lt;/p&gt;
  &lt;h3 id=&quot;rk4C&quot;&gt;Преимущества использования паттерна состояния&lt;/h3&gt;
  &lt;p id=&quot;Uib5&quot;&gt;&lt;strong&gt;Улучшение читаемости кода&lt;/strong&gt;: Код становится более структурированным и понятным, так как логика каждого состояния отделена от основной логики объекта.&lt;/p&gt;
  &lt;p id=&quot;Se0q&quot;&gt;&lt;strong&gt;Упрощение добавления новых состояний&lt;/strong&gt;: Добавить новое состояние становится легко, достаточно создать новый класс, реализующий интерфейс состояния.&lt;/p&gt;
  &lt;p id=&quot;HjEi&quot;&gt;&lt;strong&gt;Повышение гибкости&lt;/strong&gt;: Изменение поведения объекта в зависимости от состояния становится более гибким и управляемым.&lt;/p&gt;
  &lt;h2 id=&quot;657j&quot;&gt;Паттерн стратегии (Strategy)&lt;/h2&gt;
  &lt;p id=&quot;TppK&quot;&gt;Паттерн стратегии - это также поведенческий паттерн проектирования, который позволяет выбирать алгоритм или стратегию во время выполнения программы. Вместо того чтобы жестко задавать алгоритм в коде, мы можем предоставить возможность выбора из нескольких доступных стратегий. Рассмотрим игру, в которой персонаж может использовать разные виды оружия: меч, лук, магический посох и т.д. С помощью паттерна стратегии мы можем создать отдельный класс для каждого вида оружия, и каждый класс будет реализовывать свою собственную стратегию атаки.&lt;/p&gt;
  &lt;h4 id=&quot;ohp2&quot;&gt;Преимущества использования паттерна стратегии&lt;/h4&gt;
  &lt;p id=&quot;soTX&quot;&gt;&lt;strong&gt;Гибкость выбора алгоритма&lt;/strong&gt;: Мы можем легко менять алгоритм или стратегию во время выполнения программы, не изменяя основной код.&lt;/p&gt;
  &lt;p id=&quot;B7ht&quot;&gt;&lt;strong&gt;Улучшение масштабируемости&lt;/strong&gt;: Добавление новых стратегий становится простым и не требует изменения существующего кода.&lt;/p&gt;
  &lt;p id=&quot;8Qp5&quot;&gt;&lt;strong&gt;Повышение производительности&lt;/strong&gt;: Мы можем выбирать наиболее подходящую стратегию в зависимости от ситуации, что может привести к улучшению производительности игры.&lt;/p&gt;
  &lt;h3 id=&quot;Gxv8&quot;&gt;Пример кода моделей поведения игрового бота, используя оба паттерна.&lt;/h3&gt;
  &lt;pre id=&quot;ykVC&quot; data-lang=&quot;cpp&quot;&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;memory&amp;gt;

// Предварительное объявление класса Bot
class Bot;

// Базовый интерфейс состояния
class State {
public:
    virtual ~State() = default;
// Метод для обработки состояния бота
    virtual void handle(Bot&amp;amp; bot) = 0;  
};

// --- Реализация состояний ---

// Состояние &amp;quot;Спокойствие&amp;quot; - когда бот не активен, патрулирует.
class IdleState : public State {
public:
    void handle(Bot&amp;amp; bot) override;  // Переход к следующему состоянию
};

// Состояние &amp;quot;Охота&amp;quot; - когда бот преследует цель.
class ChaseState : public State {
public:
    void handle(Bot&amp;amp; bot) override;  // Переход к следующему состоянию
};

// Состояние &amp;quot;Атака&amp;quot; - когда бот готовится атаковать цель.
class AttackState : public State {
public:
    void handle(Bot&amp;amp; bot) override;  // Переход к следующему состоянию
};

// Класс бота
class Bot {
protected:
    std::shared_ptr&amp;lt;State&amp;gt; currentState;  // Текущее состояние бота

public:
    Bot();  // Конструктор, инициализирующий начальное состояние
    void setState(std::shared_ptr&amp;lt;State&amp;gt; newState);  // Установка нового состояния
    virtual void update();  // Обновление состояния бота
    virtual void attack() {}  // Виртуальная функция атаки (будет переопределена)
};

// Конструктор бота: начальное состояние - &amp;quot;Спокойствие&amp;quot;
Bot::Bot() : currentState(std::make_shared&amp;lt;IdleState&amp;gt;()) {}

void Bot::setState(std::shared_ptr&amp;lt;State&amp;gt; newState) {
    currentState = std::move(newState);  // Меняем состояние на новое
}

void Bot::update() {
    if (currentState) {
        currentState-&amp;gt;handle(*this);  // Обработка текущего состояния
    }
}

// Реализация методов состояний

// В состоянии &amp;quot;Спокойствие&amp;quot; бот патрулирует, затем переходит в состояние &amp;quot;Охота&amp;quot;
void IdleState::handle(Bot&amp;amp; bot) {
    std::cout &amp;lt;&amp;lt; &amp;quot;Бот патрулирует...\n&amp;quot;;
    bot.setState(std::make_shared&amp;lt;ChaseState&amp;gt;());  // Переход к состоянию охоты
}

// В состоянии &amp;quot;Охота&amp;quot; бот преследует игрока, затем переходит в состояние &amp;quot;Атака&amp;quot;
void ChaseState::handle(Bot&amp;amp; bot) {
    std::cout &amp;lt;&amp;lt; &amp;quot;Бот преследует игрока!\n&amp;quot;;
    bot.setState(std::make_shared&amp;lt;AttackState&amp;gt;());  // Переход к состоянию атаки
}

// В состоянии &amp;quot;Атака&amp;quot; бот готовится атаковать, затем выполняет атаку и переходит в состояние &amp;quot;Спокойствие&amp;quot;
void AttackState::handle(Bot&amp;amp; bot) {
    std::cout &amp;lt;&amp;lt; &amp;quot;Бот готовится к атаке!\n&amp;quot;;
    bot.attack();  // Вызов функции атаки
    bot.setState(std::make_shared&amp;lt;IdleState&amp;gt;());  // Переход в спокойное состояние
}

// --- Реализация стратегий атаки ---

// Интерфейс стратегии атаки
class AttackStrategy {
public:
    virtual ~AttackStrategy() = default;
    virtual void attack() = 0;  // Метод для выполнения атаки
};

// Реализация атаки в ближнем бою
class MeleeAttack : public AttackStrategy {
public:
    void attack() override {
        std::cout &amp;lt;&amp;lt; &amp;quot;Бот атакует в ближнем бою!\n&amp;quot;;  // Атака в ближнем бою
    }
};

// Реализация атаки дальнего боя
class RangedAttack : public AttackStrategy {
public:
    void attack() override {
        std::cout &amp;lt;&amp;lt; &amp;quot;Бот стреляет издалека!\n&amp;quot;;  // Атака дальнего боя
    }
};

// Добавление поддержки стратегии атаки в бота
class BotWithStrategy : public Bot {
private:
    std::shared_ptr&amp;lt;AttackStrategy&amp;gt; attackStrategy;  // Стратегия атаки

public:
    BotWithStrategy();  // Конструктор, устанавливающий начальную стратегию атаки
    void setAttackStrategy(std::shared_ptr&amp;lt;AttackStrategy&amp;gt; newStrategy);  // Установка новой стратегии атаки
    void attack() override;  // Переопределенная атака
};

// Конструктор бота с поддержкой стратегии, начальная стратегия - ближний бой
BotWithStrategy::BotWithStrategy()
    : Bot(), attackStrategy(std::make_shared&amp;lt;MeleeAttack&amp;gt;()) {}

void BotWithStrategy::setAttackStrategy(std::shared_ptr&amp;lt;AttackStrategy&amp;gt; newStrategy) {
    attackStrategy = std::move(newStrategy);  // Устанавливаем новую стратегию атаки
}

void BotWithStrategy::attack() {
    if (attackStrategy) {
        attackStrategy-&amp;gt;attack();  // Выполняем атаку с текущей стратегией
    }
    else {
        std::cout &amp;lt;&amp;lt; &amp;quot;Стратегия атаки не установлена!\n&amp;quot;;  // Если стратегия не установлена
    }
}

// --- Тестирование ---

int main() {
    system(&amp;quot;chcp 1251&amp;gt;null&amp;quot;);

    BotWithStrategy bot;  // Создаем объект бота с поддержкой стратегии

    // Симуляция игрового цикла
    bot.update();  // Бот патрулирует...
    bot.update();  // Бот преследует игрока!
    bot.update();  // Бот готовится к атаке!
    // Бот атакует в ближнем бою!

    // Меняем стратегию атаки на дальний бой
    bot.setAttackStrategy(std::make_shared&amp;lt;RangedAttack&amp;gt;());

    // Ещё один цикл для демонстрации изменения стратегии
    bot.update();  // Бот патрулирует...
    bot.update();  // Бот преследует игрока!
    bot.update();  // Бот готовится к атаке!
    // Бот стреляет издалека!
    system(&amp;quot;pause&amp;quot;);
    return 0;
}&lt;/pre&gt;
  &lt;h3 id=&quot;jYTY&quot;&gt;Заключение&lt;/h3&gt;
  &lt;p id=&quot;AB9j&quot;&gt;Паттерны состояния и стратегии - это мощные инструменты, которые могут значительно улучшить качество вашего кода при разработке игр. Они помогают сделать код более читаемым, гибким и масштабируемым. Использование этих паттернов может привести к созданию более сложных и интересных игровых механик.&lt;/p&gt;
  &lt;p id=&quot;TRVy&quot;&gt;&lt;a href=&quot;https://t.me/C_Verhovcevo_NVK&quot; target=&quot;_blank&quot;&gt;Телеграмм канал - Программирование игр С++/С#&lt;/a&gt;&lt;/p&gt;

</content></entry><entry><id>gamedeveloper:vZSevcmZ7L_</id><link rel="alternate" type="text/html" href="https://teletype.in/@gamedeveloper/vZSevcmZ7L_?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=gamedeveloper"></link><title>Задача &quot;Испытание автомата&quot;</title><published>2025-01-18T16:13:43.507Z</published><updated>2025-01-18T16:13:43.507Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/a3/6d/a36d1cf4-be91-4656-8e5f-59c768a7b76f.png"></media:thumbnail><category term="prostye-prilozheniya-na-s" label="Простые приложения на С++"></category><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/21/87/2187f6db-e02e-4220-a9ac-18af25c6db20.jpeg&quot;&gt;Компания Bookface, основанная в Ужляндии, где работает Степан, решила установить в своих офисах автоматы по продаже чая и кофе, чтобы программисты могли с пользой провести время во время перерывов. Стоимость стаканчика чая или кофе в автомате установлена равной пяти ужикам (местная валюта Ужляндии). Автоматы принимают монеты по 5 и 10 ужиков, а также купюры номиналом 10, 50 и 100 ужиков.</summary><content type="html">
  &lt;figure id=&quot;GrhW&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/21/87/2187f6db-e02e-4220-a9ac-18af25c6db20.jpeg&quot; width=&quot;1280&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;rlbB&quot;&gt;Компания Bookface, основанная в Ужляндии, где работает Степан, решила установить в своих офисах автоматы по продаже чая и кофе, чтобы программисты могли с пользой провести время во время перерывов. Стоимость стаканчика чая или кофе в автомате установлена равной пяти ужикам (местная валюта Ужляндии). Автоматы принимают монеты по 5 и 10 ужиков, а также купюры номиналом 10, 50 и 100 ужиков.&lt;/p&gt;
  &lt;p id=&quot;kvyN&quot;&gt;Если программисту необходимо дать сдачу (то есть он оплатил монетой 10 ужиков или купюрой 10, 50, 100 ужиков), автомат выдает сдачу монетами по 5 ужиков. Если программист заплатил монетой в 5 ужиков, автомат сохраняет её и может использовать для сдачи следующим покупателям.&lt;/p&gt;
  &lt;p id=&quot;6FcT&quot;&gt;Очевидно, что для обеспечения возможности выдачи сдачи всем программистам потребуется заранее загрузить в автомат некоторое количество монет номиналом 5 ужиков. Известен порядок, в котором программисты производили оплату. Ваша задача — определить минимальное количество монет номиналом 5 ужиков, которые нужно было загрузить в автомат перед началом рабочего дня, чтобы всем покупателям хватило сдачи.&lt;/p&gt;
  &lt;h3 id=&quot;nibb&quot;&gt;Входные данные:&lt;/h3&gt;
  &lt;ol id=&quot;gGpL&quot;&gt;
    &lt;li id=&quot;zWQ8&quot;&gt;В первой строке входного файла дано одно натуральное число N — количество покупок, совершенных в ходе испытания (1≤N≤50,000).&lt;/li&gt;
    &lt;li id=&quot;T2cB&quot;&gt;Во второй строке записаны N натуральных чисел, каждое из которых соответствует номиналу монеты или купюры, которую использовал очередной программист для оплаты (5,10,50,100).&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h3 id=&quot;nFSu&quot;&gt;Выходные данные:&lt;/h3&gt;
  &lt;p id=&quot;fyHk&quot;&gt;Выведите одно число — минимальное количество монет номиналом 5 ужиков, которые нужно загрузить в автомат перед началом рабочего дня.&lt;/p&gt;
  &lt;h3 id=&quot;QiGT&quot;&gt;&lt;strong&gt;Примеры ввода и вывода данных: &lt;/strong&gt;&lt;/h3&gt;
  &lt;figure id=&quot;DaiK&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f0/1b/f01b6a51-03ef-4189-8f00-f92a35ce7a64.png&quot; width=&quot;955&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;jeN6&quot;&gt;Код решения задача на С++&lt;/h3&gt;
  &lt;pre id=&quot;CNaa&quot; data-lang=&quot;cpp&quot;&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
using namespace std;

int main() {
    int n; // Количество покупок
    cin &amp;gt;&amp;gt; n;

    vector&amp;lt;int&amp;gt; payments(n); // Массив для хранения номиналов оплат
    for (int i = 0; i &amp;lt; n; ++i) {
        cin &amp;gt;&amp;gt; payments[i];
    }

    int coins_in_machine = 0; // Текущее количество монет в автомате
    int min_initial_coins = 0; // Минимальное начальное количество монет

    for (int payment : payments) {
        if (payment == 5) {
            // Если программист платит монетой 5 ужиков
            coins_in_machine += 1;
        } else {
            // Расчёт сдачи
            int change_needed = (payment - 5) / 5;
            if (coins_in_machine &amp;gt;= change_needed) {
                // Если в автомате хватает монет на сдачу
                coins_in_machine -= change_needed;
            } else {
                // Если не хватает монет, нужно добавить в автомат
                min_initial_coins += change_needed - coins_in_machine;
                coins_in_machine = 0; // Использовали все монеты
            }
        }
    }

    cout &amp;lt;&amp;lt; min_initial_coins &amp;lt;&amp;lt; endl; // Вывод минимального количества монет
    return 0;
}
&lt;/pre&gt;
  &lt;p id=&quot;RFyf&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;tHC1&quot;&gt;&lt;a href=&quot;https://t.me/C_Verhovcevo_NVK&quot; target=&quot;_blank&quot;&gt;Телеграмм канал - Программирование игр С++/С#&lt;/a&gt;&lt;/p&gt;

</content></entry></feed>