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

🚧 Add victory gamestate to backend #121

Merged
merged 17 commits into from
May 29, 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 @@ -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