Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fixed game #199

Merged
merged 18 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
@Log4j2
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CustomControllerAdvice extends ResponseEntityExceptionHandler {
@ExceptionHandler(InternalApiErrorException.class)
public ResponseEntity<String> handleInternalApiErrorException(InternalApiErrorException exception){
log.error(exception.getMessage(),exception);
return new ResponseEntity<>(exception.getMessage(),HttpStatus.SERVICE_UNAVAILABLE);
}
@ExceptionHandler(InvalidAuthenticationException.class)
public ResponseEntity<String> handleInvalidAuthenticationException(InvalidAuthenticationException exception){
log.error(exception.getMessage(),exception);
Expand All @@ -28,7 +33,11 @@ public ResponseEntity<String> handleNoSuchElementException(NoSuchElementExceptio
log.error(exception.getMessage(),exception);
return new ResponseEntity<>(exception.getMessage(),HttpStatus.NOT_FOUND);
}

@ExceptionHandler(IllegalStateException.class)
public ResponseEntity<String> handleIllegalStateException(IllegalStateException exception){
log.error(exception.getMessage(),exception);
return new ResponseEntity<>(exception.getMessage(),HttpStatus.CONFLICT);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException exception){
log.error(exception.getMessage(),exception);
Expand Down Expand Up @@ -60,7 +69,7 @@ public ResponseEntity<String> handleInternalAuthenticationServiceException(Inter
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception exception){
log.error(exception.getMessage(),exception);
return new ResponseEntity<>(exception.getMessage(),HttpStatus.INTERNAL_SERVER_ERROR);
return new ResponseEntity<>("Internal Server Error",HttpStatus.INTERNAL_SERVER_ERROR);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package lab.en2b.quizapi.commons.exceptions;

public class InternalApiErrorException extends RuntimeException{
public InternalApiErrorException(String message) {
super(message);
}
}
28 changes: 16 additions & 12 deletions api/src/main/java/lab/en2b/quizapi/game/Game.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import lab.en2b.quizapi.commons.user.User;
import lab.en2b.quizapi.questions.answer.Answer;
import lab.en2b.quizapi.questions.question.Question;
import lab.en2b.quizapi.questions.question.QuestionRepository;
import lombok.*;

import java.time.LocalDateTime;
Expand All @@ -25,10 +24,10 @@ public class Game {
@Setter(AccessLevel.NONE)
private Long id;

private int rounds = 9;
private int actualRound = 0;
private Long rounds = 9L;
private Long actualRound = 0L;

private int correctlyAnsweredQuestions = 0;
private Long correctlyAnsweredQuestions = 0L;
private String language;
private LocalDateTime roundStartTime;
@NonNull
Expand Down Expand Up @@ -56,8 +55,8 @@ public void newRound(Question question){
if(getActualRound() != 0){
if (isGameOver())
throw new IllegalStateException("You can't start a round for a finished game!");
//if(!currentQuestionAnswered)
// throw new IllegalStateException("You can't start a new round when the current round is not over yet!");
if(!currentRoundIsOver())
throw new IllegalStateException("You can't start a new round when the current round is not over yet!");
}

setCurrentQuestionAnswered(false);
Expand All @@ -71,14 +70,14 @@ private void increaseRound(){
}

public boolean isGameOver(){
return getActualRound() > getRounds();
return isGameOver && getActualRound() >= getRounds();
}

public boolean isLastRound(){
return getActualRound() >= getRounds();
}

public Question getCurrentQuestion() {
if(getRoundStartTime() == null){
throw new IllegalStateException("The round is not active!");
}
if(currentRoundIsOver())
throw new IllegalStateException("The current round is over!");
if(isGameOver())
Expand All @@ -91,10 +90,10 @@ private boolean currentRoundIsOver(){
}

private boolean roundTimeHasExpired(){
return LocalDateTime.now().isAfter(getRoundStartTime().plusSeconds(getRoundDuration()));
return getRoundStartTime()!= null && LocalDateTime.now().isAfter(getRoundStartTime().plusSeconds(getRoundDuration()));
}

public void answerQuestion(Long answerId, QuestionRepository questionRepository){
public boolean answerQuestion(Long answerId){
if(currentRoundIsOver())
throw new IllegalStateException("You can't answer a question when the current round is over!");
if (isGameOver())
Expand All @@ -106,6 +105,7 @@ public void answerQuestion(Long answerId, QuestionRepository questionRepository)
setCorrectlyAnsweredQuestions(getCorrectlyAnsweredQuestions() + 1);
}
setCurrentQuestionAnswered(true);
return q.isCorrectAnswer(answerId);
}
public void setLanguage(String language){
if(!isLanguageSupported(language))
Expand All @@ -116,4 +116,8 @@ public void setLanguage(String language){
private boolean isLanguageSupported(String language) {
return language.equals("en") || language.equals("es");
}

public boolean shouldBeGameOver() {
return getActualRound() >= getRounds() && !isGameOver;
}
}
3 changes: 2 additions & 1 deletion api/src/main/java/lab/en2b/quizapi/game/GameController.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import lab.en2b.quizapi.game.dtos.AnswerGameResponseDto;
import lab.en2b.quizapi.game.dtos.GameAnswerDto;
import lab.en2b.quizapi.game.dtos.GameResponseDto;
import lab.en2b.quizapi.questions.question.QuestionCategory;
Expand Down Expand Up @@ -57,7 +58,7 @@ public ResponseEntity<QuestionResponseDto> getCurrentQuestion(@PathVariable Long
@ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content),
})
@PostMapping("/{id}/answer")
public ResponseEntity<GameResponseDto> answerQuestion(@PathVariable Long id, @RequestBody GameAnswerDto dto, Authentication authentication){
public ResponseEntity<AnswerGameResponseDto> answerQuestion(@PathVariable Long id, @RequestBody GameAnswerDto dto, Authentication authentication){
return ResponseEntity.ok(gameService.answerQuestion(id, dto, authentication));
}

Expand Down
88 changes: 58 additions & 30 deletions api/src/main/java/lab/en2b/quizapi/game/GameService.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package lab.en2b.quizapi.game;

import lab.en2b.quizapi.commons.user.UserService;
import lab.en2b.quizapi.game.dtos.AnswerGameResponseDto;
import lab.en2b.quizapi.game.dtos.GameAnswerDto;
import lab.en2b.quizapi.game.dtos.GameResponseDto;
import lab.en2b.quizapi.game.mappers.GameResponseDtoMapper;
Expand All @@ -14,10 +15,12 @@
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

@Service
@RequiredArgsConstructor
Expand All @@ -29,23 +32,38 @@ public class GameService {
private final QuestionRepository questionRepository;
private final QuestionResponseDtoMapper questionResponseDtoMapper;
private final StatisticsRepository statisticsRepository;

@Transactional
public GameResponseDto newGame(Authentication authentication) {
if (gameRepository.findActiveGameForUser(userService.getUserByAuthentication(authentication).getId()).isPresent()){
return gameResponseDtoMapper.apply(gameRepository.findActiveGameForUser(userService.getUserByAuthentication(authentication).getId()).get());
Optional<Game> game = gameRepository.findActiveGameForUser(userService.getUserByAuthentication(authentication).getId());

if (game.isPresent()){
if (game.get().shouldBeGameOver()){
game.get().setGameOver(true);
gameRepository.save(game.get());
saveStatistics(game.get());
}else{
return gameResponseDtoMapper.apply(game.get());
}
}
Game g = gameRepository.save(Game.builder()
return gameResponseDtoMapper.apply(gameRepository.save(Game.builder()
.user(userService.getUserByAuthentication(authentication))
.questions(new ArrayList<>())
.rounds(9)
.correctlyAnsweredQuestions(0)
.rounds(9L)
.actualRound(0L)
.correctlyAnsweredQuestions(0L)
.roundDuration(30)
.language("en")
.build());
return gameResponseDtoMapper.apply(g);
.build()));
}

public GameResponseDto startRound(Long id, Authentication authentication) {
Game game = gameRepository.findByIdForUser(id, userService.getUserByAuthentication(authentication).getId()).orElseThrow();
if (game.shouldBeGameOver()){
game.setGameOver(true);
gameRepository.save(game);
saveStatistics(game);
}
game.newRound(questionService.findRandomQuestion(game.getLanguage()));

return gameResponseDtoMapper.apply(gameRepository.save(game));
Expand All @@ -56,43 +74,53 @@ public QuestionResponseDto getCurrentQuestion(Long id, Authentication authentica
return questionResponseDtoMapper.apply(game.getCurrentQuestion());
}

public GameResponseDto answerQuestion(Long id, GameAnswerDto dto, Authentication authentication){
@Transactional
public AnswerGameResponseDto answerQuestion(Long id, GameAnswerDto dto, Authentication authentication){
Game game = gameRepository.findByIdForUser(id, userService.getUserByAuthentication(authentication).getId()).orElseThrow();
game.answerQuestion(dto.getAnswerId(), questionRepository);
boolean wasCorrect = game.answerQuestion(dto.getAnswerId());

if (game.isLastRound()){
if (game.shouldBeGameOver()){
game.setGameOver(true);
gameRepository.save(game);
}
if (game.isGameOver()){
if (statisticsRepository.findByUserId(game.getUser().getId()).isPresent()){
Statistics statistics = statisticsRepository.findByUserId(game.getUser().getId()).get();
statistics.updateStatistics(Long.valueOf(game.getCorrectlyAnsweredQuestions()),
Long.valueOf(game.getQuestions().size()-game.getCorrectlyAnsweredQuestions()),
Long.valueOf(game.getRounds()));
statisticsRepository.save(statistics);
} else {
Statistics statistics = Statistics.builder()
.user(game.getUser())
.correct(Long.valueOf(game.getCorrectlyAnsweredQuestions()))
.wrong(Long.valueOf(game.getQuestions().size()-game.getCorrectlyAnsweredQuestions()))
.total(Long.valueOf(game.getRounds()))
.build();
statisticsRepository.save(statistics);
}
saveStatistics(game);
}

return gameResponseDtoMapper.apply(game);
return new AnswerGameResponseDto(wasCorrect);
}
private void saveStatistics(Game game){
if (statisticsRepository.findByUserId(game.getUser().getId()).isPresent()){
Statistics statistics = statisticsRepository.findByUserId(game.getUser().getId()).get();
statistics.updateStatistics(game.getCorrectlyAnsweredQuestions(),
game.getQuestions().size()-game.getCorrectlyAnsweredQuestions(),
game.getRounds());
statisticsRepository.save(statistics);
} else {
Statistics statistics = Statistics.builder()
.user(game.getUser())
.correct(game.getCorrectlyAnsweredQuestions())
.wrong(game.getQuestions().size()-game.getCorrectlyAnsweredQuestions())
.total(game.getRounds())
.build();
statisticsRepository.save(statistics);
}
}

public GameResponseDto changeLanguage(Long id, String language, Authentication authentication) {
Game game = gameRepository.findByIdForUser(id, userService.getUserByAuthentication(authentication).getId()).orElseThrow();
if(game.isGameOver()){
throw new IllegalStateException("Cannot change language after the game is over!");
}
game.setLanguage(language);
return gameResponseDtoMapper.apply(gameRepository.save(game));
}

public GameResponseDto getGameDetails(Long id, Authentication authentication) {
return gameResponseDtoMapper.apply(gameRepository.findByIdForUser(id, userService.getUserByAuthentication(authentication).getId()).orElseThrow());
Game game = gameRepository.findByIdForUser(id, userService.getUserByAuthentication(authentication).getId()).orElseThrow();
if (game.shouldBeGameOver()){
game.setGameOver(true);
gameRepository.save(game);
saveStatistics(game);
}
return gameResponseDtoMapper.apply(game);
}

public List<QuestionCategory> getQuestionCategories() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package lab.en2b.quizapi.game.dtos;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@Data
@NoArgsConstructor
public class AnswerGameResponseDto {
@JsonProperty("was_correct")
private boolean wasCorrect;
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,23 @@ public class GameResponseDto {
private UserResponseDto user;

@Schema(description = "Total rounds for the game", example = "9")
private int rounds;
private Long rounds;

@Schema(description = "Actual round for the game", example = "3")
private int actualRound;
@JsonProperty("actual_round")
private Long actualRound;

@Schema(description = "Number of correct answered questions", example = "2")
@JsonProperty("correctly_answered_questions")
private int correctlyAnsweredQuestions;
private Long correctlyAnsweredQuestions;

@Schema(description = "Moment when the timer has started", example = "LocalDateTime.now()")
@JsonProperty("round_start_time")
private LocalDateTime roundStartTime;

@Schema(description = "Number of seconds for the player to answer the question", example = "20")
@JsonProperty("round_duration")
private int roundDuration;
private Integer roundDuration;

@Schema(description = "Whether the game has finished or not", example = "true")
private boolean isGameOver;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package lab.en2b.quizapi.questions.question;

import lab.en2b.quizapi.commons.exceptions.InternalApiErrorException;
import lab.en2b.quizapi.questions.answer.Answer;
import lab.en2b.quizapi.questions.answer.AnswerRepository;
import lab.en2b.quizapi.questions.answer.dtos.AnswerDto;
Expand All @@ -21,6 +22,12 @@ public class QuestionService {
private final QuestionRepository questionRepository;
private final QuestionResponseDtoMapper questionResponseDtoMapper;

/**
* Answer a question
* @param id The id of the question
* @param answerDto The answer dto
* @return The response dto
*/
public AnswerCheckResponseDto answerQuestion(Long id, AnswerDto answerDto) {
Question question = questionRepository.findById(id).orElseThrow();
if(question.getCorrectAnswer().getId().equals(answerDto.getAnswerId())){
Expand All @@ -35,20 +42,22 @@ else if(question.getAnswers().stream().noneMatch(i -> i.getId().equals(answerDto
}

public QuestionResponseDto getRandomQuestion(String lang) {
if (lang==null || lang.isBlank()) {
lang = "en";
}
Question q = questionRepository.findRandomQuestion(lang);
loadAnswers(q);

return questionResponseDtoMapper.apply(q);
return questionResponseDtoMapper.apply(findRandomQuestion(lang));
}


/**
* Find a random question for the specified language
* @param lang The language to find the question for
* @return The random question
*/
public Question findRandomQuestion(String lang){
if (lang==null || lang.isBlank()) {
lang = "en";
}
Question q = questionRepository.findRandomQuestion(lang);
if(q==null) {
throw new InternalApiErrorException("No questions found for the specified language!");
}
loadAnswers(q);
return q;
}
Expand All @@ -62,7 +71,8 @@ public QuestionResponseDto getQuestionById(Long id) {
* Load the answers for a question (The distractors and the correct one)
* @param question The question to load the answers for
*/
public void loadAnswers(Question question) {
//TODO: CHAPUZAS, FIXEAR ESTO
private void loadAnswers(Question question) {
// Create the new answers list with the distractors
List<Answer> answers = new ArrayList<>(QuestionHelper.getDistractors(answerRepository, question));
// Add the correct
Expand Down
3 changes: 3 additions & 0 deletions api/src/main/java/lab/en2b/quizapi/statistics/Statistics.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ public class Statistics {
private User user;

public Long getCorrectRate() {
if(total == 0){
return 0L;
}
return (correct * 100) / total;
}

Expand Down
Loading