А, теперь понял! Вы имеете в виду стандартный компонент 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 для работы с консолью.
Создайте проект Standard EXE.
Из меню выберите Project -> Add Module (Добавить модуль).
Удалите стандартную форму (Form1) из проекта.
В свойствах проекта (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, оно откроется в отдельном новом окне, а не внутри запущенной командной строки.
Чтобы приложение работало как родная утилита командной строки:
Напишите код, используя API из Способа 1.
Скомпилируйте проект в .exe файл.
Используйте внешнюю утилиту LINK.EXE (которая идет в комплекте с VB5/Visual Studio) или стороннюю утилиту (например, EditBin.exe), чтобы изменить заголовок исполняемого файла с GUI на Console.
Команда в командной строке выглядит так:
link.exe /edit /subsystem:console MyProject.exe
После этого программа будет выводить данные прямо внутрь той консоли, из которой её запустили.
Если вам нужно принимать аргументы командной строки (например, параметры при запуске program.exe -file.txt), используйте встроенную функцию Command$, которая возвращает всю строку параметров. Хотите посмотреть пример работы с аргументами?
Поскольку ты хочешь картинку на форме, а не в меню, и при этом работаешь на чистом C с cl.exe (без C++/WinForms), есть два рабочих пути: простой (BMP без прозрачности) и с прозрачностью (PNG через GDI+).
Вариант 1: BMP на форме (самый простой, без GDI+)
Подходит, если картинка — обычный BMP без прозрачных краёв.
Картинка 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 вручную, но это заметно сложнее.
/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 и т. д.);
/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 с альфа‑каналом:
Читают PNG (или заранее готовят 32‑bit BMP с альфа‑каналом).
Создают совместимый DC и битмап.
Вызывают AlphaBlend(&BLENDFUNCTION, ...).
Это полностью на C, но требует аккуратной работы с заголовками DIB и выравниванием строк. Если скажешь, что важнее — «строго C» или «минимум кода» — могу дать вариант именно с AlphaBlend.
Практические советы под твой стек
PNG с прозрачностью — самый простой путь: GDI+ (DrawImage).
Мышь — чаще всего нужен именно клик (WM_LBUTTONDOWN) и координаты.
Клава — для горячих клавиш и отладки (например, «показать битборд», «переключить движок»).
Ресурсы — картинку можно держать как файл рядом с .exe (удобно менять без пересборки), либо встроить в ресурс как RCDATA и читать из него.
Если скажешь, какой именно сценарий тебе нужен (например: «клик по клетке — подсветить возможные ходы», или «нажал F1 — показать подсказку», или «строго C без C++»), дам адаптированный фрагмент под эту задачу.
Да, вывести текстовые символы Юникода в графическое окно pygame можно. Для этого используется модуль pygame.font.
Основная хитрость заключается в том, что стандартный шрифт Pygame не всегда корректно поддерживает шахматные символы Юникода. Чтобы они гарантированно отображались, лучше использовать системный шрифт, который точно содержит эти символы (например, Arial, Segoe UI Symbol или DejaVu Sans).
Ниже представлен готовый скрипт, который создаёт графическое окно, рисует цветную шахматную сетку и поверх неё отрисовывает фигуры из кодов символов.
Готовый код на Pygame
importsysimportpygame# Инициализация Pygame
pygame.init()
# Константы графикиCELL_SIZE = 80BOARD_SIZE = CELL_SIZE * 8WINDOW_SIZE = BOARD_SIZE + 40# Окно чуть больше доски для отступов под буквы/цифрыOFFSET = 20# Отступ доски от края окна# Цвета (RGB)COLOR_LIGHT = (240, 217, 181) # Светлые клеткиCOLOR_DARK = (181, 136, 99) # Темные клеткиCOLOR_BG = (49, 46, 43) # Фон окнаCOLOR_TEXT = (255, 255, 255) # Цвет текста разметки# Настройка окнаscreen = pygame.display.set_mode((WINDOW_SIZE, WINDOW_SIZE))
pygame.display.set_caption("Шахматная доска на Pygame (Unicode)")
# Подключение шрифта с поддержкой Юникод-фигур# SysFont автоматически подберет подходящий шрифт из системы (Arial обычно работает везде)font_pieces = pygame.font.SysFont("arial", int(CELL_SIZE * 0.8))
font_labels = pygame.font.SysFont("arial", 16)
# Словари с Юникод-кодами шахматных фигурwhite_pieces = {
'R': '\u2656', 'N': '\u2658', 'B': '\u2657',
'Q': '\u2655', 'K': '\u2654', 'P': '\u2659'
}
black_pieces = {
'r': '\u265C', 'n': '\u265E', 'b': '\u265D',
'q': '\u265B', 'k': '\u265A', 'p': '\u265F'
}
# Начальная расстановкаboard = [
['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'],
['p', 'p', 'p', 'p', 'p', 'p', 'p', 'p'],
['.', '.', '.', '.', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', '.', '.', '.'],
['P', 'P', 'P', 'P', 'P', 'P', 'P', 'P'],
['R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R']
]
defdraw_board():
# Заливка фона
screen.fill(COLOR_BG)
# Отрисовка разметки (буквы и цифры)letters = "ABCDEFGH"foriin range(8):
# Цифры слева (8-1)lbl_num = font_labels.render(str(8 - i), True, COLOR_TEXT)
screen.blit(lbl_num, (5, OFFSET + i * CELL_SIZE + CELL_SIZE // 2 - 8))
# Буквы снизу (A-H)lbl_let = font_labels.render(letters[i], True, COLOR_TEXT)
screen.blit(lbl_let, (OFFSET + i * CELL_SIZE + CELL_SIZE // 2 - 5, BOARD_SIZE + OFFSET + 2))
# Отрисовка клеток и фигурforrowin range(8):
forcolin range(8):
# Координаты левого верхнего угла клеткиx = OFFSET + col * CELL_SIZE
y = OFFSET + row * CELL_SIZE
# 1. Рисуем клетку доскиis_light = (row + col) % 2 == 0cell_color = COLOR_LIGHT if is_light else COLOR_DARK
pygame.draw.rect(screen, cell_color, (x, y, CELL_SIZE, CELL_SIZE))
# 2. Рисуем фигуру, если она естьcell_content = board[row][col]
if cell_content != '.':
# Определяем символ фигурыif cell_content.isupper():
char = white_pieces[cell_content]
# Для белых фигур на светлом фоне можно задать черный контур, # но Pygame рендерит Юникод-фигуры как обычный текст определенного цвета.# Сделаем сами фигуры черными/белыми для контраста:piece_color = (255, 255, 255) if cell_content.islower() else (255, 255, 255)
else:
char = black_pieces[cell_content]
# Рендерим символ в картинку (True включает сглаживание)# Чтобы фигуры хорошо читались на любых клетках, черные сделаем черными, белые — белымиp_color = (255, 255, 255) if cell_content.isupper() else (0, 0, 0)
piece_surface = font_pieces.render(char, True, p_color)
# Центрируем фигуру внутри клеткиp_rect = piece_surface.get_rect()
p_rect.center = (x + CELL_SIZE // 2, y + CELL_SIZE // 2)
screen.blit(piece_surface, p_rect)
# Главный цикл программыrunning = Truewhile running:
foreventin pygame.get_調整(): # Обработка событийif event.type == pygame.QUIT:
running = False# Рисуем актуальное состояние игры
draw_board()
# Обновляем экран
pygame.display.flip()
pygame.quit()
sys.exit()
Важные нюансы реализации в Pygame:
Выбор шрифта (pygame.font.SysFont): Мы берем "arial", так как он есть на Windows, Mac и Linux, и содержит корректные глифы для шахматных символов. Если фигуры отображаются как "квадратики", замените "arial" на "segoeuisymbol" (для Windows) или "dejavusans" (для Linux).
Цвет символов: В отличие от консоли, где у символа Юникода цвет фиксирован (белые фигуры закрашены белым внутри кода \u2654), в Pygame функция font.render() красит весь силуэт символа в выбранный вами RGB-цвет. Поэтому для белых фигур мы передаем белый цвет (255, 255, 255), а для черных — черный (0, 0, 0).
Выравнивание (get_rect().center): Текстовые символы могут иметь невидимые отступы. Код находит геометрический центр сгенерированной картинки символа и ровно сопоставляет его с центром клетки (CELL_SIZE // 2).
Если код запущен успешно, вы увидите полноценное графическое окно с привычной деревянно-бежевой доской и контрастными фигурами.
Если вы хотите продолжить разработку, напишите:
Хотите ли вы добавить выделение клетки при клике мышкой?
Нужно ли реализовать перетаскивание фигур (Drag-and-Drop) мышью?
Требуется ли фиксировать историю ходов на экране рядом с доской?
Вот готовый и полностью рабочий код на Pygame, в котором реализована полноценная игра для двух игроков на одном экране.
В программу добавлены:
Выделение клетки при нажатии.
Перетаскивание фигур (Drag-and-Drop) мышкой.
Ограничение по очереди ходов (белые и черные ходят строго по очереди, начиная с белых).
Возврат фигуры, если ход был сделан на ту же самую клетку или за пределы доски.
Полный код игры
importsysimportpygame# Инициализация Pygame
pygame.init()
# Константы графикиCELL_SIZE = 80BOARD_SIZE = CELL_SIZE * 8WINDOW_SIZE = BOARD_SIZE + 40# Окно чуть больше доски для отступов под буквы/цифрыOFFSET = 20# Отступ доски от края окна# Цвета (RGB)COLOR_LIGHT = (240, 217, 181) # Светлые клеткиCOLOR_DARK = (181, 136, 99) # Темные клеткиCOLOR_BG = (49, 46, 43) # Фон окнаCOLOR_TEXT = (255, 255, 255) # Цвет текста разметкиCOLOR_SELECTED = (130, 151, 105) # Цвет выделенной клетки (зеленоватый)# Настройка окнаscreen = pygame.display.set_mode((WINDOW_SIZE, WINDOW_SIZE))
pygame.display.set_caption("Шахматы для двух игроков")
# Подключение шрифта с поддержкой Юникодаfont_pieces = pygame.font.SysFont("arial", int(CELL_SIZE * 0.8))
font_labels = pygame.font.SysFont("arial", 16)
# Словари с Юникод-кодами шахматных фигурwhite_pieces = {'R': '\u2656', 'N': '\u2658', 'B': '\u2657', 'Q': '\u2655', 'K': '\u2654', 'P': '\u2659'}
black_pieces = {'r': '\u265C', 'n': '\u265E', 'b': '\u265D', 'q': '\u265B', 'k': '\u265A', 'p': '\u265F'}
# Начальная расстановкаboard = [
['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'],
['p', 'p', 'p', 'p', 'p', 'p', 'p', 'p'],
['.', '.', '.', '.', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', '.', '.', '.'],
['P', 'P', 'P', 'P', 'P', 'P', 'P', 'P'],
['R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R']
]
# Переменные состояния игрыselected_cell = None# Координаты (row, col) выбранной клеткиdragged_piece = None# Символ перетаскиваемой фигуры (например, 'P')drag_start_pos = None# Откуда начали тащить (row, col)white_turn = True# True - ходят белые, False - черныеdefget_cell_from_pos(pos):
"""Определяет строку и столбец на доске по координатам мыши."""x, y = pos
col = (x - OFFSET) // CELL_SIZE
row = (y - OFFSET) // CELL_SIZE
if0 <= col < 8and0 <= row < 8:
return row, col
returnNonedefdraw_board():
screen.fill(COLOR_BG)
# Отрисовка разметки (буквы и цифры)letters = "ABCDEFGH"foriin range(8):
lbl_num = font_labels.render(str(8 - i), True, COLOR_TEXT)
screen.blit(lbl_num, (5, OFFSET + i * CELL_SIZE + CELL_SIZE // 2 - 8))
lbl_let = font_labels.render(letters[i], True, COLOR_TEXT)
screen.blit(lbl_let, (OFFSET + i * CELL_SIZE + CELL_SIZE // 2 - 5, BOARD_SIZE + OFFSET + 2))
# Отрисовка клеток и статичных фигурforrowin range(8):
forcolin range(8):
x = OFFSET + col * CELL_SIZE
y = OFFSET + row * CELL_SIZE
# Подсветка выделенной клетки или клетки, откуда тащат фигуруif selected_cell == (row, col) or drag_start_pos == (row, col):
cell_color = COLOR_SELECTED
else:
is_light = (row + col) % 2 == 0cell_color = COLOR_LIGHT if is_light else COLOR_DARK
pygame.draw.rect(screen, cell_color, (x, y, CELL_SIZE, CELL_SIZE))
# Рисуем фигуру (только если её сейчас не тащат мышкой)cell_content = board[row][col]
if cell_content != '.'and (drag_start_pos != (row, col)):
draw_piece_at(cell_content, x + CELL_SIZE // 2, y + CELL_SIZE // 2)
# Отрисовка перетаскиваемой фигуры прямо под курсором мышиif dragged_piece:
mouse_x, mouse_y = pygame.mouse.get_pos()
draw_piece_at(dragged_piece, mouse_x, mouse_y)
defdraw_piece_at(piece, center_x, center_y):
"""Вспомогательная функция отрисовки фигуры по центру координат."""if piece.isupper():
char = white_pieces[piece]
p_color = (255, 255, 255) # Белые фигурыelse:
char = black_pieces[piece]
p_color = (0, 0, 0) # Черные фигурыpiece_surface = font_pieces.render(char, True, p_color)
p_rect = piece_surface.get_rect()
p_rect.center = (center_x, center_y)
screen.blit(piece_surface, p_rect)
# Главный цикл программыrunning = Truewhile running:
foreventin pygame.event.get():
if event.type == pygame.QUIT:
running = Falseelif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1: # Левая кнопка мышиcell = get_cell_from_pos(event.pos)
if cell:
row, col = cell
piece = board[row][col]
# Проверяем, принадлежит ли фигура игроку, который сейчас должен ходитьis_white_piece = piece.isupper()
correct_turn = (white_turn and is_white_piece) or (not white_turn andnot is_white_piece and piece != '.')
if correct_turn:
selected_cell = cell
dragged_piece = piece
drag_start_pos = cell
else:
# Клик по пустой клетке или чужой фигуре сбрасывает выделениеselected_cell = Noneelif event.type == pygame.MOUSEBUTTONUP:
if event.button == 1and dragged_piece:
cell = get_cell_from_pos(event.pos)
start_row, start_col = drag_start_pos
if cell and cell != drag_start_pos:
end_row, end_col = cell
target_piece = board[end_row][end_col]
# Запрещаем бить свои же фигурыis_destination_friendly = (white_turn and target_piece.isupper()) or (not white_turn and target_piece.islower() and target_piece != '.')
ifnot is_destination_friendly:
# Делаем ход
board[end_row][end_col] = dragged_piece
board[start_row][start_col] = '.'white_turn = not white_turn # Передаем ход другому игроку# Сбрасываем режим перетаскивания фигуркиdragged_piece = Nonedrag_start_pos = Noneselected_cell = None# Обновление графики
draw_board()
pygame.display.flip()
pygame.quit()
sys.exit()
Как играть (управление):
Очередь ходов: Игра начинается с хода Белых. Черные фигуры взять или перетащить в первый ход не получится. После успешного переноса белой фигуры ход автоматически переходит к Черным.
Перетаскивание (Drag-and-Drop): Зажмите фигуру левой кнопкой мыши (клетка подсветится зеленым), перетащите её на нужную позицию и отпустите кнопку.
Правила взятия: Вы можете перемещать фигуры на пустые клетки или на клетки с фигурами соперника (в этом случае фигура соперника исчезнет). Поставить свою фигуру на свою же — программа не позволит.
Если вы хотите продолжить улучшение шахмат, дайте знать:
Нужно ли добавить валидацию ходов по правилам шахмат (например, чтобы пешка ходила только вперед, конь буквой «Г» и т.д.)?
Хотите ли вы добавить звук взятия фигур или анимацию?
Нужно ли выводить на экран надпись, чей сейчас ход?
Чтобы связать графический интерфейс Pygame на Python с внешним скомпилированным шахматным движком (например, написанным на C в виде .exe файла), протокол WinBoard/XBoard использовать напрямую неудобно. Движки по протоколу WinBoard или UCI ожидают, что ими управляет шахматная оболочка (GUI), а не наоборот.
Самый простой способ управлять C-движком из Python — запустить его как подпроцесс через встроенный модуль subprocess. Python будет отправлять ходы в движок через текстовый поток и считывать текстовый ответ.
Ниже представлен готовый скрипт, в котором вы можете мышкой передвигать фигуры без правил, а программа автоматически передаст ваш ход C-движку, получит от него ответный ход и отобразит его на доске.
Скрипт интеграции Pygame с внешним C-движком
Перед запуском укажите точное имя или путь к вашему скомпилированному движку в переменной ENGINE_PATH.
importsysimportsubprocessimportthreadingimportpygame# --- НАСТРОЙКИ ВНЕШНЕГО ДВИЖКА ---# Укажите путь к вашему .exe (или бинарнику) движка на CENGINE_PATH = "my_chess_engine.exe"# --- НАСТРОЙКИ ГРАФИКИ PYGAME ---CELL_SIZE = 80BOARD_SIZE = CELL_SIZE * 8WINDOW_SIZE = BOARD_SIZE + 40OFFSET = 20COLOR_LIGHT = (240, 217, 181)
COLOR_DARK = (181, 136, 99)
COLOR_BG = (49, 46, 43)
COLOR_TEXT = (255, 255, 255)
COLOR_SELECTED = (130, 151, 105)
white_pieces = {'R': '\u2656', 'N': '\u2658', 'B': '\u2657', 'Q': '\u2655', 'K': '\u2654', 'P': '\u2659'}
black_pieces = {'r': '\u265C', 'n': '\u265E', 'b': '\u265D', 'q': '\u265B', 'k': '\u265A', 'p': '\u265F'}
board = [
['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'],
['p', 'p', 'p', 'p', 'p', 'p', 'p', 'p'],
['.', '.', '.', '.', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', '.', '.', '.'],
['P', 'P', 'P', 'P', 'P', 'P', 'P', 'P'],
['R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R']
]
# Переменные состояния мышиselected_cell = Nonedragged_piece = Nonedrag_start_pos = Noneengine_thinking = False# Блокирует интерфейс, пока бот думает# --- ВЗАИМОДЕЙСТВИЕ С ДВИЖКОМ НА C ---try:
# Запускаем движок как независимый процесс с перенаправлением потоков ввода/выводаengine_process = subprocess.Popen(
[ENGINE_PATH],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1# Построчная буферизация
)
except FileNotFoundError:
print(f"Ошибка: Не найден файл движка по пути '{ENGINE_PATH}'. Запустится только визуализация.")
engine_process = Nonedefsend_to_engine(text):
"""Отправляет команду в стандартный поток ввода C-движка."""if engine_process and engine_process.poll() isNone:
engine_process.stdin.write(text + "\n")
engine_process.stdin.flush()
defget_engine_move_blocking(player_move):
"""Отправляет ход игрока движку и ждет от него текстовый ответ."""global board, engine_thinking
ifnot engine_process or engine_process.poll() isnotNone:
engine_thinking = Falsereturn# 1. Передаем ход игрока в C-программу
send_to_engine(player_move)
# 2. Читаем строку ответа от C-программы (ожидается формат "e7e5")# readline() заблокирует этот поток, пока C-движок не выведет printf("...\n");engine_response = engine_process.stdout.readline().strip()
# 3. Применяем ответный ход на нашей доскеif len(engine_response) >= 4:
apply_move_to_board(engine_response)
engine_thinking = Falsedefrequest_engine_move(player_move):
"""Запускает общение с движком в отдельном потоке, чтобы окно Pygame не зависало."""global engine_thinking
engine_thinking = True
threading.Thread(target=get_engine_move_blocking, args=(player_move,), daemon=True).start()
# --- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ДОСКИ ---defto_notation(row, col):
"""Преобразует индексы матрицы в шахматную нотацию (например, 7, 4 -> 'e1')."""returnf"{chr(ord('a') + col)}{8 - row}"deffrom_notation(coord_str):
"""Преобразует строку 'e1' в индексы (row, col)."""col = ord(coord_str[0].lower()) - ord('a')
row = 8 - int(coord_str[1])
return row, col
defapply_move_to_board(move_str):
"""Простое перемещение фигуры по строке вида 'e2e4'."""global board
try:
sr, sc = from_notation(move_str[0:2])
er, ec = from_notation(move_str[2:4])
board[er][ec] = board[sr][sc]
board[sr][sc] = '.'except Exception:
passdefget_cell_from_pos(pos):
x, y = pos
col = (x - OFFSET) // CELL_SIZE
row = (y - OFFSET) // CELL_SIZE
if0 <= col < 8and0 <= row < 8:
return row, col
returnNonedefdraw_board(screen, font_pieces, font_labels):
screen.fill(COLOR_BG)
# Разметкаletters = "ABCDEFGH"foriin range(8):
lbl_num = font_labels.render(str(8 - i), True, COLOR_TEXT)
screen.blit(lbl_num, (5, OFFSET + i * CELL_SIZE + CELL_SIZE // 2 - 8))
lbl_let = font_labels.render(letters[i], True, COLOR_TEXT)
screen.blit(lbl_let, (OFFSET + i * CELL_SIZE + CELL_SIZE // 2 - 5, BOARD_SIZE + OFFSET + 2))
# Клеткиforrowin range(8):
forcolin range(8):
x = OFFSET + col * CELL_SIZE
y = OFFSET + row * CELL_SIZE
if (selected_cell == (row, col) or drag_start_pos == (row, col)) andnot engine_thinking:
cell_color = COLOR_SELECTED
else:
cell_color = COLOR_LIGHT if (row + col) % 2 == 0else COLOR_DARK
pygame.draw.rect(screen, cell_color, (x, y, CELL_SIZE, CELL_SIZE))
# Статичные фигурыcell_content = board[row][col]
if cell_content != '.'and (drag_start_pos != (row, col) or engine_thinking):
draw_piece_at(screen, font_pieces, cell_content, x + CELL_SIZE // 2, y + CELL_SIZE // 2)
# Перетаскиваемая фигура под мышьюif dragged_piece andnot engine_thinking:
mouse_x, mouse_y = pygame.mouse.get_pos()
draw_piece_at(screen, font_pieces, dragged_piece, mouse_x, mouse_y)
defdraw_piece_at(screen, font, piece, center_x, center_y):
char = white_pieces[piece] if piece.isupper() else black_pieces[piece]
color = (255, 255, 255) if piece.isupper() else (0, 0, 0)
surface = font.render(char, True, color)
rect = surface.get_rect(center=(center_x, center_y))
screen.blit(surface, rect)
# --- ГЛАВНЫЙ ГРАФИЧЕСКИЙ ЦИКЛ ---defmain():
global selected_cell, dragged_piece, drag_start_pos
pygame.init()
screen = pygame.display.set_mode((WINDOW_SIZE, WINDOW_SIZE))
pygame.display.set_caption("Pygame + C Chess Engine")
font_pieces = pygame.font.SysFont("arial", int(CELL_SIZE * 0.8))
font_labels = pygame.font.SysFont("arial", 16)
running = Truewhile running:
foreventin pygame.event.get():
if event.type == pygame.QUIT:
running = Falseelif event.type == pygame.MOUSEBUTTONDOWN andnot engine_thinking:
if event.button == 1:
cell = get_cell_from_pos(event.pos)
if cell and board[cell[0]][cell[1]] != '.':
selected_cell = cell
dragged_piece = board[cell[0]][cell[1]]
drag_start_pos = cell
elif event.type == pygame.MOUSEBUTTONUP andnot engine_thinking:
if event.button == 1and dragged_piece:
cell = get_cell_from_pos(event.pos)
if cell and cell != drag_start_pos:
start_str = to_notation(*drag_start_pos)
end_str = to_notation(*cell)
move_notation = start_str + end_str # Строка вида "e2e4"# 1. Двигаем фигуру игрока на экране
apply_move_to_board(move_notation)
# 2. Отправляем этот ход в C-движок и запрашиваем ответ
request_engine_move(move_notation)
dragged_piece = Nonedrag_start_pos = Noneselected_cell = None
draw_board(screen, font_pieces, font_labels)
pygame.display.flip()
# Корректно закрываем процесс движка при выходеif engine_process and engine_process.poll() isNone:
engine_process.kill()
pygame.quit()
if __name__ == "__main__":
main()
Как должен принимать и отдавать ходы ваш C-движок:
Чтобы эта связка работала, код вашей программы на C должен быть устроен по следующему базовому принципу (структура бесконечного консольного цикла):
#include<stdio.h>#include<string.h>intmain() {
char player_move[10];
// Важно отключить буферизацию printf, чтобы Python сразу получал строки
setbuf(stdout, NULL);
while (1) {
// 1. Движок замирает и ждет ход от Python через stdinif (scanf("%s", player_move) == EOF) break;
// Здесь ваш Си-движок применяет ход player_move (например, "e2e4")// и рассчитывает свой ответный лучший ход...// 2. Движок ОБЯЗАТЕЛЬНО должен вывести только строку хода и перенос строки \nchar engine_move[] = "e7e5"; // Пример рассчитанного хода
printf("%s\n", engine_move);
}
return0;
}
Главные особенности решения:
Асинхронность (threading): Ожидание ответа от C-движка вынесено в отдельный фоновый поток. Кнопки и окно Pygame не зависают, пока движок думает над позицией.
Отсутствие правил в Python: Вы можете перетащить любую фигуру на любую клетку. Строка перемещения (например, b1c3) тут же улетит в C-движок.
Обмен данными через стандартные потоки (Pipes): Python использует stdin.write для отправки и stdout.readline для чтения, что избавляет вас от необходимости сложной линковки C-библиотек или DLL.
Если ваш C-движок использует специализированный протокол инициализации перед началом игры, сообщите:
Нужно ли слать движку какие-то стартовые команды (например, uci или xboard) при запуске?
Передаются ли ходы по одному накопительной строкой (историей партии) или только по одному последнему ходу
Да, конечно. Шахматные движки часто используют символ # для вывода отладочной информации, логов или комментариев (например, # thinking... или # score: +1.2).
Чтобы Pygame не воспринимал эти строки как ходы и не пытался их отрисовать, нужно добавить простой цикл while в функцию чтения ответа. Программа будет считывать и игнорировать все строки, начинающиеся с #, пока не встретит чистую строку хода.
Вот обновленный фоновый метод get_engine_move_blocking. Замените им аналогичную функцию в вашем Python-скрипте:
defget_engine_move_blocking(player_move):
"""Отправляет ход игрока движку и ждет от него текстовый ответ, игнорируя строки с '#'."""global board, engine_thinking
ifnot engine_process or engine_process.poll() isnotNone:
engine_thinking = Falsereturn# 1. Передаем ход игрока в Си-программу
send_to_engine(player_move)
# 2. Читаем строки ответа от Си-программы в циклеwhileTrue:
engine_response = engine_process.stdout.readline()
ifnot engine_response: # Если поток закрылся (движок упал)breakengine_response = engine_response.strip()
# Если строка пустая или начинается с #, просто пропускаем её и читаем дальшеifnot engine_response or engine_response.startswith('#'):
continue# Как только нашли строку, которая не начинается с #, считаем её ходом движкаif len(engine_response) >= 4:
apply_move_to_board(engine_response)
break# Выходим из цикла чтения, так как ход полученengine_thinking = False
Python молча пропустит первые три строки и применит к доске только финальный ход e7e5.
Если ваш движок присылает информацию в каком-то другом формате (например, пишет логи в конце хода или разделяет их пробелами), напишите — я скорректирую парсер под ваш вывод.
Да, так сделать можно, и это самый надежный способ избавить Python-скрипт от знания каких-либо правил. Вместо обмена короткими ходами (e2e4), Python будет отправлять движку всю текущую матрицу доски одной текстовой строкой (или 8 строками) плюс сделанный игроком ход.
Движок на Си проверит ход по своим правилам и вернет обратно:
Либо новую позицию (в таком же текстовом формате), если ход игрока был правильным. При этом движок сразу сделает и свой ответный ход.
Либо ключевое слово ERROR, если игрок попытался сходить не по правилам. В этом случае Python просто вернет фигуру на место.
Ниже представлен полностью переработанный скрипт.
Как устроен обмен данными (Протокол)
Игрок делает ход: Python берет текущую доску (64 символа) и добавляет ход.
Пример отправки в Си:rnbqkbnrpppppppp................................PPPPPPPPRNBQKBNR e2e4
Движок отвечает:
Если ход правильный: Возвращает строку из 64 символов (уже с учетом хода игрока и ответного хода компьютера).
Если ход ошибочный: Возвращает строку ERROR (или любую строку, начинающуюся с # для отладки).
Обновленный Python-скрипт
importsysimportsubprocessimportthreadingimportpygame# --- НАСТРОЙКИ ВНЕШНЕГО ДВИЖКА ---ENGINE_PATH = "my_chess_engine.exe"# --- НАСТРОЙКИ ГРАФИКИ PYGAME ---CELL_SIZE = 80BOARD_SIZE = CELL_SIZE * 8WINDOW_SIZE = BOARD_SIZE + 40OFFSET = 20COLOR_LIGHT = (240, 217, 181)
COLOR_DARK = (181, 136, 99)
COLOR_BG = (49, 46, 43)
COLOR_TEXT = (255, 255, 255)
COLOR_SELECTED = (130, 151, 105)
white_pieces = {'R': '\u2656', 'N': '\u2658', 'B': '\u2657', 'Q': '\u2655', 'K': '\u2654', 'P': '\u2659'}
black_pieces = {'r': '\u265C', 'n': '\u265E', 'b': '\u265D', 'q': '\u265B', 'k': '\u265A', 'p': '\u265F'}
# Начальное состояние (двумерный массив для удобства отрисовки)board = [
['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'],
['p', 'p', 'p', 'p', 'p', 'p', 'p', 'p'],
['.', '.', '.', '.', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', '.', '.', '.'],
['.', '.', '.', '.', '.', '.', '.', '.'],
['P', 'P', 'P', 'P', 'P', 'P', 'P', 'P'],
['R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R']
]
selected_cell = Nonedragged_piece = Nonedrag_start_pos = Noneengine_thinking = False# --- ФУНКЦИИ КОНВЕРТАЦИИ ПОЗИЦИИ ---defboard_to_string():
"""Превращает матрицу доски 8х8 в одномерную строку из 64 символов."""return"".join("".join(row) forrowin board)
defstring_to_board(board_str):
"""Обновляет матрицу доски 8х8 из одномерной строки (64 символа)."""global board
forrin range(8):
forcin range(8):
board[r][c] = board_str[r * 8 + c]
defto_notation(row, col):
returnf"{chr(ord('a') + col)}{8 - row}"defget_cell_from_pos(pos):
x, y = pos
col = (x - OFFSET) // CELL_SIZE
row = (y - OFFSET) // CELL_SIZE
if0 <= col < 8and0 <= row < 8:
return row, col
returnNone# --- ВЗАИМОДЕЙСТВИЕ С ДВИЖКОМ НА C ---try:
engine_process = subprocess.Popen(
[ENGINE_PATH],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1
)
except FileNotFoundError:
print(f"Ошибка: Не найден движок '{ENGINE_PATH}'. Работает только интерфейс.")
engine_process = Nonedefget_engine_response_blocking(current_board_str, move_str):
"""Отправляет строку доски + ход, ждет новую доску или ошибку."""global engine_thinking
ifnot engine_process or engine_process.poll() isnotNone:
engine_thinking = Falsereturn# Отправляем данные формата: "64_символа_доски e2e4"
engine_process.stdin.write(f"{current_board_str}{move_str}\n")
engine_process.stdin.flush()
whileTrue:
response = engine_process.stdout.readline()
ifnot response:
breakresponse = response.strip()
# Игнорируем логи и комментарии движкаifnot response or response.startswith('#'):
continue# Если ход нелегальный, Си-движок прислал ERROR -> ничего не меняем (фигура вернется назад)if"ERROR"in response.upper():
print("Движок отклонил ход: не по правилам!")
break# Если пришла строка из 64 символов — это валидная новая позицияif len(response) == 64:
string_to_board(response)
breakbreakengine_thinking = False# --- ОТРИСОВКА ---defdraw_board(screen, font_pieces, font_labels):
screen.fill(COLOR_BG)
letters = "ABCDEFGH"foriin range(8):
lbl_num = font_labels.render(str(8 - i), True, COLOR_TEXT)
screen.blit(lbl_num, (5, OFFSET + i * CELL_SIZE + CELL_SIZE // 2 - 8))
lbl_let = font_labels.render(letters[i], True, COLOR_TEXT)
screen.blit(lbl_let, (OFFSET + i * CELL_SIZE + CELL_SIZE // 2 - 5, BOARD_SIZE + OFFSET + 2))
forrowin range(8):
forcolin range(8):
x = OFFSET + col * CELL_SIZE
y = OFFSET + row * CELL_SIZE
if (selected_cell == (row, col) or drag_start_pos == (row, col)) andnot engine_thinking:
cell_color = COLOR_SELECTED
else:
cell_color = COLOR_LIGHT if (row + col) % 2 == 0else COLOR_DARK
pygame.draw.rect(screen, cell_color, (x, y, CELL_SIZE, CELL_SIZE))
cell_content = board[row][col]
if cell_content != '.'and (drag_start_pos != (row, col) or engine_thinking):
draw_piece_at(screen, font_pieces, cell_content, x + CELL_SIZE // 2, y + CELL_SIZE // 2)
if dragged_piece andnot engine_thinking:
mouse_x, mouse_y = pygame.mouse.get_pos()
draw_piece_at(screen, font_pieces, dragged_piece, mouse_x, mouse_y)
defdraw_piece_at(screen, font, piece, center_x, center_y):
char = white_pieces[piece] if piece.isupper() else black_pieces[piece]
color = (255, 255, 255) if piece.isupper() else (0, 0, 0)
surface = font.render(char, True, color)
rect = surface.get_rect(center=(center_x, center_y))
screen.blit(surface, rect)
# --- ГЛАВНЫЙ ЦИКЛ ---defmain():
global selected_cell, dragged_piece, drag_start_pos, engine_thinking
pygame.init()
screen = pygame.display.set_mode((WINDOW_SIZE, WINDOW_SIZE))
pygame.display.set_caption("Chess GUI (No Rules in Python)")
font_pieces = pygame.font.SysFont("arial", int(CELL_SIZE * 0.8))
font_labels = pygame.font.SysFont("arial", 16)
running = Truewhile running:
foreventin pygame.event.get():
if event.type == pygame.QUIT:
running = Falseelif event.type == pygame.MOUSEBUTTONDOWN andnot engine_thinking:
if event.button == 1:
cell = get_cell_from_pos(event.pos)
if cell and board[cell[0]][cell[1]] != '.':
selected_cell = cell
dragged_piece = board[cell[0]][cell[1]]
drag_start_pos = cell
elif event.type == pygame.MOUSEBUTTONUP andnot engine_thinking:
if event.button == 1and dragged_piece:
cell = get_cell_from_pos(event.pos)
if cell and cell != drag_start_pos:
# Запоминаем старую доску на случай отправкиold_board_str = board_to_string()
start_str = to_notation(*drag_start_pos)
end_str = to_notation(*cell)
move_str = start_str + end_str
# Временно двигаем фигуру на экране, чтобы визуализировать намерение игрока
board[cell[0]][cell[1]] = dragged_piece
board[drag_start_pos[0]][drag_start_pos[1]] = '.'# Отправляем состояние ДО ХОДА и сам ход на валидацию в Сиengine_thinking = True
threading.Thread(
target=get_engine_response_blocking,
args=(old_board_str, move_str),
daemon=True
).start()
dragged_piece = Nonedrag_start_pos = Noneselected_cell = None
draw_board(screen, font_pieces, font_labels)
pygame.display.flip()
if engine_process and engine_process.poll() isNone:
engine_process.kill()
pygame.quit()
if __name__ == "__main__":
main()
Как должен выглядеть прием данных на стороне C (Си):
В вашей Си-программе функция main должна обрабатывать входящую строку следующим образом:
#include<stdio.h>#include<string.h>intmain() {
char board_string[65]; // 64 символа + '\0'char move_string[6]; // e.g. "e2e4" + '\0'
setbuf(stdout, NULL); // Отключаем буферизациюwhile (1) {
// Ожидаем строку формата: "rnbq...BNR e2e4"if (scanf("%s %s", board_string, move_string) == EOF) break;
// 1. Загружаем board_string в массив вашего движка// 2. Проверяем, легален ли ход move_stringintis_valid = check_move_rules(board_string, move_string);
if (!is_valid) {
// Если ход не по правилам — пишем ошибку
printf("ERROR\n");
} else {
// Если ход правильный:// 1. Применяем ход игрока на виртуальной доске движка// 2. Запускаем поиск лучшего ответного хода компьютера// 3. Применяем ответный ход компьютераchar new_board_string[65];
generate_new_board_string(new_board_string); // Генерируем 64 символа новой позиции// Отправляем новую доску обратно в Python
printf("%s\n", new_board_string);
}
}
return0;
}
Почему это идеальное разделение:
Python — просто глупый экран: Он понятия не имеет, как ходит конь или пешка. Если игрок передвинет фигуру неверно, Си ответит ERROR, и Python мгновенно перерисует доску обратно в состояние old_board_str.
Синхронизация рокировок и взятий: Если игрок делает рокировку (король ходит на 2 клетки), Си-движок видит этот ход, понимает его, делает рокировку ладьи у себя в памяти, делает свой ответный ход и присылает готовую итоговую строку из 64 символов. Python просто отобразит её «как есть».
Если ваш Си-движок принимает позицию в другом популярном формате (например, FEN-строка, где пустые клетки заменяются цифрами вроде rnbqkbnr/pppppppp/8/...), скажите мне, и я добавлю в Python-скрипт функцию генерации FEN-строк.
Вы абсолютно правы! Использовать fgets в Си для этой задачи гораздо надежнее и безопаснее, чем scanf, так как fgets считывает всю строку целиком до переноса \n и гарантированно защищает от переполнения буфера.
Поскольку Python отправляет доску и ход на одной строке через пробел (64_символа e2e4\n), мы можем считать её с помощью fgets, а затем аккуратно разбить на две части с помощью функции sscanf или strtok.
Вот полностью рабочий, безопасный и готовый к компиляции код на Си, использующий fgets:
#include<stdio.h>#include<string.h>#define BUFFER_SIZE 256intmain() {
char input_line[BUFFER_SIZE];
char board_string[65]; // 64 символа + '\0'char move_string[10]; // Хватит для "e2e4", "e7e8q" + '\0'// ОТКЛЮЧАЕМ БУФЕРИЗАЦИЮ: без этого Python зависнет в ожидании ответа
setvbuf(stdout, NULL, _IONBF, 0);
// Бесконечный цикл чтения строк из Python через stdinwhile (fgets(input_line, sizeof(input_line), stdin) != NULL) {
// Убираем символ переноса строки \n в конце, если он есть
input_line[strcspn(input_line, "\r\n")] = '\0';
// Пропускаем пустые строкиif (strlen(input_line) == 0) {
continue;
}
// Парсим строку: читаем сначала 64 символа доски, затем строку хода// %64s считает ровно 64 символа, %9s считает оставшийся ходif (sscanf(input_line, "%64s %9s", board_string, move_string) == 2) {
// --- ОТЛАДКА ---// Строки с префиксом '#' Python просто напечатает в консоль и проигнорирует
printf("# Движок Си получил строку через fgets!\n");
printf("# Доска: %s\n", board_string);
printf("# Ход: %s\n", move_string);
// --- ЗДЕСЬ ВАША ШАХМАТНАЯ ЛОГИКА ---// Для примера: делаем проверку (замените на свои правила шахмат)intmove_is_legal = 1;
if (!move_is_legal) {
// Если ход не по правилам — возвращаем ошибку
printf("ERROR\n");
} else {
// Если ход правильный:// 1. Применяете move_string к board_string// 2. Считаете ответный ход вашего Си-бота// 3. Модифицируете board_string (формируете новую позицию)// Для теста просто отправляем доску обратно без изменений
printf("%s\n", board_string);
}
} else {
// Если пришло что-то некорректное
printf("ERROR\n");
}
}
return0;
}
Почему этот вариант с fgets + sscanf идеален:
Безопасность памяти: fgets(..., sizeof(input_line), stdin) никогда не прочитает больше 256 байт, что полностью исключает падение Си-программы из-за кривых данных.
Четкие ограничения: Конструкция sscanf(..., "%64s %9s", ...) жестко контролирует, чтобы в массив доски записалось ровно 64 символа, а в ход — не более 9, защищая от переполнения внутренних массивов.
Устойчивость к мусору: Если в поток случайно попадет пустая строка или пробелы, fgets корректно их переварит, а sscanf вернет результат отличный от 2, и программа не упадет, а просто отправит в Python ERROR.
Если вам потребуется помощь с тем, как в Си удобнее преобразовать эту 64-символьную строку в двумерный массивboard[8][8] для вашего движка, дайте знать! На каком компиляторе (GCC, Clang, MSVC) вы собираете Си-код?
Формат ответа движка — это ровно 64 символа в одну строку (плюс \n в самом конце). Каждый символ в этой строке обозначает конкретную клетку шахматной доски, начиная с левого верхнего угла (клетка A8) и заканчивая правым нижним углом (клетка H1).
Карта соответствия строки и доски (8х8)
Строка длиной 64 символа мысленно нарезается движком и скриптом на 8 частей по 8 символов:
Вот какую строку ваш Си-движок должен отправить в Python в самом начале игры (или вернуть, если он просто соглашается с ходом игрока): rnbqkbnrpppppppp................................PPPPPPPPRNBQKBNR
Как Python-скрипт автоматически её отрисовывает
В рабочем Python-коде за это отвечает функция string_to_board(response). Вам не нужно ничего дописывать, скрипт делает это сам по следующему алгоритму:
Функция берёт строку из 64 символов.
С помощью формулы row * 8 + col она раскладывает её обратно в двумерную матрицу доски 8х8.
В цикле отрисовки Pygame пробегается по этой матрице. Если видит точку ., то просто рисует пустую клетку (светлую или темную). Если видит букву — подставляет соответствующий код Юникода:
Заглавные буквы (P, R, N, B, Q, K) — отрисовываются белым цветом.
Строчные буквы (p, r, n, b, q, k) — отрисовываются черным цветом.
Пример: Игрок сходил e2e4, Си-движок ответил e7e5
Игрок передвинул пешку. Python отправляет в Си начальную доску и ход: rnbqkbnrpppppppp................................PPPPPPPPRNBQKBNR e2e4
Ваш Си-движок у себя в памяти передвигает белую пешку с E2 на E4, затем делает свой ответный ход (черная пешка с E7 на E5).
Си-движок формирует новую строку из 64 символов, где пешки поменяли позиции, и отправляет её в printf: rnbqkbnrppp.pppp............p...................ReferencesPPPP.PPPRNBQKBNR\n
Python мгновенно считывает эту строку, обновляет экран, и игроки видят на красивой графической доске сразу два сделанных хода.
Если вы хотите, чтобы при превращении пешки (дойдя до края доски) на экране рисовался Ферзь или другая фигура, Си-движку достаточно просто заменить символ P на Q (или p на q) на нужной позиции в этой 64-символьной строке. Python увидит новую букву и сам нарисует правильную фигуру.
Уточните, нужно ли показать пример кода на Си, который принимает индексы клетки (строку и столбец) и находит их правильное место в этой 64-символьной строке?