using qtc_api.Enums; using qtc_api.Services.GameRoomService; namespace qtc_api.Hubs { [Authorize] public class TicTacToeHub : Hub { private IUserService _userService; private ILogger _logger; private GameRoomService _gameRoomService; public TicTacToeHub(ILogger logger, IUserService userService, GameRoomService gameRoomService) { _userService = userService; _gameRoomService = gameRoomService; _logger = logger; } public override async Task OnDisconnectedAsync(Exception? exception) { // find any existing room user was in var room = _gameRoomService.GameRooms.FirstOrDefault(e => (e.Player1?.Id == Context.UserIdentifier) || (e.Player2?.Id == Context.UserIdentifier)); if (room != null) { // just end the game room.Status = GameStatus.PlayerDisconnected; string usernameDisconnected = string.Empty; // determine which player to null out if (room.Player1?.Id == Context.UserIdentifier) { usernameDisconnected = room.Player1!.Username; room.Player1 = null; } else { usernameDisconnected = room.Player2!.Username; room.Player2 = null; } bool isWin = room.Status == GameStatus.P1Win || room.Status == GameStatus.P2Win || room.Status == GameStatus.NoWin; if (!isWin) await Clients.Group(room.Id).SendAsync("GameEnd", room, usernameDisconnected); // remove the room _gameRoomService.RemoveRoom(room); } } [HubMethodName("FindRoom")] public async Task FindRoom() { // find user from useridentifier var user = await _userService.GetUserById(Context.UserIdentifier!); if (user != null && user.Success && user.Data != null) { // find any room waiting on a player var roomWOP = _gameRoomService.GameRooms.FirstOrDefault(e => e.Status == GameStatus.WaitingForPlayer && e.Player2 == null && e.Player1?.Id != Context.UserIdentifier); if (roomWOP != null) { // add player to said group await Groups.AddToGroupAsync(Context.ConnectionId, roomWOP.Id); // set player two roomWOP.Player2 = user.Data; // this game can now start symbol selection roomWOP.Status = GameStatus.SelectingSymbol; await Clients.User(roomWOP.Player1!.Id).SendAsync("SelectSymbol", roomWOP); await Clients.Group(roomWOP.Id).SendAsync("SelectingSymbol", roomWOP); return; } // find any room who's player one matches the user and is stuck in 'WaitingForPlayer' status var roomWFPP1 = _gameRoomService.GameRooms.FirstOrDefault(e => e.Player1?.Id == user.Data.Id && e.Status == GameStatus.WaitingForPlayer); if (roomWFPP1 != null) // just remove it _gameRoomService.RemoveRoom(roomWFPP1); // if all if statements above don't match, make a new room var gameRoom = new GameRoom { Id = Guid.NewGuid().ToString(), Player1 = user.Data, Status = GameStatus.WaitingForPlayer }; _gameRoomService.AddRoom(gameRoom); // add user to group await Groups.AddToGroupAsync(Context.ConnectionId, gameRoom.Id); // waiting for player await Clients.Group(gameRoom.Id).SendAsync("WaitingForPlayer", gameRoom); } } [HubMethodName("SetSymbol")] public async Task SetPlayerSymbol(string roomId, TicTacToeSymbol symbol) { // find the room var room = _gameRoomService.GameRooms.FirstOrDefault(e => e.Id == roomId); if (room != null) { // set player one symbol room.P1Symbol = symbol; // set player two symbol based on what player one picked switch (room.P1Symbol) { case TicTacToeSymbol.X: room.P2Symbol = TicTacToeSymbol.O; break; case TicTacToeSymbol.O: room.P2Symbol = TicTacToeSymbol.X; break; } // the game can now start room.Status = GameStatus.Ongoing; await Clients.Group(room.Id).SendAsync("GameStart", room); // start player ones turn await Clients.User(room.Player1!.Id).SendAsync("StartTurn", room); await Clients.User(room.Player2!.Id).SendAsync("EndTurn", room); } } [HubMethodName("MakeMove")] public async Task MakeMove(string roomId, TicTacToeMove move) { // find the room var room = _gameRoomService.GameRooms.FirstOrDefault(e => e.Id == roomId); if (room != null) { // update board based on move (TOOD - figure out a better way to do this) if (room.Player1?.Id == move.User.Id) { switch (move.Point) { case 1: room.Board.Square1 = room.P1Symbol; break; case 2: room.Board.Square2 = room.P1Symbol; break; case 3: room.Board.Square3 = room.P1Symbol; break; case 4: room.Board.Square4 = room.P1Symbol; break; case 5: room.Board.Square5 = room.P1Symbol; break; case 6: room.Board.Square6 = room.P1Symbol; break; case 7: room.Board.Square7 = room.P1Symbol; break; case 8: room.Board.Square8 = room.P1Symbol; break; case 9: room.Board.Square9 = room.P1Symbol; break; } } else { switch (move.Point) { case 1: room.Board.Square1 = room.P2Symbol; break; case 2: room.Board.Square2 = room.P2Symbol; break; case 3: room.Board.Square3 = room.P2Symbol; break; case 4: room.Board.Square4 = room.P2Symbol; break; case 5: room.Board.Square5 = room.P2Symbol; break; case 6: room.Board.Square6 = room.P2Symbol; break; case 7: room.Board.Square7 = room.P2Symbol; break; case 8: room.Board.Square8 = room.P2Symbol; break; case 9: room.Board.Square9 = room.P2Symbol; break; } } // send board update await Clients.Group(roomId).SendAsync("UpdateBoard", room, move); // check for draw if (room.Board.IsDraw()) { room.Status = GameStatus.NoWin; await Clients.Group(room.Id).SendAsync("GameEnd", room, string.Empty); return; } // check for winner var winningSymbol = room.Board.GetWinner(); if (winningSymbol != TicTacToeSymbol.Blank) { if (winningSymbol == room.P1Symbol) { // player one has won the game room.Status = GameStatus.P1Win; await Clients.Group(room.Id).SendAsync("GameEnd", room, string.Empty); } else { // player two has won the game room.Status = GameStatus.P2Win; await Clients.Group(room.Id).SendAsync("GameEnd", room, string.Empty); } return; } // switch turns if (move.User.Id == room.Player1?.Id) { await Clients.User(room.Player1!.Id).SendAsync("EndTurn", room); await Clients.User(room.Player2!.Id).SendAsync("StartTurn", room); } else { await Clients.User(room.Player2!.Id).SendAsync("EndTurn", room); await Clients.User(room.Player1!.Id).SendAsync("StartTurn", room); } } } [HubMethodName("RestartGame")] public async Task RestartGame(string roomId) { // find the room var room = _gameRoomService.GameRooms.FirstOrDefault(e => e.Id == roomId); if (room != null) { // room still exists and the game can be restarted room.Status = GameStatus.Ongoing; room.Board = new TicTacToeBoard(); // clears the board await Clients.Group(room.Id).SendAsync("RestartGame", room); // have player one start their turn await Clients.User(room.Player1!.Id).SendAsync("StartTurn", room); await Clients.User(room.Player2!.Id).SendAsync("EndTurn", room); } else { // the user trying to restart the game needs to be informed that the other user has left await Clients.Client(Context.ConnectionId).SendAsync("CannotRestart"); } } [HubMethodName("SendRoomMessage")] public async Task SendRoomMessage(string roomId, string message) { // find the room var room = _gameRoomService.GameRooms.FirstOrDefault(e => e.Id == roomId); if (room != null) await Clients.Group(room.Id).SendAsync("ReceiveMessage", message); } } }