Skip to content

Commit

Permalink
Backend/enhancement/implement game similarity algorithm #612 (#636)
Browse files Browse the repository at this point in the history
* #612 implement GameSimilarityService

* #612 add similarity update to create game function

* #612 add similar games to game dto

* #612 update getFieldValues function

* #612 add updateSimilarGamesFields function

* #612 add updateSimilarGamesField endpoint

* #612 change set to mutableSet
  • Loading branch information
Legervad authored Dec 24, 2023
1 parent 6b45a48 commit bb296b6
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,11 @@ class GameController(
return ResponseEntity.ok(ResponseMessage(message = "Editing game was requested successfully."))
}

@PostMapping("/update-similar-games")
@ResponseStatus(HttpStatus.CREATED)
fun updateSimilarGamesFields(): ResponseEntity<ResponseMessage>{
gameService.updateSimilarGamesFields()
return ResponseEntity.ok(ResponseMessage("Games' similar games fields updated successfully!"))
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ class Game(
@ElementCollection(targetClass = GameGenre::class)
@Enumerated(EnumType.STRING)
@CollectionTable(name = "game_genres", joinColumns = [JoinColumn(name = "gameId")])
var genres: Set<GameGenre> = emptySet(),
var genres: MutableSet<GameGenre> = mutableSetOf(),

@ElementCollection(targetClass = GamePlatform::class)
@Enumerated(EnumType.STRING)
@CollectionTable(name = "game_platforms", joinColumns = [JoinColumn(name = "gameId")])
var platforms: Set<GamePlatform> = emptySet(),
var platforms: MutableSet<GamePlatform> = mutableSetOf(),

@Enumerated(EnumType.STRING)
var playerNumber: NumberOfPlayers = NumberOfPlayers.Empty,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ class RequestedEditingGame(
@ElementCollection(targetClass = GameGenre::class)
@Enumerated(EnumType.STRING)
@CollectionTable(name = "edited_game_genres", joinColumns = [JoinColumn(name = "gameId")])
var genres: Set<GameGenre> = emptySet(),
var genres: MutableSet<GameGenre> = mutableSetOf(),

@ElementCollection(targetClass = GamePlatform::class)
@Enumerated(EnumType.STRING)
@CollectionTable(name = "edited_game_platforms", joinColumns = [JoinColumn(name = "gameId")])
var platforms: Set<GamePlatform> = emptySet(),
var platforms: MutableSet<GamePlatform> = mutableSetOf(),

@Enumerated(EnumType.STRING)
var playerNumber: NumberOfPlayers = NumberOfPlayers.Empty,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ data class GameDTO(
var tags: List<TagDTO>,
var gamePicture: String? = null,
var status: String = "",
var isDeleted: Boolean = false
var isDeleted: Boolean = false,
var similarGames: List<GameDTO> = mutableListOf()
)
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ interface GameRepository: JpaRepository<Game, Long> {
fun findByStatus(status: GameStatus): List<Game>
fun findByTitleContainingOrDescriptionContaining(title: String, description: String): List<Game>

fun findByGameId(gameId: Long): Game
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,18 @@ class GameService(
private val userRepository: UserRepository,
private val recommendationService: RecommendationService,
private val reportRepository: ReportRepository,
val s3Service: S3Service
val s3Service: S3Service,
private val gameSimilarityService: GameSimilarityService
) {
fun createGame(sessionId: UUID, game: CreateGameRequest, image: MultipartFile?): Game {
val userId = sessionAuth.getUserIdFromSession(sessionId)
val user = userRepository.findById(userId).orElseThrow { UsernameNotFoundException("User not found") }

// Convert genre strings to GameGenre enum values
val genres = game.genres.map { GameGenre.valueOf(it) }.toSet()
val genres = game.genres.map { GameGenre.valueOf(it) }.toMutableSet()

// Convert platform strings to GamePlatform enum values
val platforms = game.platforms.map { GamePlatform.valueOf(it) }.toSet()
val platforms = game.platforms.map { GamePlatform.valueOf(it) }.toMutableSet()

val newGame = Game(
title = game.title,
Expand All @@ -54,8 +55,10 @@ class GameService(
user = user,
status = GameStatus.PENDING_APPROVAL,
)
gameSimilarityService.updateSimilarGamesField(newGame)
gameRepository.save(newGame) // save to get gameId for image name
image?.let { saveImageInS3AndImageURLInDBForGame(image, newGame) }

return gameRepository.save(newGame)
}

Expand All @@ -79,10 +82,10 @@ class GameService(
}

// Convert genre strings to GameGenre enum values
val genres = editedGame.genres.map { GameGenre.valueOf(it) }.toSet()
val genres = editedGame.genres.map { GameGenre.valueOf(it) }.toMutableSet()

// Convert platform strings to GamePlatform enum values
val platforms = editedGame.platforms.map { GamePlatform.valueOf(it) }.toSet()
val platforms = editedGame.platforms.map { GamePlatform.valueOf(it) }.toMutableSet()

val requestEditingGame = RequestedEditingGame(
gameId = gameId,
Expand Down Expand Up @@ -137,10 +140,10 @@ class GameService(
}

// Convert genre strings to GameGenre enum values
val genres = updatedGame.genres.map { GameGenre.valueOf(it) }.toSet()
val genres = updatedGame.genres.map { GameGenre.valueOf(it) }.toMutableSet()

// Convert platform strings to GamePlatform enum values
val platforms = updatedGame.platforms.map { GamePlatform.valueOf(it) }.toSet()
val platforms = updatedGame.platforms.map { GamePlatform.valueOf(it) }.toMutableSet()

game.title = updatedGame.title
game.description = updatedGame.description
Expand Down Expand Up @@ -279,8 +282,8 @@ class GameService(

game.title = requestedEditingGame.title
game.description = requestedEditingGame.description
game.genres = requestedEditingGame.genres
game.platforms = requestedEditingGame.platforms
game.genres = requestedEditingGame.genres.toHashSet()
game.platforms = requestedEditingGame.platforms.toHashSet()
game.playerNumber = requestedEditingGame.playerNumber
game.releaseYear = requestedEditingGame.releaseYear
game.universe = requestedEditingGame.universe
Expand Down Expand Up @@ -402,4 +405,8 @@ class GameService(
val reportedGameIds = reportedGames.map { it.reportedGame?.gameId }
return gameRepository.findAllById(reportedGameIds).filter { !it.isDeleted }
}

fun updateSimilarGamesFields(){
gameSimilarityService.updateAllSimilarGamesFields()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.gamelounge.backend.service

import com.gamelounge.backend.entity.Game
import com.gamelounge.backend.repository.GameRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import org.springframework.stereotype.Service

@Service
class GameSimilarityService(
private val gameRepository: GameRepository
) {

fun updateAllSimilarGamesFields() = runBlocking{
val allGames = gameRepository.findAll()

val deferredUpdates = allGames.map { game ->
async(Dispatchers.IO) {
updateSimilarGamesField(game, allGames)
gameRepository.save(game)
}
}
deferredUpdates.awaitAll()
}

fun updateSimilarGamesField(game: Game){ // This could be wrong
val allGames = gameRepository.findAll()
val similarGames = getSimilarGames(game, allGames)

game.similarGames = similarGames
}

fun updateSimilarGamesField(game: Game, allGames: List<Game>){ // This could be wrong
val similarGames = getSimilarGames(game, allGames)

game.similarGames = similarGames
}

fun getSimilarGames(referenceGame: Game, allGames: List<Game>): List<Game>{
val similarityMap = allGames
.filter { it != referenceGame }
.map { game -> game to calculateJaccardSimilarity(referenceGame, game) }
.sortedByDescending { it.second }

return similarityMap.take(7).map { it.first }
}

fun calculateJaccardSimilarity(referenceGame: Game, targetGame: Game): Int {
val game1FieldValues = getFieldValuesAsSet(referenceGame)
val game2FieldValues = getFieldValuesAsSet(targetGame)

return game1FieldValues.intersect(game2FieldValues).size
}

fun getFieldValuesAsSet(game: Game): HashSet<String>{
val valuesSet = hashSetOf<String>()

valuesSet.addAll(game.genres.map { it.name })
valuesSet.addAll(game.platforms.map { it.name })
valuesSet.add(game.playerNumber.name)
valuesSet.add(game.universe.name)
valuesSet.add(game.mechanics.name)

return valuesSet
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ object ConverterDTO {
downvotes = comment.downvotes,
)
}

fun convertBulkToCommentDTO(comments: List<Comment>) : List<CommentDTO> {
return comments.map { convertToCommentDTO(it) }
}
Expand Down Expand Up @@ -106,7 +107,8 @@ object ConverterDTO {
convertBulkToTagDTO(game.tags),
game.gamePicture,
(game.status ?: GameStatus.PENDING_APPROVAL).toString(),
game.isDeleted
game.isDeleted,
convertBulkToGameDTO(game.similarGames)
)
}

Expand Down

0 comments on commit bb296b6

Please sign in to comment.