Skip to content

Repository files navigation

🎮 Tic-Tac-Toe API

REST API для классической игры Крестики-нолики.
Проект реализован с использованием NestJS, архитектуры с разделением на слои (domain, datasource, web) и покрыт тестами (Jest).


🚀 Возможности

  • Создание новой игры
  • Совершение хода игрока
  • Ходы компьютера (AI)
  • Проверка состояния игры (активна, победа, ничья)
  • Возврат игрового поля в удобном для чтения виде

📦 Технологии

  • NestJS — каркас приложения
  • TypeScript — строгая типизация
  • Jest — тестирование
  • Используются паттерны: ООП, MVC, Domain-driven design (DDD), Clean Architecture, DRY, SOLID, KISS;

🏛 Архитектура

Три слоя, зависимости направлены внутрь — к домену.

src/modules/
  domain/                 # бизнес-логика
    model/                #   Game, Board
    port/                 #   GameRepositoryPort — интерфейс хранилища
    service/              #   GameService — правила игры и ИИ
  datasource/             # хранение
    repository/           #   GameRepository implements GameRepositoryPort
    storage/              #   InMemoryGameStorage
    mapper/ model/        #   GameEntity ↔ Game
  web/                    # HTTP
    controller/           #   GameController
    model/                #   DTO с class-validator
    mapper/

Инверсия зависимости

Домен объявляет интерфейс хранилища и токен для внедрения:

// domain/port/game.repository.port.ts
export const GAME_REPOSITORY_PORT = 'GAME_REPOSITORY_PORT';

export interface GameRepositoryPort {
  save(game: Game): void;
  findById(id: string): Game | null;
  findAll(): Game[];
}

GameService работает только с этим интерфейсом и никогда не видит конкретную реализацию:

constructor(
  @Inject(GAME_REPOSITORY_PORT)
  private readonly repository: GameRepositoryPort,
) {}

Реализация подставляется в инфраструктурном слое:

// datasource/datasource.module.ts
{ provide: GAME_REPOSITORY_PORT, useClass: GameRepository }

Поэтому InMemoryGameStorage заменяется на реальную БД без единой правки в домене.

Разделение моделей

Доменная Game и персистентная GameEntity — разные типы, преобразование идёт через GameDatasourceMapper (toDomain / toEntity). Схема хранения может меняться независимо от бизнес-модели.

Игровая логика

  • Ход компьютера — минимакс с полным перебором и штрафом за глубину (10 - depth), поэтому ИИ играет оптимально и выбирает наиболее быструю победу.
  • makePlayerMove не мутирует состояние: доска копируется, возвращается новый Game.
  • validateGameBoard проверяет, что за ход изменилась ровно одна пустая клетка, а уже занятые остались нетронутыми.

🔧 Установка и запуск

# Клонировать репозиторий
git clone https://github.com/YanaKris/tic-tac-toe-api.git
cd tic-tac-toe-api

# Установить зависимости
npm install

# Запустить приложение
npm run start:dev

# Запустить unit-тесты
npm run test

📡 API

Создать новую игру

POST /game

Пример ответа:

{
  "uuid": "123e4567-e89b-12d3-a456-426614174000",
  "board": [
    [0, 0, 0],
    [0, 0, 0],
    [0, 0, 0]
  ],
  "status": "active"
}

Сделать ход

POST /game/:uuid/move

Тело запроса:

{
  "x": 1,
  "y": 2
}

Пример ответа:

{
  "uuid": "123e4567-e89b-12d3-a456-426614174000",
  "board": [
    [2, 0, 0],
    [0, 1, 0],
    [0, 0, 0]
  ],
  "status": "active"
}

About

REST API крестиков-ноликов на NestJS: слои domain / datasource / web, порт репозитория объявлен в домене, DTO с class-validator, unit-тесты на Jest

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages