23
0
0
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники
Назад

Как создать FEN-to-Image Converter на Java: от шахматных фигур до красивых досок

Время чтения 23 минуты
Нет времени читать?
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники
23
0
0
Нет времени читать?
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники

Привет. Меня зовут Николай Пискунов, я руководитель направления Big Data и эксперт курса Cloud DevSecOps по безопасной разработке от Академии вАЙТИ. Недавно я работал над шахматным ботом для игры по переписке в Телеграм. Одна из ключевых задач — генерация изображений шахматной доски из FEN-нотации.

FEN (Forsyth-Edwards Notation) — текстовый формат записи шахматной позиции. Пример:

rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1

Пользователь хочет видеть красивое изображение, а не просто текст. Но готовых решений, которые удовлетворяли бы требованиям, не нашлось:

  1. Производительность — генерация должна быть быстрой.
  2. Кастомизация — нужны разные виды доски (для белых/черных).
  3. Подсветка — выделение последнего хода.
  4. Кеширование — чтобы не генерировать одно и то же изображение дважды.
Как создать FEN-to-Image Converter на Java: от шахматных фигур до красивых досок

Архитектура решения

Мое решение `FenToImageConverter` — сервис на Java, который:

  • принимает FEN-строку и параметры отображения;
  • генерирует картинку шахматной доски;
  • поддерживает кеширование;
  • возвращает изображение в формате PNG.

@Service
public class FenToImageConverter {
            
             public BufferedImage convertFenToImage(
            String fen,
            boolean whiteView,
            String[] highlightedSquares
             ) {
            // Основная логика конвертации
             }
}

 

Техническая реализация

1. Парсинг FEN.

Первая задача — корректно распарсить FEN-строку:


public class FenValidator {
             // Регулярное выражение для валидации FEN
             private static final Pattern FEN_PATTERN = Pattern.compile(
            "^([rnbqkpRNBQKP1-8]+/){7}[rnbqkpRNBQKP1-8]+\s[wb]\s([KQkq]+|-)\s([a-h][36]|-)\s\d+\s\d+$"
             );
            
             public boolean isValidFen(String fen) {
            // Проверка структуры FEN
            // Валидация количества фигур
            // Проверка корректности позиции
            }
}

Нужно учесть, что FEN может быть некорректным. Цифры надо обрабатывать как пустые клетки и проверять наличие обоих королей.

2. Отрисовка доски.

Самая интересная часть — рендеринг:


private static void drawBoard(Graphics2D g2d) {

            for (int row = 0; row < 8; row++) {
            for (int col = 0; col < 8; col++) {
            int x = BORDER_SIZE + col * SQUARE_SIZE;
            int y = BORDER_SIZE + row * SQUARE_SIZE;
           
            // Чередование цветов клеток
            boolean isLight = (row + col) % 2 == 0;
            g2d.setColor(isLight ? LIGHT_SQUARE : DARK_SQUARE);
            g2d.fillRect(x, y, SQUARE_SIZE, SQUARE_SIZE);
            }
            }
}

 

Цвета клеток:


private static final Color LIGHT_SQUARE = new Color(240, 217, 181);  // Светлая клетка
private static final Color DARK_SQUARE = new Color(181, 136, 99);  // Темная клетка
private static final Color HIGHLIGHT_COLOR = new Color(255, 255, 0, 50); // Подсветка

 

3. Размещение фигур.

Фигуры отображаются с помощью Unicode-символов:


private static String getPieceSymbol(char piece) {
             return switch (piece) {
            case 'K' -> "";  // Белый король
            case 'Q' -> "";  // Белый ферзь
            case 'R' -> "";  // Белая ладья
            case 'B' -> "";  // Белый слон
            case 'N' -> "";  // Белый конь
            case 'P' -> "";  // Белая пешка
            case 'k' -> "";  // Черный король
            case 'q' -> "";  // Черный ферзь
            case 'r' -> "";  // Черная ладья
            case 'b' -> "";  // Черный слон
            case 'n' -> "";  // Черный конь
            case 'p' -> "";  // Черная пешка
            default -> String.valueOf(piece);
            };
}

 

Есть проблема со шрифтами: не все они поддерживают Unicode-символы шахматных фигур. Вот как решить эту проблему:


private static Font getPieceFont() {
             String[] preferredFonts = {
            "Segoe UI Symbol",
            "Arial Unicode MS",
            "DejaVu Sans",
            "Arial"
             };
            
             for (String fontName : preferredFonts) {
            Font font = new Font(fontName, Font.PLAIN, PIECE_FONT_SIZE);
            if (font.getFamily().equals(fontName)) {
            return font;
            }
             }
             return new Font(Font.SANS_SERIF, Font.PLAIN, PIECE_FONT_SIZE);
}

 

4. Поддержка разных видов доски.

Пользователь может смотреть доску за белых или за черных:


// Если whiteView = false, переворачиваем доску
if (!whiteView) {
             // Переворачиваем вертикально
             List<String> revRows = new ArrayList<>(Arrays.asList(rows));
             Collections.reverse(revRows);
            
             // Переворачиваем горизонтально
             revRows.replaceAll(FenToImageConverter::reverseRow);
             rows = revRows.toArray(new String[0]);
}
private static String reverseRow(String row) {
             // Преобразование строки FEN в обратном порядке
             // Пример: "r1bqkbnr" → "rnbkq1br"
}

 

5. Подсветка клеток.

Для наглядности подсвечиваем клетки последнего хода:


if (highlightedSquares != null && highlightedSquares.length > 0) {
             g2d.setColor(HIGHLIGHT_COLOR);
             for (String sq : highlightedSquares) {
            // Конвертация шахматной нотации в координаты
            int col = sq.charAt(0) - 'a';
            int row = 8 - Character.getNumericValue(sq.charAt(1));
           
            // Корректировка координат для черного вида
            if (!whiteView) {
            col = 7 - col;
            row = 7 - row;
            }
            int x = BORDER_SIZE + col * SQUARE_SIZE;
            int y = BORDER_SIZE + row * SQUARE_SIZE;
            g2d.fillRect(x, y, SQUARE_SIZE, SQUARE_SIZE);
            }
}

 

6. Кеширование изображений.

Чтобы не перерисовывать одно и то же изображение многократно, делаем кеширование:


@Configuration
@EnableCaching
public class CacheConfig {
             @Bean
             public CacheManager cacheManager() {
            return new ConcurrentMapCacheManager("boardImages");
            }
}

@Service
public class FenToImageConverter {
             @Cacheable(
            value = "boardImages",
            key = "#fen + #whiteView + (#highlightedSquares != null ? Arrays.toString(#highlightedSquares) : '')"
             )
             public BufferedImage convertFenToImage(
            String fen,
            boolean whiteView,
            String[] highlightedSquares
             ) {
            // Генерация изображения
            }
}

 

Ключ кеша включает все параметры, влияющие на результат:

  • FEN-строка,
  • вид доски (whiteView),
  • подсвеченные клетки.

Производительность

Я протестировал скорость генерации изображений и получил такие результаты:

Оптимизации: 

  • Кеширование — основной прирост производительности.
  • Reusable Graphics2D — переиспользование объектов.
  • Font caching — кеширование шрифтов.
  • Color caching — кеширование объектов Color.

Результат

Пример сгенерированного изображения для FEN:

r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3

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

Для такого компонента критически важны тесты:


@Test
public void testValidFenConversion() {
             String fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
            
             BufferedImage image = converter.convertFenToImage(fen, true, null);
            
             assertNotNull(image);
             assertEquals(TOTAL_SIZE, image.getWidth());
             assertEquals(TOTAL_SIZE, image.getHeight());
}
@Test
public void testInvalidFenThrowsException() {
             String invalidFen = "invalid fen string";
            
             assertThrows(IllegalArgumentException.class, () -> {
        converter.convertFenToImage(invalidFen, true, null);
             });
}

 

Проблемы и решения

Для удобства и наглядности я составил таблицу возможных проблем и способов с ними справиться.

Альтернативные подходы

  • SVG-генерация — сложнее, но масштабируется лучше.
  • HTML/CSS рендеринг — требует браузера.
  • Готовые библиотеки — недостаточная кастомизация.
  • WebGL/Canvas — избыточно для задачи.

В таблице — сравнение моего решения с альтернативами:

Заключение

Создание FEN-to-Image оказалось интересной задачей на стыке:

  • парсинга текстовых форматов,
  • компьютерной графики,
  • оптимизации производительности,
  • кеширования.

Получился высокопроизводительный компонент, который генерирует красивые изображения шахматных досок за 15–25 мс.

Полезные ссылки:

  1. Стандарт FEN.
  2. Unicode шахматные символы.
  3. Java2D Graphics Tutorial.
  4. Spring Cache Abstraction.

Комментарии0
Тоже интересно
Комментировать
Поделиться
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники