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

feat: game configuration #228

Merged
merged 19 commits into from
Apr 14, 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
@@ -0,0 +1,57 @@
package lab.en2b.quizapi.commons.utils;

import lab.en2b.quizapi.game.Game;
import lab.en2b.quizapi.game.GameMode;
import lab.en2b.quizapi.questions.question.QuestionCategory;

import java.util.List;

import static lab.en2b.quizapi.game.GameMode.KIWI_QUEST;

public class GameModeUtils {
public static List<QuestionCategory> getQuestionCategoriesForGamemode(GameMode gamemode, List<QuestionCategory> questionCategoriesForCustom){
if(gamemode == null){
gamemode = KIWI_QUEST;
}
return switch (gamemode) {
case KIWI_QUEST -> List.of(QuestionCategory.ART, QuestionCategory.MUSIC, QuestionCategory.GEOGRAPHY);
case FOOTBALL_SHOWDOWN -> List.of(QuestionCategory.SPORTS);
case GEO_GENIUS -> List.of(QuestionCategory.GEOGRAPHY);
case VIDEOGAME_ADVENTURE -> List.of(QuestionCategory.VIDEOGAMES);
case ANCIENT_ODYSSEY -> List.of(QuestionCategory.MUSIC,QuestionCategory.ART);
case RANDOM -> List.of(QuestionCategory.values());
case CUSTOM -> questionCategoriesForCustom;
};
}
public static void setGamemodeParams(Game game){
switch(game.getGamemode()){
case KIWI_QUEST:
game.setRounds(9L);
game.setRoundDuration(30);
break;
case FOOTBALL_SHOWDOWN:
game.setRounds(9L);
game.setRoundDuration(30);
break;
case GEO_GENIUS:
game.setRounds(9L);
game.setRoundDuration(30);
break;
case VIDEOGAME_ADVENTURE:
game.setRounds(9L);
game.setRoundDuration(30);
break;
case ANCIENT_ODYSSEY:
game.setRounds(9L);
game.setRoundDuration(30);
break;
case RANDOM:
game.setRounds(9L);
game.setRoundDuration(30);
break;
default:
game.setRounds(9L);
game.setRoundDuration(30);
}
}
}
54 changes: 49 additions & 5 deletions api/src/main/java/lab/en2b/quizapi/game/Game.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import lab.en2b.quizapi.commons.user.User;
import lab.en2b.quizapi.commons.utils.GameModeUtils;
import lab.en2b.quizapi.game.dtos.CustomGameDto;
import lab.en2b.quizapi.questions.answer.Answer;
import lab.en2b.quizapi.questions.question.Question;
import lab.en2b.quizapi.questions.question.QuestionCategory;
import lombok.*;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;

import static lab.en2b.quizapi.game.GameMode.*;

@Entity
@Table(name = "games")
@NoArgsConstructor
Expand All @@ -20,7 +24,6 @@
@Setter
@Builder
public class Game {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Setter(AccessLevel.NONE)
Expand All @@ -35,7 +38,8 @@ public class Game {
@NonNull
private Integer roundDuration;
private boolean currentQuestionAnswered;

@Enumerated(EnumType.STRING)
private GameMode gamemode;
@ManyToOne
@NotNull
@JoinColumn(name = "user_id")
Expand All @@ -52,6 +56,19 @@ public class Game {
@OrderColumn
private List<Question> questions;
private boolean isGameOver;
@Enumerated(EnumType.STRING)
private List<QuestionCategory> questionCategoriesForCustom;

public Game(User user,GameMode gamemode,String lang, CustomGameDto gameDto){
this.user = user;
this.questions = new ArrayList<>();
this.actualRound = 0L;
setLanguage(lang);
if(gamemode == CUSTOM)
setCustomGameMode(gameDto);
else
setGameMode(gamemode);
}

public void newRound(Question question){
if(getActualRound() != 0){
Expand Down Expand Up @@ -110,16 +127,43 @@ public boolean answerQuestion(Long answerId){
return q.isCorrectAnswer(answerId);
}
public void setLanguage(String language){
if(language == null){
language = "en";
}
if(!isLanguageSupported(language))
throw new IllegalArgumentException("The language you provided is not supported");
this.language = language;
}
public void setCustomGameMode(CustomGameDto gameDto){
setRounds(gameDto.getRounds());
setRoundDuration(gameDto.getRoundDuration());
this.gamemode = CUSTOM;
setQuestionCategoriesForCustom(gameDto.getCategories());
}
public void setGameMode(GameMode gamemode){
if(gamemode == null){
gamemode = KIWI_QUEST;
}
this.gamemode = gamemode;
GameModeUtils.setGamemodeParams(this);
}

public void setQuestionCategoriesForCustom(List<QuestionCategory> questionCategoriesForCustom) {
if(gamemode != CUSTOM)
throw new IllegalStateException("You can't set custom categories for a non-custom gamemode!");
if(questionCategoriesForCustom == null || questionCategoriesForCustom.isEmpty())
throw new IllegalArgumentException("You can't set an empty list of categories for a custom gamemode!");
this.questionCategoriesForCustom = questionCategoriesForCustom;
}

public List<QuestionCategory> getQuestionCategoriesForGamemode(){
return GameModeUtils.getQuestionCategoriesForGamemode(gamemode,questionCategoriesForCustom);
}
private boolean isLanguageSupported(String language) {
return language.equals("en") || language.equals("es");
}

public boolean shouldBeGameOver() {
return getActualRound() >= getRounds() && !isGameOver;
return getActualRound() >= getRounds() && !isGameOver && currentRoundIsOver();
}
}
49 changes: 39 additions & 10 deletions api/src/main/java/lab/en2b/quizapi/game/GameController.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package lab.en2b.quizapi.game;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
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 jakarta.validation.Valid;
import lab.en2b.quizapi.game.dtos.*;
import lab.en2b.quizapi.questions.question.QuestionCategory;
import lab.en2b.quizapi.questions.question.dtos.QuestionResponseDto;
import lombok.RequiredArgsConstructor;
Expand All @@ -24,39 +25,50 @@ public class GameController {
@Operation(summary = "Starts new game", description = "Requests the API to create a new game for a given authentication (a player)")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully retrieved"),
@ApiResponse(responseCode = "400", description = "Given when: \n * language provided is not valid \n * gamemode provided is not valid \n * body is not provided with custom game", content = @io.swagger.v3.oas.annotations.media.Content),
@ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content),
})
@PostMapping("/new")
public ResponseEntity<GameResponseDto> newGame(Authentication authentication){
return ResponseEntity.ok(gameService.newGame(authentication));
@Parameters({
@Parameter(name = "lang", description = "The language of the game", example = "en"),
@Parameter(name = "gamemode", description = "The gamemode of the game", example = "KIWI_QUEST")
})
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "The custom game dto, only required if the gamemode is CUSTOM")
@PostMapping("/play")
public ResponseEntity<GameResponseDto> newGame(@RequestParam(required = false) String lang, @RequestParam(required=false) GameMode gamemode, @RequestBody(required = false) @Valid CustomGameDto customGameDto, Authentication authentication){
if(gamemode == GameMode.CUSTOM && customGameDto == null)
throw new IllegalArgumentException("Custom game mode requires a body");
return ResponseEntity.ok(gameService.newGame(lang,gamemode,customGameDto,authentication));
}

@Operation(summary = "Starts a new round", description = "Starts the round (asks a question and its possible answers to the API and start the timer) for a given authentication (a player)")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully retrieved"),
@ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content),
})
@Parameter(name = "id", description = "The id of the game to start the round for", example = "1")
@PostMapping("/{id}/startRound")
public ResponseEntity<GameResponseDto> startRound(@PathVariable Long id, Authentication authentication){
return ResponseEntity.ok(gameService.startRound(id, authentication));
}

@Operation(summary = "Starts a new round", description = "Gets the question and its possible answers from the API for a given authentication (a player)")
@Operation(summary = "Gets the current question", description = "Gets the question and its possible answers from the API for a given authentication (a player)")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully retrieved"),
@ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content),
})
@Parameter(name = "id", description = "The id of the game to get the current question for", example = "1")
@GetMapping("/{id}/question")
public ResponseEntity<QuestionResponseDto> getCurrentQuestion(@PathVariable Long id, Authentication authentication){
return ResponseEntity.ok(gameService.getCurrentQuestion(id, authentication));
}

@Operation(summary = "Starts a new round", description = "Starts the round (getting a question and its possible answers and start the timer) for a given authentication (a player)")
@Operation(summary = "Answers the question", description = "Answers the question for the current game")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully retrieved"),
@ApiResponse(responseCode = "400", description = "Not a valid answer", content = @io.swagger.v3.oas.annotations.media.Content),
@ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content),
})
@Parameter(name = "id", description = "The id of the game to answer the question for", example = "1")
@PostMapping("/{id}/answer")
public ResponseEntity<AnswerGameResponseDto> answerQuestion(@PathVariable Long id, @RequestBody GameAnswerDto dto, Authentication authentication){
return ResponseEntity.ok(gameService.answerQuestion(id, dto, authentication));
Expand All @@ -65,9 +77,10 @@ public ResponseEntity<AnswerGameResponseDto> answerQuestion(@PathVariable Long i
@Operation(summary = "Changing languages", description = "Changes the language of the game for a given authentication (a player) and a language supported. Changes may are applied on the next round.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully retrieved"),
@ApiResponse(responseCode = "400", description = "Not a valid answer", content = @io.swagger.v3.oas.annotations.media.Content),
@ApiResponse(responseCode = "400", description = "Not a valid language to change to", content = @io.swagger.v3.oas.annotations.media.Content),
@ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content),
})
@Parameter(name = "id", description = "The id of the game to change the language for", example = "1")
@PutMapping("/{id}/language")
public ResponseEntity<GameResponseDto> changeLanguage(@PathVariable Long id, @RequestParam String language, Authentication authentication){
return ResponseEntity.ok(gameService.changeLanguage(id, language, authentication));
Expand All @@ -78,12 +91,28 @@ public ResponseEntity<GameResponseDto> changeLanguage(@PathVariable Long id, @Re
@ApiResponse(responseCode = "200", description = "Successfully retrieved"),
@ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content),
})
@Parameter(name = "id", description = "The id of the game to get the summary for", example = "1")
@GetMapping("/{id}/details")
public ResponseEntity<GameResponseDto> getGameDetails(@PathVariable Long id, Authentication authentication){
return ResponseEntity.ok(gameService.getGameDetails(id, authentication));
}

@GetMapping("/questionCategories")
@Operation(summary = "Get the list of gamemodes a game can have")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully retrieved"),
@ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content)
})
@GetMapping("/gamemodes")
public ResponseEntity<List<GameModeDto>> getQuestionGameModes(){
return ResponseEntity.ok(gameService.getQuestionGameModes());
}

@Operation(summary = "Get the list of categories a game can have")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully retrieved"),
@ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content)
})
@GetMapping("/question-categories")
public ResponseEntity<List<QuestionCategory>> getQuestionCategories(){
return ResponseEntity.ok(gameService.getQuestionCategories());
}
Expand Down
12 changes: 12 additions & 0 deletions api/src/main/java/lab/en2b/quizapi/game/GameMode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package lab.en2b.quizapi.game;

public enum GameMode {
KIWI_QUEST,
FOOTBALL_SHOWDOWN,
GEO_GENIUS,
VIDEOGAME_ADVENTURE,
ANCIENT_ODYSSEY,
RANDOM,
CUSTOM
}

Loading