< Summary

Information
Class: TicTacToeApp.API.Endpoints.GameEndpoints
Assembly: TicTacToeApp.API
File(s): /home/runner/work/TicTacToe/TicTacToe/TicTacToeApp.API/Endpoints/UseGameEndpoints.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 194
Coverable lines: 194
Total lines: 221
Line coverage: 0%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
UseGameEndpoints(...)100%210%

File(s)

/home/runner/work/TicTacToe/TicTacToe/TicTacToeApp.API/Endpoints/UseGameEndpoints.cs

#LineLine coverage
 1using FluentValidation;
 2using Microsoft.AspNetCore.Mvc;
 3using Microsoft.OpenApi.Models;
 4using TicTacToeApp.API.Dtos;
 5using TicTacToeApp.API.Entity;
 6using TicTacToeApp.API.Entity.Enums;
 7using TicTacToeApp.API.Interfaces;
 8using TicTacToeApp.API.Response;
 9using TicTacToeApp.API.Services;
 10
 11namespace TicTacToeApp.API.Endpoints;
 12
 13public static class GameEndpoints
 14{
 15    public static WebApplication UseGameEndpoints(
 16        this WebApplication app
 17    )
 18    {
 019        int TICTACTOE_BOARD_SIZE = int.Parse(Environment.GetEnvironmentVariable("TICTACTOE_BOARD_SIZE")!);
 20
 021        int TICTACTOE_LINE_TO_WIN = int.Parse(Environment.GetEnvironmentVariable("TICTACTOE_LINE_TO_WIN")!);
 22
 023        int TICTACTOE_CHANCE =
 024            int.Parse(Environment.GetEnvironmentVariable("TICTACTOE_LINE_TO_WIN")!); // процент вероятности замены хода
 025        int TICTACTOE_NUMBER_STEP =
 026            int.Parse(Environment.GetEnvironmentVariable("TICTACTOE_LINE_TO_WIN")!); // на каком ходу
 27
 028        app.MapGet("/api/games",
 029                async (IGameAsyncRepository repo, CancellationToken ct) =>
 030                {
 031                    return Results.Ok(await repo.GetGamesAsync(ct));
 032                })
 033            .WithTags("TicTacToe.API")
 034            .WithName("GetAllGames")
 035            .WithSummary("Список доступных игр")
 036            .WithDescription("Возвращает список объектов Game")
 037            .Produces<List<Game>>(StatusCodes.Status200OK)
 038            .Produces<ErrorResponse>(StatusCodes.Status400BadRequest);
 39
 040        app.MapGet("/api/games/{Id:guid}",
 041                async (IGameAsyncRepository repo, Guid Id, CancellationToken ct) =>
 042                {
 043                    return Results.Ok(await repo.FindGameByGuidAsync(Id, ct));
 044                })
 045            .WithTags("TicTacToe.API")
 046            .WithName("GetGameById")
 047            .WithOpenApi(operation =>
 048            {
 049                operation.Summary = "Получение игры по Id";
 050                operation.Description = "Возвращает объект Game";
 051                return operation;
 052            })
 053            .Produces<Game>(StatusCodes.Status200OK)
 054            .Produces<ErrorResponse>(StatusCodes.Status404NotFound);
 55
 056        app.MapPost("/api/games/new", async (IGameAsyncRepository repo, GameOption? gameOption, CancellationToken ct) =>
 057            {
 058
 059                if (gameOption!.size < 3 || gameOption.line_to_win < 1 || gameOption.chance < 1 || gameOption.step < 1 )
 060                    return Results.Json<ErrorResponse>(new ErrorResponse(
 061                            statusCode: "400",
 062                            message: "Размерность должна быть больше от 3, условие победы, вероятность замены и шаг веро
 063                        ),
 064                        statusCode: StatusCodes.Status400BadRequest,
 065                        contentType: "application/json"
 066                    );
 067
 068                if (gameOption.line_to_win > gameOption.size)
 069                    return Results.Json<ErrorResponse>(new ErrorResponse(
 070                            statusCode: "400",
 071                            message: "Количество одинаковых элементов должно быть меньше или равно размерности доски!"
 072                        ),
 073                        statusCode: StatusCodes.Status400BadRequest,
 074                        contentType: "application/json"
 075                    );
 076
 077
 078                TICTACTOE_BOARD_SIZE = gameOption!.size;
 079                TICTACTOE_LINE_TO_WIN = gameOption.line_to_win;
 080                TICTACTOE_CHANCE = gameOption.chance;
 081                TICTACTOE_NUMBER_STEP = gameOption.step;
 082
 083                var game = await repo.CreateGameAsync(TICTACTOE_BOARD_SIZE, ct);
 084
 085                return Results.CreatedAtRoute("GetGameById", game, value: game);
 086            })
 087            .WithTags("TicTacToe.API")
 088            .WithName("CreateGame")
 089            .WithSummary("Создание новой игры")
 090            .WithDescription("Возвращает объект игры Game")
 091            .Produces<Game>(StatusCodes.Status201Created)
 092            .Produces<ErrorResponse>(StatusCodes.Status400BadRequest);
 93
 094        app.MapPost("api/games/{gameId:guid}/move",
 095                async (HttpResponse r,
 096                    [FromHeader(Name = "If-Match")] string? ifMatchHeader,
 097                    IGameAsyncRepository repo,
 098                    Guid gameId,
 099                    Move move,
 0100                    ILogger<Program> log,
 0101                    CancellationToken ct) =>
 0102                {
 0103                    var game = await repo.FindGameByGuidAsync(gameId, ct);
 0104
 0105                    var currentETag = EtagService.GenerateETag(game);
 0106
 0107                    if (ifMatchHeader != null && ifMatchHeader != currentETag)
 0108                    {
 0109                        log.LogError("Etag не совпадает");
 0110                        return Results.Json(
 0111                            new ErrorResponse(
 0112                                statusCode: "412",
 0113                                message: "Обновите состояние игры"
 0114                            ),
 0115                            statusCode: StatusCodes.Status412PreconditionFailed
 0116                        );
 0117                    }
 0118
 0119                    if (game.Status != StatusGame.Active)
 0120                    {
 0121                        log.LogError("Игра завершена");
 0122                        return Results.BadRequest(new ErrorResponse(
 0123                                statusCode: "400",
 0124                                message: $"Данная игра уже завершена! Итог: {game.Result}"
 0125                            )
 0126                        );
 0127                    }
 0128
 0129                    if (move.x < 0 || move.x > TICTACTOE_BOARD_SIZE - 1 || move.y < 0 ||
 0130                        move.y > TICTACTOE_BOARD_SIZE - 1)
 0131                    {
 0132                        log.LogError("Координаты выходят за пределы поля");
 0133                        return Results.BadRequest(new ErrorResponse(
 0134                                statusCode: "400",
 0135                                message: $"Неверные координаты доски. Разрешено: 0-{TICTACTOE_BOARD_SIZE - 1}"
 0136                            )
 0137                        );
 0138                    }
 0139
 0140                    if (game.CurrentMove != move.p)
 0141                    {
 0142                        log.LogError("Ход вне очереди");
 0143                        return Results.BadRequest(new ErrorResponse(
 0144                                statusCode: "400",
 0145                                message: $"Не ваш ход! Сейчас ход: {game.CurrentMove}"
 0146                            )
 0147                        );
 0148                    }
 0149
 0150                    if (game.Board[move.x][move.y] != null)
 0151                    {
 0152                        log.LogError($"Ячейка ({move.x},{move.y}) занята");
 0153                        return Results.Conflict(new ErrorResponse(
 0154                                statusCode: "409",
 0155                                message: $"Нельзя осуществить данный ход! Ячейка занята!"
 0156                            )
 0157                        );
 0158                    }
 0159
 0160                    game.Board[move.x][move.y] = (move.p.ToString());
 0161                    game.CurrentStep += 1;
 0162
 0163                    // Проверяем особое условие на каждый n ход с шансом m % замена выбора
 0164
 0165                    bool maybeReplace = false;
 0166                    if (game.CurrentStep > 0 && game.CurrentStep % TICTACTOE_NUMBER_STEP == 0)
 0167                    {
 0168                        var random = new Random();
 0169                        double probability = TICTACTOE_CHANCE / 100.0; // %
 0170                        maybeReplace = random.NextDouble() < probability; // true с вероятностью %
 0171
 0172                        if (maybeReplace)
 0173                        {
 0174                            log.LogWarning(
 0175                                $"Сработала вероятность {TICTACTOE_CHANCE}%. Текущий ход: {game.CurrentStep}. Выбор игро
 0176                            game.Board[move.x][move.y] = move.p == Player.X ? Player.O.ToString() : Player.X.ToString();
 0177                        }
 0178                    }
 0179
 0180                    game.CurrentMove = move.p == Player.X ? Player.O : Player.X;
 0181
 0182                    game.Result = GameService.CheckBoardN(game.Board, (move.p).ToString(), TICTACTOE_LINE_TO_WIN);
 0183
 0184                    if (game.Result != ResultGame.None)
 0185                    {
 0186                        game.Status = StatusGame.Complete;
 0187                    }
 0188
 0189                    await repo.UpdateGameAsync(game, ct);
 0190
 0191                    var response = new
 0192                    {
 0193                        Id = gameId,
 0194                        Board = game.Board,
 0195                        Status = game.Status,
 0196                        Result = game.Result,
 0197                        DateTime = DateTime.UtcNow,
 0198                        CurrentStep = game.CurrentStep,
 0199                        CurrentMove = game.CurrentMove,
 0200                        ReplaceMove = maybeReplace
 0201                    };
 0202
 0203                    r.Headers.ETag = EtagService.GenerateETag(game);
 0204                    return Results.Ok(response);
 0205                })
 0206            .WithOpenApi(operation =>
 0207            {
 0208                operation.Tags = new List<OpenApiTag>(){ new OpenApiTag(){Name = "TicTacToe.API"}};
 0209                operation.Summary = "Ход игрока X или O";
 0210                operation.Description = "Возвращает состояние игры в виде объекта Game";
 0211                return operation;
 0212            })
 0213            .Produces<Game>(StatusCodes.Status200OK)
 0214            .Produces<ErrorResponse>(StatusCodes.Status404NotFound)
 0215            .Produces<ErrorResponse>(StatusCodes.Status400BadRequest)
 0216            .Produces<ErrorResponse>(StatusCodes.Status409Conflict)
 0217            .Produces<ErrorResponse>(StatusCodes.Status412PreconditionFailed);
 218
 0219        return app;
 220    }
 221}