Разработка AI-системы персонализации игрового опыта

Проектируем и внедряем системы искусственного интеллекта: от прототипа до production-ready решения. Наша команда объединяет экспертизу в машинном обучении, дата-инжиниринге и MLOps, чтобы AI работал не в лаборатории, а в реальном бизнесе.
Показано 1 из 1 услугВсе 1566 услуг
Разработка AI-системы персонализации игрового опыта
Средняя
~1-2 недели
Часто задаваемые вопросы
Направления AI-разработки
Этапы разработки AI-решения
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1240
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1167
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    867
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1084
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    563
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    829

AI-система персонализации игрового опыта

Персонализация в играх — это не только рекомендации предметов. AI адаптирует сложность в реальном времени, генерирует нарратив под стиль игрока, подбирает соперников одного уровня и создаёт уникальные события для каждого пользователя. Цель: максимизировать time-in-game и lifetime value.

Динамическая адаптация сложности (DDA)

import numpy as np
from collections import deque
from dataclasses import dataclass
from typing import Optional

@dataclass
class GameSession:
    player_id: str
    skill_level: float       # 0-1
    current_difficulty: float  # 0-1
    recent_outcomes: deque    # True=win, False=lose
    frustration_score: float
    boredom_score: float

class DynamicDifficultyAdjuster:
    """
    Flow theory: игрок в состоянии потока между скукой и фрустрацией.
    Target win rate: 65-75% для оптимального engagement.
    """

    TARGET_WIN_RATE = 0.70
    ADJUSTMENT_SPEED = 0.05  # Изменение сложности за шаг
    WINDOW_SIZE = 10          # Последние N исходов для оценки

    def update_difficulty(self, session: GameSession,
                           last_outcome: bool,
                           time_to_complete_seconds: float,
                           health_remaining_pct: float = 1.0) -> float:
        """Обновление сложности после каждого энкаунтера"""
        session.recent_outcomes.append(last_outcome)

        if len(session.recent_outcomes) < 3:
            return session.current_difficulty

        recent_win_rate = sum(session.recent_outcomes) / len(session.recent_outcomes)

        # Фрустрация: серия поражений + низкое здоровье
        if not last_outcome and health_remaining_pct < 0.1:
            session.frustration_score = min(1.0, session.frustration_score + 0.2)
        else:
            session.frustration_score = max(0.0, session.frustration_score - 0.05)

        # Скука: слишком быстрое прохождение + высокое здоровье
        if last_outcome and time_to_complete_seconds < 30 and health_remaining_pct > 0.8:
            session.boredom_score = min(1.0, session.boredom_score + 0.15)
        else:
            session.boredom_score = max(0.0, session.boredom_score - 0.05)

        # Корректировка сложности
        new_difficulty = session.current_difficulty

        if session.frustration_score > 0.6:
            new_difficulty -= self.ADJUSTMENT_SPEED * 1.5  # Быстрее снижаем
        elif session.boredom_score > 0.6:
            new_difficulty += self.ADJUSTMENT_SPEED * 1.5  # Быстрее повышаем
        elif recent_win_rate > self.TARGET_WIN_RATE + 0.1:
            new_difficulty += self.ADJUSTMENT_SPEED
        elif recent_win_rate < self.TARGET_WIN_RATE - 0.1:
            new_difficulty -= self.ADJUSTMENT_SPEED

        session.current_difficulty = float(np.clip(new_difficulty, 0.1, 1.0))
        return session.current_difficulty

    def scale_enemy_parameters(self, base_enemy: dict,
                                difficulty: float) -> dict:
        """Масштабирование параметров врага под сложность"""
        scale_factor = 0.5 + difficulty * 1.0  # 0.1 → 0.6x, 1.0 → 1.5x

        return {
            'hp': int(base_enemy['hp'] * scale_factor),
            'damage': round(base_enemy['damage'] * scale_factor, 2),
            'speed': round(base_enemy['speed'] * (0.8 + difficulty * 0.4), 2),
            'accuracy': min(0.95, base_enemy['accuracy'] * scale_factor),
            'ai_reaction_ms': int(base_enemy['ai_reaction_ms'] / scale_factor),
            'loot_bonus_pct': int(difficulty * 50)  # Больше наград за высокую сложность
        }


class MatchmakingSystem:
    """Система подбора соперников и команд"""

    def __init__(self):
        self.rating_k_factor = 32  # Elo K-factor

    def compute_elo_rating(self, player_rating: float,
                            opponent_rating: float,
                            outcome: float) -> float:
        """Обновление Elo рейтинга"""
        expected = 1 / (1 + 10 ** ((opponent_rating - player_rating) / 400))
        return player_rating + self.rating_k_factor * (outcome - expected)

    def find_match(self, player_id: str,
                    player_rating: float,
                    player_latency_ms: int,
                    available_players: list[dict],
                    max_wait_seconds: int = 60) -> Optional[list[dict]]:
        """
        Балансировка: рейтинг + пинг + время ожидания.
        Расширяем коридор рейтинга с течением времени ожидания.
        """
        wait_factor = min(max_wait_seconds, 30) / 30  # 0-1
        rating_window = 100 + wait_factor * 200  # 100-300 Elo

        candidates = []
        for p in available_players:
            if p['player_id'] == player_id:
                continue

            rating_diff = abs(p['rating'] - player_rating)
            if rating_diff > rating_window:
                continue

            # Пинг: предпочтительно < 80ms разница
            latency_diff = abs(p.get('latency_ms', 50) - player_latency_ms)
            latency_score = max(0, 1.0 - latency_diff / 150)

            rating_score = 1.0 - rating_diff / rating_window
            combined_score = rating_score * 0.7 + latency_score * 0.3

            candidates.append({**p, 'match_score': combined_score})

        if not candidates:
            return None

        # Возвращаем лучшего кандидата
        return sorted(candidates, key=lambda x: -x['match_score'])[:1]


class PersonalizedEventGenerator:
    """Генерация персональных внутриигровых событий"""

    PLAYER_MOTIVATIONS = {
        'explorer': ['discovery_event', 'hidden_area_unlock', 'lore_reveal'],
        'achiever': ['challenge_milestone', 'rare_achievement', 'collection_complete'],
        'socializer': ['guild_event', 'co-op_mission', 'friend_challenge'],
        'competitor': ['ranked_event', 'leaderboard_challenge', 'pvp_tournament'],
    }

    def generate_personal_event(self, player: dict,
                                  days_since_last_event: int,
                                  player_motivation: str) -> dict:
        """Персональное событие под мотивационный профиль"""
        # Частота: максимум 1 событие в 3 дня
        if days_since_last_event < 3:
            return {'event': None, 'reason': 'Too soon since last event'}

        event_types = self.PLAYER_MOTIVATIONS.get(
            player_motivation, self.PLAYER_MOTIVATIONS['achiever']
        )
        event_type = np.random.choice(event_types)

        # Параметры события от профиля игрока
        event_config = self._configure_event(event_type, player)

        return {
            'event_type': event_type,
            'config': event_config,
            'expires_in_hours': 48,
            'notification_channel': 'push_and_in_game',
            'reward_scale': min(2.0, 1.0 + player.get('days_active', 0) / 100)
        }

    def _configure_event(self, event_type: str, player: dict) -> dict:
        configs = {
            'challenge_milestone': {
                'target': player.get('current_level', 1) * 10,
                'reward': 'unique_badge',
                'difficulty': 'hard'
            },
            'pvp_tournament': {
                'bracket': 'skill_based',
                'rounds': 3,
                'prize_pool': '1000 gems'
            },
            'hidden_area_unlock': {
                'area_tier': min(5, player.get('current_level', 1) // 10 + 1),
                'hint_provided': True
            }
        }
        return configs.get(event_type, {'type': event_type})

DDA в крупных мобильных играх (данные Supercell, King) показывает: оптимальный win rate 65-75% максимизирует D7 retention. При win rate ниже 55% — frustation churn, выше 85% — boredom churn. Персонализированные события увеличивают DAU/MAU ratio на 8-15% при корректной сегментации мотиваций.