May 8, 2020

Мини-скринсейвер с летающими шариками

Это просто интересная программа, с кодом которой можно поэкспериментировать и на работу которой можно позалипать.

Если кратко:

Создаются n шариков (в коде ниже — 20), поле, по которому они будут перемещаться и, по желанию, препятствие внутри поля, от которого шарики будут также отталкиваться.

Можно менять размеры и положение поля, препятствия, размер, цвет и количество самих шариков. Начнём.

Описание кода

Я буду писать на C++, поэтому для начала подключаем библиотеки:

#include <windows.h>
#include <stdlib.h>
#include <math.h>
#include <ddraw.h>
using namespace std;

const int ballsCount = 20;

Создаём структуры Шар, Поле и Препятствие:

struct Ball {
    float x, y; // координаты шарика
    float dx, dy; // изменение координат по х и у
    float R, G, B; // доли красного, зелёного и синего цветов
    float r; // радиус шарика
};
struct Field {
    float x, y;
    float width, height;
};
struct Obstacle {
    float x, y;
    float width, height;
};

Функция, убирающая курсор:

void ShowConsoleCursor(bool showFlag)
{
    HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_CURSOR_INFO cursorInfo;
    
    GetConsoleCursorInfo(out, &cursorInfo);
    cursorInfo.bVisible = showFlag;
    SetConsoleCursorInfo(out, &cursorInfo);
}

Главная функция:

Создаём наши поле и препятствие и задаём их параметры (можете изменить их на любые другие положительные числа.

Obstacle obstacle;
obstacle.x = 200;
obstacle.y = 90;
obstacle.width = 20;
obstacle.height = 90;

Field board;
board.x = 1;
board.y = 1;
board.width = 350;
board.height = 250;

Создаём массив из 20 шариков и прописываем их параметры:

Ball balls[ballsCount];
for (int i = 0; i < ballsCount; i++) {
    balls[i].r = 10 + rand() % 10; // радиус от 10 до 19
    balls[i].x = 0 + rand() % (int)board.width;
    balls[i].y = 0 + rand() % (int)board.height;
    balls[i].dx = 5;
    balls[i].dy = 5;
    balls[i].R = rand() % 256; // интенсивность цветов от 0 до 255
    balls[i].G = rand() % 256;
    balls[i].B = rand() % 256;
}

Это всё отвечает за рисование в консоли. Можете не обращать внимания:

RECT rect;
HWND window = GetConsoleWindow();
HDC handler = GetDC(window);
GetClientRect(window, &rect);
HPEN fieldPen = CreatePen(PS_SOLID, 2, RGB(255, 255, 255));
HBRUSH fieldBrush = CreateSolidBrush(RGB(0, 0, 0));
HPEN ballPen = CreatePen(PS_SOLID, 2, RGB(255, 255, 255));
HBRUSH ballBrush = CreateSolidBrush(RGB(0, 0, 0));
// фикс морганий
HDC memDC = CreateCompatibleDC(handler);
HBITMAP hBitmap = CreateCompatibleBitmap(handler, rect.right, rect.bottom);
SelectObject(memDC, hBitmap);
PatBlt(memDC, 0, 0, rect.right, rect.left, BLACKNESS);
// удаляем _ из консоли
ShowConsoleCursor(false);
int fps = 70;

Далее в вечном цикле while(true) последует ещё немного графики:

SelectObject(memDC, fieldPen);
SelectObject(memDC, fieldBrush);
Rectangle(memDC, board.x, board.y, board.x + board.width, board.y + board.height);
// Obstacle
Rectangle(memDC, obstacle.x, obstacle.y, obstacle.x + obstacle.width, obstacle.y + obstacle.height);

Сдвинем каждый шарик и оттолкнём от стенки, если он до неё долетел:

for (int i = 0; i < ballsCount; i++) {
       balls[i].x += balls[i].dx;
       balls[i].y += balls[i].dy;
       if (balls[i].x + balls[i].r > board.width) // правая стенка
       {
            balls[i].x -= balls[i].x - board.width + balls[i].r;
            balls[i].dx = -balls[i].dx;
       }
       if (balls[i].y + balls[i].r > board.height) // верхняя стенка
       {
            balls[i].y -= balls[i].y - board.height + balls[i].r;
            balls[i].dy = -balls[i].dy;
            balls[i].dy += rand() % 1;
        }
        if (balls[i].x - balls[i].r < 0) // левая стенка
        {
            balls[i].x = balls[i].r;
            balls[i].dx = -balls[i].dx;
            balls[i].dx += rand() % 1;
        }
        if (balls[i].y - balls[i].r < 0) // нижняя стенка
        {
            balls[i].y = balls[i].r;
            balls[i].dy = -balls[i].dy;
            balls[i].dy += rand() % 1;
        }
        if ((balls[i].x + balls[i].r > obstacle.x) && (balls[i].x + balls[i].r < obstacle.x + obstacle.width) 
            && (balls[i].y >= obstacle.y) && (balls[i].y <= obstacle.y + obstacle.height)) // левая стенка препятствия
        {
            balls[i].x = balls[i].x - (balls[i].x - obstacle.x) - balls[i].r;
            balls[i].dx = -balls[i].dx;
        }
        if ((balls[i].x >= obstacle.x) && (balls[i].x <= obstacle.x + obstacle.width) && (balls[i].y + balls[i].r > obstacle.y) 
            && (balls[i].y + balls[i].r < obstacle.y + obstacle.height)) // верхняя
        {
            balls[i].y = balls[i].y - (balls[i].y - obstacle.y) - balls[i].r;
            balls[i].dy = -balls[i].dy;
            balls[i].dy += rand() % 1;
        }
        if ((balls[i].x - balls[i].r > obstacle.x) && (balls[i].x - balls[i].r < obstacle.x + obstacle.width) && (balls[i].y >= obstacle.y) 
            && (balls[i].y <= obstacle.y + obstacle.height)) // правая
        {
            balls[i].x = balls[i].x + (obstacle.x + obstacle.width - balls[i].x) + balls[i].r;
            balls[i].dx = -balls[i].dx;
        }
        if ((balls[i].x >= obstacle.x) && (balls[i].x <= obstacle.x + obstacle.width) && (balls[i].y - balls[i].r > obstacle.y) 
            && (balls[i].y - balls[i].r < obstacle.y + obstacle.height)) // нижняя
        {
            balls[i].y = balls[i].y + (obstacle.y + obstacle.height - balls[i].y) + balls[i].r;
            balls[i].dy = -balls[i].dy;
            balls[i].dy += rand() % 1;
       }

Ещё немного графики в том же цикле for:

ballPen = CreatePen(PS_SOLID, 2, RGB(255, 255, 255));
ballBrush = CreateSolidBrush(RGB(balls[i].R, balls[i].G, balls[i].B));
SelectObject(memDC, ballPen);
SelectObject(memDC, ballBrush);
Ellipse(memDC, balls[i].x - balls[i].r, balls[i].y - balls[i].r, balls[i].x + balls[i].r, balls[i].y + balls[i].r);
DeleteObject(ballPen);
DeleteObject(ballBrush);

Оставшийся код тоже посвящён графике, не буду его разбирать, приведу ниже

код целиком:

#include <windows.h>
#include <stdlib.h>
#include <math.h>
#include <ddraw.h>
using namespace std;
const int ballsCount = 20;
struct Ball {
    float x, y; // координаты шарика
    float dx, dy; // изменение координат по х и у
    float R, G, B; // доли красного, зелёного и синего цветов
    float r; // радиус шарика
};
struct Field {
    float x, y;
    float width, height;
};
struct Obstacle {
    float x, y;
    float width, height;
};
void ShowConsoleCursor(bool showFlag)
{
    HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_CURSOR_INFO cursorInfo;
    GetConsoleCursorInfo(out, &cursorInfo);
    cursorInfo.bVisible = showFlag;
    SetConsoleCursorInfo(out, &cursorInfo);
}
int main()
{
    Obstacle obstacle;
    obstacle.x = 200;
    obstacle.y = 90;
    obstacle.width = 20;
    obstacle.height = 90;
    Field board;
    board.x = 1;
    board.y = 1;
    board.width = 350;
    board.height = 250;
    Ball balls[ballsCount];
    for (int i = 0; i < ballsCount; i++) {
        balls[i].r = 10 + rand() % 10;
        balls[i].x = 0 + rand() % (int)board.width;
        balls[i].y = 0 + rand() % (int)board.height;
        balls[i].dx = 5;
        balls[i].dy = 5;
        balls[i].R = rand() % 256;
        balls[i].G = rand() % 256;
        balls[i].B = rand() % 256;
    }
    RECT rect;
    HWND window = GetConsoleWindow();
    HDC handler = GetDC(window);
    GetClientRect(window, &rect);
    HPEN fieldPen = CreatePen(PS_SOLID, 2, RGB(255, 255, 255));
    HBRUSH fieldBrush = CreateSolidBrush(RGB(0, 0, 0));
    HPEN ballPen = CreatePen(PS_SOLID, 2, RGB(255, 255, 255));
    HBRUSH ballBrush = CreateSolidBrush(RGB(0, 0, 0));
    // фикс морганий
    HDC memDC = CreateCompatibleDC(handler);
    HBITMAP hBitmap = CreateCompatibleBitmap(handler, rect.right, rect.bottom);
    SelectObject(memDC, hBitmap);
    PatBlt(memDC, 0, 0, rect.right, rect.left, BLACKNESS);
    // удаляем _ из консоли
    ShowConsoleCursor(false);
    int fps = 70;
    while (true) {
        SelectObject(memDC, fieldPen);
        SelectObject(memDC, fieldBrush);
        Rectangle(memDC, board.x, board.y, board.x + board.width, board.y + board.height);
        // Obstacle
        Rectangle(memDC, obstacle.x, obstacle.y, obstacle.x + obstacle.width, obstacle.y + obstacle.height);
        for (int i = 0; i < ballsCount; i++) {
            balls[i].x += balls[i].dx;
            balls[i].y += balls[i].dy;
            if (balls[i].x + balls[i].r > board.width) // правая стенка
            {
                balls[i].x -= balls[i].x - board.width + balls[i].r;
                balls[i].dx = -balls[i].dx;
            }
            if (balls[i].y + balls[i].r > board.height) // верхняя стенка
            {
                balls[i].y -= balls[i].y - board.height + balls[i].r;
                balls[i].dy = -balls[i].dy;
                balls[i].dy += rand() % 1;
            }
            if (balls[i].x - balls[i].r < 0) // левая стенка
            {
                balls[i].x = balls[i].r;
                balls[i].dx = -balls[i].dx;
                balls[i].dx += rand() % 1;
            }
            if (balls[i].y - balls[i].r < 0) // нижняя стенка
            {
                balls[i].y = balls[i].r;
                balls[i].dy = -balls[i].dy;
                balls[i].dy += rand() % 1;
            }
            if ((balls[i].x + balls[i].r > obstacle.x) && (balls[i].x + balls[i].r < obstacle.x + obstacle.width) 
                && (balls[i].y >= obstacle.y) && (balls[i].y <= obstacle.y + obstacle.height)) // левая стенка препятствия
            {
                balls[i].x = balls[i].x - (balls[i].x - obstacle.x) - balls[i].r;
                balls[i].dx = -balls[i].dx;
            }
            if ((balls[i].x >= obstacle.x) && (balls[i].x <= obstacle.x + obstacle.width) && (balls[i].y + balls[i].r > obstacle.y) 
                && (balls[i].y + balls[i].r < obstacle.y + obstacle.height)) // верхняя
            {
                balls[i].y = balls[i].y - (balls[i].y - obstacle.y) - balls[i].r;
                balls[i].dy = -balls[i].dy;
                balls[i].dy += rand() % 1;
            }
            if ((balls[i].x - balls[i].r > obstacle.x) && (balls[i].x - balls[i].r < obstacle.x + obstacle.width) && (balls[i].y >= obstacle.y) 
                && (balls[i].y <= obstacle.y + obstacle.height)) // правая
            {
                balls[i].x = balls[i].x + (obstacle.x + obstacle.width - balls[i].x) + balls[i].r;
                balls[i].dx = -balls[i].dx;
            }
            if ((balls[i].x >= obstacle.x) && (balls[i].x <= obstacle.x + obstacle.width) && (balls[i].y - balls[i].r > obstacle.y) 
                && (balls[i].y - balls[i].r < obstacle.y + obstacle.height)) // нижняя
            {
                balls[i].y = balls[i].y + (obstacle.y + obstacle.height - balls[i].y) + balls[i].r;
                balls[i].dy = -balls[i].dy;
                balls[i].dy += rand() % 1;
            }
            ballPen = CreatePen(PS_SOLID, 2, RGB(255, 255, 255));
            ballBrush = CreateSolidBrush(RGB(balls[i].R, balls[i].G, balls[i].B));
            SelectObject(memDC, ballPen);
            SelectObject(memDC, ballBrush);
            Ellipse(memDC, balls[i].x - balls[i].r, balls[i].y - balls[i].r, balls[i].x + balls[i].r, balls[i].y + balls[i].r);
            DeleteObject(ballPen);
            DeleteObject(ballBrush);
        }
        BitBlt(handler, 0, 0, rect.right, rect.bottom, memDC, 0, 0, SRCCOPY);
        Sleep(2000 / fps);
    }
    ReleaseDC(window, handler);
    DeleteObject(memDC);
}

Бонус)

Если вставить этот код в начале цикла for, шарики будут переливаться разными цветами:

if (balls[i].R >= 255)
    balls[i].R = 0;
if (balls[i].G >= 255)
    balls[i].G = 0;
if (balls[i].B >= 255)
    balls[i].B = 0;
balls[i].R += 1 + rand() % 10;
balls[i].G += 1 + rand() % 5;
balls[i].B += 1 + rand() % 4;

https://tlgg.ru/Selfinstallation