воскресенье, 5 июля 2026 г.

Таймер сброс

 А, теперь понял! Вы имеете в виду стандартный компонент Timer (часики на панели элементов), а не расчет времени через API.

Да, если игра «подвисла» (например, из-за вызова MsgBox или долгого расчета), стандартный таймер VB5 тоже накапливает очередь событий в Windows. Как только программа отвиснет, таймер начнет судорожно выстреливать все пропущенные тики подряд, из-за чего игра резко ускорится.
В VB5 стандартный таймер нельзя «очистить» от накопившейся очереди команд напрямую, потому что эта очередь хранится в самой операционной системе Windows (в очереди сообщений WM_TIMER).
Но этот эффект можно полностью нейтрализовать двумя способами.

Способ 1. Выключение и включение (Самый простой)

Когда вы знаете, что сейчас произойдет задержка (например, перед показом окна или сложным расчетом), выключите таймер, а затем включите обратно. Это полностью стирает всю очередь тиков в Windows:
' 1. Выключаем таймер перед задержкой
Timer1.Enabled = False

' 2. Тут происходит то, что вешает программу (например, сообщение пользователю)
MsgBox "Игра на паузе. Нажмите ОК для продолжения", vbInformation, "Пауза"

' 3. Включаем обратно — таймер начнет отсчет чистого времени с нуля, без рывков
Timer1.Enabled = True

Способ 2. Защита внутри самого таймера (Если зависания случайные)

Если программа зависает непредсказуемо (например, из-за лагов системы), можно аппаратно игнорировать «набежавшие» тики. Для этого внутри события таймера проверяют реальное время. Если с момента последнего тика прошло слишком много времени — мы просто пропускаем лишние расчеты.
Для этого в самый верх кода формы (в раздел General Declarations) добавьте WinAPI функцию для точного времени:
Private Declare Function GetTickCount Lib "kernel32" () As Long
Private LastTickTime As Long ' Переменная для запоминания времени
А в самом событии таймера сделайте проверку:
Private Sub Timer1_Timer()
    Dim CurrentTime As Long
    CurrentTime = GetTickCount()
    
    ' Если интервал таймера равен 33 мс, а прошло, например, больше 100 мс —
    ' значит программа зависала, и этот тик "догоняющий" (из очереди).
    ' Мы его просто игнорируем.
    If (CurrentTime - LastTickTime) > (Timer1.Interval + 50) Then
        ' Сбрасываем время и выходим, не двигая игру
        LastTickTime = CurrentTime
        Exit Sub
    End If
    
    ' Запоминаем время текущего успешного тика
    LastTickTime = CurrentTime
    
    ' --- ДАЛЕЕ ВАШ ОБЫЧНЫЙ КОД ИГРЫ ---
    UpdateGameLogic
    RenderGraphics
End Sub
Если этот вариант вам подходит, подскажите:
  • Какой Interval сейчас выставлен у вашего компонента Timer?
  • Какое именно действие в программе (например, MsgBox, открытие формы) сейчас вызывает эту задержку?

Да, в Visual Basic 5.0 можно делать полноценные консольные приложения, но среда разработки (IDE) изначально для этого не предназначена. По умолчанию VB5 компилирует только GUI-приложения (с окнами).
Чтобы сделать консольное приложение, используют два основных способа.

Способ 1: Использование Windows API (Стандартный путь)

Вы можете создать обычный проект (Standard EXE), удалить из него автоматическую форму, добавить стандартный модуль (.bas) и использовать функции Win32 API для работы с консолью.
  1. Создайте проект Standard EXE.
  2. Из меню выберите Project -> Add Module (Добавить модуль).
  3. Удалите стандартную форму (Form1) из проекта.
  4. В свойствах проекта (Project -> Properties) на вкладке General установите Startup Object в значение Sub Main.
Вставьте следующий код в созданный модуль:
Option Explicit

' Объявление функций Windows API для работы с консолью
Private Declare Function AllocConsole Lib "kernel32" () As Long
Private Declare Function FreeConsole Lib "kernel32" () As Long
Private Declare Function GetStdHandle Lib "kernel32" (ByVal nStdHandle As Long) As Long
Private Declare Function WriteConsole Lib "kernel32" Alias "WriteConsoleA" (ByVal hConsoleOutput As Long, ByVal lpBuffer As String, ByVal nNumberOfCharsToWrite As Long, lpNumberOfCharsWritten As Long, lpReserved As Any) As Long
Private Declare Function ReadConsole Lib "kernel32" Alias "ReadConsoleA" (ByVal hConsoleInput As Long, ByVal lpBuffer As String, ByVal nNumberOfCharsToRead As Long, lpNumberOfCharsRead As Long, lpReserved As Any) As Long

Private Const STD_OUTPUT_HANDLE As Long = -11&
Private Const STD_INPUT_HANDLE As Long = -10&

Public Sub Main()
    Dim hStdout As Long
    Dim hStdin As Long
    Dim msg As String
    Dim userInput As String * 256
    Dim charsRead As Long
    
    ' Создаем окно консоли
    AllocConsole
    
    ' Получаем указатели на ввод и вывод
    hStdout = GetStdHandle(STD_OUTPUT_HANDLE)
    hStdin = GetStdHandle(STD_INPUT_HANDLE)
    
    ' Выводим текст в консоль
    msg = "Hello from VB5 Console!" & vbCrLf
    WriteConsole hStdout, msg, Len(msg), 0&, ByVal 0&
    
    msg = "Enter your text: "
    WriteConsole hStdout, msg, Len(msg), 0&, ByVal 0&
    
    ' Читаем ввод пользователя
    ReadConsole hStdin, userInput, Len(userInput), charsRead, ByVal 0&
    
    ' Эхо-вывод полученного текста
    msg = "You entered: " & Left$(userInput, charsRead) & vbCrLf
    WriteConsole hStdout, msg, Len(msg), 0&, ByVal 0&
    
    ' Небольшая пауза перед закрытием
    msg = "Press Enter to exit..."
    WriteConsole hStdout, msg, Len(msg), 0&, ByVal 0&
    ReadConsole hStdin, userInput, Len(userInput), charsRead, ByVal 0&
    
    ' Освобождаем консоль
    FreeConsole
End Sub

Способ 2: Изменение флага подсистемы в EXE (Настоящая консоль)

При использовании Способа 1 скомпилированный .exe файл Windows все равно будет считать "оконным" приложением. Если запустить его из командной строки cmd.exe, оно откроется в отдельном новом окне, а не внутри запущенной командной строки.
Чтобы приложение работало как родная утилита командной строки:
  1. Напишите код, используя API из Способа 1.
  2. Скомпилируйте проект в .exe файл.
  3. Используйте внешнюю утилиту LINK.EXE (которая идет в комплекте с VB5/Visual Studio) или стороннюю утилиту (например, EditBin.exe), чтобы изменить заголовок исполняемого файла с GUI на Console.
Команда в командной строке выглядит так:
link.exe /edit /subsystem:console MyProject.exe
После этого программа будет выводить данные прямо внутрь той консоли, из которой её запустили.
Если вам нужно принимать аргументы командной строки (например, параметры при запуске program.exe -file.txt), используйте встроенную функцию Command$, которая возвращает всю строку параметров. Хотите посмотреть пример работы с аргументами?


среда, 1 июля 2026 г.

Win resurs graf

 Поскольку ты хочешь картинку на форме, а не в меню, и при этом работаешь на чистом C с cl.exe (без C++/WinForms), есть два рабочих пути: простой (BMP без прозрачности) и с прозрачностью (PNG через GDI+).


Вариант 1: BMP на форме (самый простой, без GDI+)

Подходит, если картинка — обычный BMP без прозрачных краёв.

.rc (resources.rc)

#include <windows.h>

IDB_LOGO BITMAP "logo.bmp"

main.c

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#define IDB_LOGO 201

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, int nShowCmd)
{
    const char CLASS_NAME[] = "ImgForm";

    WNDCLASS wc = {0};
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CLASS_NAME;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

    RegisterClass(&wc);

    HWND hwnd = CreateWindowEx(
        0, CLASS_NAME, "Картинка на форме",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 640, 480,
        NULL, NULL, hInstance, NULL
    );

    if (!hwnd) return 0;

    ShowWindow(hwnd, nShowCmd);
    UpdateWindow(hwnd);

    MSG msg = {0};
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return (int)msg.wParam;
}

static HBITMAP hLogo = NULL;

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg) {
        case WM_CREATE: {
            HINSTANCE hInst = (HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE);
            hLogo = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_LOGO));
            return 0;
        }

        case WM_PAINT: {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);

            if (hLogo) {
                HDC hMemDC = CreateCompatibleDC(hdc);
                HBITMAP hOld = (HBITMAP)SelectObject(hMemDC, hLogo);

                BITMAP bm;
                GetObject(hLogo, sizeof(bm), &bm);

                // рисуем картинку в точке (50,50)
                BitBlt(hdc, 50, 50, bm.bmWidth, bm.bmHeight,
                       hMemDC, 0, 0, SRCCOPY);

                SelectObject(hMemDC, hOld);
                DeleteDC(hMemDC);
            }

            EndPaint(hwnd, &ps);
            return 0;
        }

        case WM_DESTROY:
            if (hLogo) DeleteObject(hLogo);
            PostQuitMessage(0);
            return 0;
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
}

Сборка:

rc resources.rc
cl /TC main.c resources.res /link user32.lib gdi32.lib

Картинка logo.bmp должна лежать рядом с исходниками (или указать полный путь).


Вариант 2: PNG с прозрачными краями на форме (через GDI+)

Если нужны прозрачные края — BMP не годится: нужен PNG и GDI+.

Важное: GDI+ в чистом C

GDI+ API сделан под C++, но его можно использовать из C: вместо классов используем указатели на интерфейсы и вызовы через таблицы функций. Для простоты покажу вариант с использованием C++ синтаксиса только для инициализации GDI+ и работы с Image/Graphics, но логика остаётся максимально близкой к C‑подходу. Если критично «строго C без C++», можно использовать AlphaBlend с 32‑bit DIB вручную, но это заметно сложнее.

gdiplus_img.c (упрощённо)

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <gdiplus.h>

#pragma comment(lib, "gdiplus.lib")

ULONG_PTR gdiToken;
Gdiplus::Image *pImage = NULL;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, int nShowCmd)
{
    GdiplusStartupInput input = {1};
    GdiplusStartup(&gdiToken, &input, NULL);

    // PNG с прозрачностью
    pImage = Gdiplus::Image::FromFile(L"logo.png");

    const char CLASS_NAME[] = "GdiPlusImg";
    WNDCLASS wc = {0};
    wc.lpfnWndProc = [](HWND h, UINT m, WPARAM w, LPARAM l) -> LRESULT {
        if (m == WM_PAINT) {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(h, &ps);
            Gdiplus::Graphics g(hdc);
            if (pImage) g.DrawImage(pImage, 50, 50);
            EndPaint(h, &ps);
            return 0;
        } else if (m == WM_DESTROY) {
            delete pImage;
            GdiplusShutdown(gdiToken);
            PostQuitMessage(0);
            return 0;
        }
        return DefWindowProc(h, m, w, l);
    };

    wc.hInstance = hInstance;
    wc.lpszClassName = CLASS_NAME;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    RegisterClass(&wc);

    CreateWindowEx(0, CLASS_NAME, "PNG с прозрачностью",
                   WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
                   640, 480, NULL, NULL, hInstance, NULL);

    ShowWindow(GetLastActivePopup(), nShowCmd);
    UpdateWindow(GetLastActivePopup());

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return (int)msg.wParam;
}

Сборка:

cl /TP gdiplus_img.c /link gdiplus.lib user32.lib gdi32.lib

/TP — компилировать как C++ (нужно для GDI+ классов). Если принципиально только /TC (чистый C), тогда вместо GDI+ придётся вручную готовить 32‑bit DIB с альфа‑каналом и вызывать AlphaBlend.

PNG logo.png должен лежать рядом с .exe и иметь альфа‑канал.


Что выбрать под твои задачи

  • Если ты делаешь шахматную программу (учитывая прошлые вопросы про битборды, Syzygy и т. д.) и нужна, например, простая иконка/логотип на форме без сложной графики — Вариант 1 (BMP + BitBlt) проще, быстрее и полностью на чистом C.

  • Если нужны красивые картинки с прозрачными краями (например, фигуры или декоративные элементы) — Вариант 2 (GDI+).


Практические советы

  • Формат BMP для Варианта 1: лучше 24‑bit или 32‑bit BMP без сжатия; не используй PNG/JPG напрямую в ресурсе — BITMAP в .rc работает только с BMP.

  • Позиционирование: в BitBlt меняй координаты (50, 50) на любые нужные. Чтобы центрировать картинку, считай размеры окна в WM_SIZE и перерисовывай.

  • Ресурсы vs файлы: в Варианте 1 картинка встроена в .exe; в Варианте 2 (GDI+) проще читать файл с диска — так удобнее менять картинки без пересборки.


Учитывая твой интерес к сборке через cl.exe, ресурсам и «чистому C», я могу дать полностью C‑версию с AlphaBlend (без GDI+), если скажешь, что важнее: «строго C» или «максимальная простота кода». Что ближе?



Да, в Win32 на чистом C можно:

  • обрабатывать события мыши (WM_LBUTTONDOWN, WM_MOUSEMOVE, WM_LBUTTONUP и т. д.);

  • обрабатывать нажатия клавиш (WM_KEYDOWN, WM_CHAR);

  • рисовать PNG с альфа‑каналом (прозрачностью) — но для этого почти всегда используют GDI+ либо AlphaBlend с 32‑bit DIB.

Учитывая твой стек (чистый C, cl.exe, шахматные задачи), покажу рабочий пример, где:

  • рисуется PNG с прозрачностью через GDI+;

  • отслеживаются клики мыши и нажатия клавиш;

  • всё собрано одной командой через cl.exe.


Полный пример: окно, PNG с прозрачностью, события мыши и клавиатуры

main.c

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <gdiplus.h>

#pragma comment(lib, "gdiplus.lib")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "gdi32.lib")

ULONG_PTR gdiToken;
Gdiplus::Image *pImage = NULL;

// Глобальные переменные для демонстрации реакции на ввод
int lastX = 0, lastY = 0;
char lastKey = 0;
BOOL mouseDown = FALSE;

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg) {
        case WM_CREATE:
            // Инициализация GDI+ уже сделана в WinMain
            return 0;

        case WM_PAINT: {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);

            if (pImage) {
                Gdiplus::Graphics g(hdc);
                // Рисуем PNG с прозрачностью
                g.DrawImage(pImage, 50, 50);
            }

            EndPaint(hwnd, &ps);
            return 0;
        }

        // --- События мыши ---
        case WM_LBUTTONDOWN: {
            lastX = LOWORD(lParam);
            lastY = HIWORD(lParam);
            mouseDown = TRUE;
            // Можно что-то сделать при клике
            InvalidateRect(hwnd, NULL, FALSE); // перерисовать, если нужно показать эффект
            return 0;
        }
        case WM_MOUSEMOVE: {
            if (mouseDown) {
                lastX = LOWORD(lParam);
                lastY = HIWORD(lParam);
                InvalidateRect(hwnd, NULL, FALSE);
            }
            return 0;
        }
        case WM_LBUTTONUP: {
            mouseDown = FALSE;
            return 0;
        }

        // --- События клавиатуры ---
        case WM_KEYDOWN: {
            // wParam = виртуальный код клавиши (VK_*)
            // Например, VK_ESCAPE, VK_LEFT, VK_A и т.д.
            // Здесь можно реагировать на конкретные клавиши
            return 0;
        }
        case WM_CHAR: {
            // wParam = ASCII-код символа (для печатаемых символов)
            lastKey = (char)wParam;
            InvalidateRect(hwnd, NULL, FALSE);
            return 0;
        }

        case WM_DESTROY: {
            delete pImage;
            GdiplusShutdown(gdiToken);
            PostQuitMessage(0);
            return 0;
        }
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, int nShowCmd)
{
    // Инициализация GDI+
    GdiplusStartupInput input = {1};
    GdiplusStartup(&gdiToken, &input, NULL);

    // Загрузка PNG с прозрачностью (альфа‑канал)
    pImage = Gdiplus::Image::FromFile(L"logo.png");

    const char CLASS_NAME[] = "ImgInputDemo";

    WNDCLASS wc = {0};
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CLASS_NAME;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

    RegisterClass(&wc);

    HWND hwnd = CreateWindowEx(
        0, CLASS_NAME, "PNG + мышь + клава",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 640, 480,
        NULL, NULL, hInstance, NULL
    );

    if (!hwnd) return 0;

    ShowWindow(hwnd, nShowCmd);
    UpdateWindow(hwnd);

    MSG msg = {0};
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}

Как собрать

cl /TP main.c /link gdiplus.lib user32.lib gdi32.lib

/TP обязателен: GDI+ в стандартной поставке — это C++ API (классы Image, Graphics). Если принципиально только чистый C без C++ синтаксиса, тогда вместо GDI+ используют AlphaBlend и 32‑bit DIB вручную — код будет заметно длиннее.

Рядом с .exe должен лежать logo.png с альфа‑каналом.


Какие события реально пригодятся для шахматной программы

Учитывая твои прошлые задачи (битборды, Syzygy, SEE, движок):

  • WM_LBUTTONDOWN — самый нужный: по нему определяют, куда кликнул пользователь, берут координаты, переводят в клетку доски и генерируют ходы.

  • WM_MOUSEMOVE — можно подсвечивать клетку под курсором или показывать возможные ходы «в предпросмотре».

  • WM_KEYDOWN — например, VK_ESCAPE для выхода, VK_F2 для переключения режима, стрелки для навигации по истории ходов и т. п.

  • WM_CHAR — если делаешь ввод текста (комментарии, PGN и т. д.).

Координаты мыши приходят в lParam:

int x = LOWORD(lParam);
int y = HIWORD(lParam);

Чтобы превратить их в клетку доски, обычно делают:

int cellX = (x - boardX) / cellSize;
int cellY = (y - boardY) / cellSize;

Если нужна строго чистая C‑реализация без C++ (без GDI+)

Тогда для PNG/альфы используют AlphaBlend из msimg32.lib и 32‑bit DIB с альфа‑каналом:

  1. Читают PNG (или заранее готовят 32‑bit BMP с альфа‑каналом).

  2. Создают совместимый DC и битмап.

  3. Вызывают AlphaBlend(&BLENDFUNCTION, ...).

Это полностью на C, но требует аккуратной работы с заголовками DIB и выравниванием строк. Если скажешь, что важнее — «строго C» или «минимум кода» — могу дать вариант именно с AlphaBlend.


Практические советы под твой стек

  • PNG с прозрачностью — самый простой путь: GDI+ (DrawImage).

  • Мышь — чаще всего нужен именно клик (WM_LBUTTONDOWN) и координаты.

  • Клава — для горячих клавиш и отладки (например, «показать битборд», «переключить движок»).

  • Ресурсы — картинку можно держать как файл рядом с .exe (удобно менять без пересборки), либо встроить в ресурс как RCDATA и читать из него.

Если скажешь, какой именно сценарий тебе нужен (например: «клик по клетке — подсветить возможные ходы», или «нажал F1 — показать подсказку», или «строго C без C++»), дам адаптированный фрагмент под эту задачу.