TicTacToe Game Backend (WIP)
Implement `JoinRoomGuest` For Non-Authorization Communication
This commit is contained in:
parent
8dcd38cc83
commit
0508db6742
12
qtc-net-server/Enums/GameStatus.cs
Normal file
12
qtc-net-server/Enums/GameStatus.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace qtc_api.Enums
|
||||
{
|
||||
public enum GameStatus
|
||||
{
|
||||
WaitingForPlayer,
|
||||
Ongoing,
|
||||
P1Win,
|
||||
P2Win,
|
||||
NoWin,
|
||||
PlayerDisconnected
|
||||
}
|
||||
}
|
||||
9
qtc-net-server/Enums/TicTacToeSymbol.cs
Normal file
9
qtc-net-server/Enums/TicTacToeSymbol.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace qtc_api.Enums
|
||||
{
|
||||
public enum TicTacToeSymbol
|
||||
{
|
||||
X,
|
||||
O,
|
||||
Blank
|
||||
}
|
||||
}
|
||||
@ -1,20 +1,22 @@
|
||||
using System.Text.Json;
|
||||
using qtc_api.Services.RoomService;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace qtc_api.Hubs
|
||||
{
|
||||
[Authorize]
|
||||
public class ChatHub : Hub
|
||||
{
|
||||
private IUserService _userService;
|
||||
private IRoomService _roomService;
|
||||
private ILogger<ChatHub> _logger;
|
||||
private static List<UserConnectionDto> ConnectedUsers = new();
|
||||
private static List<User> OnlineUsers = new();
|
||||
|
||||
private static Dictionary<string, List<User>> GroupUsers = new();
|
||||
|
||||
public ChatHub(IUserService userService, ILogger<ChatHub> logger)
|
||||
public ChatHub(IUserService userService, IRoomService roomService, ILogger<ChatHub> logger)
|
||||
{
|
||||
_userService = userService;
|
||||
_roomService = roomService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@ -35,8 +37,6 @@ namespace qtc_api.Hubs
|
||||
await LogoutAsync(user);
|
||||
}
|
||||
}
|
||||
|
||||
await base.OnDisconnectedAsync(ex);
|
||||
}
|
||||
|
||||
public async override Task OnConnectedAsync()
|
||||
@ -55,11 +55,24 @@ namespace qtc_api.Hubs
|
||||
await LoginAsync(user.Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await base.OnConnectedAsync();
|
||||
[HubMethodName("JoinRoomGuest")]
|
||||
public async Task JoinRoomGuestAsync(string roomId, string username)
|
||||
{
|
||||
// here we can just add the client to the room group and call it a day since the user isn't authenticated
|
||||
var room = await _roomService.GetRoom(roomId);
|
||||
|
||||
if(room != null && room.Success && room.Data != null)
|
||||
{
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, room.Data.Id);
|
||||
|
||||
await Clients.Group(room.Data.Id).SendAsync("GuestJoin", username);
|
||||
}
|
||||
}
|
||||
|
||||
[HubMethodName("UpdateStatus")]
|
||||
[Authorize]
|
||||
public async Task UpdateStatusAsync(User user, int status)
|
||||
{
|
||||
var statusDto = new UserStatusDto { Id = user.Id, Status = status };
|
||||
@ -72,6 +85,7 @@ namespace qtc_api.Hubs
|
||||
}
|
||||
|
||||
[HubMethodName("JoinLobby")]
|
||||
[Authorize]
|
||||
public async Task JoinLobbyAsync(User user)
|
||||
{
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, "LOBBY");
|
||||
@ -86,6 +100,7 @@ namespace qtc_api.Hubs
|
||||
}
|
||||
|
||||
[HubMethodName("LeaveLobby")]
|
||||
[Authorize]
|
||||
public async Task LeaveLobbyAsync(User user)
|
||||
{
|
||||
await Groups.RemoveFromGroupAsync(Context.ConnectionId, "LOBBY");
|
||||
@ -99,6 +114,7 @@ namespace qtc_api.Hubs
|
||||
}
|
||||
|
||||
[HubMethodName("JoinRoom")]
|
||||
[Authorize]
|
||||
public async Task JoinRoomAsync(User user, Room room)
|
||||
{
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, room.Id);
|
||||
@ -113,6 +129,7 @@ namespace qtc_api.Hubs
|
||||
}
|
||||
|
||||
[HubMethodName("LeaveRoom")]
|
||||
[Authorize]
|
||||
public async Task LeaveRoomAsync(User user, Room room)
|
||||
{
|
||||
await Groups.RemoveFromGroupAsync(Context.ConnectionId, room.Id);
|
||||
@ -126,6 +143,7 @@ namespace qtc_api.Hubs
|
||||
}
|
||||
|
||||
[HubMethodName("HandleDeletedRoom")]
|
||||
[Authorize]
|
||||
public async Task HandleDeletedRoomAsync(Room room)
|
||||
{
|
||||
await Clients.Group(room.Id).SendAsync("RoomMessage", $"[SERVER] This Room Has Been Deleted By An Administrator.");
|
||||
@ -135,6 +153,7 @@ namespace qtc_api.Hubs
|
||||
}
|
||||
|
||||
[HubMethodName("RefreshContactsListOnUser")]
|
||||
[Authorize]
|
||||
public async Task RefreshContactsListForUser(UserInformationDto user, User execUser)
|
||||
{
|
||||
var connection = ConnectedUsers.FirstOrDefault(e => e.ConnectedUser.Id == user.Id);
|
||||
@ -148,6 +167,7 @@ namespace qtc_api.Hubs
|
||||
}
|
||||
|
||||
[HubMethodName("SendMessage")]
|
||||
[Authorize]
|
||||
public async Task SendMessageAsync(User user, Message message, bool IsLobbyMsg, Room room = null!)
|
||||
{
|
||||
if(IsLobbyMsg == true) { await Clients.Group("LOBBY").SendAsync("RoomMessage", $"[{user.Username}] {message.Content}"); return; }
|
||||
@ -155,6 +175,7 @@ namespace qtc_api.Hubs
|
||||
}
|
||||
|
||||
[HubMethodName("SendDirectMessage")]
|
||||
[Authorize]
|
||||
public async Task SendDirectMessageAsync(User user, UserInformationDto userToMsg, Message message)
|
||||
{
|
||||
// send direct message directly to connected user
|
||||
|
||||
243
qtc-net-server/Hubs/TicTacToeHub.cs
Normal file
243
qtc-net-server/Hubs/TicTacToeHub.cs
Normal file
@ -0,0 +1,243 @@
|
||||
using qtc_api.Enums;
|
||||
using qtc_api.Models;
|
||||
using qtc_api.Schema;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Timers;
|
||||
|
||||
namespace qtc_api.Hubs
|
||||
{
|
||||
[Authorize]
|
||||
public class TicTacToeHub : Hub
|
||||
{
|
||||
private IUserService _userService;
|
||||
private ILogger<TicTacToeHub> _logger;
|
||||
|
||||
private Dictionary<Guid, GameRoom> GameRooms = [];
|
||||
public TicTacToeHub(ILogger<TicTacToeHub> logger, IUserService userService)
|
||||
{
|
||||
_userService = userService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async override Task OnConnectedAsync()
|
||||
{
|
||||
// tell the client that the server is finding a room
|
||||
await Clients.Client(Context.ConnectionId).SendAsync("FindingRoom");
|
||||
|
||||
// check game rooms dictionary for any open rooms (any rooms that are waiting on players)
|
||||
if(GameRooms.Count > 0 && GameRooms.Any(e => e.Value.Status == GameStatus.WaitingForPlayer))
|
||||
{
|
||||
// get first available room waiting for a player
|
||||
var roomKVP = GameRooms.FirstOrDefault(e => (e.Value.Status == GameStatus.WaitingForPlayer) || (e.Value.Status == GameStatus.PlayerDisconnected));
|
||||
|
||||
var room = roomKVP.Value;
|
||||
var roomId = roomKVP.Key.ToString();
|
||||
|
||||
if (room != null && room.Status == GameStatus.WaitingForPlayer)
|
||||
{
|
||||
// get user from user identifier
|
||||
var user = await _userService.GetUserById(Context.UserIdentifier ?? "");
|
||||
|
||||
if (user != null && user.Success && user.Data != null)
|
||||
{
|
||||
// set player 2
|
||||
room.Player2 = user.Data;
|
||||
|
||||
// add player to room
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, roomId);
|
||||
|
||||
// start symbol selection (on player one)
|
||||
await Clients.User(room.Player1.Id).SendAsync("SelectSymbol");
|
||||
|
||||
// set room status to ongoing
|
||||
room.Status = GameStatus.Ongoing;
|
||||
}
|
||||
} else if (room != null && room.Status == GameStatus.PlayerDisconnected)
|
||||
{
|
||||
// add player back into group
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, roomId);
|
||||
|
||||
if (room.Player1?.Id == Context.UserIdentifier) await Clients.Client(roomId).SendAsync("HostReconnect");
|
||||
else await Clients.Client(roomId).SendAsync("PlayerReconnect");
|
||||
}
|
||||
} else
|
||||
{
|
||||
// get user from user identifier
|
||||
var user = await _userService.GetUserById(Context.UserIdentifier ?? "");
|
||||
|
||||
if(user != null && user.Success && user.Data != null)
|
||||
{
|
||||
// create a room
|
||||
var guid = new Guid();
|
||||
GameRooms.Add(guid, new GameRoom { Status = GameStatus.WaitingForPlayer, Player1 = user.Data });
|
||||
|
||||
var guidString = guid.ToString();
|
||||
|
||||
// add player to new room
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, guidString);
|
||||
|
||||
// send waiting for player message
|
||||
await Clients.Group(guidString).SendAsync("WaitingForPlayer");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async override Task OnDisconnectedAsync(Exception? exception)
|
||||
{
|
||||
// find the game room the user is in
|
||||
var roomKVP = GameRooms.FirstOrDefault(e => (e.Value.Player1?.Id == Context.UserIdentifier) || (e.Value.Player2?.Id == Context.UserIdentifier));
|
||||
|
||||
if(roomKVP.Value != null)
|
||||
{
|
||||
var room = roomKVP.Value;
|
||||
var roomId = roomKVP.Key.ToString();
|
||||
|
||||
// inform the room that one of the players has disconnected
|
||||
if (room.Player1?.Id == Context.UserIdentifier) await Clients.Group(roomId).SendAsync("HostDisconnected", exception);
|
||||
else await Clients.Group(roomId).SendAsync("PlayerDisconnected", exception);
|
||||
|
||||
// set room status to discconnect
|
||||
room.Status = GameStatus.PlayerDisconnected;
|
||||
|
||||
// schedule the game to be ended if player does not return
|
||||
await ScheduleGameEnd(roomKVP, Context.UserIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
[HubMethodName("SetSymbol")]
|
||||
public async Task SetPlayerSymbol(User user, TicTacToeSymbol symbol)
|
||||
{
|
||||
// find the room
|
||||
var roomKVP = GameRooms.FirstOrDefault(e => (e.Value.Player1?.Id == user.Id) || (e.Value.Player2?.Id == user.Id));
|
||||
var room = roomKVP.Value;
|
||||
|
||||
if(room != null)
|
||||
{
|
||||
var roomId = roomKVP.Key.ToString();
|
||||
|
||||
// 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
|
||||
await Clients.Group(roomId).SendAsync("GameStart");
|
||||
}
|
||||
}
|
||||
|
||||
[HubMethodName("MakeMove")]
|
||||
public async Task MakeMoveAsync(TicTacToeMove move)
|
||||
{
|
||||
// find the room
|
||||
var roomKVP = GameRooms.FirstOrDefault(e => (e.Value.Player1?.Id == move.User.Id) || (e.Value.Player2?.Id == move.User.Id));
|
||||
var room = roomKVP.Value;
|
||||
|
||||
if (room != null)
|
||||
{
|
||||
var roomId = roomKVP.Key.ToString();
|
||||
|
||||
// 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.Board);
|
||||
|
||||
// TODO - win logic
|
||||
}
|
||||
}
|
||||
|
||||
private Task ScheduleGameEnd(KeyValuePair<Guid, GameRoom> roomKVP, string? userId)
|
||||
{
|
||||
System.Timers.Timer tmr = new System.Timers.Timer(TimeSpan.FromSeconds(60));
|
||||
tmr.Start();
|
||||
|
||||
tmr.Elapsed += async (sender, args) =>
|
||||
{
|
||||
var client = Clients.User(userId!);
|
||||
if (client == null)
|
||||
{
|
||||
// user never reconnected, end game
|
||||
roomKVP.Value.Status = GameStatus.NoWin;
|
||||
await Clients.Group(roomKVP.Key.ToString()).SendAsync("GameEnd", roomKVP.Value.Status);
|
||||
|
||||
GameRooms.Remove(roomKVP.Key);
|
||||
}
|
||||
};
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -71,5 +71,6 @@ app.UseAuthorization();
|
||||
app.MapControllers();
|
||||
|
||||
app.MapHub<ChatHub>("/chat");
|
||||
app.MapHub<TicTacToeHub>("/tttgame");
|
||||
|
||||
app.Run();
|
||||
|
||||
14
qtc-net-server/Schema/GameRoom.cs
Normal file
14
qtc-net-server/Schema/GameRoom.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using qtc_api.Enums;
|
||||
|
||||
namespace qtc_api.Schema
|
||||
{
|
||||
public class GameRoom
|
||||
{
|
||||
public GameStatus Status { get; set; }
|
||||
public TicTacToeBoard Board { get; set; } = new();
|
||||
public User? Player1 { get; set; }
|
||||
public TicTacToeSymbol P1Symbol { get; set; } = TicTacToeSymbol.Blank;
|
||||
public User? Player2 { get; set; }
|
||||
public TicTacToeSymbol P2Symbol { get; set; } = TicTacToeSymbol.Blank;
|
||||
}
|
||||
}
|
||||
17
qtc-net-server/Schema/TicTacToeBoard.cs
Normal file
17
qtc-net-server/Schema/TicTacToeBoard.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using qtc_api.Enums;
|
||||
|
||||
namespace qtc_api.Schema
|
||||
{
|
||||
public class TicTacToeBoard
|
||||
{
|
||||
public TicTacToeSymbol Square1 { get; set; } = TicTacToeSymbol.Blank;
|
||||
public TicTacToeSymbol Square2 { get; set; } = TicTacToeSymbol.Blank;
|
||||
public TicTacToeSymbol Square3 { get; set; } = TicTacToeSymbol.Blank;
|
||||
public TicTacToeSymbol Square4 { get; set; } = TicTacToeSymbol.Blank;
|
||||
public TicTacToeSymbol Square5 { get; set; } = TicTacToeSymbol.Blank;
|
||||
public TicTacToeSymbol Square6 { get; set; } = TicTacToeSymbol.Blank;
|
||||
public TicTacToeSymbol Square7 { get; set; } = TicTacToeSymbol.Blank;
|
||||
public TicTacToeSymbol Square8 { get; set; } = TicTacToeSymbol.Blank;
|
||||
public TicTacToeSymbol Square9 { get; set; } = TicTacToeSymbol.Blank;
|
||||
}
|
||||
}
|
||||
10
qtc-net-server/Schema/TicTacToeMove.cs
Normal file
10
qtc-net-server/Schema/TicTacToeMove.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using qtc_api.Enums;
|
||||
|
||||
namespace qtc_api.Schema
|
||||
{
|
||||
public class TicTacToeMove
|
||||
{
|
||||
public User User { get; set; } = new();
|
||||
public int Point { get; set; }
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user