ГлавнаяБлогСоздание системы скрещивания в игре: непрерывные признаки и мутации
Алгоритмы

Создание системы скрещивания в игре: непрерывные признаки и мутации

Узнайте, как реализовать увлекательную механику скрещивания в игре на Python: непрерывные признаки, асимметричные мутации и компактные геномы для обмена.

Al
Редакция Algolitalgolit.ru
9 мин чтения9 июля 2026 г.

Зачем нужна продвинутая система скрещивания?

Большинство игр используют дискретные аллели и решётку Пеннета — предсказуемо и скучно. После десяти скрещиваний игрок видит все комбинации и теряет интерес. В этой статье я покажу альтернативу: непрерывные числовые признаки, асимметричные мутации и компактный геном, умещающийся в одну строку. Вы узнаете, как сделать механику глубокой и увлекательной.

Непрерывные признаки вместо аллелей

Каждый признак — это число в заданном диапазоне, а не пара доминантных/рецессивных аллелей. Например, для пяти генов:

# Ключи генов и их диапазоны:
# оттенок (hue): -180..180 (градусы сдвига оттенка спрайта)
# урожайность (yield): 0..4 (дополнительные плоды)
# скорость (speed): 0..0.5 (доля среза времени роста)
# ценность (value): 1..3.2 (множитель цены продажи)
# выносливость (hardiness): 0..1 (выживание вне сезона)
GENE_KEYS = ['hue', 'yield', 'speed', 'value', 'hardiness']

Нет доминантности, нет аллельных пар — только «сколько». Это открывает пятимерное непрерывное пространство, и игрок выполняет градиентный спуск к нужному углу. Видимый признак — оттенок, привязанный к гену, которым можно управлять. Так числа ощущаются как биология, а не таблица.

Скрещивание: среднее родителей + управляемый шум

Скрещивание берёт среднее значение каждого гена родителей и добавляет возмущение. Вот реализация на Python:

import random, math

def clamp(value, lo, hi):
    return max(lo, min(hi, value))

def cross(itemA, itemB, rng=random.Random()):
    ga = itemA['genes']
    gb = itemB['genes']
    avg = lambda a, b: (a + b) / 2
    def mut(v, lo, hi, jitter_lo, jitter_hi, big_chance, big):
        n = v + rng.uniform(jitter_lo, jitter_hi)  # маленький дрейф
        if rng.random() < big_chance:
            n += rng.uniform(-big, big)  # редкий большой скачок
        return clamp(n, lo, hi)
    genes = {
        'hue': round(clamp(avg(ga['hue'], gb['hue']) + rng.uniform(-22, 22) +
                           (rng.uniform(-60, 60) if rng.random() < 0.18 else 0), -180, 180)),
        'yield': round(mut(avg(ga['yield'], gb['yield']), 0, 4, -0.2, 0.45, 0.15, 1), 2),
        'speed': round(mut(avg(ga['speed'], gb['speed']), 0, 0.5, -0.04, 0.07, 0.15, 0.12), 3),
        'value': round(mut(avg(ga['value'], gb['value']), 1, 3.2, -0.08, 0.22, 0.18, 0.4), 2),
        'hardiness': round(mut(avg(ga['hardiness'], gb['hardiness']), 0, 1, -0.08, 0.13, 0.12, 0.25), 2)
    }
    return genes

Два ключевых приёма:

  • Асимметричный джиттер: окно дрейфа смещено в положительную сторону (например, для yield: -0.2..0.45). Со временем популяция дрейфует вверх, вознаграждая терпеливого игрока. Если центрировать на нуле, селекция превращается в случайное блуждание.
  • Два масштаба мутации: маленький постоянный дрейф + редкий большой скачок (вероятность 0.15-0.18). Дрейф даёт предсказуемость, скачок — лотерейный билет, который заставляет скрещивать «ещё разок».

Скрещивание разных базовых культур добавляет дополнительный сдвиг оттенка — намёк на гибридную силу.

Предпросмотр и результат: разрыв как игровая механика

Предпросмотр показывает детерминированное среднее без шума. Игрок видит ожидаемое значение при наведении. Реальное скрещивание добавляет шум. Разрыв между предсказанием и результатом создаёт напряжение — это и есть игра.

def preview_cross(itemA, itemB):
    ga = itemA['genes']
    gb = itemB['genes']
    avg = lambda a, b: (a + b) / 2
    genes = {
        'hue': round(avg(ga['hue'], gb['hue'])),
        'yield': round(avg(ga['yield'], gb['yield']), 2),
        'speed': round(avg(ga['speed'], gb['speed']), 3),
        'value': round(avg(ga['value'], gb['value']), 2),
        'hardiness': round(avg(ga['hardiness'], gb['hardiness']), 2)
    }
    return genes  # чистое среднее, без шума

Геном, умещающийся в сообщение чата

Каждый штамм кодируется в компактную строку base-36 без сервера. Пять чисел с плавающей точкой и индекс базовой культуры упаковываются:

def encode(strain):
    g = strain['genes']
    bi = CROPS.index(strain['base'])
    parts = [
        bi,
        round(g['hue']) + 360,        # сдвиг отрицательных
        round(g['yield'] * 10),        # фиксированная точка: 1 десятичный знак
        round(g['speed'] * 1000),      # 3 знака
        round(g['value'] * 100),       # 2 знака
        round(g['hardiness'] * 100)
    ]
    return 'GRN1-' + '.'.join(format(max(0, n), 'x') for n in parts)

Декодирование обращает процесс и повторно зажимает значения (строка пришла извне):

def decode(code):
    if not code or not code.startswith('GRN1-'):
        return None
    parts = code[5:].split('.')
    if len(parts) != 6:
        return None
    ints = [int(p, 36) for p in parts]
    if any(math.isnan(i) for i in ints):
        return None
    base_idx = ints[0]
    if base_idx < 0 or base_idx >= len(CROPS):
        return None
    base = CROPS[base_idx]
    genes = {
        'hue': clamp(ints[1] - 360, -180, 180),
        'yield': ints[2] / 10,
        'speed': ints[3] / 1000,
        'value': ints[4] / 100,
        'hardiness': ints[5] / 100
    }
    return {'base': base, 'genes': genes}

Пример: штамм (base 3, hue +42, yield 1.2, speed 0.12, value 1.5, hardiness 0.3) кодируется как GRN1-3.b6.c.3c.46.u. Достаточно коротко для комментария — без базы данных и аккаунтов.

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

Если вы добавляете скрещивание или наследование в игру, используйте эти принципы:

  • Непрерывные признаки вместо дискретных аллелей — пространство больше, градиенты ощущаются осознанно.
  • Сместите окно мутации в положительную сторону — терпение вознаграждается без жёсткого трека опыта.
  • Разделите мутацию на малую частую и большую редкую — стабильность и джекпоты.
  • Показывайте детерминированное среднее, применяйте шум при коммите — разрыв создаёт веселье.
  • Версионируйте коды обмена и перепроверяйте при декодировании.

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

#скрещивание#генетические алгоритмы#игровая механика#мутации#кодирование
Al
Редакция Algolit

Пишем про алгоритмы, подготовку к собеседованиям и карьеру в IT — так, чтобы было понятно и полезно.

Хочешь закрепить знания на практике?

Решай задачи на Algolit — интерактивная платформа для обучения

Начать бесплатно →