Improve concurrency

Fix GameState.currentPlayerTurn and nextPlayer
Add ItsTheTurnOfNotification
Improve test
This commit is contained in:
2025-03-10 22:18:06 +01:00
parent 3a685496fd
commit 6028846828
23 changed files with 344 additions and 236 deletions

View File

@@ -69,7 +69,7 @@ class GameStateBuilderTest :
val event = CardIsPlayedEvent(gameId, playedCard, player1)
apply(event).also { state ->
state.gameId shouldBeEqual gameId
assertNotNull(state.lastCard).card shouldBeEqual playedCard
assertNotNull(state.cardOnCurrentStack).card shouldBeEqual playedCard
assertIs<Card.NumericCard>(playedCard).let {
it.number shouldBeEqual 0
it.color shouldBeEqual Card.Color.Red
@@ -80,7 +80,7 @@ class GameStateBuilderTest :
val event = CardIsPlayedEvent(gameId, playedCard, player2)
apply(event).also { state ->
state.gameId shouldBeEqual gameId
assertNotNull(state.lastCard).card shouldBeEqual playedCard
assertNotNull(state.cardOnCurrentStack).card shouldBeEqual playedCard
assertIs<Card.NumericCard>(playedCard).let {
it.number shouldBeEqual 7
it.color shouldBeEqual Card.Color.Red

View File

@@ -4,6 +4,7 @@ import eventDemo.app.command.GameCommandHandler
import eventDemo.app.command.command.IWantToJoinTheGameCommand
import eventDemo.app.command.command.IWantToPlayCardCommand
import eventDemo.app.command.command.IamReadyToPlayCommand
import eventDemo.app.entity.Card
import eventDemo.app.entity.GameId
import eventDemo.app.entity.Player
import eventDemo.app.event.GameEventStream
@@ -12,6 +13,7 @@ import eventDemo.app.event.projection.GameState
import eventDemo.app.event.projection.buildStateFromEventStream
import eventDemo.app.eventListener.GameEventPlayerNotificationListener
import eventDemo.app.eventListener.GameEventReactionListener
import eventDemo.app.notification.ItsTheTurnOfNotification
import eventDemo.app.notification.PlayerAsJoinTheGameNotification
import eventDemo.app.notification.PlayerAsPlayACardNotification
import eventDemo.app.notification.PlayerWasReadyNotification
@@ -29,6 +31,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import org.koin.dsl.koinApplication
import kotlin.test.assertIs
@@ -43,75 +46,105 @@ class GameStateTest :
val id = GameId()
val player1 = Player(name = "Nikola")
val player2 = Player(name = "Einstein")
val channelIn1 = Channel<Frame>()
val channelIn2 = Channel<Frame>()
val channelOut1 = Channel<Frame>(Channel.BUFFERED)
val channelOut2 = Channel<Frame>(Channel.BUFFERED)
val channelCommand1 = Channel<Frame>(Channel.BUFFERED)
val channelCommand2 = Channel<Frame>(Channel.BUFFERED)
val channelNotification1 = Channel<Frame>(Channel.BUFFERED)
val channelNotification2 = Channel<Frame>(Channel.BUFFERED)
var playedCard1: Card? = null
var playedCard2: Card? = null
val player1Job =
launch {
channelCommand1.send(IWantToJoinTheGameCommand(IWantToJoinTheGameCommand.Payload(id, player1)).toFrame())
channelNotification1.receive().toNotification().let {
assertIs<WelcomeToTheGameNotification>(it).players shouldBeEqual setOf(player1)
}
channelNotification1.receive().toNotification().let {
assertIs<PlayerAsJoinTheGameNotification>(it).player shouldBeEqual player2
}
channelCommand1.send(IamReadyToPlayCommand(IamReadyToPlayCommand.Payload(id, player1)).toFrame())
channelNotification1.receive().toNotification().let {
assertIs<PlayerWasReadyNotification>(it).player shouldBeEqual player2
}
val player1Hand =
channelNotification1.receive().toNotification().let {
assertIs<TheGameWasStartedNotification>(it).hand shouldHaveSize 7
}
playedCard1 = player1Hand.first()
channelNotification1.receive().toNotification().let {
assertIs<ItsTheTurnOfNotification>(it).apply {
player shouldBeEqual player1
}
}
channelCommand1.send(IWantToPlayCardCommand(IWantToPlayCardCommand.Payload(id, player1, player1Hand.first())).toFrame())
channelNotification1.receive().toNotification().let {
assertIs<ItsTheTurnOfNotification>(it).apply {
player shouldBeEqual player2
}
}
channelNotification1.receive().toNotification().let {
assertIs<PlayerAsPlayACardNotification>(it).apply {
player shouldBeEqual player2
card shouldBeEqual assertNotNull(playedCard2)
}
}
}
val player2Job =
launch {
delay(100)
channelCommand2.send(IWantToJoinTheGameCommand(IWantToJoinTheGameCommand.Payload(id, player2)).toFrame())
channelNotification2.receive().toNotification().let {
assertIs<WelcomeToTheGameNotification>(it).players shouldBeEqual setOf(player1, player2)
}
channelNotification2.receive().toNotification().let {
assertIs<PlayerWasReadyNotification>(it).player shouldBeEqual player1
}
channelCommand2.send(IamReadyToPlayCommand(IamReadyToPlayCommand.Payload(id, player2)).toFrame())
val player2Hand =
channelNotification2.receive().toNotification().let {
assertIs<TheGameWasStartedNotification>(it).hand shouldHaveSize 7
}
channelNotification2.receive().toNotification().let {
assertIs<ItsTheTurnOfNotification>(it).apply {
player shouldBeEqual player1
}
}
channelNotification2.receive().toNotification().let {
assertIs<PlayerAsPlayACardNotification>(it).apply {
player shouldBeEqual player1
card shouldBeEqual assertNotNull(playedCard1)
}
}
playedCard2 = player2Hand.first()
channelNotification2.receive().toNotification().let {
assertIs<ItsTheTurnOfNotification>(it).apply {
player shouldBeEqual player2
}
}
channelCommand2.send(IWantToPlayCardCommand(IWantToPlayCardCommand.Payload(id, player2, player2Hand.first())).toFrame())
}
koinApplication { modules(appKoinModule) }.koin.apply {
val commandHandler by inject<GameCommandHandler>()
val playerNotificationListener by inject<GameEventPlayerNotificationListener>()
val eventStream by inject<GameEventStream>()
val playerNotificationListener by inject<GameEventPlayerNotificationListener>()
GameEventReactionListener(get(), get(), get()).init()
playerNotificationListener.startListening(channelOut1, player1)
playerNotificationListener.startListening(channelOut2, player2)
playerNotificationListener.startListening(channelNotification1, player1)
playerNotificationListener.startListening(channelNotification2, player2)
GlobalScope.launch(Dispatchers.IO) {
commandHandler.handle(player1, channelIn1, channelOut1)
commandHandler.handle(player1, channelCommand1, channelNotification1)
}
GlobalScope.launch(Dispatchers.IO) {
commandHandler.handle(player2, channelIn2, channelOut2)
commandHandler.handle(player2, channelCommand2, channelNotification2)
}
channelIn1.send(IWantToJoinTheGameCommand(IWantToJoinTheGameCommand.Payload(id, player1)).toFrame())
delay(50)
channelIn2.send(IWantToJoinTheGameCommand(IWantToJoinTheGameCommand.Payload(id, player2)).toFrame())
delay(50)
channelOut1.receive().toNotification().let {
assertIs<WelcomeToTheGameNotification>(it).players shouldBeEqual setOf(player1)
}
channelOut2.receive().toNotification().let {
assertIs<WelcomeToTheGameNotification>(it).players shouldBeEqual setOf(player1, player2)
}
channelOut1.receive().toNotification().let {
assertIs<PlayerAsJoinTheGameNotification>(it).player shouldBeEqual player2
}
channelIn1.send(IamReadyToPlayCommand(IamReadyToPlayCommand.Payload(id, player1)).toFrame())
delay(50)
channelIn2.send(IamReadyToPlayCommand(IamReadyToPlayCommand.Payload(id, player2)).toFrame())
delay(50)
channelOut1.receive().toNotification().let {
assertIs<PlayerWasReadyNotification>(it).player shouldBeEqual player2
}
channelOut2.receive().toNotification().let {
assertIs<PlayerWasReadyNotification>(it).player shouldBeEqual player1
}
val player1Hand =
channelOut1.receive().toNotification().let {
assertIs<TheGameWasStartedNotification>(it).hand shouldHaveSize 7
}
val player2Hand =
channelOut2.receive().toNotification().let {
assertIs<TheGameWasStartedNotification>(it).hand shouldHaveSize 7
}
channelIn1.send(IWantToPlayCardCommand(IWantToPlayCardCommand.Payload(id, player1, player1Hand.first())).toFrame())
delay(50)
channelOut2.receive().toNotification().let {
assertIs<PlayerAsPlayACardNotification>(it).player shouldBeEqual player1
assertIs<PlayerAsPlayACardNotification>(it).card shouldBeEqual player1Hand.first()
}
channelIn2.send(IWantToPlayCardCommand(IWantToPlayCardCommand.Payload(id, player2, player2Hand.first())).toFrame())
delay(50)
channelOut1.receive().toNotification().let {
assertIs<PlayerAsPlayACardNotification>(it).player shouldBeEqual player2
assertIs<PlayerAsPlayACardNotification>(it).card shouldBeEqual player2Hand.first()
}
joinAll(player1Job, player2Job)
val state = id.buildStateFromEventStream(eventStream)
@@ -120,7 +153,7 @@ class GameStateTest :
state.players shouldBeEqual setOf(player1, player2)
state.readyPlayers shouldBeEqual setOf(player1, player2)
state.direction shouldBeEqual GameState.Direction.CLOCKWISE
assertNotNull(state.lastCard) shouldBeEqual GameState.LastCard(player2Hand.first(), player2)
assertNotNull(state.cardOnCurrentStack) shouldBeEqual GameState.LastCard(assertNotNull(playedCard2), player2)
}
}
})