Узнайте, как реализовать увлекательную механику скрещивания в игре на Python: непрерывные признаки, асимметричные мутации и компактные геномы для обмена.
Большинство игр используют дискретные аллели и решётку Пеннета — предсказуемо и скучно. После десяти скрещиваний игрок видит все комбинации и теряет интерес. В этой статье я покажу альтернативу: непрерывные числовые признаки, асимметричные мутации и компактный геном, умещающийся в одну строку. Вы узнаете, как сделать механику глубокой и увлекательной.
Каждый признак — это число в заданном диапазоне, а не пара доминантных/рецессивных аллелей. Например, для пяти генов:
# Ключи генов и их диапазоны:
# оттенок (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Два ключевых приёма:
Скрещивание разных базовых культур добавляет дополнительный сдвиг оттенка — намёк на гибридную силу.
Предпросмотр показывает детерминированное среднее без шума. Игрок видит ожидаемое значение при наведении. Реальное скрещивание добавляет шум. Разрыв между предсказанием и результатом создаёт напряжение — это и есть игра.
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 — вы увидите, как механика становится глубже.
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →