Skip to content

Commit

Permalink
Update quizzer backend to support answer question functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
Waterball12 committed Feb 24, 2021
1 parent 90f7b06 commit ae5eec5
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 38 deletions.
26 changes: 19 additions & 7 deletions server/src/ApiGateways/WebSocket/Web.Socket/Hubs/QuizzerGateway.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,27 @@ public override async Task OnConnectedAsync()
if (!ulong.TryParse(queryRoomId, out ulong roomId))
throw new ArgumentException("Room Id should be ulong type");

var exist = await _service.QuizExist(roomId);
var game = await _service.QuizExist(roomId); // TODO maybe changing it to simple Get request

if (exist == null)
if (game == null)
{
await Clients.Caller.SendAsync("Error", "Room does not exist");
Context.Abort();
return;
}

await _service.JoinGame(roomId, Context.User?.Identity?.Name ?? Context.ConnectionId);
// Temporary way to understand if its the owner
if (game.Users.Count <= 0)
game.IsOwner = true;

// Reply with connected
await Clients.Caller.SendAsync("Connected", game);
await Clients.Caller.SendAsync("Ready", Context.ConnectionId);

// Add user to the group
await Groups.AddToGroupAsync(Context.ConnectionId, roomId.ToString());
// Add user to the game
await _service.JoinGame(roomId, Context.ConnectionId);

await base.OnConnectedAsync();
}
Expand All @@ -49,11 +61,11 @@ public async Task NextQuestion(ulong id)
{
await _service.NextQuestion(id);
}

public Task SendMessage(string user, string message)

[HubMethodName("SubmitAnswer")]
public async Task SubmitAnswer(ulong id, string answer)
{
return Clients.All.SendAsync("ReceivedMessage", user, message);
await _service.SubmitAnswer(id, Context.ConnectionId, answer);
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ public QuizService(HttpClient http, IConfiguration config)
_config = config;
}

public Task<QuizExistResponse> QuizExist(ulong id)
public Task<QuizCreatedResponse> QuizExist(ulong id)
{
return GrpcCallerService.CallService(_config.GetValue<string>("QuizGrpcApi"), async channel =>
{
var client = new Quizer.QuizerClient(channel);

return await client.QuizExistAsync(new QuizExistRequest()
return await client.GetQuizAsync(new GetQuizRequest()
{
Id = id
});
Expand Down Expand Up @@ -56,6 +56,20 @@ public Task JoinGame(ulong id, string user)
});
});
}
public Task SubmitAnswer(ulong id, string user, string answer)
{
return GrpcCallerService.CallService(_config.GetValue<string>("QuizGrpcApi"), async channel =>
{
var client = new Quizer.QuizerClient(channel);

return await client.SubmitAnswerAsync(new SubmitAnswerRequest()
{
Answer = answer,
UserId = user,
Id = id
});
});
}

public Task NextQuestion(ulong id)
{
Expand Down
56 changes: 43 additions & 13 deletions server/src/Services/Quizer/Quiz.API/Services/QuizerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,34 +20,64 @@ public QuizerService(QuizManager manager)
_manager = manager;
}

public override Task<Empty> JoinGame(JoinGameRequest request, ServerCallContext context)
public override async Task<Empty> JoinGame(JoinGameRequest request, ServerCallContext context)
{
_manager.JoinGame(request.Id, request.User);
await _manager.JoinGame(request.Id, request.User).ConfigureAwait(false);

return Task.FromResult(new Empty());
return new Empty();
}

public override Task<QuizExistResponse> QuizExist(QuizExistRequest request, ServerCallContext context)
public override Task<QuizCreatedResponse> GetQuiz(GetQuizRequest request, ServerCallContext context)
{
try
{
var quiz = _manager.TryGetQuiz(request.Id);
var game = _manager.TryGetQuiz(request.Id);

return Task.FromResult(new QuizExistResponse()
return Task.FromResult(new QuizCreatedResponse()
{
Id = game.Id,
Quiz = new QuizData()
{
Description = quiz.Quiz.Description,
ImageUrl = quiz.Quiz.ImageUrl,
Title = quiz.Quiz.Title
}
Title = game.Quiz.Title,
Description = game.Quiz.Description,
ImageUrl = game.Quiz.ImageUrl,
Questions = { } // TODO finish mapping this model
},
Users = { game.Users.Select(x => x.Id)}
});
}
catch (GameNotFoundException)
{
context.Status = new Status(StatusCode.NotFound, "Quiz does not exist");

return Task.FromResult(new QuizExistResponse());
return Task.FromResult(new QuizCreatedResponse());
}
}

public override Task<Empty> SubmitAnswer(SubmitAnswerRequest request, ServerCallContext context)
{
try
{
var game = _manager.TryGetQuiz(request.Id);

var questions = game.Quiz.Questions[game.CurrentQuestion - 1];

var answer = questions?.Answer.FirstOrDefault(x => x.Description == request.Answer);

if (answer != null && answer.IsCorrect)
{
var player = game.Users.FirstOrDefault(x => x.Id == request.UserId);

if (player != null) player.Score += new Random().Next(5, 20); // XD
}

return Task.FromResult(new Empty());
}
catch (GameNotFoundException)
{
context.Status = new Status(StatusCode.NotFound, "Quiz does not exist");

return Task.FromResult(new Empty());
}
}

Expand All @@ -58,7 +88,6 @@ public override Task<QuizCreatedResponse> CreateGame(QuizCreateRequest request,
Title = request.Quiz.Title,
Description = request.Quiz.Description,
ImageUrl = request.Quiz.ImageUrl,
Users = new List<string>(),
Questions = request.Quiz.Questions.Select(x => new Question()
{
Timeout = x.Timeout,
Expand All @@ -79,7 +108,8 @@ public override Task<QuizCreatedResponse> CreateGame(QuizCreateRequest request,
{
Quiz = quiz,
Id = id,
Started = DateTime.UtcNow
Started = DateTime.UtcNow,
Users = new List<Player>()
};

var result = _manager.CreateNew(game);
Expand Down
78 changes: 62 additions & 16 deletions server/src/Services/Quizer/Quiz.Infrastructure/QuizManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Threading.Tasks;
using MassTransit;
using Quizzer.Domain.Entities;
using Quizzer.Domain.Enums;
using Quizzer.Domain.Events;
using Quizzer.Domain.Exceptions;

Expand All @@ -20,13 +21,17 @@ public QuizManager(IBusControl bus)

private readonly ConcurrentDictionary<ulong, QuizGame> _runningGame = new ();

private readonly ConcurrentDictionary<ulong, Quiz> _runningQuiz = new();

//public bool Add(string game) => _runningGame.Add(userId);
//public bool Remove(ulong userId) => _runningGame.TryRemove(userId);
public void Clear() => _runningGame.Clear();

public QuizGame TryGetQuiz(ulong id)
/// <summary>
/// Tries to get the quiz based on the given id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
/// <exception cref="GameNotFoundException">Thrown when the game is not found</exception>
public QuizGame TryGetQuiz(ulong id) // TODO find a better name since Try indicate it should not throw an exception
{
if (!_runningGame.TryGetValue(id, out QuizGame game))
{
Expand All @@ -41,11 +46,21 @@ public QuizGame TryGetQuiz(ulong id)
/// </summary>
/// <param name="id"></param>
/// <param name="user"></param>
public void JoinGame(ulong id, string user)
public async Task JoinGame(ulong id, string user)
{
var game = TryGetQuiz(id);

game.Quiz.Users.Add(user);
game.Users.Add(new Player()
{
Id = user,
Score = 0
});

await _bus.Publish(new UserJoinedGameEvent()
{
UserId = user,
GameId = id
});
}

/// <summary>
Expand All @@ -69,58 +84,89 @@ public QuizGame CreateNew(QuizGame game)
public QuizGame EndGame(ulong id)
{
return TryGetQuiz(id);

// TODO end the game
}

/// <summary>
/// Starts a quiz game based on the given id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
/// <exception cref="Exception">Thrown when the game is in a state different than IDLE</exception>
public async Task Start(ulong id)
{
var game = TryGetQuiz(id);

_runningQuiz.TryAdd(id, game.Quiz);
if (game.Status != GameStatus.Idle)
throw new Exception("Expected game to be idle but was in a different state"); // TODO add a custom exception

game.Status = GameStatus.Running;

var gameStartedEvent = new GameStartedEvent()
{
GameId = id,
Users = game.Quiz.Users
Users = game.Users.Select(x => x.Id).ToList(),
Status = game.Status,
CurrentQuestion = game.CurrentQuestion
};

await _bus.Publish(gameStartedEvent);

// TODO find a better solution
await Task.Delay(500);

await StartQuestion(id, game.CurrentQuestion);
}

public async Task<Question> StartQuestion(ulong quizId, int index)
/// <summary>
/// Start the given question in the specified game id
/// </summary>
/// <param name="gameId"></param>
/// <param name="index"></param>
/// <returns></returns>
public async Task<Question> StartQuestion(ulong gameId, int index)
{
var quiz = TryGetQuiz(quizId);
var game = TryGetQuiz(gameId);

if (quiz.Quiz.Questions.Count <= index)
if (game.Quiz.Questions.Count <= index)
{
await _bus.Publish(new GameEndedEvent() {GameId = quizId});
// End the game and remove it from running games
game.Status = GameStatus.Ended;
await _bus.Publish(new GameEndedEvent() {Game = game});
_runningGame.TryRemove(game.Id, out _);
return null;
}

var question = quiz.Quiz.Questions[index];
// TODO what if its out of index?
var question = game.Quiz.Questions[index];

if (question == null) return null; // Throw exception
if (question == null) return null;

quiz.CurrentQuestion = quiz.CurrentQuestion += 1;
// TODO check if this is really necessary
game.CurrentQuestion = game.CurrentQuestion += 1;

var questionStartedEvent = new QuestionStartedEvent()
{
Question = question.Title,
Answers = question.Answer.Select(x => x.Description).ToList()
Answers = question.Answer.Select(x => x.Description).ToList(),
GameId = gameId,
CurrentQuestion = game.CurrentQuestion,
EndAt = DateTimeOffset.UtcNow.AddSeconds(16) // TODO use the question timeout instead
};

await _bus.Publish(questionStartedEvent);

// TODO add a scheduler
_ = Task.Run(async () =>
{
// TODO respect timeout from the question entity
await Task.Delay(TimeSpan.FromSeconds(15));

await _bus.Publish(new QuestionEndedEvent()
{
CorrectAnswer = question.Answer.Where(x => x.IsCorrect).Select(x => x.Description).ToList()
Answers = question.Answer,
GameId = gameId
});
});

Expand Down

0 comments on commit ae5eec5

Please sign in to comment.