Skip to content

Commit

Permalink
Merge pull request #121 from Poeschl/add-victory
Browse files Browse the repository at this point in the history
🚧 Add victory gamestate to backend
  • Loading branch information
Poeschl authored May 29, 2024
2 parents 3f38e98 + 603c962 commit 7ad1ef3
Show file tree
Hide file tree
Showing 19 changed files with 123 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,17 @@ class DummyBots(

private val dummyUser1: User = userRepository.findByUsername("dummy1") ?: userRepository.save(User(null, "dummy1", DUMMY_PASSWORD))
private val dummyUser2: User = userRepository.findByUsername("dummy2") ?: userRepository.save(User(null, "dummy2", DUMMY_PASSWORD))
private val robot1 = robotRepository.findRobotByUser(dummyUser1) ?: robotRepository.save(Robot(null, Color.randomColor(), dummyUser1))
private val robot2 = robotRepository.findRobotByUser(dummyUser2) ?: robotRepository.save(Robot(null, Color.randomColor(), dummyUser2))
private val dummyUser3: User = userRepository.findByUsername("dummy3") ?: userRepository.save(User(null, "dummy3", DUMMY_PASSWORD))
private val robot1 = robotRepository.findRobotByUser(dummyUser1) ?: robotRepository.save(Robot(null, Color.randomColor(), 1, dummyUser1))
private val robot2 = robotRepository.findRobotByUser(dummyUser2) ?: robotRepository.save(Robot(null, Color.randomColor(), 0, dummyUser2))
private val robot3 = robotRepository.findRobotByUser(dummyUser3) ?: robotRepository.save(Robot(null, Color.randomColor(), 0, dummyUser3))

@Scheduled(fixedRate = 1000, timeUnit = TimeUnit.MILLISECONDS)
fun dummyRobots() {
if (gameStateService.isInState(GameState.WAIT_FOR_PLAYERS)) {
gameHandler.registerRobotForNextGame(robot1.id!!)
gameHandler.registerRobotForNextGame(robot2.id!!)
gameHandler.registerRobotForNextGame(robot3.id!!)
} else if (gameStateService.isInState(GameState.WAIT_FOR_ACTION)) {
val activeRobot1 = gameHandler.getActiveRobot(robot1.id!!)!!
val activeRobot2 = gameHandler.getActiveRobot(robot2.id!!)!!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import xyz.poeschl.roborush.exceptions.PositionOutOfMapException
import xyz.poeschl.roborush.gamelogic.actions.RobotAction
import xyz.poeschl.roborush.gamelogic.internal.MapHandler
import xyz.poeschl.roborush.gamelogic.internal.RobotHandler
import xyz.poeschl.roborush.models.*
import xyz.poeschl.roborush.models.ActiveRobot
import xyz.poeschl.roborush.models.Game
import xyz.poeschl.roborush.models.GameTimeouts
import xyz.poeschl.roborush.models.Position
import xyz.poeschl.roborush.models.settings.SettingKey
import xyz.poeschl.roborush.repositories.Map
import xyz.poeschl.roborush.repositories.Tile
Expand Down Expand Up @@ -41,6 +44,10 @@ class GameHandler(
return mapHandler.getTileAtPosition(position)
}

fun getTargetPosition(): Position {
return mapHandler.getTargetPosition()
}

/**
* Will check the position for a valid movement.
* For an incorrect movement exceptions are thrown.
Expand Down Expand Up @@ -78,6 +85,10 @@ class GameHandler(
return robotHandler.getActiveRobot(robotId)
}

fun wonTheCurrentRound(robot: ActiveRobot) {
robotHandler.wonTheCurrentRound(robot)
}

fun nextActionForRobot(robotId: Long, action: RobotAction<*>) {
val activeRobot = robotHandler.setNextMove(robotId, this, action)
activeRobot?.let { websocketController.sendUserRobotData(activeRobot) }
Expand Down Expand Up @@ -130,7 +141,8 @@ class GameHandler(
waitForPlayers = configService.getDurationSetting(SettingKey.TIMEOUT_WAIT_FOR_PLAYERS).value.inWholeMilliseconds,
waitForAction = configService.getDurationSetting(SettingKey.TIMEOUT_WAIT_FOR_ACTION).value.inWholeMilliseconds,
gameEnd = configService.getDurationSetting(SettingKey.TIMEOUT_GAME_END).value.inWholeMilliseconds
)
),
nameOfWinningRobot = robotHandler.getWinningRobot()?.user?.username
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,17 @@ class GameLoop(
} else {
LOGGER.debug("Execute robot actions")
gameHandler.executeAllRobotMoves()
gameStateService.setGameState(GameState.WAIT_FOR_ACTION)

val winningRobot = gameHandler.getActiveRobots().find { robot ->
robot.position == gameHandler.getTargetPosition()
}

if (winningRobot != null) {
gameHandler.wonTheCurrentRound(winningRobot)
gameStateService.setGameState(GameState.ENDED)
} else {
gameStateService.setGameState(GameState.WAIT_FOR_ACTION)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package xyz.poeschl.roborush.gamelogic.internal

import org.slf4j.LoggerFactory
import org.springframework.cache.annotation.CacheEvict
import xyz.poeschl.roborush.configuration.GameLogic
import xyz.poeschl.roborush.exceptions.GameStateException
import xyz.poeschl.roborush.gamelogic.GameHandler
Expand All @@ -10,6 +11,7 @@ import xyz.poeschl.roborush.gamelogic.actions.MoveAction
import xyz.poeschl.roborush.gamelogic.actions.RobotAction
import xyz.poeschl.roborush.models.ActiveRobot
import xyz.poeschl.roborush.models.Position
import xyz.poeschl.roborush.repositories.Robot
import xyz.poeschl.roborush.repositories.RobotRepository

@GameLogic
Expand All @@ -26,6 +28,10 @@ class RobotHandler(

private val activeRobots = mutableSetOf<ActiveRobot>()

private var winningRobot: Robot? = null

fun getWinningRobot() = winningRobot

fun registerRobotForGame(robotId: Long, startPosition: Position): ActiveRobot? {
if (!gameStateService.isInState(GameState.WAIT_FOR_PLAYERS)) {
throw GameStateException("Robot registration is only possible during 'Preparation' stage!")
Expand Down Expand Up @@ -54,6 +60,7 @@ class RobotHandler(

fun clearActiveRobots() {
activeRobots.clear()
winningRobot = null
}

fun setNextMove(robotId: Long, gameHandler: GameHandler, nextAction: RobotAction<*>): ActiveRobot? {
Expand Down Expand Up @@ -117,4 +124,13 @@ class RobotHandler(

return positionsAfterNextActions.none { it == position }
}

@CacheEvict(cacheNames = ["gameInfoCache"], allEntries = true)
fun wonTheCurrentRound(robot: ActiveRobot) {
val actualRobot = robotRepository.findById(robot.id)
actualRobot.ifPresent {
it.score += 1
winningRobot = robotRepository.save(it)
}
}
}
3 changes: 2 additions & 1 deletion backend/src/main/kotlin/xyz/poeschl/roborush/models/Game.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ data class Game(
val currentTurn: Int,
val targetPosition: Position?,
val solarChargePossible: Boolean,
val gameTimeoutsInMillis: GameTimeouts
val gameTimeoutsInMillis: GameTimeouts,
val nameOfWinningRobot: String?
)

data class GameTimeouts(val waitForPlayers: Long, val waitForAction: Long, val gameEnd: Long)
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ interface RobotRepository : CrudRepository<Robot, Long> {
data class Robot(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(insertable = false) val id: Long?,
@Column(name = "color") val color: Color,
@Column(name = "score") var score: Long,
@OneToOne val user: User
)
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class UserDetailsService(
fun registerNewUser(username: String, password: String) {
val encodedPassword = passwordEncoder.encode(password)
val user = userRepository.save(User(username, encodedPassword))
robotRepository.save(Robot(null, Color.randomColor(), user))
robotRepository.save(Robot(null, Color.randomColor(), 0, user))
}

fun loadUserByToken(token: String): UserDetails? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ CREATE TABLE robots
id BIGINT UNIQUE DEFAULT nextval('robot_id_seq'::regclass) NOT NULL,
color VARCHAR(12) NOT NULL,
user_id BIGINT NOT NULL,
score BIGINT DEFAULT 0 NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (user_id) REFERENCES users
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import org.junit.jupiter.params.provider.MethodSource
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.util.Comparator
import java.util.stream.Stream

class DBMigrationIntegrityTest {
Expand All @@ -29,7 +28,7 @@ class DBMigrationIntegrityTest {
private val changeChecker = MigrationChangeChecker.setup()
.withHashAlgorithm(HashAlgorithm.MD5)
.withHashPair("V1_000__create_user_schemas.sql", "5982877bb3946da9beea4e57f4d1f57e")
.withHashPair("V1_001__create_robot_schemas.sql", "6b37f4a87fa7ff214b3f61b997cc83c4")
.withHashPair("V1_001__create_robot_schemas.sql", "1cf42c0d26713eb7b6d42894118b82e1")
.withHashPair("V1_002__create_config_schemas.sql", "536f4e48da2794ab9df48f2bb9670da3")
.withHashPair("V1_003__create_map_schemas.sql", "1241f041234a0e4b6a62e9f0b2b4ba20")
.withHashPair("V1_004__add_map_icon_map.sql", "2aa3f876a708aaf32eb95b8f6cec980a")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import xyz.poeschl.roborush.exceptions.PositionOutOfMapException
import xyz.poeschl.roborush.gamelogic.actions.MoveAction
import xyz.poeschl.roborush.gamelogic.internal.MapHandler
import xyz.poeschl.roborush.gamelogic.internal.RobotHandler
import xyz.poeschl.roborush.models.*
import xyz.poeschl.roborush.models.Position
import xyz.poeschl.roborush.models.settings.BooleanSetting
import xyz.poeschl.roborush.models.settings.SettingKey
import xyz.poeschl.roborush.repositories.Tile
Expand All @@ -27,6 +27,7 @@ import xyz.poeschl.roborush.test.utils.builder.GameLogicBuilder.Companion.`$Dire
import xyz.poeschl.roborush.test.utils.builder.GameLogicBuilder.Companion.`$GameState`
import xyz.poeschl.roborush.test.utils.builder.GameLogicBuilder.Companion.`$Map`
import xyz.poeschl.roborush.test.utils.builder.GameLogicBuilder.Companion.`$Position`
import xyz.poeschl.roborush.test.utils.builder.GameLogicBuilder.Companion.`$Robot`
import xyz.poeschl.roborush.test.utils.builder.GameLogicBuilder.Companion.`$Tile`
import xyz.poeschl.roborush.test.utils.builder.NativeTypes.Companion.`$Boolean`
import xyz.poeschl.roborush.test.utils.builder.NativeTypes.Companion.`$Int`
Expand Down Expand Up @@ -270,10 +271,12 @@ class GameHandlerTest {
val state = a(`$GameState`())
val target = a(`$Position`())
val chargingPossible = a(`$Boolean`())
val robot = a(`$Robot`())

every { gameStateMachine.getCurrentState() } returns state
every { mapHandler.getTargetPosition() } returns target
every { mapHandler.isSolarChargePossible() } returns chargingPossible
every { robotHandler.getWinningRobot() } returns robot
every { configService.getBooleanSetting(SettingKey.TARGET_POSITION_IN_GAMEINFO) } returns BooleanSetting(SettingKey.TARGET_POSITION_IN_GAMEINFO, true)
every { configService.getDurationSetting(SettingKey.TIMEOUT_WAIT_FOR_PLAYERS) } returns a(`$DurationSetting`())
every { configService.getDurationSetting(SettingKey.TIMEOUT_WAIT_FOR_ACTION) } returns a(`$DurationSetting`())
Expand All @@ -287,6 +290,7 @@ class GameHandlerTest {
assertThat(game.targetPosition).isEqualTo(target)
assertThat(game.solarChargePossible).isEqualTo(chargingPossible)
assertThat(game.currentTurn).isEqualTo(0)
assertThat(game.nameOfWinningRobot).isEqualTo(robot?.user!!.username)
}

@Test
Expand All @@ -295,10 +299,12 @@ class GameHandlerTest {
val state = a(`$GameState`())
val target = a(`$Position`())
val chargingPossible = a(`$Boolean`())
val robot = a(`$Robot`())

every { gameStateMachine.getCurrentState() } returns state
every { mapHandler.getTargetPosition() } returns target
every { mapHandler.isSolarChargePossible() } returns chargingPossible
every { robotHandler.getWinningRobot() } returns robot
every { configService.getBooleanSetting(SettingKey.TARGET_POSITION_IN_GAMEINFO) } returns BooleanSetting(SettingKey.TARGET_POSITION_IN_GAMEINFO, false)
every { configService.getDurationSetting(SettingKey.TIMEOUT_WAIT_FOR_PLAYERS) } returns a(`$DurationSetting`())
every { configService.getDurationSetting(SettingKey.TIMEOUT_WAIT_FOR_ACTION) } returns a(`$DurationSetting`())
Expand All @@ -311,6 +317,7 @@ class GameHandlerTest {
assertThat(game.currentState).isEqualTo(state)
assertThat(game.targetPosition).isNull()
assertThat(game.solarChargePossible).isEqualTo(chargingPossible)
assertThat(game.nameOfWinningRobot).isEqualTo(robot?.user!!.username)
}

@Test
Expand Down Expand Up @@ -374,10 +381,12 @@ class GameHandlerTest {
val state = a(`$GameState`())
val target = a(`$Position`())
val chargingPossible = a(`$Boolean`())
val robot = a(`$Robot`())

every { gameStateMachine.getCurrentState() } returns state
every { mapHandler.getTargetPosition() } returns target
every { mapHandler.isSolarChargePossible() } returns chargingPossible
every { robotHandler.getWinningRobot() } returns robot
every { configService.getBooleanSetting(SettingKey.TARGET_POSITION_IN_GAMEINFO) } returns BooleanSetting(SettingKey.TARGET_POSITION_IN_GAMEINFO, false)
every { configService.getDurationSetting(SettingKey.TIMEOUT_WAIT_FOR_PLAYERS) } returns a(`$DurationSetting`())
every { configService.getDurationSetting(SettingKey.TIMEOUT_WAIT_FOR_ACTION) } returns a(`$DurationSetting`())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class GameStateMachineTest {
Arguments.of(listOf<GameState>(), GameState.WAIT_FOR_PLAYERS, false),
Arguments.of(listOf<GameState>(), GameState.WAIT_FOR_ACTION, false),
Arguments.of(listOf<GameState>(), GameState.ACTION, false),
Arguments.of(listOf<GameState>(), GameState.ENDED, false),
Arguments.of(listOf(GameState.PREPARE), GameState.WAIT_FOR_PLAYERS, true),
Arguments.of(listOf(GameState.PREPARE), GameState.WAIT_FOR_ACTION, false),
Arguments.of(listOf(GameState.PREPARE), GameState.ACTION, false),
Expand Down Expand Up @@ -57,6 +58,11 @@ class GameStateMachineTest {
GameState.PREPARE,
true
),
Arguments.of(
listOf(GameState.PREPARE, GameState.WAIT_FOR_PLAYERS, GameState.WAIT_FOR_ACTION, GameState.ACTION, GameState.ENDED),
GameState.WAIT_FOR_PLAYERS,
false
),
Arguments.of(
listOf(GameState.PREPARE, GameState.WAIT_FOR_PLAYERS, GameState.WAIT_FOR_ACTION, GameState.ACTION, GameState.ENDED),
GameState.WAIT_FOR_ACTION,
Expand Down
6 changes: 6 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@fortawesome/free-regular-svg-icons": "^6.5.2",
"@fortawesome/free-solid-svg-icons": "^6.5.2",
"@fortawesome/vue-fontawesome": "^3.0.6",
"@neoconfetti/vue": "^2.2.1",
"@stomp/stompjs": "^7.0.0",
"axios": "^1.6.8",
"bulma": "^1.0.0",
Expand Down
33 changes: 33 additions & 0 deletions frontend/src/components/WinnerBanner.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<template>
<div
class="box is-color-black is-flex is-justify-content-center is-size-4 p-3"
:class="{ 'has-background-success': winnerAvailable, 'has-background-warning': !winnerAvailable }"
v-if="gameStore.currentGame.currentState === GameState.ENDED"
>
<div class="is-flex is-flex-direction-column is-align-items-center" v-if="winnerAvailable">
<div>
🏆 <span class="has-text-weight-bold">{{ winnerName }}</span> won the round! 🏆
</div>
<div v-confetti="{ stageHeight: 1000, particleCount: 300 }" />
</div>
<div class="has-text-weight-bold" v-else>No robot reached the target!</div>
</div>
</template>

<script setup lang="ts">
import { GameState } from "@/models/Game";
import { useGameStore } from "@/stores/GameStore";
import { computed } from "vue";
import { vConfetti } from "@neoconfetti/vue";
const gameStore = useGameStore();
const winnerAvailable = computed<boolean>(() => gameStore.currentGame.nameOfWinningRobot != null);
const winnerName = computed<string | undefined>(() => gameStore.currentGame.nameOfWinningRobot);
</script>

<style scoped lang="scss">
.box {
width: 100%;
}
</style>
1 change: 1 addition & 0 deletions frontend/src/models/Game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface Game {
currentTurn: number;
solarChargePossible: boolean;
targetPosition?: Position;
nameOfWinningRobot?: string;
}

export enum GameState {
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/stores/GameStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { defineStore } from "pinia";
import type { ComputedRef, Ref } from "vue";
import { computed, ref } from "vue";
import type { ActiveRobot, PublicRobot } from "@/models/Robot";
import type { PlaygroundMap, Tile } from "@/models/Map";
import type { PlaygroundMap } from "@/models/Map";
import { useWebSocket, WebSocketTopic } from "@/services/WebsocketService";
import type { User } from "@/models/User";
import { useGameService } from "@/services/GameService";
Expand Down Expand Up @@ -103,11 +103,15 @@ export const useGameStore = defineStore("gameStore", () => {
if (previousGameState == GameState.ENDED && gameState === GameState.PREPARE) {
// Reset robot list on prepare stage
internalRobots.value.data = [];
updateGameInfo();
}
if (previousGameState == GameState.PREPARE && gameState === GameState.WAIT_FOR_PLAYERS) {
// Update map data after game preparations
updateMap();
}
if (gameState === GameState.ENDED) {
updateGameInfo();
}
};

const updateGameTurnTo = (turnCount: number) => {
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/views/FullScreenView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
<FullScreenInfoBar class="mb-5" />
<div class="fullscreen-container ml-4 mr-4">
<div class="columns">
<div class="column is-flex is-justify-content-center is-align-items-start">
<div class="column is-flex is-align-items-center is-flex-direction-column pr-5">
<WinnerBanner />
<MapCanvasComponent :robots="gameStore.robots" :map="gameStore.currentMap" style="height: 100%" />
</div>
<div class="column is-one-fifth is-flex-direction-column is-narrow data-column">
Expand All @@ -21,6 +22,7 @@ import GameStateBox from "@/components/GameStateBox.vue";
import RobotActiveList from "@/components/RobotActiveList.vue";
import RobotScoreBoard from "@/components/RobotScoreBoard.vue";
import FullScreenInfoBar from "@/components/FullScreenInfoBar.vue";
import WinnerBanner from "@/components/WinnerBanner.vue";
const gameStore = useGameStore();
</script>
Expand Down
Loading

0 comments on commit 7ad1ef3

Please sign in to comment.