Files
Shogi/Gameboard.ShogiUI.Sockets/Controllers/GameController.cs
2022-06-12 12:37:32 -05:00

290 lines
8.6 KiB
C#

using Gameboard.ShogiUI.Sockets.Managers;
using Gameboard.ShogiUI.Sockets.Repositories;
using Gameboard.ShogiUI.Sockets.ServiceModels.Api;
using Gameboard.ShogiUI.Sockets.ServiceModels.Socket;
using Gameboard.ShogiUI.Sockets.ServiceModels.Types;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
namespace Gameboard.ShogiUI.Sockets.Controllers
{
[ApiController]
[Route("[controller]")]
[Authorize(Roles = "Shogi")]
public class GameController : ControllerBase
{
private readonly IGameboardManager gameboardManager;
private readonly IGameboardRepository gameboardRepository;
private readonly ISocketConnectionManager communicationManager;
public GameController(
IGameboardRepository repository,
IGameboardManager manager,
ISocketConnectionManager communicationManager)
{
gameboardManager = manager;
gameboardRepository = repository;
this.communicationManager = communicationManager;
}
[HttpPost("JoinCode")]
public async Task<IActionResult> PostGameInvitation([FromBody] PostGameInvitation request)
{
//var isPlayer1 = await gameboardManager.IsPlayer1(request.SessionName, userName);
//if (isPlayer1)
//{
// var code = await gameboardRepository.PostJoinCode(request.SessionName, userName);
// return new CreatedResult("", new PostGameInvitationResponse(code));
//}
//else
//{
return new UnauthorizedResult();
//}
}
[AllowAnonymous]
[HttpPost("GuestJoinCode")]
public async Task<IActionResult> PostGuestGameInvitation([FromBody] PostGuestGameInvitation request)
{
//var isGuest = gameboardManager.IsGuest(request.GuestId);
//var isPlayer1 = gameboardManager.IsPlayer1(request.SessionName, request.GuestId);
//if (isGuest && await isPlayer1)
//{
// var code = await gameboardRepository.PostJoinCode(request.SessionName, request.GuestId);
// return new CreatedResult("", new PostGameInvitationResponse(code));
//}
//else
//{
return new UnauthorizedResult();
//}
}
[HttpPost("{gameName}/Move")]
public async Task<IActionResult> PostMove([FromRoute] string gameName, [FromBody] PostMove request)
{
var user = await gameboardManager.ReadUser(User);
var session = await gameboardRepository.ReadSession(gameName);
if (session == null)
{
return NotFound();
}
if (user == null || (session.Player1.Id != user.Id && session.Player2?.Id != user.Id))
{
return Forbid("User is not seated at this game.");
}
var move = request.Move;
var moveModel = move.PieceFromCaptured.HasValue
? new Models.Move(move.PieceFromCaptured.Value, move.To, move.IsPromotion)
: new Models.Move(move.From!, move.To, move.IsPromotion);
var moveSuccess = session.Shogi.Move(moveModel);
if (moveSuccess)
{
var createSuccess = await gameboardRepository.CreateBoardState(session);
if (!createSuccess)
{
throw new ApplicationException("Unable to persist board state.");
}
await communicationManager.BroadcastToPlayers(new MoveResponse
{
GameName = session.Name,
PlayerName = user.Id
}, session.Player1.Id, session.Player2?.Id);
return Ok();
}
return Conflict("Illegal move.");
}
// TODO: Use JWT tokens for guests so they can authenticate and use API routes, too.
//[Route("")]
//public async Task<IActionResult> PostSession([FromBody] PostSession request)
//{
// var model = new Models.Session(request.Name, request.IsPrivate, request.Player1, request.Player2);
// var success = await repository.CreateSession(model);
// if (success)
// {
// var message = new ServiceModels.Socket.Messages.CreateGameResponse(ServiceModels.Types.ClientAction.CreateGame)
// {
// Game = model.ToServiceModel(),
// PlayerName =
// }
// var task = request.IsPrivate
// ? communicationManager.BroadcastToPlayers(response, userName)
// : communicationManager.BroadcastToAll(response);
// return new CreatedResult("", null);
// }
// return new ConflictResult();
//}
[HttpPost]
public async Task<IActionResult> PostSession([FromBody] PostSession request)
{
var user = await ReadUserOrThrow();
var session = new Models.SessionMetadata(request.Name, request.IsPrivate, user!);
var success = await gameboardRepository.CreateSession(session);
if (success)
{
try
{
await communicationManager.BroadcastToAll(new CreateGameResponse
{
Game = session.ToServiceModel(),
PlayerName = user.Id
});
}
catch (Exception e)
{
Console.Error.WriteLine("Error broadcasting during PostSession");
}
return Ok();
}
return Conflict();
}
/// <summary>
/// Reads the board session and subscribes the caller to socket events for that session.
/// </summary>
[HttpGet("{gameName}")]
public async Task<IActionResult> GetSession([FromRoute] string gameName)
{
var user = await ReadUserOrThrow();
var session = await gameboardRepository.ReadSession(gameName);
if (session == null)
{
return NotFound();
}
var playerPerspective = session.Player2Name == user.Id
? WhichPlayer.Player2
: WhichPlayer.Player1;
var response = new Session
{
BoardState = new BoardState
{
Board = null,
Player1Hand = session.Player1Hand.Select(MapPiece).ToList(),
Player2Hand = session.Player2Hand.Select(MapPiece).ToList(),
PlayerInCheck = session.InCheck.HasValue ? Map(session.InCheck.Value) : null
},
GameName = session.Name,
Player1 = session.Player1Name,
Player2 = session.Player2Name
};
return this.Ok(response);
static WhichPlayer Map(Shogi.Domain.WhichPlayer whichPlayer)
{
return whichPlayer == Shogi.Domain.WhichPlayer.Player1
? WhichPlayer.Player1
: WhichPlayer.Player2;
}
static Piece MapPiece(Shogi.Domain.Pieces.Piece piece)
{
var owner = Map(piece.Owner);
var whichPiece = piece.WhichPiece switch
{
Shogi.Domain.WhichPiece.King => WhichPiece.King,
Shogi.Domain.WhichPiece.GoldGeneral => WhichPiece.GoldGeneral,
Shogi.Domain.WhichPiece.SilverGeneral => WhichPiece.SilverGeneral,
Shogi.Domain.WhichPiece.Bishop => WhichPiece.Bishop,
Shogi.Domain.WhichPiece.Rook => WhichPiece.Rook,
Shogi.Domain.WhichPiece.Knight => WhichPiece.Knight,
Shogi.Domain.WhichPiece.Lance => WhichPiece.Lance,
Shogi.Domain.WhichPiece.Pawn => WhichPiece.Pawn,
_ => throw new ArgumentException($"Unknown value for {nameof(WhichPiece)}")
};
return new Piece { IsPromoted = piece.IsPromoted, Owner = owner, WhichPiece = whichPiece };
}
}
[HttpGet]
public async Task<GetSessionsResponse> GetSessions()
{
var user = await ReadUserOrThrow();
var sessions = await gameboardRepository.ReadSessionMetadatas();
var sessionsJoinedByUser = sessions
.Where(s => s.IsSeated(user))
.Select(s => s.ToServiceModel())
.ToList();
var sessionsNotJoinedByUser = sessions
.Where(s => !s.IsSeated(user))
.Select(s => s.ToServiceModel())
.ToList();
return new GetSessionsResponse
{
PlayerHasJoinedSessions = new Collection<Session>(sessionsJoinedByUser),
AllOtherSessions = new Collection<Session>(sessionsNotJoinedByUser)
};
}
[HttpPut("{gameName}")]
public async Task<IActionResult> PutJoinSession([FromRoute] string gameName)
{
var user = await ReadUserOrThrow();
var session = await gameboardRepository.ReadSessionMetaData(gameName);
if (session == null)
{
return NotFound();
}
if (session.Player2 != null)
{
return this.Conflict("This session already has two seated players and is full.");
}
session.SetPlayer2(user);
var success = await gameboardRepository.UpdateSession(session);
if (!success) return this.Problem(detail: "Unable to update session.");
var opponentName = user.Id == session.Player1.Id
? session.Player2!.Id
: session.Player1.Id;
await communicationManager.BroadcastToPlayers(new JoinGameResponse
{
GameName = session.Name,
PlayerName = user.Id
}, opponentName);
return Ok();
}
[Authorize(Roles = "Admin, Shogi")]
[HttpDelete("{gameName}")]
public async Task<IActionResult> DeleteSession([FromRoute] string gameName)
{
var user = await ReadUserOrThrow();
if (user.IsAdmin)
{
return Ok();
}
else
{
return Unauthorized();
}
}
private async Task<Models.User> ReadUserOrThrow()
{
var user = await gameboardManager.ReadUser(User);
if (user == null)
{
throw new UnauthorizedAccessException("Unknown user claims.");
}
return user;
}
}
}