вторник, 30 июня 2026 г.

Sdl corners

 #include <SDL2/SDL.h>

#include <iostream>

#include <vector>

#include <cmath>


// Размеры

const int CELL_SIZE = 64;

const int BOARD_SIZE = 8;

const int WINDOW_WIDTH = CELL_SIZE * BOARD_SIZE;

const int WINDOW_HEIGHT = CELL_SIZE * BOARD_SIZE;


// Цвета (SDL_RGBA)

SDL_Color COLOR_WHITE = {255, 255, 255, 255};

SDL_Color COLOR_BLACK = {0, 0, 0, 255};

SDL_Color COLOR_BOARD_LIGHT = {240, 217, 181, 255};

SDL_Color COLOR_BOARD_DARK = {181, 136, 99, 255};

SDL_Color COLOR_SELECTED = {100, 200, 255, 150};


// Типы фигур

enum PieceType { NONE, WHITE, BLACK };


struct Position {

    int row;

    int col;

    bool operator==(const Position& other) const {

        return row == other.row && col == other.col;

    }

};


// Класс игры

class Game {

public:

    std::vector<std::vector<PieceType>> board;

    PieceType currentPlayer;

    Position selectedPiece;

    bool pieceSelected;


    Game() {

        board.resize(BOARD_SIZE, std::vector<PieceType>(BOARD_SIZE, NONE));

        currentPlayer = WHITE;

        pieceSelected = false;

        selectedPiece = {-1, -1};

        

        // Расстановка фигур (как в шахматах, но только пешки и ладьи для примера)

        // Белые внизу (строки 6-7)

        for (int c = 0; c < BOARD_SIZE; c++) {

            board[6][c] = WHITE; // Верхний ряд белых

            board[7][c] = WHITE; // Нижний ряд белых

        }

        // Черные вверху (строки 0-1)

        for (int c = 0; c < BOARD_SIZE; c++) {

            board[0][c] = BLACK; 

            board[1][c] = BLACK;

        }

    }


    bool isInsideBoard(int r, int c) {

        return r >= 0 && r < BOARD_SIZE && c >= 0 && c < BOARD_SIZE;

    }


    bool makeMove(Position from, Position to) {

        if (board[from.row][from.col] != currentPlayer) return false;

        if (board[to.row][to.col] != NONE) return false;


        int dr = std::abs(to.row - from.row);

        int dc = std::abs(to.col - from.col);


        // Проверка правил

        if (dr == 1 && dc == 1) {

            // Обычный ход по диагонали

            board[to.row][to.col] = currentPlayer;

            board[from.row][from.col] = NONE;

            return true;

        } 

        else if (dr == 2 && dc == 2) {

            // Прыжок через фигуру

            int midRow = (from.row + to.row) / 2;

            int midCol = (from.col + to.col) / 2;

            if (board[midRow][midCol] != NONE && board[midRow][midCol] != currentPlayer) {

                board[to.row][to.col] = currentPlayer;

                board[from.row][from.col] = NONE;

                board[midRow][midCol] = NONE; // Съедаем фигуру

                return true;

            }

        }

        return false;

    }


    void switchTurn() {

        currentPlayer = (currentPlayer == WHITE) ? BLACK : WHITE;

    }

};


// Функции отрисовки

void drawBoard(SDL_Renderer* renderer) {

    for (int r = 0; r < BOARD_SIZE; r++) {

        for (int c = 0; c < BOARD_SIZE; c++) {

            SDL_Rect rect = {c * CELL_SIZE, r * CELL_SIZE, CELL_SIZE, CELL_SIZE};

            if ((r + c) % 2 == 0) {

                SDL_SetRenderDrawColor(renderer, COLOR_BOARD_LIGHT.r, COLOR_BOARD_LIGHT.g, COLOR_BOARD_LIGHT.b, 255);

            } else {

                SDL_SetRenderDrawColor(renderer, COLOR_BOARD_DARK.r, COLOR_BOARD_DARK.g, COLOR_BOARD_DARK.b, 255);

            }

            SDL_RenderFillRect(renderer, &rect);

        }

    }

}


void drawPieces(SDL_Renderer* renderer, Game& game) {

    for (int r = 0; r < BOARD_SIZE; r++) {

        for (int c = 0; c < BOARD_SIZE; c++) {

            if (game.board[r][c] == NONE) continue;


            SDL_Rect rect = {c * CELL_SIZE + 8, r * CELL_SIZE + 8, CELL_SIZE - 16, CELL_SIZE - 16};

            

            // Выбор цвета фигуры

            if (game.board[r][c] == WHITE) {

                SDL_SetRenderDrawColor(renderer, COLOR_WHITE.r, COLOR_WHITE.g, COLOR_WHITE.b, 255);

            } else {

                SDL_SetRenderDrawColor(renderer, COLOR_BLACK.r, COLOR_BLACK.g, COLOR_BLACK.b, 255);

            }

            

            // Рисуем круг (используем многоугольник для простоты или можно использовать SDL_RenderDrawPoint, но это медленно)

            // Для простоты рисуем квадрат со скругленными углами, но в SDL2 нет встроенной функции круга.

            // Нарисуем эллипс вручную (или просто квадрат для скорости).

            // Давайте нарисуем круг через серию точек.

            int cx = rect.x + rect.w/2;

            int cy = rect.y + rect.h/2;

            int radius = rect.w/2 - 2;

            

            for (int w = 0; w < radius * 2; w++) {

                for (int h = 0; h < radius * 2; h++) {

                    int dx = radius - w;

                    int dy = radius - h;

                    if ((dx*dx + dy*dy) <= (radius * radius)) {

                        SDL_RenderDrawPoint(renderer, cx + dx, cy + dy);

                    }

                }

            }

            

            // Если фигура выбрана, обводим её

            if (game.pieceSelected && game.selectedPiece.row == r && game.selectedPiece.col == c) {

                SDL_SetRenderDrawColor(renderer, COLOR_SELECTED.r, COLOR_SELECTED.g, COLOR_SELECTED.b, 255);

                SDL_Rect outline = {rect.x - 2, rect.y - 2, rect.w + 4, rect.h + 4};

                SDL_RenderDrawRect(renderer, &outline);

            }

        }

    }

}


int main(int argc, char* argv[]) {

    if (SDL_Init(SDL_INIT_VIDEO) != 0) {

        std::cerr << "SDL Init Error: " << SDL_GetError() << std::endl;

        return 1;

    }


    SDL_Window* window = SDL_CreateWindow("Уголки (Corners)", 

                                          SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,

                                          WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_SHOWN);

    if (!window) {

        std::cerr << "Window Error: " << SDL_GetError() << std::endl;

        SDL_Quit();

        return 1;

    }


    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);

    if (!renderer) {

        std::cerr << "Renderer Error: " << SDL_GetError() << std::endl;

        SDL_DestroyWindow(window);

        SDL_Quit();

        return 1;

    }


    Game game;

    bool running = true;

    SDL_Event event;


    while (running) {

        while (SDL_PollEvent(&event)) {

            if (event.type == SDL_QUIT) {

                running = false;

            }

            else if (event.type == SDL_MOUSEBUTTONDOWN) {

                int mouseX = event.button.x;

                int mouseY = event.button.y;

                int col = mouseX / CELL_SIZE;

                int row = mouseY / CELL_SIZE;


                if (game.isInsideBoard(row, col)) {

                    // Если фигура уже выбрана

                    if (game.pieceSelected) {

                        // Пытаемся сделать ход

                        if (game.makeMove(game.selectedPiece, {row, col})) {

                            game.switchTurn();

                            game.pieceSelected = false;

                        } else {

                            // Если ход не удался, либо снимаем выделение, либо выбираем новую фигуру

                            if (game.board[row][col] == game.currentPlayer) {

                                game.selectedPiece = {row, col};

                            } else {

                                game.pieceSelected = false;

                            }

                        }

                    } 

                    // Выбираем фигуру

                    else {

                        if (game.board[row][col] == game.currentPlayer) {

                            game.selectedPiece = {row, col};

                            game.pieceSelected = true;

                        }

                    }

                }

            }

        }


        // Отрисовка

        SDL_SetRenderDrawColor(renderer, 50, 50, 50, 255);

        SDL_RenderClear(renderer);


        drawBoard(renderer);

        drawPieces(renderer, game);


        SDL_RenderPresent(renderer);

        SDL_Delay(10);

    }


    SDL_DestroyRenderer(renderer);

    SDL_DestroyWindow(window);

    SDL_Quit();

    return 0;

}

/////////

/*

g++ main.cpp -o corners.exe -I"path\to\SDL2\include" -L"path\to\SDL2\lib" -lSDL2main -lSDL2


    Да, SDL2.dll (или SDL2main.dll, если у вас статическая линковка) обязан находиться в той же папке, где лежит ваш .exe файл.

    Как это сделать правильно:

    1. Скачайте библиотеку SDL2 с официального сайта (обычно это SDL2-devel-2.x.x-mingw.zip).



Откройте командную строку (cmd) или PowerShell в папке с вашим corners.exe и введите команду:

cmd
dumpbin /headers corners.exe | find "machine"

Что вы увидите:

  • Если в выводе есть 14C → это x86 (32-bit).

  • Если в выводе есть 8664 → это x64 (64-bit).


  •  */


понедельник, 29 июня 2026 г.

Связь по винбоард

 

Да, вывести текстовые символы Юникода в графическое окно pygame можно. Для этого используется модуль pygame.font.
Основная хитрость заключается в том, что стандартный шрифт Pygame не всегда корректно поддерживает шахматные символы Юникода. Чтобы они гарантированно отображались, лучше использовать системный шрифт, который точно содержит эти символы (например, Arial, Segoe UI Symbol или DejaVu Sans).
Ниже представлен готовый скрипт, который создаёт графическое окно, рисует цветную шахматную сетку и поверх неё отрисовывает фигуры из кодов символов.

Готовый код на Pygame

import sys
import pygame

# Инициализация Pygame
pygame.init()

# Константы графики
CELL_SIZE = 80
BOARD_SIZE = CELL_SIZE * 8
WINDOW_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']
]

def draw_board():
    # Заливка фона
    screen.fill(COLOR_BG)
    
    # Отрисовка разметки (буквы и цифры)
    letters = "ABCDEFGH"
    for i in 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))

    # Отрисовка клеток и фигур
    for row in range(8):
        for col in range(8):
            # Координаты левого верхнего угла клетки
            x = OFFSET + col * CELL_SIZE
            y = OFFSET + row * CELL_SIZE
            
            # 1. Рисуем клетку доски
            is_light = (row + col) % 2 == 0
            cell_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 = True
while running:
    for event in 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, в котором реализована полноценная игра для двух игроков на одном экране.
В программу добавлены:
  1. Выделение клетки при нажатии.
  2. Перетаскивание фигур (Drag-and-Drop) мышкой.
  3. Ограничение по очереди ходов (белые и черные ходят строго по очереди, начиная с белых).
  4. Возврат фигуры, если ход был сделан на ту же самую клетку или за пределы доски.

Полный код игры

import sys
import pygame

# Инициализация Pygame
pygame.init()

# Константы графики
CELL_SIZE = 80
BOARD_SIZE = CELL_SIZE * 8
WINDOW_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 - черные

def get_cell_from_pos(pos):
    """Определяет строку и столбец на доске по координатам мыши."""
    x, y = pos
    col = (x - OFFSET) // CELL_SIZE
    row = (y - OFFSET) // CELL_SIZE
    if 0 <= col < 8 and 0 <= row < 8:
        return row, col
    return None

def draw_board():
    screen.fill(COLOR_BG)
    
    # Отрисовка разметки (буквы и цифры)
    letters = "ABCDEFGH"
    for i in 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))

    # Отрисовка клеток и статичных фигур
    for row in range(8):
        for col in 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 == 0
                cell_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)

def draw_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 = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
            
        elif 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 and not is_white_piece and piece != '.')
                    
                    if correct_turn:
                        selected_cell = cell
                        dragged_piece = piece
                        drag_start_pos = cell
                    else:
                        # Клик по пустой клетке или чужой фигуре сбрасывает выделение
                        selected_cell = None

        elif event.type == pygame.MOUSEBUTTONUP:
            if event.button == 1 and 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 != '.')
                    
                    if not is_destination_friendly:
                        # Делаем ход
                        board[end_row][end_col] = dragged_piece
                        board[start_row][start_col] = '.'
                        white_turn = not white_turn  # Передаем ход другому игроку
                
                # Сбрасываем режим перетаскивания фигурки
                dragged_piece = None
                drag_start_pos = None
                selected_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.
import sys
import subprocess
import threading
import pygame

# --- НАСТРОЙКИ ВНЕШНЕГО ДВИЖКА ---
# Укажите путь к вашему .exe (или бинарнику) движка на C
ENGINE_PATH = "my_chess_engine.exe" 

# --- НАСТРОЙКИ ГРАФИКИ PYGAME ---
CELL_SIZE = 80
BOARD_SIZE = CELL_SIZE * 8
WINDOW_SIZE = BOARD_SIZE + 40
OFFSET = 20

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)

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  
dragged_piece = None  
drag_start_pos = None 
engine_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 = None

def send_to_engine(text):
    """Отправляет команду в стандартный поток ввода C-движка."""
    if engine_process and engine_process.poll() is None:
        engine_process.stdin.write(text + "\n")
        engine_process.stdin.flush()

def get_engine_move_blocking(player_move):
    """Отправляет ход игрока движку и ждет от него текстовый ответ."""
    global board, engine_thinking
    if not engine_process or engine_process.poll() is not None:
        engine_thinking = False
        return

    # 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 = False

def request_engine_move(player_move):
    """Запускает общение с движком в отдельном потоке, чтобы окно Pygame не зависало."""
    global engine_thinking
    engine_thinking = True
    threading.Thread(target=get_engine_move_blocking, args=(player_move,), daemon=True).start()

# --- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ДОСКИ ---
def to_notation(row, col):
    """Преобразует индексы матрицы в шахматную нотацию (например, 7, 4 -> 'e1')."""
    return f"{chr(ord('a') + col)}{8 - row}"

def from_notation(coord_str):
    """Преобразует строку 'e1' в индексы (row, col)."""
    col = ord(coord_str[0].lower()) - ord('a')
    row = 8 - int(coord_str[1])
    return row, col

def apply_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:
        pass

def get_cell_from_pos(pos):
    x, y = pos
    col = (x - OFFSET) // CELL_SIZE
    row = (y - OFFSET) // CELL_SIZE
    if 0 <= col < 8 and 0 <= row < 8:
        return row, col
    return None

def draw_board(screen, font_pieces, font_labels):
    screen.fill(COLOR_BG)
    
    # Разметка
    letters = "ABCDEFGH"
    for i in 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))

    # Клетки
    for row in range(8):
        for col in range(8):
            x = OFFSET + col * CELL_SIZE
            y = OFFSET + row * CELL_SIZE
            
            if (selected_cell == (row, col) or drag_start_pos == (row, col)) and not engine_thinking:
                cell_color = COLOR_SELECTED
            else:
                cell_color = COLOR_LIGHT if (row + col) % 2 == 0 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) or engine_thinking):
                draw_piece_at(screen, font_pieces, cell_content, x + CELL_SIZE // 2, y + CELL_SIZE // 2)

    # Перетаскиваемая фигура под мышью
    if dragged_piece and not engine_thinking:
        mouse_x, mouse_y = pygame.mouse.get_pos()
        draw_piece_at(screen, font_pieces, dragged_piece, mouse_x, mouse_y)

def draw_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)

# --- ГЛАВНЫЙ ГРАФИЧЕСКИЙ ЦИКЛ ---
def main():
    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 = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
                
            elif event.type == pygame.MOUSEBUTTONDOWN and not 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 and not engine_thinking:
                if event.button == 1 and 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 = None
                    drag_start_pos = None
                    selected_cell = None

        draw_board(screen, font_pieces, font_labels)
        pygame.display.flip()

    # Корректно закрываем процесс движка при выходе
    if engine_process and engine_process.poll() is None:
        engine_process.kill()
    pygame.quit()

if __name__ == "__main__":
    main()

Как должен принимать и отдавать ходы ваш C-движок:

Чтобы эта связка работала, код вашей программы на C должен быть устроен по следующему базовому принципу (структура бесконечного консольного цикла):
#include <stdio.h>
#include <string.h>

int main() {
    char player_move[10];
    
    // Важно отключить буферизацию printf, чтобы Python сразу получал строки
    setbuf(stdout, NULL); 

    while (1) {
        // 1. Движок замирает и ждет ход от Python через stdin
        if (scanf("%s", player_move) == EOF) break; 
        
        // Здесь ваш Си-движок применяет ход player_move (например, "e2e4")
        // и рассчитывает свой ответный лучший ход...
        
        // 2. Движок ОБЯЗАТЕЛЬНО должен вывести только строку хода и перенос строки \n
        char engine_move[] = "e7e5"; // Пример рассчитанного хода
        printf("%s\n", engine_move); 
    }
    return 0;
}

Главные особенности решения:

  1. Асинхронность (threading): Ожидание ответа от C-движка вынесено в отдельный фоновый поток. Кнопки и окно Pygame не зависают, пока движок думает над позицией.
  2. Отсутствие правил в Python: Вы можете перетащить любую фигуру на любую клетку. Строка перемещения (например, b1c3) тут же улетит в C-движок.
  3. Обмен данными через стандартные потоки (Pipes): Python использует stdin.write для отправки и stdout.readline для чтения, что избавляет вас от необходимости сложной линковки C-библиотек или DLL.
Если ваш C-движок использует специализированный протокол инициализации перед началом игры, сообщите:
  • Нужно ли слать движку какие-то стартовые команды (например, uci или xboard) при запуске?
  • Передаются ли ходы по одному накопительной строкой (историей партии) или только по одному последнему ходу
Да, конечно. Шахматные движки часто используют символ # для вывода отладочной информации, логов или комментариев (например, # thinking... или # score: +1.2).
Чтобы Pygame не воспринимал эти строки как ходы и не пытался их отрисовать, нужно добавить простой цикл while в функцию чтения ответа. Программа будет считывать и игнорировать все строки, начинающиеся с #, пока не встретит чистую строку хода.
Вот обновленный фоновый метод get_engine_move_blocking. Замените им аналогичную функцию в вашем Python-скрипте:
def get_engine_move_blocking(player_move):
    """Отправляет ход игрока движку и ждет от него текстовый ответ, игнорируя строки с '#'."""
    global board, engine_thinking
    if not engine_process or engine_process.poll() is not None:
        engine_thinking = False
        return

    # 1. Передаем ход игрока в Си-программу
    send_to_engine(player_move)
    
    # 2. Читаем строки ответа от Си-программы в цикле
    while True:
        engine_response = engine_process.stdout.readline()
        if not engine_response:  # Если поток закрылся (движок упал)
            break
            
        engine_response = engine_response.strip()
        
        # Если строка пустая или начинается с #, просто пропускаем её и читаем дальше
        if not engine_response or engine_response.startswith('#'):
            continue
            
        # Как только нашли строку, которая не начинается с #, считаем её ходом движка
        if len(engine_response) >= 4:
            apply_move_to_board(engine_response)
        
        break # Выходим из цикла чтения, так как ход получен
        
    engine_thinking = False

Как теперь это работает:

  • Если ваш C-движок напишет в консоль:
    # Анализ позиции...
    # Найдена лучшая ветка: e7e5 d2d4
    # Оценка: +0.15
    e7e5
    
  • Python молча пропустит первые три строки и применит к доске только финальный ход e7e5.
Если ваш движок присылает информацию в каком-то другом формате (например, пишет логи в конце хода или разделяет их пробелами), напишите — я скорректирую парсер под ваш вывод.


Да, так сделать можно, и это самый надежный способ избавить Python-скрипт от знания каких-либо правил. Вместо обмена короткими ходами (e2e4), Python будет отправлять движку всю текущую матрицу доски одной текстовой строкой (или 8 строками) плюс сделанный игроком ход.
Движок на Си проверит ход по своим правилам и вернет обратно:
  • Либо новую позицию (в таком же текстовом формате), если ход игрока был правильным. При этом движок сразу сделает и свой ответный ход.
  • Либо ключевое слово ERROR, если игрок попытался сходить не по правилам. В этом случае Python просто вернет фигуру на место.
Ниже представлен полностью переработанный скрипт.

Как устроен обмен данными (Протокол)

  1. Игрок делает ход: Python берет текущую доску (64 символа) и добавляет ход.
    • Пример отправки в Си: rnbqkbnrpppppppp................................PPPPPPPPRNBQKBNR e2e4
  2. Движок отвечает:
    • Если ход правильный: Возвращает строку из 64 символов (уже с учетом хода игрока и ответного хода компьютера).
    • Если ход ошибочный: Возвращает строку ERROR (или любую строку, начинающуюся с # для отладки).

Обновленный Python-скрипт

import sys
import subprocess
import threading
import pygame

# --- НАСТРОЙКИ ВНЕШНЕГО ДВИЖКА ---
ENGINE_PATH = "my_chess_engine.exe" 

# --- НАСТРОЙКИ ГРАФИКИ PYGAME ---
CELL_SIZE = 80
BOARD_SIZE = CELL_SIZE * 8
WINDOW_SIZE = BOARD_SIZE + 40
OFFSET = 20

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)

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  
dragged_piece = None  
drag_start_pos = None 
engine_thinking = False 

# --- ФУНКЦИИ КОНВЕРТАЦИИ ПОЗИЦИИ ---
def board_to_string():
    """Превращает матрицу доски 8х8 в одномерную строку из 64 символов."""
    return "".join("".join(row) for row in board)

def string_to_board(board_str):
    """Обновляет матрицу доски 8х8 из одномерной строки (64 символа)."""
    global board
    for r in range(8):
        for c in range(8):
            board[r][c] = board_str[r * 8 + c]

def to_notation(row, col):
    return f"{chr(ord('a') + col)}{8 - row}"

def get_cell_from_pos(pos):
    x, y = pos
    col = (x - OFFSET) // CELL_SIZE
    row = (y - OFFSET) // CELL_SIZE
    if 0 <= col < 8 and 0 <= row < 8:
        return row, col
    return None

# --- ВЗАИМОДЕЙСТВИЕ С ДВИЖКОМ НА 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 = None

def get_engine_response_blocking(current_board_str, move_str):
    """Отправляет строку доски + ход, ждет новую доску или ошибку."""
    global engine_thinking
    if not engine_process or engine_process.poll() is not None:
        engine_thinking = False
        return

    # Отправляем данные формата: "64_символа_доски e2e4"
    engine_process.stdin.write(f"{current_board_str} {move_str}\n")
    engine_process.stdin.flush()
    
    while True:
        response = engine_process.stdout.readline()
        if not response:
            break
            
        response = response.strip()
        
        # Игнорируем логи и комментарии движка
        if not response or response.startswith('#'):
            continue
            
        # Если ход нелегальный, Си-движок прислал ERROR -> ничего не меняем (фигура вернется назад)
        if "ERROR" in response.upper():
            print("Движок отклонил ход: не по правилам!")
            break
            
        # Если пришла строка из 64 символов — это валидная новая позиция
        if len(response) == 64:
            string_to_board(response)
            break
            
        break 
        
    engine_thinking = False

# --- ОТРИСОВКА ---
def draw_board(screen, font_pieces, font_labels):
    screen.fill(COLOR_BG)
    letters = "ABCDEFGH"
    for i in 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))

    for row in range(8):
        for col in range(8):
            x = OFFSET + col * CELL_SIZE
            y = OFFSET + row * CELL_SIZE
            
            if (selected_cell == (row, col) or drag_start_pos == (row, col)) and not engine_thinking:
                cell_color = COLOR_SELECTED
            else:
                cell_color = COLOR_LIGHT if (row + col) % 2 == 0 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) or engine_thinking):
                draw_piece_at(screen, font_pieces, cell_content, x + CELL_SIZE // 2, y + CELL_SIZE // 2)

    if dragged_piece and not engine_thinking:
        mouse_x, mouse_y = pygame.mouse.get_pos()
        draw_piece_at(screen, font_pieces, dragged_piece, mouse_x, mouse_y)

def draw_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)

# --- ГЛАВНЫЙ ЦИКЛ ---
def main():
    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 = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
                
            elif event.type == pygame.MOUSEBUTTONDOWN and not 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 and not engine_thinking:
                if event.button == 1 and 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 = None
                    drag_start_pos = None
                    selected_cell = None

        draw_board(screen, font_pieces, font_labels)
        pygame.display.flip()

    if engine_process and engine_process.poll() is None:
        engine_process.kill()
    pygame.quit()

if __name__ == "__main__":
    main()

Как должен выглядеть прием данных на стороне C (Си):

В вашей Си-программе функция main должна обрабатывать входящую строку следующим образом:
#include <stdio.h>
#include <string.h>

int main() {
    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_string
        
        int is_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); 
        }
    }
    return 0;
}

Почему это идеальное разделение:

  1. Python — просто глупый экран: Он понятия не имеет, как ходит конь или пешка. Если игрок передвинет фигуру неверно, Си ответит ERROR, и Python мгновенно перерисует доску обратно в состояние old_board_str.
  2. Синхронизация рокировок и взятий: Если игрок делает рокировку (король ходит на 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 256

int main() {
    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 через stdin
    while (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);

            // --- ЗДЕСЬ ВАША ШАХМАТНАЯ ЛОГИКА ---
            // Для примера: делаем проверку (замените на свои правила шахмат)
            int move_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");
        }
    }

    return 0;
}

Почему этот вариант с fgets + sscanf идеален:

  1. Безопасность памяти: fgets(..., sizeof(input_line), stdin) никогда не прочитает больше 256 байт, что полностью исключает падение Си-программы из-за кривых данных.
  2. Четкие ограничения: Конструкция sscanf(..., "%64s %9s", ...) жестко контролирует, чтобы в массив доски записалось ровно 64 символа, а в ход — не более 9, защищая от переполнения внутренних массивов.
  3. Устойчивость к мусору: Если в поток случайно попадет пустая строка или пробелы, fgets корректно их переварит, а sscanf вернет результат отличный от 2, и программа не упадет, а просто отправит в Python ERROR.
Если вам потребуется помощь с тем, как в Си удобнее преобразовать эту 64-символьную строку в двумерный массив board[8][8] для вашего движка, дайте знать! На каком компиляторе (GCC, Clang, MSVC) вы собираете Си-код?

Формат ответа движка — это ровно 64 символа в одну строку (плюс \n в самом конце). Каждый символ в этой строке обозначает конкретную клетку шахматной доски, начиная с левого верхнего угла (клетка A8) и заканчивая правым нижним углом (клетка H1).

Карта соответствия строки и доски (8х8)

Строка длиной 64 символа мысленно нарезается движком и скриптом на 8 частей по 8 символов:
Индексы в строке:
 0..7   ->  [A8, B8, C8, D8, E8, F8, G8, H8]  (Черные фигуры)
 8..15  ->  [A7, B7, C7, D7, E7, F7, G7, H7]  (Черные пешки)
16..23  ->  [A6, B6, C6, D6, E6, F6, G6, H6]  (Пустое поле)
...
56..63  ->  [A1, B1, C1, D1, E1, F1, G1, H1]  (Белые фигуры)

Пример начальной позиции

Вот какую строку ваш Си-движок должен отправить в Python в самом начале игры (или вернуть, если он просто соглашается с ходом игрока):
rnbqkbnrpppppppp................................PPPPPPPPRNBQKBNR

Как Python-скрипт автоматически её отрисовывает

В рабочем Python-коде за это отвечает функция string_to_board(response). Вам не нужно ничего дописывать, скрипт делает это сам по следующему алгоритму:
  1. Функция берёт строку из 64 символов.
  2. С помощью формулы row * 8 + col она раскладывает её обратно в двумерную матрицу доски 8х8.
  3. В цикле отрисовки Pygame пробегается по этой матрице. Если видит точку ., то просто рисует пустую клетку (светлую или темную). Если видит букву — подставляет соответствующий код Юникода:
  • Заглавные буквы (P, R, N, B, Q, K) — отрисовываются белым цветом.
  • Строчные буквы (p, r, n, b, q, k) — отрисовываются черным цветом.

Пример: Игрок сходил e2e4, Си-движок ответил e7e5

  1. Игрок передвинул пешку. Python отправляет в Си начальную доску и ход:
    rnbqkbnrpppppppp................................PPPPPPPPRNBQKBNR e2e4
  2. Ваш Си-движок у себя в памяти передвигает белую пешку с E2 на E4, затем делает свой ответный ход (черная пешка с E7 на E5).
  3. Си-движок формирует новую строку из 64 символов, где пешки поменяли позиции, и отправляет её в printf:
    rnbqkbnrppp.pppp............p...................ReferencesPPPP.PPPRNBQKBNR\n
  4. Python мгновенно считывает эту строку, обновляет экран, и игроки видят на красивой графической доске сразу два сделанных хода.
Если вы хотите, чтобы при превращении пешки (дойдя до края доски) на экране рисовался Ферзь или другая фигура, Си-движку достаточно просто заменить символ P на Q (или p на q) на нужной позиции в этой 64-символьной строке. Python увидит новую букву и сам нарисует правильную фигуру.
Уточните, нужно ли показать пример кода на Си, который принимает индексы клетки (строку и столбец) и находит их правильное место в этой 64-символьной строке?