# ТЗ: Типы игр "Прохождение" и "Челленджи"

## Описание задачи

Добавить систему типов для игр, которая определяет логику выпадения заданий при спине колеса.

### Два типа игр:

| Тип | Название | Поведение при выпадении |
|-----|----------|------------------------|
| `playthrough` | Прохождение | Основное задание — пройти игру. Челленджи становятся **дополнительными** заданиями |
| `challenges` | Челленджи | Выдаётся **случайный челлендж** из списка челленджей игры (текущее поведение) |

---

## Детальное описание логики

### Тип "Прохождение" (`playthrough`)

**При создании игры** с типом "Прохождение" указываются дополнительные поля:
- **Очки за прохождение** (`playthrough_points`) — количество очков за прохождение игры
- **Описание прохождения** (`playthrough_description`) — описание задания (например: "Пройти основной сюжет игры")
- **Тип пруфа** (`playthrough_proof_type`) — screenshot / video / steam
- **Подсказка для пруфа** (`playthrough_proof_hint`) — опционально (например: "Скриншот финальных титров")

**При выпадении игры** с типом "Прохождение":

1. **Основное задание**: Пройти игру (очки и описание берутся из полей игры)
2. **Дополнительные задания**: Все челленджи игры становятся **опциональными** бонусными заданиями
3. **Пруфы**:
   - Требуется **отдельный пруф на прохождение** игры (тип из `playthrough_proof_type`)
   - Для каждого бонусного челленджа **тоже требуется пруф** (по типу челленджа)
   - **Прикрепление файла не обязательно** — можно отправить только комментарий со ссылкой на видео
4. **Система очков**:
   - За основное прохождение — `playthrough_points` (указанные при создании)
   - За каждый выполненный доп. челлендж — очки челленджа
5. **Завершение**: Задание считается выполненным после прохождения основной игры. Доп. челленджи **не обязательны** — можно выполнять параллельно или игнорировать

### Тип "Челленджи" (`challenges`)

При выпадении игры с типом "Челленджи":

1. Выбирается **один случайный челлендж** из списка челленджей игры
2. Участник выполняет только этот челлендж
3. Логика остаётся **без изменений** (текущее поведение системы)

---

### Фильтрация игр при спине

При выборе игры для спина необходимо исключать уже пройденные/дропнутые игры:

| Тип игры | Условие исключения из спина |
|----------|----------------------------|
| `playthrough` | Игра **исключается**, если участник **завершил ИЛИ дропнул** прохождение этой игры |
| `challenges` | Игра **исключается**, только если участник выполнил **все** челленджи этой игры |

**Логика:**
```
Для каждой игры в марафоне:
  ЕСЛИ game_type == "playthrough":
    Проверить: есть ли Assignment с is_playthrough=True для этой игры
              со статусом COMPLETED или DROPPED?
    Если да → исключить игру

  ЕСЛИ game_type == "challenges":
    Получить все челленджи игры
    Получить все завершённые Assignment участника для этих челленджей
    Если количество завершённых == количество челленджей → исключить игру
```

**Важно:** Если все игры исключены (всё пройдено), спин должен вернуть ошибку или специальный статус "Все игры пройдены!"

### Бонусные челленджи

Бонусные челленджи доступны **только пока основное задание активно**:
- После **завершения** прохождения — бонусные челленджи недоступны
- После **дропа** прохождения — бонусные челленджи недоступны
- Нельзя вернуться к бонусным челленджам позже

### Взаимодействие с событиями

**Все события игнорируются** при выпадении игры с типом `playthrough`:

| Событие | Поведение для `playthrough` |
|---------|----------------------------|
| **JACKPOT** (x3 за hard) | Игнорируется |
| **GAME_CHOICE** (выбор из 3) | Игнорируется |
| **GOLDEN_HOUR** (x1.5) | Игнорируется |
| **DOUBLE_RISK** (x0.5, бесплатный дроп) | Игнорируется |
| **COMMON_ENEMY** | Игнорируется |
| **SWAP** | Игнорируется |

Игрок получает стандартные очки `playthrough_points` без модификаторов.

---

## Изменения в Backend

### 1. Модель Game (`backend/app/models/game.py`)

Добавить поля для типа игры и прохождения:

```python
class GameType(str, Enum):
    PLAYTHROUGH = "playthrough"  # Прохождение
    CHALLENGES = "challenges"    # Челленджи

class Game(Base):
    # ... существующие поля ...

    # Тип игры
    game_type: Mapped[str] = mapped_column(
        String(20),
        default=GameType.CHALLENGES.value,
        nullable=False
    )

    # Поля для типа "Прохождение" (nullable, заполняются только для playthrough)
    playthrough_points: Mapped[int | None] = mapped_column(
        Integer,
        nullable=True
    )
    playthrough_description: Mapped[str | None] = mapped_column(
        Text,
        nullable=True
    )
    playthrough_proof_type: Mapped[str | None] = mapped_column(
        String(20),  # screenshot, video, steam
        nullable=True
    )
    playthrough_proof_hint: Mapped[str | None] = mapped_column(
        Text,
        nullable=True
    )
```

### 2. Схемы Pydantic (`backend/app/schemas/`)

Обновить схемы для Game:

```python
# schemas/game.py
class GameType(str, Enum):
    PLAYTHROUGH = "playthrough"
    CHALLENGES = "challenges"

class GameCreate(BaseModel):
    # ... существующие поля ...
    game_type: GameType = GameType.CHALLENGES

    # Поля для типа "Прохождение"
    playthrough_points: int | None = None
    playthrough_description: str | None = None
    playthrough_proof_type: ProofType | None = None
    playthrough_proof_hint: str | None = None

    @model_validator(mode='after')
    def validate_playthrough_fields(self) -> Self:
        if self.game_type == GameType.PLAYTHROUGH:
            if self.playthrough_points is None:
                raise ValueError('playthrough_points обязателен для типа "Прохождение"')
            if self.playthrough_description is None:
                raise ValueError('playthrough_description обязателен для типа "Прохождение"')
            if self.playthrough_proof_type is None:
                raise ValueError('playthrough_proof_type обязателен для типа "Прохождение"')
            if self.playthrough_points < 1 or self.playthrough_points > 500:
                raise ValueError('playthrough_points должен быть от 1 до 500')
        return self

class GameResponse(BaseModel):
    # ... существующие поля ...
    game_type: GameType
    playthrough_points: int | None
    playthrough_description: str | None
    playthrough_proof_type: ProofType | None
    playthrough_proof_hint: str | None

class GameUpdate(BaseModel):
    """Схема для редактирования игры"""
    title: str | None = None
    download_url: str | None = None
    genre: str | None = None
    game_type: GameType | None = None
    playthrough_points: int | None = None
    playthrough_description: str | None = None
    playthrough_proof_type: ProofType | None = None
    playthrough_proof_hint: str | None = None

    @model_validator(mode='after')
    def validate_playthrough_fields(self) -> Self:
        # Валидация только если меняем на playthrough
        if self.game_type == GameType.PLAYTHROUGH:
            if self.playthrough_points is not None:
                if self.playthrough_points < 1 or self.playthrough_points > 500:
                    raise ValueError('playthrough_points должен быть от 1 до 500')
        return self
```

### 3. Миграция Alembic

```python
# Новая миграция
def upgrade():
    # Тип игры
    op.add_column('games', sa.Column(
        'game_type',
        sa.String(20),
        nullable=False,
        server_default='challenges'
    ))

    # Поля для прохождения
    op.add_column('games', sa.Column(
        'playthrough_points',
        sa.Integer(),
        nullable=True
    ))
    op.add_column('games', sa.Column(
        'playthrough_description',
        sa.Text(),
        nullable=True
    ))
    op.add_column('games', sa.Column(
        'playthrough_proof_type',
        sa.String(20),
        nullable=True
    ))
    op.add_column('games', sa.Column(
        'playthrough_proof_hint',
        sa.Text(),
        nullable=True
    ))

def downgrade():
    op.drop_column('games', 'playthrough_proof_hint')
    op.drop_column('games', 'playthrough_proof_type')
    op.drop_column('games', 'playthrough_description')
    op.drop_column('games', 'playthrough_points')
    op.drop_column('games', 'game_type')
```

### 4. Логика спина (`backend/app/api/v1/wheel.py`)

Изменить функцию `spin_wheel`:

```python
async def get_available_games(
    participant: Participant,
    marathon_games: list[Game],
    db: AsyncSession
) -> list[Game]:
    """Получить список игр, доступных для спина"""
    available = []

    for game in marathon_games:
        if game.game_type == GameType.PLAYTHROUGH.value:
            # Проверяем, прошёл ли участник эту игру
            # Исключаем если COMPLETED или DROPPED
            finished = await db.scalar(
                select(Assignment)
                .where(
                    Assignment.participant_id == participant.id,
                    Assignment.game_id == game.id,
                    Assignment.is_playthrough == True,
                    Assignment.status.in_([
                        AssignmentStatus.COMPLETED.value,
                        AssignmentStatus.DROPPED.value
                    ])
                )
            )
            if not finished:
                available.append(game)

        else:  # GameType.CHALLENGES
            # Проверяем, остались ли невыполненные челленджи
            completed_challenge_ids = await db.scalars(
                select(Assignment.challenge_id)
                .where(
                    Assignment.participant_id == participant.id,
                    Assignment.challenge_id.in_([c.id for c in game.challenges]),
                    Assignment.status == AssignmentStatus.COMPLETED.value
                )
            )
            completed_ids = set(completed_challenge_ids.all())
            all_challenge_ids = {c.id for c in game.challenges}

            if completed_ids != all_challenge_ids:
                available.append(game)

    return available


async def spin_wheel(...):
    # Получаем доступные игры (исключаем пройденные)
    available_games = await get_available_games(participant, marathon_games, db)

    if not available_games:
        raise HTTPException(
            status_code=400,
            detail="Все игры пройдены! Поздравляем!"
        )

    game = random.choice(available_games)

    if game.game_type == GameType.PLAYTHROUGH.value:
        # Для playthrough НЕ выбираем челлендж — основное задание это прохождение
        # Данные берутся из полей игры: playthrough_points, playthrough_description
        challenge = None  # Или создаём виртуальный объект

        # Все челленджи игры становятся дополнительными
        bonus_challenges = list(game.challenges)

        # Создаём Assignment с флагом is_playthrough=True
        assignment = Assignment(
            participant_id=participant.id,
            challenge_id=None,  # Нет привязки к челленджу
            game_id=game.id,    # Новое поле — привязка к игре
            is_playthrough=True,
            status=AssignmentStatus.ACTIVE,
            # ...
        )

    else:  # GameType.CHALLENGES
        # Выбираем случайный НЕВЫПОЛНЕННЫЙ челлендж
        completed_challenge_ids = await db.scalars(
            select(Assignment.challenge_id)
            .where(
                Assignment.participant_id == participant.id,
                Assignment.challenge_id.in_([c.id for c in game.challenges]),
                Assignment.status == AssignmentStatus.COMPLETED.value
            )
        )
        completed_ids = set(completed_challenge_ids.all())

        available_challenges = [c for c in game.challenges if c.id not in completed_ids]
        challenge = random.choice(available_challenges)
        bonus_challenges = []

        assignment = Assignment(
            participant_id=participant.id,
            challenge_id=challenge.id,
            is_playthrough=False,
            status=AssignmentStatus.ACTIVE,
            # ...
        )

    # ... сохранение Assignment ...
```

### 5. Модель Assignment (`backend/app/models/assignment.py`)

Обновить модель для поддержки прохождений:

```python
class Assignment(Base):
    # ... существующие поля ...

    # Для прохождений: привязка к игре вместо челленджа
    game_id: Mapped[int | None] = mapped_column(
        ForeignKey("games.id"),
        nullable=True
    )
    is_playthrough: Mapped[bool] = mapped_column(Boolean, default=False)

    # Relationships
    game: Mapped["Game"] = relationship(back_populates="playthrough_assignments")

# Отдельная таблица для бонусных челленджей
class BonusAssignment(Base):
    __tablename__ = "bonus_assignments"

    id: Mapped[int] = mapped_column(primary_key=True)
    main_assignment_id: Mapped[int] = mapped_column(ForeignKey("assignments.id"))
    challenge_id: Mapped[int] = mapped_column(ForeignKey("challenges.id"))
    status: Mapped[str] = mapped_column(String(20), default="pending")  # pending, completed
    proof_path: Mapped[str | None] = mapped_column(Text, nullable=True)
    proof_url: Mapped[str | None] = mapped_column(Text, nullable=True)
    completed_at: Mapped[datetime | None] = mapped_column(nullable=True)
    points_earned: Mapped[int] = mapped_column(Integer, default=0)

    # Relationships
    main_assignment: Mapped["Assignment"] = relationship(back_populates="bonus_assignments")
    challenge: Mapped["Challenge"] = relationship()
```

### 6. API эндпоинты

Добавить/обновить эндпоинты:

```python
# Обновить ответ спина
class PlaythroughInfo(BaseModel):
    """Информация о прохождении (для playthrough игр)"""
    description: str
    points: int

class SpinResult(BaseModel):
    assignment_id: int
    game: GameResponse
    challenge: ChallengeResponse | None  # None для playthrough
    is_playthrough: bool
    playthrough_info: PlaythroughInfo | None  # Заполняется для playthrough
    bonus_challenges: list[ChallengeResponse] = []  # Для playthrough
    can_drop: bool
    drop_penalty: int

# Завершение бонусного челленджа
@router.post("/assignments/{assignment_id}/bonus/{challenge_id}/complete")
async def complete_bonus_challenge(
    assignment_id: int,
    challenge_id: int,
    proof: ProofData,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user)
) -> BonusAssignmentResponse:
    """Завершить дополнительный челлендж для игры-прохождения"""
    ...

# Получение бонусных челленджей
@router.get("/assignments/{assignment_id}/bonus")
async def get_bonus_assignments(
    assignment_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user)
) -> list[BonusAssignmentResponse]:
    """Получить список бонусных челленджей и их статус"""
    ...

# Получение количества доступных игр для спина
@router.get("/marathons/{marathon_id}/available-games-count")
async def get_available_games_count(
    marathon_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user)
) -> dict:
    """
    Получить количество игр, доступных для спина.
    Возвращает: { "available": 5, "total": 10 }
    """
    participant = await get_participant(...)
    marathon_games = await get_marathon_games(...)
    available = await get_available_games(participant, marathon_games, db)

    return {
        "available": len(available),
        "total": len(marathon_games)
    }

# Редактирование игры
@router.patch("/marathons/{marathon_id}/games/{game_id}")
async def update_game(
    marathon_id: int,
    game_id: int,
    game_data: GameUpdate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user)
) -> GameResponse:
    """
    Редактировать игру.

    Доступно только организатору марафона.
    При смене типа на 'playthrough' необходимо указать playthrough_points и playthrough_description.
    """
    # Проверка прав (организатор)
    # Валидация: если меняем тип на playthrough, проверить что поля заполнены
    # Обновление полей
    ...
```

---

## Изменения в Frontend

### 1. Типы (`frontend/src/types/index.ts`)

```typescript
export type GameType = 'playthrough' | 'challenges'

export interface Game {
  // ... существующие поля ...
  game_type: GameType
  playthrough_points: number | null
  playthrough_description: string | null
}

export interface PlaythroughInfo {
  description: string
  points: number
}

export interface SpinResult {
  assignment_id: number
  game: Game
  challenge: Challenge | null  // null для playthrough
  is_playthrough: boolean
  playthrough_info: PlaythroughInfo | null
  bonus_challenges: Challenge[]
  can_drop: boolean
  drop_penalty: number
}

export interface BonusAssignment {
  id: number
  challenge: Challenge
  status: 'pending' | 'completed'
  proof_url: string | null
  completed_at: string | null
  points_earned: number
}

export interface GameUpdate {
  title?: string
  download_url?: string
  genre?: string
  game_type?: GameType
  playthrough_points?: number
  playthrough_description?: string
}
```

### 2. Форма добавления игры

Добавить выбор типа игры и условные поля:

```tsx
// components/AddGameForm.tsx
const [gameType, setGameType] = useState<GameType>('challenges')
const [playthroughPoints, setPlaythroughPoints] = useState<number>(100)
const [playthroughDescription, setPlaythroughDescription] = useState<string>('')

return (
  <form>
    {/* ... существующие поля ... */}

    <Select
      label="Тип игры"
      value={gameType}
      onChange={setGameType}
      options={[
        { value: 'challenges', label: 'Челленджи' },
        { value: 'playthrough', label: 'Прохождение' }
      ]}
    />

    {/* Поля только для типа "Прохождение" */}
    {gameType === 'playthrough' && (
      <>
        <Input
          type="number"
          label="Очки за прохождение"
          value={playthroughPoints}
          onChange={setPlaythroughPoints}
          min={1}
          max={500}
          required
        />
        <Textarea
          label="Описание прохождения"
          value={playthroughDescription}
          onChange={setPlaythroughDescription}
          placeholder="Например: Пройти основной сюжет игры"
          required
        />
      </>
    )}
  </form>
)
```

### 3. Отображение результата спина

Для типа "Прохождение" показывать:
- Основное задание с описанием из `playthrough_info`
- Очки за прохождение
- Список дополнительных челленджей (опциональные)

```tsx
// components/SpinResult.tsx
{result.is_playthrough ? (
  <PlaythroughCard
    game={result.game}
    info={result.playthrough_info}
    bonusChallenges={result.bonus_challenges}
  />
) : (
  <ChallengeCard challenge={result.challenge} />
)}
```

### 4. Карточка текущего задания

Для playthrough показывать прогресс по доп. челленджам:

```tsx
// components/CurrentAssignment.tsx
{assignment.is_playthrough && (
  <div className="mt-4">
    <h4>Дополнительные задания (опционально)</h4>
    <BonusChallengesList
      assignmentId={assignment.id}
      challenges={assignment.bonus_challenges}
      onComplete={handleBonusComplete}
    />
    <p className="text-sm text-gray-500">
      Выполнено: {completedCount} / {totalCount} (+{bonusPoints} очков)
    </p>
  </div>
)}
```

### 5. Форма завершения бонусного челленджа

```tsx
// components/BonusChallengeCompleteModal.tsx
<Modal>
  <h3>Завершить челлендж: {challenge.title}</h3>
  <p>{challenge.description}</p>
  <p>Очки: +{challenge.points}</p>

  <ProofUpload
    proofType={challenge.proof_type}
    onUpload={handleProofUpload}
  />

  <Button onClick={handleComplete}>
    Завершить (+{challenge.points} очков)
  </Button>
</Modal>
```

### 6. Редактирование игры

Добавить модалку/страницу редактирования игры:

```tsx
// components/EditGameModal.tsx
interface EditGameModalProps {
  game: Game
  onSave: (data: GameUpdate) => void
  onClose: () => void
}

const EditGameModal = ({ game, onSave, onClose }: EditGameModalProps) => {
  const [title, setTitle] = useState(game.title)
  const [downloadUrl, setDownloadUrl] = useState(game.download_url)
  const [genre, setGenre] = useState(game.genre)
  const [gameType, setGameType] = useState<GameType>(game.game_type)
  const [playthroughPoints, setPlaythroughPoints] = useState(game.playthrough_points ?? 100)
  const [playthroughDescription, setPlaythroughDescription] = useState(game.playthrough_description ?? '')

  const handleSubmit = () => {
    const data: GameUpdate = {
      title,
      download_url: downloadUrl,
      genre,
      game_type: gameType,
      ...(gameType === 'playthrough' && {
        playthrough_points: playthroughPoints,
        playthrough_description: playthroughDescription,
      }),
    }
    onSave(data)
  }

  return (
    <Modal onClose={onClose}>
      <h2>Редактирование игры</h2>

      <Input label="Название" value={title} onChange={setTitle} />
      <Input label="Ссылка на скачивание" value={downloadUrl} onChange={setDownloadUrl} />
      <Input label="Жанр" value={genre} onChange={setGenre} />

      <Select
        label="Тип игры"
        value={gameType}
        onChange={setGameType}
        options={[
          { value: 'challenges', label: 'Челленджи' },
          { value: 'playthrough', label: 'Прохождение' }
        ]}
      />

      {gameType === 'playthrough' && (
        <>
          <Input
            type="number"
            label="Очки за прохождение"
            value={playthroughPoints}
            onChange={setPlaythroughPoints}
            min={1}
            max={500}
          />
          <Textarea
            label="Описание прохождения"
            value={playthroughDescription}
            onChange={setPlaythroughDescription}
          />
        </>
      )}

      <div className="flex gap-2">
        <Button variant="secondary" onClick={onClose}>Отмена</Button>
        <Button onClick={handleSubmit}>Сохранить</Button>
      </div>
    </Modal>
  )
}
```

### 7. Кнопка редактирования в списке игр

```tsx
// components/GameCard.tsx (или GamesList)
{isOrganizer && (
  <Button
    variant="ghost"
    size="sm"
    onClick={() => setEditingGame(game)}
  >
    Редактировать
  </Button>
)}
```

### 8. Счётчик доступных игр

Отображать количество игр, которые ещё могут выпасть при спине:

```tsx
// components/AvailableGamesCounter.tsx
interface AvailableGamesCounterProps {
  available: number
  total: number
}

const AvailableGamesCounter = ({ available, total }: AvailableGamesCounterProps) => {
  const allCompleted = available === 0

  return (
    <div className="text-sm text-gray-500">
      {allCompleted ? (
        <span className="text-green-600 font-medium">
          Все игры пройдены!
        </span>
      ) : (
        <span>
          Доступно игр: <strong>{available}</strong> из {total}
        </span>
      )}
    </div>
  )
}

// Использование на странице марафона / рядом с колесом
<AvailableGamesCounter available={gamesCount.available} total={gamesCount.total} />
```

---

## Уточнённые требования

| Вопрос | Решение |
|--------|---------|
| Очки за прохождение | Устанавливаются при создании игры (поле `playthrough_points`) |
| Обязательность доп. челленджей | **Не обязательны** — можно завершить задание без них |
| Пруф на прохождение | Тип указывается при создании (`playthrough_proof_type`) |
| Пруфы на бонусные челленджи | **Требуются** — по типу челленджа (screenshot/video/steam) |
| Прикрепление файла | **Не обязательно** — можно отправить комментарий со ссылкой |
| Миграция существующих игр | Тип по умолчанию: `challenges` |
| Дроп игры (playthrough) | Дропнутая игра **не выпадает** повторно |
| Бонусные челленджи после завершения | **Недоступны** — только пока задание активно |
| Счётчик игр | Показывать "Доступно игр: X из Y" |
| События для playthrough | **Все игнорируются** — стандартные очки без модификаторов |

---

## План реализации

### Этап 1: Backend (модели и миграции) ✅
- [x] Добавить enum `GameType` в `backend/app/models/game.py`
- [x] Добавить поля `game_type`, `playthrough_points`, `playthrough_description`, `playthrough_proof_type`, `playthrough_proof_hint` в модель Game
- [x] Создать модель `BonusAssignment` в `backend/app/models/bonus_assignment.py`
- [x] Обновить модель `Assignment` — добавить `game_id`, `is_playthrough`
- [x] Создать миграцию Alembic (`020_add_game_types.py`)

### Этап 2: Backend (схемы и API) ✅
- [x] Обновить Pydantic схемы для Game (`GameCreate`, `GameResponse`)
- [x] Добавить схему `GameUpdate` с валидацией
- [x] Обновить API создания игры
- [x] Добавить API редактирования игры (`PATCH /games/{id}`)
- [x] Добавить API счётчика игр (`GET /available-games-count`)
- [x] Добавить схемы для `BonusAssignment`, `PlaythroughInfo`
- [x] Добавить эндпоинты для бонусных челленджей

### Этап 3: Backend (логика спина) ✅
- [x] Добавить функцию `get_available_games()` для фильтрации пройденных игр
- [x] Обновить логику `spin_wheel` для обработки типов
- [x] Для типа `challenges` — выбирать только невыполненные челленджи
- [x] Обработать случай "Все игры пройдены"
- [x] Обновить ответ SpinResult
- [x] Обновить логику завершения задания для playthrough
- [x] Добавить логику завершения бонусных челленджей
- [x] Игнорирование событий для playthrough

### Этап 4: Frontend (типы и формы) ✅
- [x] Обновить типы TypeScript (`Game`, `SpinResult`, `BonusAssignment`, `GameUpdate`, `AvailableGamesCount`)
- [x] Добавить выбор типа в форму создания игры
- [x] Добавить условные поля "Очки", "Описание", "Тип пруфа", "Подсказка" для типа "Прохождение"
- [x] Добавить API метод `gamesApi.update()` и `gamesApi.getAvailableGamesCount()`
- [x] Добавить API методы для бонусных челленджей

### Этап 5: Frontend (UI) ✅
- [x] Обновить отображение результата спина для playthrough
- [x] Обновить карточку текущего задания (PlayPage)
- [x] Показ бонусных челленджей со статусами
- [x] Бейдж "Прохождение" на карточках игр в лобби
- [x] Поддержка пруфа через комментарий для playthrough

### Этап 6: Тестирование
- [ ] Тестирование миграции на существующих данных
- [ ] Проверка создания игр обоих типов
- [ ] Проверка редактирования игр (смена типа, обновление полей)
- [ ] Проверка спина для playthrough и challenges
- [ ] Проверка фильтрации пройденных игр (playthrough не выпадает повторно)
- [ ] Проверка фильтрации челленджей (выпадают только невыполненные)
- [ ] Проверка состояния "Все игры пройдены"
- [ ] Проверка завершения основного и бонусных заданий

---

## Схема работы

### Создание игры

```
┌─────────────────────────────────────────────────────────────────┐
│                      СОЗДАНИЕ ИГРЫ                              │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
                    ┌─────────────────┐
                    │  Выбор типа     │
                    └─────────────────┘
                              │
              ┌───────────────┴───────────────┐
              ▼                               ▼
    ┌─────────────────┐             ┌─────────────────┐
    │ "Прохождение"   │             │ "Челленджи"     │
    │                 │             │                 │
    │ Доп. поля:      │             │ Стандартные     │
    │ • Очки          │             │ поля            │
    │ • Описание      │             │                 │
    └─────────────────┘             └─────────────────┘
```

### Спин колеса

```
┌─────────────────────────────────────────────────────────────────┐
│                         СПИН КОЛЕСА                             │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
                    ┌─────────────────┐
                    │  Выбор игры     │
                    │  (random)       │
                    └─────────────────┘
                              │
              ┌───────────────┴───────────────┐
              ▼                               ▼
    ┌─────────────────┐             ┌─────────────────┐
    │ game_type =     │             │ game_type =     │
    │ "playthrough"   │             │ "challenges"    │
    └─────────────────┘             └─────────────────┘
              │                               │
              ▼                               ▼
    ┌─────────────────┐             ┌─────────────────┐
    │ Основное:       │             │ Случайный       │
    │ playthrough_    │             │ челлендж        │
    │ description     │             │                 │
    │                 │             │ (текущая        │
    │ Очки:           │             │  логика)        │
    │ playthrough_    │             │                 │
    │ points          │             │                 │
    │                 │             │                 │
    │ Доп. задания:   │             │                 │
    │ Все челленджи   │             │                 │
    │ (опционально)   │             │                 │
    └─────────────────┘             └─────────────────┘
              │                               │
              ▼                               ▼
    ┌─────────────────┐             ┌─────────────────┐
    │ Пруф:           │             │ Пруф:           │
    │ На прохождение  │             │ По типу         │
    │ игры            │             │ челленджа       │
    └─────────────────┘             └─────────────────┘
              │                               │
              ▼                               ▼
    ┌─────────────────┐             ┌─────────────────┐
    │ Очки:           │             │ Очки:           │
    │ + За прохождение│             │ + За челлендж   │
    │ + Бонус за доп. │             │                 │
    │   челленджи     │             │                 │
    └─────────────────┘             └─────────────────┘
```
