Compare commits
6
Commits
9c9d057f0a
..
v0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7291419c9f
|
||
|
|
b2b8fcf92f
|
||
|
|
4e4b307275
|
||
|
|
e87a36caa5
|
||
|
|
e2d7942c7e
|
||
|
|
b313b39cf4
|
+14
-14
@@ -18,10 +18,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up JDK 21
|
- name: Set up JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
run: echo "key=gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}" >> $GITHUB_OUTPUT
|
run: echo "key=gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Cache Gradle dependencies
|
- name: Cache Gradle dependencies
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v6
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
@@ -48,16 +48,16 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up JDK 21
|
- name: Set up JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
|
|
||||||
- name: Restore Gradle cache
|
- name: Restore Gradle cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v6
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
@@ -73,7 +73,7 @@ jobs:
|
|||||||
run: ./gradlew ktlintCheck
|
run: ./gradlew ktlintCheck
|
||||||
|
|
||||||
- name: Publish ktlint report
|
- name: Publish ktlint report
|
||||||
uses: yutailang0119/action-ktlint@v4
|
uses: yutailang0119/action-ktlint@v5
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
report-path: build/reports/ktlint/**/*.xml
|
report-path: build/reports/ktlint/**/*.xml
|
||||||
@@ -84,16 +84,16 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up JDK 21
|
- name: Set up JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
|
|
||||||
- name: Restore Gradle cache
|
- name: Restore Gradle cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v6
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
@@ -106,20 +106,20 @@ jobs:
|
|||||||
run: chmod +x gradlew
|
run: chmod +x gradlew
|
||||||
|
|
||||||
- name: Start CI Docker Compose services
|
- name: Start CI Docker Compose services
|
||||||
run: ./gradlew composeUp -Pci
|
run: ./gradlew ciComposeUp -Pci
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: ./gradlew test -x composeUp --no-daemon
|
run: ./gradlew test -x ciComposeUp -Pci --no-daemon
|
||||||
|
|
||||||
- name: Upload test reports
|
- name: Upload test reports
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: test-results
|
name: test-results
|
||||||
path: build/reports/tests/test
|
path: build/reports/tests/test
|
||||||
|
|
||||||
- name: Publish Test Report
|
- name: Publish Test Report
|
||||||
uses: dorny/test-reporter@v1
|
uses: dorny/test-reporter@v3
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: JUnit Tests
|
name: JUnit Tests
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="docker composeUp" type="GradleRunConfiguration" factoryName="Gradle">
|
||||||
|
<ExternalSystemSettings>
|
||||||
|
<option name="executionName" />
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="externalSystemIdString" value="GRADLE" />
|
||||||
|
<option name="scriptParameters" value="" />
|
||||||
|
<option name="taskDescriptions">
|
||||||
|
<list />
|
||||||
|
</option>
|
||||||
|
<option name="taskNames">
|
||||||
|
<list>
|
||||||
|
<option value="composeUp" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="vmOptions" />
|
||||||
|
</ExternalSystemSettings>
|
||||||
|
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||||
|
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||||
|
<ExternalSystemDebugDisabled>false</ExternalSystemDebugDisabled>
|
||||||
|
<DebugAllEnabled>false</DebugAllEnabled>
|
||||||
|
<RunAsTest>false</RunAsTest>
|
||||||
|
<GradleProfilingDisabled>false</GradleProfilingDisabled>
|
||||||
|
<GradleCoverageDisabled>false</GradleCoverageDisabled>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
+39
-26
@@ -1,3 +1,4 @@
|
|||||||
|
import com.avast.gradle.dockercompose.ComposeExtension
|
||||||
import org.jlleitschuh.gradle.ktlint.KtlintExtension
|
import org.jlleitschuh.gradle.ktlint.KtlintExtension
|
||||||
|
|
||||||
val ktorVersion: Provider<String> = providers.gradleProperty("ktor_version")
|
val ktorVersion: Provider<String> = providers.gradleProperty("ktor_version")
|
||||||
@@ -49,29 +50,42 @@ tasks.withType<Test>().configureEach {
|
|||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
dockerCompose {
|
configure<ComposeExtension> {
|
||||||
val composeFile =
|
createNested("test").apply {
|
||||||
if (project.hasProperty("ci")) {
|
useComposeFiles.set(listOf("docker/docker-compose-test.yaml"))
|
||||||
// Use docker-compose-ci.yaml for the CI
|
|
||||||
"docker/docker-compose-ci.yaml"
|
|
||||||
} else {
|
|
||||||
// Use docker-compose-test.yaml for local tests
|
|
||||||
"docker/docker-compose-test.yaml"
|
|
||||||
}
|
|
||||||
useComposeFiles.set(listOf(composeFile))
|
|
||||||
setProjectName("event-demo-test")
|
setProjectName("event-demo-test")
|
||||||
}
|
}
|
||||||
|
createNested("dev").apply {
|
||||||
|
useComposeFiles.set(listOf("docker/docker-compose-dev.yaml"))
|
||||||
|
setProjectName("event-demo-dev")
|
||||||
|
}
|
||||||
|
createNested("ci").apply {
|
||||||
|
useComposeFiles.set(listOf("docker/docker-compose-ci.yaml"))
|
||||||
|
setProjectName("event-demo-ci")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tasks.test {
|
tasks.test {
|
||||||
dependsOn("composeUp")
|
if (project.hasProperty("ci")) {
|
||||||
dockerCompose.useComposeFiles.set(listOf("docker/docker-compose-test.yaml"))
|
dependsOn("ciComposeUp")
|
||||||
dockerCompose.setProjectName("event-demo-test")
|
} else {
|
||||||
|
dependsOn("testComposeUp")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named("run") {
|
tasks.named("run") {
|
||||||
dependsOn("composeUp")
|
dependsOn("devComposeUp")
|
||||||
dockerCompose.useComposeFiles.set(listOf("docker/docker-compose-test.yaml"))
|
}
|
||||||
dockerCompose.setProjectName("event-demo-dev")
|
tasks.composeUp {
|
||||||
|
dependsOn("copyEnv")
|
||||||
|
}
|
||||||
|
tasks.named("devComposeUp") {
|
||||||
|
dependsOn("copyEnv")
|
||||||
|
}
|
||||||
|
tasks.named("testComposeUp") {
|
||||||
|
dependsOn("copyEnv")
|
||||||
|
}
|
||||||
|
tasks.named("ciComposeUp") {
|
||||||
|
dependsOn("copyEnv")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register<Copy>("copyEnv") {
|
tasks.register<Copy>("copyEnv") {
|
||||||
@@ -88,13 +102,15 @@ tasks.register<Copy>("copyEnv") {
|
|||||||
exclude()
|
exclude()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
doLast {
|
|
||||||
val files =
|
val files =
|
||||||
listOf(
|
buildList {
|
||||||
File("docker/pgadmin.secret"),
|
add(File("docker/postgresql.secret"))
|
||||||
File("docker/postgresql.secret"),
|
if (!project.hasProperty("ci")) {
|
||||||
)
|
add(File("docker/pgadmin.secret"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outputs.files(*files.toTypedArray())
|
||||||
|
doLast {
|
||||||
files.forEach {
|
files.forEach {
|
||||||
if (!it.exists()) {
|
if (!it.exists()) {
|
||||||
it.writeText("changeit")
|
it.writeText("changeit")
|
||||||
@@ -102,9 +118,6 @@ tasks.register<Copy>("copyEnv") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tasks.composeUp {
|
|
||||||
dependsOn("copyEnv")
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("io.ktor:ktor-server-core-jvm")
|
implementation("io.ktor:ktor-server-core-jvm")
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
name: event-demo-test
|
name: event-demo-ci
|
||||||
include:
|
include:
|
||||||
- path:
|
- path:
|
||||||
- parts/docker-compose-databases.yaml
|
- parts/docker-compose-databases.yaml
|
||||||
- parts/docker-compose-databases-expose.yaml
|
- parts/docker-compose-databases-expose.yaml
|
||||||
- parts/docker-compose-traefik.yaml
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
name: event-demo-test
|
||||||
|
include:
|
||||||
|
- path:
|
||||||
|
- docker-compose-test.yaml
|
||||||
@@ -24,8 +24,6 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
POSTGRES_PASSWORD_FILE: /run/secrets/postgresql_password
|
POSTGRES_PASSWORD_FILE: /run/secrets/postgresql_password
|
||||||
POSTGRES_USER: event-demo
|
POSTGRES_USER: event-demo
|
||||||
secrets:
|
|
||||||
- postgresql_password
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "sh -c 'pg_isready -U event-demo'"]
|
test: ["CMD-SHELL", "sh -c 'pg_isready -U event-demo'"]
|
||||||
interval: 1s
|
interval: 1s
|
||||||
@@ -33,6 +31,7 @@ services:
|
|||||||
retries: 10
|
retries: 10
|
||||||
volumes:
|
volumes:
|
||||||
- postgresql_data:/var/lib/postgresql
|
- postgresql_data:/var/lib/postgresql
|
||||||
|
- ../postgresql.secret:/run/secrets/postgresql_password:ro
|
||||||
|
|
||||||
rabbitmq:
|
rabbitmq:
|
||||||
image: rabbitmq:4-management-alpine
|
image: rabbitmq:4-management-alpine
|
||||||
@@ -47,10 +46,6 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- rabbitmq_data:/var/lib/rabbitmq/
|
- rabbitmq_data:/var/lib/rabbitmq/
|
||||||
|
|
||||||
secrets:
|
|
||||||
postgresql_password:
|
|
||||||
file: ../postgresql.secret
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
redis_data:
|
redis_data:
|
||||||
redisinsight_data:
|
redisinsight_data:
|
||||||
|
|||||||
@@ -4,10 +4,20 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
PGADMIN_DEFAULT_EMAIL: $PGADMIN_DEFAULT_EMAIL
|
PGADMIN_DEFAULT_EMAIL: $PGADMIN_DEFAULT_EMAIL
|
||||||
PGADMIN_DEFAULT_PASSWORD_FILE: /run/secrets/pgadmin_password
|
PGADMIN_DEFAULT_PASSWORD_FILE: /run/secrets/pgadmin_password
|
||||||
|
PGADMIN_CONFIG_SERVER_MODE: 'False'
|
||||||
|
PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: 'False'
|
||||||
secrets:
|
secrets:
|
||||||
- pgadmin_password
|
- pgadmin_password
|
||||||
volumes:
|
volumes:
|
||||||
- pgadmin_data:/var/lib/pgadmin
|
- pgadmin_data:/var/lib/pgadmin
|
||||||
|
configs:
|
||||||
|
- source: pgpass
|
||||||
|
target: /pgpass
|
||||||
|
mode: 0600
|
||||||
|
uid: "5050"
|
||||||
|
gid: "5050"
|
||||||
|
- source: servers_json
|
||||||
|
target: /pgadmin4/servers.json
|
||||||
labels:
|
labels:
|
||||||
- "traefik.http.routers.pgadmin.rule=Host(`pgadmin.postgresql.traefik.me`)"
|
- "traefik.http.routers.pgadmin.rule=Host(`pgadmin.postgresql.traefik.me`)"
|
||||||
- "traefik.http.services.pgadmin.loadbalancer.server.port=80"
|
- "traefik.http.services.pgadmin.loadbalancer.server.port=80"
|
||||||
@@ -24,6 +34,27 @@ services:
|
|||||||
- "traefik.http.routers.rabbitmq-management.service=rabbitmq-management"
|
- "traefik.http.routers.rabbitmq-management.service=rabbitmq-management"
|
||||||
- "traefik.http.services.rabbitmq-management.loadbalancer.server.port=15672"
|
- "traefik.http.services.rabbitmq-management.loadbalancer.server.port=15672"
|
||||||
|
|
||||||
|
configs:
|
||||||
|
pgpass:
|
||||||
|
content: |
|
||||||
|
*:*:*:event-demo:changeit
|
||||||
|
servers_json:
|
||||||
|
content: |
|
||||||
|
{
|
||||||
|
"Servers": {
|
||||||
|
"1": {
|
||||||
|
"Name": "Event demo",
|
||||||
|
"Group": "Servers",
|
||||||
|
"Host": "postgresql",
|
||||||
|
"Port": 5432,
|
||||||
|
"MaintenanceDB": "event-demo",
|
||||||
|
"Username": "event-demo",
|
||||||
|
"PassFile": "/pgpass",
|
||||||
|
"SSLMode": "prefer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
secrets:
|
secrets:
|
||||||
pgadmin_password:
|
pgadmin_password:
|
||||||
file: ../pgadmin.secret
|
file: ../pgadmin.secret
|
||||||
|
|||||||
+2
-10
@@ -37,16 +37,8 @@ abstract class GameEventManager(
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun <G : Game> Game.isStatusOrFail(
|
protected inline fun <reified G : Game> Game.isStatusOrFail(message: String): G =
|
||||||
kClass: KClass<G>,
|
this as? G ?: throw CommandException(message)
|
||||||
message: String,
|
|
||||||
): G {
|
|
||||||
if (kClass.isInstance(this)) {
|
|
||||||
return this as G
|
|
||||||
} else {
|
|
||||||
throw CommandException(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun <T> retry(
|
protected fun <T> retry(
|
||||||
mapAttempts: Int = 5,
|
mapAttempts: Int = 5,
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ class JoinTheGameHandler(
|
|||||||
retry {
|
retry {
|
||||||
command
|
command
|
||||||
.getGame()
|
.getGame()
|
||||||
.isStatusOrFail(GameCreated::class, "The game is started")
|
.isStatusOrFail<GameCreated>("The game is started")
|
||||||
.userJoinTheGame(
|
.userJoinTheGame(
|
||||||
userId = command.userId,
|
userId = command.userId,
|
||||||
name = user.username,
|
name = user.username,
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ class PlayCardHandler(
|
|||||||
override fun handle(command: PlayCardCommand) {
|
override fun handle(command: PlayCardCommand) {
|
||||||
command
|
command
|
||||||
.getGame()
|
.getGame()
|
||||||
.isStatusOrFail(GameStarted::class, "The game is not started")
|
.isStatusOrFail<GameStarted>("The game is not started")
|
||||||
.playTheCard(
|
.playTheCard(
|
||||||
card = command.payload.card,
|
card = command.payload.card,
|
||||||
playerId = command.payload.playerId,
|
playerId = command.payload.playerId,
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ class ReadyToPlayHandler(
|
|||||||
override fun handle(command: ReadyToPlayCommand) {
|
override fun handle(command: ReadyToPlayCommand) {
|
||||||
command
|
command
|
||||||
.getGame()
|
.getGame()
|
||||||
.isStatusOrFail(GameCreated::class, "The game is started")
|
.isStatusOrFail<GameCreated>("The game is started")
|
||||||
.setReadyPlayer(command.payload.playerId)
|
.setReadyPlayer(command.payload.playerId)
|
||||||
.saveEvents()
|
.saveEvents()
|
||||||
.publishEvents()
|
.publishEvents()
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ class TakeCartFromDrawPileHandler(
|
|||||||
override fun handle(command: TakeCartFromDrawPileCommand) {
|
override fun handle(command: TakeCartFromDrawPileCommand) {
|
||||||
command
|
command
|
||||||
.getGame()
|
.getGame()
|
||||||
.isStatusOrFail(GameStarted::class, "The game is not started")
|
.isStatusOrFail<GameStarted>("The game is not started")
|
||||||
.playerTakeCartFromDrawPile(command.payload.playerId, 1)
|
.playerTakeCartFromDrawPile(command.payload.playerId, 1)
|
||||||
.saveEvents()
|
.saveEvents()
|
||||||
.publishEvents()
|
.publishEvents()
|
||||||
|
|||||||
+13
-8
@@ -24,6 +24,10 @@ import eventDemo.contexts.game.domain.events.PlayerWinEvent
|
|||||||
import eventDemo.contexts.game.domain.game.gameState.Game
|
import eventDemo.contexts.game.domain.game.gameState.Game
|
||||||
import eventDemo.contexts.game.domain.game.gameState.GameStarted
|
import eventDemo.contexts.game.domain.game.gameState.GameStarted
|
||||||
import eventDemo.sharedKernel.UserId
|
import eventDemo.sharedKernel.UserId
|
||||||
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import io.github.oshai.kotlinlogging.withLoggingContext
|
||||||
|
|
||||||
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
fun GameEvent.toNotification(
|
fun GameEvent.toNotification(
|
||||||
game: Game,
|
game: Game,
|
||||||
@@ -31,15 +35,16 @@ fun GameEvent.toNotification(
|
|||||||
): Iterable<Notification> =
|
): Iterable<Notification> =
|
||||||
Iterable {
|
Iterable {
|
||||||
iterator {
|
iterator {
|
||||||
val currentPlayerId = game.players.get(currentUserId).id
|
|
||||||
|
|
||||||
context(iterator: SequenceScope<Notification>)
|
context(iterator: SequenceScope<Notification>)
|
||||||
suspend fun Notification.send() {
|
suspend fun Notification.send() {
|
||||||
|
withLoggingContext("notification" to (this).toString()) {
|
||||||
|
logger.info { "Notification sent" }
|
||||||
iterator.yield(this)
|
iterator.yield(this)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun PlayerActionEvent.isFromCurrentUser(): Boolean =
|
fun PlayerActionEvent.isFromCurrentUser(): Boolean =
|
||||||
currentPlayerId != playerId
|
game.players.get(currentUserId).id == playerId
|
||||||
|
|
||||||
when (this@toNotification) {
|
when (this@toNotification) {
|
||||||
is GameCreatedEvent -> {
|
is GameCreatedEvent -> {
|
||||||
@@ -51,7 +56,7 @@ fun GameEvent.toNotification(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is NewPlayerEvent -> {
|
is NewPlayerEvent -> {
|
||||||
if (this@toNotification.isFromCurrentUser()) {
|
if (this@toNotification.player.userId != currentUserId) {
|
||||||
PlayerAsJoinTheGameNotification(
|
PlayerAsJoinTheGameNotification(
|
||||||
player = this@toNotification.player,
|
player = this@toNotification.player,
|
||||||
).send()
|
).send()
|
||||||
@@ -85,6 +90,7 @@ fun GameEvent.toNotification(
|
|||||||
|
|
||||||
if (game is GameStarted) {
|
if (game is GameStarted) {
|
||||||
ItsTheTurnOfNotification(player = game.nextPlayer)
|
ItsTheTurnOfNotification(player = game.nextPlayer)
|
||||||
|
.send()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,21 +107,20 @@ fun GameEvent.toNotification(
|
|||||||
|
|
||||||
if (game is GameStarted) {
|
if (game is GameStarted) {
|
||||||
ItsTheTurnOfNotification(player = game.nextPlayer)
|
ItsTheTurnOfNotification(player = game.nextPlayer)
|
||||||
|
.send()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is PlayerReadyEvent -> {
|
is PlayerReadyEvent -> {
|
||||||
if (this@toNotification.isFromCurrentUser()) {
|
|
||||||
PlayerWasReadyNotification(
|
PlayerWasReadyNotification(
|
||||||
playerId = this@toNotification.playerId,
|
playerId = this@toNotification.playerId,
|
||||||
)
|
).send()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
is PlayerWinEvent -> {
|
is PlayerWinEvent -> {
|
||||||
PlayerWinNotification(
|
PlayerWinNotification(
|
||||||
playerId = this@toNotification.playerId,
|
playerId = this@toNotification.playerId,
|
||||||
)
|
).send()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class ReactionListener(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun sendWinnerEvent(game: Game) {
|
private fun sendWinnerEvent(game: Game) {
|
||||||
if (game is GameStarted) {
|
if (game is GameStarted && game.lastPlayerId != null) {
|
||||||
val lastPlayerWin =
|
val lastPlayerWin =
|
||||||
game
|
game
|
||||||
.players
|
.players
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import eventDemo.contexts.game.domain.game.DiscardPile
|
|||||||
import eventDemo.contexts.game.domain.game.DrawPile
|
import eventDemo.contexts.game.domain.game.DrawPile
|
||||||
import eventDemo.contexts.game.domain.game.GameId
|
import eventDemo.contexts.game.domain.game.GameId
|
||||||
import eventDemo.contexts.game.domain.game.Player
|
import eventDemo.contexts.game.domain.game.Player
|
||||||
|
import eventDemo.contexts.game.domain.game.PlayerHand
|
||||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.EventIdSerializer
|
import eventDemo.contexts.game.infrastructure.persistence.serializers.EventIdSerializer
|
||||||
import eventDemo.contexts.game.infrastructure.persistence.serializers.PlayerIdSerializer
|
import eventDemo.contexts.game.infrastructure.persistence.serializers.PlayerIdSerializer
|
||||||
import eventDemo.libs.eventSource.EventId
|
import eventDemo.libs.eventSource.EventId
|
||||||
@@ -20,6 +21,7 @@ data class GameStartedEvent(
|
|||||||
override val aggregateId: GameId,
|
override val aggregateId: GameId,
|
||||||
@Serializable(with = PlayerIdSerializer::class)
|
@Serializable(with = PlayerIdSerializer::class)
|
||||||
val firstPlayer: Player.PlayerId,
|
val firstPlayer: Player.PlayerId,
|
||||||
|
val playersHans: Map<Player.PlayerId, PlayerHand>,
|
||||||
val drawPile: DrawPile,
|
val drawPile: DrawPile,
|
||||||
val discardPile: DiscardPile,
|
val discardPile: DiscardPile,
|
||||||
override val version: Int,
|
override val version: Int,
|
||||||
@@ -28,9 +30,3 @@ data class GameStartedEvent(
|
|||||||
override val eventId: EventId = EventId(UUID.randomUUID())
|
override val eventId: EventId = EventId(UUID.randomUUID())
|
||||||
override val createdAt: Instant = Clock.System.now()
|
override val createdAt: Instant = Clock.System.now()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var isDisabled = false
|
|
||||||
|
|
||||||
internal fun disableShuffleDeck() {
|
|
||||||
isDisabled = true
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import kotlinx.serialization.Serializable
|
|||||||
value class DrawPile(
|
value class DrawPile(
|
||||||
val cards: Set<Card> = emptySet(),
|
val cards: Set<Card> = emptySet(),
|
||||||
) {
|
) {
|
||||||
|
val size: Int get() = cards.size
|
||||||
|
|
||||||
fun take(number: Int): Pair<DrawPile, Set<Card>> =
|
fun take(number: Int): Pair<DrawPile, Set<Card>> =
|
||||||
cards.drop(number).toDrawPile() to cards.take(number).toSet()
|
cards.drop(number).toDrawPile() to cards.take(number).toSet()
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ data class Player(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
class PlayerList(
|
class PlayerList(
|
||||||
val players: Set<Player> = emptySet(),
|
val players: Set<Player> = emptySet(),
|
||||||
) : Set<Player> by players {
|
) : Set<Player> by players {
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ sealed interface Game {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun loadFromHistory(events: Set<GameEvent>): Game =
|
fun loadFromHistory(events: Set<GameEvent>): Game =
|
||||||
events.fold(GameInit(events.first().aggregateId)) { game: Game, event ->
|
events
|
||||||
|
.fold(GameInit(events.first().aggregateId)) { game: Game, event ->
|
||||||
game.run {
|
game.run {
|
||||||
when (event) {
|
when (event) {
|
||||||
is GameCreatedEvent if this is GameInit -> applyEvent(event)
|
is GameCreatedEvent if this is GameInit -> applyEvent(event)
|
||||||
@@ -60,6 +61,13 @@ sealed interface Game {
|
|||||||
is DrawFilledWithDiscardEvent -> error("Game is end")
|
is DrawFilledWithDiscardEvent -> error("Game is end")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}.let {
|
||||||
|
when (it) {
|
||||||
|
is GameInit -> it
|
||||||
|
is GameCreated -> it.copy(recordedEvents = emptySet())
|
||||||
|
is GameEnded -> it.copy(recordedEvents = emptySet())
|
||||||
|
is GameStarted -> it.copy(recordedEvents = emptySet())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,18 +79,3 @@ internal fun <T : GameEvent> T.checkState(
|
|||||||
if (!block(this)) throw exception(this)
|
if (!block(this)) throw exception(this)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* recordedEvents versions must be ordered and incremental.
|
|
||||||
*/
|
|
||||||
internal fun Game.checkRecorderEventsConsistency() {
|
|
||||||
recordedEvents
|
|
||||||
.also { if (it.size != (it.lastOrNull()?.version ?: 0)) throw InconsistentEventVersionException(recordedEvents) }
|
|
||||||
.mapIndexed { index, event ->
|
|
||||||
(index + 1) == event.version
|
|
||||||
}.run {
|
|
||||||
if (any { !it }) {
|
|
||||||
throw InconsistentEventVersionException(recordedEvents)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import eventDemo.contexts.game.domain.game.DiscardPile
|
|||||||
import eventDemo.contexts.game.domain.game.DrawPile
|
import eventDemo.contexts.game.domain.game.DrawPile
|
||||||
import eventDemo.contexts.game.domain.game.GameId
|
import eventDemo.contexts.game.domain.game.GameId
|
||||||
import eventDemo.contexts.game.domain.game.Player
|
import eventDemo.contexts.game.domain.game.Player
|
||||||
|
import eventDemo.contexts.game.domain.game.PlayerHand
|
||||||
import eventDemo.contexts.game.domain.game.PlayerList
|
import eventDemo.contexts.game.domain.game.PlayerList
|
||||||
import eventDemo.contexts.game.domain.game.errors.AllPlayerNotReadyException
|
import eventDemo.contexts.game.domain.game.errors.AllPlayerNotReadyException
|
||||||
import eventDemo.contexts.game.domain.game.errors.DeckMissingCardsException
|
import eventDemo.contexts.game.domain.game.errors.DeckMissingCardsException
|
||||||
@@ -23,24 +24,28 @@ data class GameCreated(
|
|||||||
override val recordedEvents: Set<GameEvent>,
|
override val recordedEvents: Set<GameEvent>,
|
||||||
override val version: Int,
|
override val version: Int,
|
||||||
) : Game {
|
) : Game {
|
||||||
init {
|
|
||||||
checkRecorderEventsConsistency()
|
|
||||||
}
|
|
||||||
|
|
||||||
val allPlayerIsReady: Boolean
|
val allPlayerIsReady: Boolean
|
||||||
get() {
|
get() {
|
||||||
return playersStatus.values.all { it == PlayerStatus.Ready }
|
return playersStatus.isNotEmpty() && playersStatus.values.all { it == PlayerStatus.Ready }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startGame(deck: Deck = newDeck().shuffleDeck()): GameStarted {
|
fun startGame(deck: Deck = newDeck().shuffleDeck()): GameStarted {
|
||||||
val (drawPile, discardPile) = initPiles(deck)
|
val (drawPile, discardPile, playersHands) =
|
||||||
|
initPiles(deck)
|
||||||
|
.let { (drawPile, discardPile) ->
|
||||||
|
createHandsFromDrawPile(drawPile)
|
||||||
|
.let { (drawPile, playersHands) ->
|
||||||
|
Triple(drawPile, discardPile, playersHands)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return GameStartedEvent(
|
return GameStartedEvent(
|
||||||
aggregateId = aggregateId,
|
aggregateId = aggregateId,
|
||||||
firstPlayer = players.random().id,
|
firstPlayer = players.randomPlayer().id,
|
||||||
version = version + 1,
|
version = version + 1,
|
||||||
drawPile = drawPile,
|
drawPile = drawPile,
|
||||||
discardPile = discardPile,
|
discardPile = discardPile,
|
||||||
|
playersHans = playersHands,
|
||||||
).checkState(
|
).checkState(
|
||||||
{ players.size > 1 },
|
{ players.size > 1 },
|
||||||
{ NeedMorePlayersToStartGameException(players) },
|
{ NeedMorePlayersToStartGameException(players) },
|
||||||
@@ -50,7 +55,8 @@ data class GameCreated(
|
|||||||
).checkState(
|
).checkState(
|
||||||
{ deck.size == 108 },
|
{ deck.size == 108 },
|
||||||
{ DeckMissingCardsException(players, deck) },
|
{ DeckMissingCardsException(players, deck) },
|
||||||
).run(::applyEvent)
|
).also { if (it.drawPile.size + it.discardPile.size + playersHands.values.sumOf { it.size } != 108) error("missing cards!") }
|
||||||
|
.run(::applyEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initPiles(deck: Set<Card>): Pair<DrawPile, DiscardPile> =
|
private fun initPiles(deck: Set<Card>): Pair<DrawPile, DiscardPile> =
|
||||||
@@ -61,6 +67,20 @@ data class GameCreated(
|
|||||||
draw to DiscardPile(cards)
|
draw to DiscardPile(cards)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createHandsFromDrawPile(drawPile: DrawPile) =
|
||||||
|
players
|
||||||
|
.map { it.id }
|
||||||
|
.fold(Pair(drawPile, emptyMap<Player.PlayerId, PlayerHand>())) { (drawAcc, handsAcc), playerId ->
|
||||||
|
drawAcc
|
||||||
|
.take(7)
|
||||||
|
.let { (draw, hand) ->
|
||||||
|
Pair(
|
||||||
|
draw,
|
||||||
|
handsAcc + (playerId to PlayerHand(hand)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun userJoinTheGame(
|
fun userJoinTheGame(
|
||||||
userId: UserId,
|
userId: UserId,
|
||||||
name: String,
|
name: String,
|
||||||
@@ -107,8 +127,13 @@ data class GameCreated(
|
|||||||
internal fun applyEvent(event: GameStartedEvent): GameStarted =
|
internal fun applyEvent(event: GameStartedEvent): GameStarted =
|
||||||
GameStarted(
|
GameStarted(
|
||||||
aggregateId = event.aggregateId,
|
aggregateId = event.aggregateId,
|
||||||
players = players,
|
players =
|
||||||
lastPlayerId = event.firstPlayer,
|
players
|
||||||
|
.map {
|
||||||
|
it.copy(hand = event.playersHans[it.id] ?: error("Player ${it.id} not found"))
|
||||||
|
}.let { PlayerList(it.toSet()) },
|
||||||
|
lastPlayerId = null,
|
||||||
|
nextPlayerId = event.firstPlayer,
|
||||||
drawPile = event.drawPile,
|
drawPile = event.drawPile,
|
||||||
discardPile = event.discardPile,
|
discardPile = event.discardPile,
|
||||||
version = event.version + 1,
|
version = event.version + 1,
|
||||||
@@ -133,7 +158,22 @@ fun newDeck(): Deck =
|
|||||||
(1..2).map { Card.PassCard(color) }
|
(1..2).map { Card.PassCard(color) }
|
||||||
}.let {
|
}.let {
|
||||||
it + (1..4).map { Card.Plus4Card() }
|
it + (1..4).map { Card.Plus4Card() }
|
||||||
|
}.let {
|
||||||
|
it + (1..4).map { Card.ChangeColorCard() }
|
||||||
}.toSet()
|
}.toSet()
|
||||||
|
|
||||||
fun Set<Card>.shuffleDeck() =
|
fun Set<Card>.shuffleDeck(): Set<Card> {
|
||||||
shuffled().toSet()
|
if (isDisabled) return this
|
||||||
|
return shuffled().toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PlayerList.randomPlayer(): Player {
|
||||||
|
if (isDisabled) return first()
|
||||||
|
return random()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isDisabled = false
|
||||||
|
|
||||||
|
fun disableRandomForTest() {
|
||||||
|
isDisabled = true
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,6 +16,5 @@ data class GameEnded(
|
|||||||
if (!players.map { it.id }.containsAll(playerWins)) {
|
if (!players.map { it.id }.containsAll(playerWins)) {
|
||||||
throw IllegalArgumentException("Player ${players.map { it.id }} were not in players")
|
throw IllegalArgumentException("Player ${players.map { it.id }} were not in players")
|
||||||
}
|
}
|
||||||
checkRecorderEventsConsistency()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,41 +23,14 @@ import eventDemo.contexts.game.domain.game.errors.ThePlayerIsNotInTheGameExcepti
|
|||||||
import eventDemo.contexts.game.domain.game.errors.ThePlayerMustPlayACardException
|
import eventDemo.contexts.game.domain.game.errors.ThePlayerMustPlayACardException
|
||||||
import eventDemo.contexts.game.domain.game.gameState.Game.Direction
|
import eventDemo.contexts.game.domain.game.gameState.Game.Direction
|
||||||
|
|
||||||
data class GameStarted(
|
fun PlayerList.nextPlayerTurn(
|
||||||
override val aggregateId: GameId,
|
lastPlayerId: Player.PlayerId,
|
||||||
override val players: PlayerList,
|
direction: Direction,
|
||||||
val drawPile: DrawPile,
|
): Player.PlayerId {
|
||||||
val discardPile: DiscardPile,
|
val lastPlayer = get(lastPlayerId)
|
||||||
val lastPlayerId: Player.PlayerId,
|
val playersLastTurn = filter { it.hand.cards.isNotEmpty() || it == lastPlayer }
|
||||||
val currentColor: Color,
|
|
||||||
val playedTurnHistory: List<History> = emptyList(),
|
|
||||||
val direction: Direction = Direction.CLOCKWISE,
|
|
||||||
val playerWins: Set<Player.PlayerId> = emptySet(),
|
|
||||||
override val version: Int,
|
|
||||||
override val recordedEvents: Set<GameEvent>,
|
|
||||||
) : Game {
|
|
||||||
val playersInGame by lazy { players.filter { it.hand.cards.isNotEmpty() } }
|
|
||||||
|
|
||||||
val lastPlayedCard: Card? by lazy { discardPile.topCard }
|
return playersLastTurn
|
||||||
|
|
||||||
val lastPlayed: Player by lazy { players.get(lastPlayerId) }
|
|
||||||
|
|
||||||
init {
|
|
||||||
checkRecorderEventsConsistency()
|
|
||||||
}
|
|
||||||
|
|
||||||
data class History(
|
|
||||||
val playerId: Player.PlayerId,
|
|
||||||
val event: GameEvent,
|
|
||||||
val direction: Direction,
|
|
||||||
)
|
|
||||||
|
|
||||||
val lastPlayer by lazy { players.get(lastPlayerId) }
|
|
||||||
|
|
||||||
val nextPlayer: Player by lazy {
|
|
||||||
val playersLastTurn = players.filter { it.hand.cards.isNotEmpty() || it == lastPlayer }
|
|
||||||
|
|
||||||
playersLastTurn
|
|
||||||
.indexOf(lastPlayer)
|
.indexOf(lastPlayer)
|
||||||
.let { lastPlayerIndex ->
|
.let { lastPlayerIndex ->
|
||||||
if (direction == Direction.CLOCKWISE) {
|
if (direction == Direction.CLOCKWISE) {
|
||||||
@@ -73,9 +46,37 @@ data class GameStarted(
|
|||||||
lastPlayerIndex - 1
|
lastPlayerIndex - 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.let { nextPlayerIndex -> players.elementAt(nextPlayerIndex) }
|
}.let { nextPlayerIndex -> elementAt(nextPlayerIndex).id }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class GameStarted(
|
||||||
|
override val aggregateId: GameId,
|
||||||
|
override val players: PlayerList,
|
||||||
|
val drawPile: DrawPile,
|
||||||
|
val discardPile: DiscardPile,
|
||||||
|
val lastPlayerId: Player.PlayerId?,
|
||||||
|
val nextPlayerId: Player.PlayerId,
|
||||||
|
val currentColor: Color,
|
||||||
|
val playedTurnHistory: List<History> = emptyList(),
|
||||||
|
val direction: Direction = Direction.CLOCKWISE,
|
||||||
|
val playerWins: Set<Player.PlayerId> = emptySet(),
|
||||||
|
override val version: Int,
|
||||||
|
override val recordedEvents: Set<GameEvent>,
|
||||||
|
) : Game {
|
||||||
|
val playersInGame by lazy { players.filter { it.hand.cards.isNotEmpty() } }
|
||||||
|
|
||||||
|
val lastPlayedCard: Card? by lazy { discardPile.topCard }
|
||||||
|
|
||||||
|
data class History(
|
||||||
|
val playerId: Player.PlayerId,
|
||||||
|
val event: GameEvent,
|
||||||
|
val direction: Direction,
|
||||||
|
)
|
||||||
|
|
||||||
|
val lastPlayer: Player? by lazy { lastPlayerId?.let { players.get(it) } }
|
||||||
|
|
||||||
|
val nextPlayer: Player by lazy { players.get(nextPlayerId) }
|
||||||
|
|
||||||
fun canBePlayThisCard(card: Card): Boolean {
|
fun canBePlayThisCard(card: Card): Boolean {
|
||||||
val cardOnBoard = discardPile.topCard ?: return false
|
val cardOnBoard = discardPile.topCard ?: return false
|
||||||
return when (cardOnBoard) {
|
return when (cardOnBoard) {
|
||||||
@@ -144,8 +145,12 @@ data class GameStarted(
|
|||||||
): GameStarted =
|
): GameStarted =
|
||||||
CardIsPlayedEvent(aggregateId, card, playerId, chosenColor, version + 1)
|
CardIsPlayedEvent(aggregateId, card, playerId, chosenColor, version + 1)
|
||||||
.checkPlayerTurn()
|
.checkPlayerTurn()
|
||||||
.checkState({ card is Card.CardWithColor && chosenColor != null }, { TheCardIsAColorCardException(playerId) })
|
.checkState({
|
||||||
.checkState({ card is Card.CardWith4Color && chosenColor == null }, { TheCardHasNoColorException(playerId) })
|
(card is Card.CardWithColor && chosenColor == null) || card is Card.CardWith4Color
|
||||||
|
}, { TheCardIsAColorCardException(playerId) })
|
||||||
|
.checkState({
|
||||||
|
(card is Card.CardWith4Color && chosenColor != null) || card is Card.CardWithColor
|
||||||
|
}, { TheCardHasNoColorException(playerId) })
|
||||||
.run(::applyEvent)
|
.run(::applyEvent)
|
||||||
|
|
||||||
internal fun applyEvent(event: CardIsPlayedEvent): GameStarted =
|
internal fun applyEvent(event: CardIsPlayedEvent): GameStarted =
|
||||||
@@ -167,6 +172,7 @@ data class GameStarted(
|
|||||||
discardPile = discardPile.withNewCard(card = event.card),
|
discardPile = discardPile.withNewCard(card = event.card),
|
||||||
currentColor = color,
|
currentColor = color,
|
||||||
lastPlayerId = event.playerId,
|
lastPlayerId = event.playerId,
|
||||||
|
nextPlayerId = players.nextPlayerTurn(event.playerId, nextDirectionAfterPlay),
|
||||||
playedTurnHistory = playedTurnHistory - History(event.playerId, event, direction),
|
playedTurnHistory = playedTurnHistory - History(event.playerId, event, direction),
|
||||||
direction = nextDirectionAfterPlay,
|
direction = nextDirectionAfterPlay,
|
||||||
version = event.version,
|
version = event.version,
|
||||||
@@ -202,6 +208,8 @@ data class GameStarted(
|
|||||||
copy(
|
copy(
|
||||||
players = players.withNewCardOnPlayerHand(event.playerId, event.takenCards),
|
players = players.withNewCardOnPlayerHand(event.playerId, event.takenCards),
|
||||||
drawPile = drawPile.take(event.takenCards.size).first,
|
drawPile = drawPile.take(event.takenCards.size).first,
|
||||||
|
lastPlayerId = event.playerId,
|
||||||
|
nextPlayerId = players.nextPlayerTurn(event.playerId, direction),
|
||||||
version = event.version,
|
version = event.version,
|
||||||
recordedEvents = recordedEvents + event,
|
recordedEvents = recordedEvents + event,
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ fun Route.gameWebSocket(channelSubscriber: GameChannelsSubscriber) {
|
|||||||
authenticate {
|
authenticate {
|
||||||
webSocket("/games/{id}") {
|
webSocket("/games/{id}") {
|
||||||
channelSubscriber.subscribePlayerToGameChannels(
|
channelSubscriber.subscribePlayerToGameChannels(
|
||||||
gameId = GameId(UUID.nameUUIDFromBytes(call.parameters["id"]?.encodeToByteArray()!!)),
|
gameId = GameId(UUID.fromString(call.parameters["id"]!!)),
|
||||||
userId = call.currentUserId,
|
userId = call.currentUserId,
|
||||||
incomingCommandChannel = toObjectChannel(incoming),
|
incomingCommandChannel = toObjectChannel(incoming),
|
||||||
sendNotificationChannel = fromFrameChannel(outgoing),
|
sendNotificationChannel = fromFrameChannel(outgoing),
|
||||||
|
|||||||
@@ -78,7 +78,11 @@ class BusInRabbitMQ<E>(
|
|||||||
body: ByteArray,
|
body: ByteArray,
|
||||||
) {
|
) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
block(stringToObject(body.toString(Charsets.UTF_8)))
|
val obj = stringToObject(body.toString(Charsets.UTF_8))
|
||||||
|
withLoggingContext("item" to obj.toString()) {
|
||||||
|
logger.info { "Received delivery of $exchangeName" }
|
||||||
|
}
|
||||||
|
block(obj)
|
||||||
}
|
}
|
||||||
channel.basicAck(envelope.deliveryTag, false)
|
channel.basicAck(envelope.deliveryTag, false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package eventDemo.contexts.game.application
|
package eventDemo.contexts.game.application
|
||||||
|
|
||||||
import eventDemo.Tag
|
import eventDemo.Tag
|
||||||
|
import eventDemo.contexts.auth.application.eventStores.UserRepository
|
||||||
|
import eventDemo.contexts.auth.domain.User
|
||||||
import eventDemo.contexts.game.application.channels.GameChannelsSubscriber
|
import eventDemo.contexts.game.application.channels.GameChannelsSubscriber
|
||||||
import eventDemo.contexts.game.application.command.models.GameCommand
|
import eventDemo.contexts.game.application.command.models.GameCommand
|
||||||
import eventDemo.contexts.game.application.eventStores.GameRepository
|
import eventDemo.contexts.game.application.eventStores.GameRepository
|
||||||
@@ -11,11 +13,11 @@ import eventDemo.contexts.game.application.notification.models.PlayerAsPlayACard
|
|||||||
import eventDemo.contexts.game.application.notification.models.PlayerWasReadyNotification
|
import eventDemo.contexts.game.application.notification.models.PlayerWasReadyNotification
|
||||||
import eventDemo.contexts.game.application.notification.models.TheGameWasStartedNotification
|
import eventDemo.contexts.game.application.notification.models.TheGameWasStartedNotification
|
||||||
import eventDemo.contexts.game.application.notification.models.WelcomeToTheGameNotification
|
import eventDemo.contexts.game.application.notification.models.WelcomeToTheGameNotification
|
||||||
import eventDemo.contexts.game.domain.events.disableShuffleDeck
|
|
||||||
import eventDemo.contexts.game.domain.game.Card
|
import eventDemo.contexts.game.domain.game.Card
|
||||||
import eventDemo.contexts.game.domain.game.GameId
|
import eventDemo.contexts.game.domain.game.GameId
|
||||||
import eventDemo.contexts.game.domain.game.gameState.Game
|
import eventDemo.contexts.game.domain.game.gameState.Game
|
||||||
import eventDemo.contexts.game.domain.game.gameState.GameStarted
|
import eventDemo.contexts.game.domain.game.gameState.GameStarted
|
||||||
|
import eventDemo.contexts.game.domain.game.gameState.disableRandomForTest
|
||||||
import eventDemo.testHelpers.CreateGameWithCommandsInChannelsHelpers.createGameWithCommandsInChannels
|
import eventDemo.testHelpers.CreateGameWithCommandsInChannelsHelpers.createGameWithCommandsInChannels
|
||||||
import eventDemo.testHelpers.CreateGameWithCommandsInChannelsHelpers.joinTheGame
|
import eventDemo.testHelpers.CreateGameWithCommandsInChannelsHelpers.joinTheGame
|
||||||
import eventDemo.testHelpers.CreateGameWithCommandsInChannelsHelpers.playCard
|
import eventDemo.testHelpers.CreateGameWithCommandsInChannelsHelpers.playCard
|
||||||
@@ -25,7 +27,11 @@ import eventDemo.testHelpers.testKoinApplicationWithConfig
|
|||||||
import io.kotest.assertions.nondeterministic.eventually
|
import io.kotest.assertions.nondeterministic.eventually
|
||||||
import io.kotest.assertions.nondeterministic.until
|
import io.kotest.assertions.nondeterministic.until
|
||||||
import io.kotest.core.spec.style.FunSpec
|
import io.kotest.core.spec.style.FunSpec
|
||||||
|
import io.kotest.matchers.collections.shouldContainExactly
|
||||||
import io.kotest.matchers.equals.shouldBeEqual
|
import io.kotest.matchers.equals.shouldBeEqual
|
||||||
|
import io.kotest.matchers.equals.shouldEqual
|
||||||
|
import io.kotest.matchers.nulls.shouldNotBeNull
|
||||||
|
import io.kotest.matchers.types.shouldBeInstanceOf
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
@@ -44,7 +50,7 @@ class GameSimulationTest :
|
|||||||
|
|
||||||
test("Simulation of a game") {
|
test("Simulation of a game") {
|
||||||
withTimeout(10.seconds) {
|
withTimeout(10.seconds) {
|
||||||
disableShuffleDeck()
|
disableRandomForTest()
|
||||||
val gameId = GameId()
|
val gameId = GameId()
|
||||||
val user1 = createNewUser("user1")
|
val user1 = createNewUser("user1")
|
||||||
val user2 = createNewUser("user2")
|
val user2 = createNewUser("user2")
|
||||||
@@ -61,6 +67,11 @@ class GameSimulationTest :
|
|||||||
|
|
||||||
testKoinApplicationWithConfig {
|
testKoinApplicationWithConfig {
|
||||||
val gameRepository = get<GameRepository>()
|
val gameRepository = get<GameRepository>()
|
||||||
|
val userRepository = get<UserRepository>()
|
||||||
|
userRepository.run {
|
||||||
|
save(user1)
|
||||||
|
save(user2)
|
||||||
|
}
|
||||||
|
|
||||||
gameRepository.create(gameId)
|
gameRepository.create(gameId)
|
||||||
|
|
||||||
@@ -104,8 +115,9 @@ class GameSimulationTest :
|
|||||||
// Player 1 actions
|
// Player 1 actions
|
||||||
val player1Job =
|
val player1Job =
|
||||||
launch {
|
launch {
|
||||||
createGameWithCommandsInChannels(channelCommand1) { getPlayer ->
|
createGameWithCommandsInChannels(channelCommand1, gameId, user1) {
|
||||||
user1.joinTheGame()
|
|
||||||
|
joinTheGame()
|
||||||
player1Notifications.waitNotification<WelcomeToTheGameNotification> {
|
player1Notifications.waitNotification<WelcomeToTheGameNotification> {
|
||||||
players.map { it.userId }.contains(user1.id)
|
players.map { it.userId }.contains(user1.id)
|
||||||
}
|
}
|
||||||
@@ -116,20 +128,38 @@ class GameSimulationTest :
|
|||||||
player.userId == user2.id
|
player.userId == user2.id
|
||||||
}
|
}
|
||||||
|
|
||||||
getPlayer(user1).readyToPlay()
|
readyToPlay()
|
||||||
player1Notifications.waitNotification<PlayerWasReadyNotification> {
|
player1Notifications.waitNotification<PlayerWasReadyNotification> {
|
||||||
playerId == getPlayer(user2).id
|
playerId == getPlayer(user2).id
|
||||||
}
|
}
|
||||||
|
|
||||||
val player1Hand =
|
playedCard1 =
|
||||||
player1Notifications.waitNotification<TheGameWasStartedNotification> { hand.size == 7 }.hand
|
player1Notifications
|
||||||
|
.waitNotification<TheGameWasStartedNotification> { hand.size == 7 }
|
||||||
|
.hand
|
||||||
|
.first()
|
||||||
|
.apply {
|
||||||
|
this.shouldBeInstanceOf<Card.NumericCard>()
|
||||||
|
number shouldEqual 1
|
||||||
|
color shouldEqual Card.Color.Red
|
||||||
|
}
|
||||||
|
|
||||||
playedCard1 = player1Hand.first()
|
|
||||||
player1Notifications.waitNotification<ItsTheTurnOfNotification> {
|
player1Notifications.waitNotification<ItsTheTurnOfNotification> {
|
||||||
|
if (player.userId == user2.id) error("WRONG PLAYER TURN")
|
||||||
player.userId == user1.id
|
player.userId == user1.id
|
||||||
}
|
}
|
||||||
|
|
||||||
getPlayer(user1).playCard(playedCard1!!, null)
|
game
|
||||||
|
.shouldBeInstanceOf<GameStarted>()
|
||||||
|
.discardPile
|
||||||
|
.topCard
|
||||||
|
.shouldNotBeNull()
|
||||||
|
.shouldBeInstanceOf<Card.NumericCard> {
|
||||||
|
it.number shouldEqual 0
|
||||||
|
it.color shouldEqual Card.Color.Red
|
||||||
|
}
|
||||||
|
|
||||||
|
playCard(playedCard1!!)
|
||||||
|
|
||||||
player1Notifications.waitNotification<ItsTheTurnOfNotification> {
|
player1Notifications.waitNotification<ItsTheTurnOfNotification> {
|
||||||
player == getPlayer(user2)
|
player == getPlayer(user2)
|
||||||
@@ -139,8 +169,18 @@ class GameSimulationTest :
|
|||||||
playerId == getPlayer(user2).id && card == playedCard2
|
playerId == getPlayer(user2).id && card == playedCard2
|
||||||
}
|
}
|
||||||
|
|
||||||
playedCard1 = player1Hand.elementAt(1)
|
playedCard1 =
|
||||||
getPlayer(user1).playCard(playedCard1)
|
assertInstanceOf<GameStarted>(game)
|
||||||
|
.playableCards(currentPlayer.id)
|
||||||
|
.first()
|
||||||
|
|
||||||
|
playedCard1.run {
|
||||||
|
this.shouldBeInstanceOf<Card.NumericCard>()
|
||||||
|
number shouldEqual 2
|
||||||
|
color shouldEqual Card.Color.Red
|
||||||
|
}
|
||||||
|
|
||||||
|
playCard(playedCard1)
|
||||||
|
|
||||||
player1Notifications.waitNotification<ItsTheTurnOfNotification> {
|
player1Notifications.waitNotification<ItsTheTurnOfNotification> {
|
||||||
player == getPlayer(user2)
|
player == getPlayer(user2)
|
||||||
@@ -151,11 +191,11 @@ class GameSimulationTest :
|
|||||||
// Player 2 actions
|
// Player 2 actions
|
||||||
val player2Job =
|
val player2Job =
|
||||||
launch {
|
launch {
|
||||||
createGameWithCommandsInChannels(channelCommand2) { getPlayer ->
|
createGameWithCommandsInChannels(channelCommand2, gameId, user2) {
|
||||||
// wait player 1 has joined the game
|
// wait player 1 has joined the game
|
||||||
until(3.seconds) { player1HasJoin }
|
until(3.seconds) { player1HasJoin }
|
||||||
|
|
||||||
user2.joinTheGame()
|
joinTheGame()
|
||||||
|
|
||||||
player2Notifications.waitNotification<WelcomeToTheGameNotification> {
|
player2Notifications.waitNotification<WelcomeToTheGameNotification> {
|
||||||
players.map { it.userId }.contains(user1.id) &&
|
players.map { it.userId }.contains(user1.id) &&
|
||||||
@@ -163,30 +203,48 @@ class GameSimulationTest :
|
|||||||
}
|
}
|
||||||
player2Notifications.waitNotification<PlayerWasReadyNotification> { playerId == getPlayer(user1).id }
|
player2Notifications.waitNotification<PlayerWasReadyNotification> { playerId == getPlayer(user1).id }
|
||||||
|
|
||||||
getPlayer(user2).readyToPlay()
|
readyToPlay()
|
||||||
|
|
||||||
val player2Hand =
|
playedCard2 =
|
||||||
player2Notifications.waitNotification<TheGameWasStartedNotification> { hand.size == 7 }.hand
|
player2Notifications
|
||||||
|
.waitNotification<TheGameWasStartedNotification> { hand.size == 7 }
|
||||||
|
.hand
|
||||||
|
.first()
|
||||||
|
.apply {
|
||||||
|
this.shouldBeInstanceOf<Card.NumericCard>()
|
||||||
|
number shouldEqual 8
|
||||||
|
color shouldEqual Card.Color.Red
|
||||||
|
}
|
||||||
|
|
||||||
player2Notifications.waitNotification<ItsTheTurnOfNotification> {
|
player2Notifications.waitNotification<ItsTheTurnOfNotification> {
|
||||||
|
if (player.userId == user2.id) error("WRONG PLAYER TURN")
|
||||||
player.userId == user1.id
|
player.userId == user1.id
|
||||||
}
|
}
|
||||||
player2Notifications.waitNotification<PlayerAsPlayACardNotification> {
|
player2Notifications.waitNotification<PlayerAsPlayACardNotification> {
|
||||||
playerId == getPlayer(user1).id && card == playedCard1
|
playerId == getPlayer(user1).id && card == playedCard1
|
||||||
}
|
}
|
||||||
playedCard2 = player2Hand.first()
|
|
||||||
|
|
||||||
player2Notifications.waitNotification<ItsTheTurnOfNotification> {
|
player2Notifications.waitNotification<ItsTheTurnOfNotification> {
|
||||||
player == getPlayer(user2)
|
player == currentPlayer
|
||||||
}
|
}
|
||||||
|
|
||||||
getPlayer(user2).playCard(playedCard2)
|
game
|
||||||
|
.shouldBeInstanceOf<GameStarted>()
|
||||||
|
.discardPile
|
||||||
|
.topCard
|
||||||
|
.shouldNotBeNull()
|
||||||
|
.shouldBeInstanceOf<Card.NumericCard> {
|
||||||
|
it.number shouldEqual 1
|
||||||
|
it.color shouldEqual Card.Color.Red
|
||||||
|
}
|
||||||
|
|
||||||
|
playCard(playedCard2)
|
||||||
|
|
||||||
player2Notifications.waitNotification<ItsTheTurnOfNotification> {
|
player2Notifications.waitNotification<ItsTheTurnOfNotification> {
|
||||||
player.userId == user1.id
|
player.userId == user1.id
|
||||||
}
|
}
|
||||||
player2Notifications.waitNotification<PlayerAsPlayACardNotification> {
|
player2Notifications.waitNotification<PlayerAsPlayACardNotification> {
|
||||||
playerId == getPlayer(user2).id && card == playedCard2
|
playerId == currentPlayer.id && card == playedCard2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -200,20 +258,25 @@ class GameSimulationTest :
|
|||||||
|
|
||||||
// Check if the state is correct
|
// Check if the state is correct
|
||||||
game.aggregateId shouldBeEqual gameId
|
game.aggregateId shouldBeEqual gameId
|
||||||
game.players.map { it.id } shouldBeEqual setOf(user1.id, user2.id)
|
game.players.map { it.userId } shouldContainExactly setOf(user1.id, user2.id)
|
||||||
assertNotNull(game.players.find { it.userId == user1.id })
|
assertNotNull(game.players.find { it.userId == user1.id })
|
||||||
.hand.size shouldBeEqual 5
|
.hand.size shouldBeEqual 5
|
||||||
assertNotNull(game.players.find { it.userId == user2.id })
|
assertNotNull(game.players.find { it.userId == user2.id })
|
||||||
.hand.size shouldBeEqual 6
|
.hand.size shouldBeEqual 6
|
||||||
game.direction shouldBeEqual Game.Direction.CLOCKWISE
|
game.direction shouldBeEqual Game.Direction.CLOCKWISE
|
||||||
assertNotNull(game.lastPlayed.userId) shouldBeEqual user1
|
assertNotNull(game.lastPlayer?.userId) shouldBeEqual user1.id
|
||||||
assertNotNull(game.discardPile.topCard) shouldBeEqual assertNotNull(playedCard1)
|
assertNotNull(game.discardPile.topCard) shouldBeEqual assertNotNull(playedCard1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
private suspend inline fun <reified T : Notification> MutableList<Notification>.waitNotification(crossinline block: T.() -> Boolean): T =
|
context(user: User)
|
||||||
eventually(3.seconds) {
|
private suspend inline fun <reified T : Notification> MutableList<Notification>.waitNotification(crossinline block: T.() -> Boolean): T {
|
||||||
filterIsInstance<T>().first { block(it) }
|
println("NOTIFICATION WAITING: ${T::class.simpleName} for user: ${user.username}")
|
||||||
|
return eventually(3.seconds) {
|
||||||
|
filterIsInstance<T>()
|
||||||
|
.first { block(it) }
|
||||||
|
.also { remove(it) }
|
||||||
|
}.also { println("NOTIFICATION RECEIVED: ${T::class.simpleName} for user: ${user.username}") }
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-7
@@ -1,6 +1,7 @@
|
|||||||
package eventDemo.contexts.game.application.notification
|
package eventDemo.contexts.game.application.notification
|
||||||
|
|
||||||
import eventDemo.contexts.auth.application.eventStores.UserEventStoreRepository
|
import eventDemo.contexts.auth.application.eventStores.UserEventStoreRepository
|
||||||
|
import eventDemo.contexts.auth.application.eventStores.UserRepository
|
||||||
import eventDemo.contexts.auth.infrastructure.persistence.eventStore.UserEventStoreInMemory
|
import eventDemo.contexts.auth.infrastructure.persistence.eventStore.UserEventStoreInMemory
|
||||||
import eventDemo.contexts.game.application.command.handlers.GameCommandHandlerDispatcher
|
import eventDemo.contexts.game.application.command.handlers.GameCommandHandlerDispatcher
|
||||||
import eventDemo.contexts.game.application.command.handlers.JoinTheGameHandler
|
import eventDemo.contexts.game.application.command.handlers.JoinTheGameHandler
|
||||||
@@ -14,6 +15,8 @@ import eventDemo.contexts.game.application.notification.models.WelcomeToTheGameN
|
|||||||
import eventDemo.contexts.game.infrastructure.persistence.eventBus.GameEventBusInMemory
|
import eventDemo.contexts.game.infrastructure.persistence.eventBus.GameEventBusInMemory
|
||||||
import eventDemo.contexts.game.infrastructure.persistence.eventStore.GameEventStoreInMemory
|
import eventDemo.contexts.game.infrastructure.persistence.eventStore.GameEventStoreInMemory
|
||||||
import eventDemo.sharedKernel.UserId
|
import eventDemo.sharedKernel.UserId
|
||||||
|
import eventDemo.testHelpers.createNewUser
|
||||||
|
import io.kotest.assertions.nondeterministic.eventually
|
||||||
import io.kotest.core.spec.style.FunSpec
|
import io.kotest.core.spec.style.FunSpec
|
||||||
import io.kotest.matchers.collections.shouldContain
|
import io.kotest.matchers.collections.shouldContain
|
||||||
import io.kotest.matchers.equals.shouldEqual
|
import io.kotest.matchers.equals.shouldEqual
|
||||||
@@ -21,6 +24,7 @@ import kotlinx.coroutines.GlobalScope
|
|||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.junit.jupiter.api.assertInstanceOf
|
import org.junit.jupiter.api.assertInstanceOf
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
class EventToNotificationSubscriberTest :
|
class EventToNotificationSubscriberTest :
|
||||||
FunSpec({
|
FunSpec({
|
||||||
@@ -46,8 +50,12 @@ class EventToNotificationSubscriberTest :
|
|||||||
|
|
||||||
val game = gameRepository.create()
|
val game = gameRepository.create()
|
||||||
|
|
||||||
val user1 = UserId()
|
val user1 = createNewUser("user1")
|
||||||
val user2 = UserId()
|
val user2 = createNewUser("user2")
|
||||||
|
userRepository.run {
|
||||||
|
save(user1)
|
||||||
|
save(user2)
|
||||||
|
}
|
||||||
|
|
||||||
val player1Notifications = mutableListOf<Notification>()
|
val player1Notifications = mutableListOf<Notification>()
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
@@ -56,20 +64,22 @@ class EventToNotificationSubscriberTest :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
commentDispatcher.dispatch(JoinTheGameCommand(user1, JoinTheGameCommand.Payload(game.aggregateId)))
|
commentDispatcher.dispatch(JoinTheGameCommand(user1.id, JoinTheGameCommand.Payload(game.aggregateId)))
|
||||||
subscriber
|
subscriber
|
||||||
.subscribeToEventsAndSendNotification(
|
.subscribeToEventsAndSendNotification(
|
||||||
game.aggregateId,
|
game.aggregateId,
|
||||||
user1,
|
user2.id,
|
||||||
notificationChannel,
|
notificationChannel,
|
||||||
).use {
|
).use {
|
||||||
commentDispatcher.dispatch(JoinTheGameCommand(user2, JoinTheGameCommand.Payload(game.aggregateId)))
|
commentDispatcher.dispatch(JoinTheGameCommand(user2.id, JoinTheGameCommand.Payload(game.aggregateId)))
|
||||||
}
|
}
|
||||||
|
|
||||||
player1Notifications.size shouldEqual 2
|
eventually(duration = 1.seconds) {
|
||||||
|
player1Notifications.size shouldEqual 1
|
||||||
|
}
|
||||||
player1Notifications.first().let { notification ->
|
player1Notifications.first().let { notification ->
|
||||||
assertInstanceOf<WelcomeToTheGameNotification>(notification)
|
assertInstanceOf<WelcomeToTheGameNotification>(notification)
|
||||||
notification.players.map { it.userId } shouldContain user1
|
notification.players.map { it.userId } shouldContain user2.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
+301
@@ -0,0 +1,301 @@
|
|||||||
|
package eventDemo.contexts.game.application.notification
|
||||||
|
|
||||||
|
import eventDemo.contexts.game.application.notification.models.ItsTheTurnOfNotification
|
||||||
|
import eventDemo.contexts.game.application.notification.models.PilesShuffledNotification
|
||||||
|
import eventDemo.contexts.game.application.notification.models.PlayerAsPlayACardNotification
|
||||||
|
import eventDemo.contexts.game.application.notification.models.PlayerHavePassNotification
|
||||||
|
import eventDemo.contexts.game.application.notification.models.PlayerWasReadyNotification
|
||||||
|
import eventDemo.contexts.game.application.notification.models.TheGameWasStartedNotification
|
||||||
|
import eventDemo.contexts.game.application.notification.models.WelcomeToTheGameNotification
|
||||||
|
import eventDemo.contexts.game.application.notification.models.YourNewCardNotification
|
||||||
|
import eventDemo.contexts.game.domain.events.CardIsPlayedEvent
|
||||||
|
import eventDemo.contexts.game.domain.events.DrawFilledWithDiscardEvent
|
||||||
|
import eventDemo.contexts.game.domain.events.GameStartedEvent
|
||||||
|
import eventDemo.contexts.game.domain.events.NewPlayerEvent
|
||||||
|
import eventDemo.contexts.game.domain.events.PlayerHaveDrawCardEvent
|
||||||
|
import eventDemo.contexts.game.domain.events.PlayerReadyEvent
|
||||||
|
import eventDemo.contexts.game.domain.game.Card
|
||||||
|
import eventDemo.contexts.game.domain.game.DiscardPile
|
||||||
|
import eventDemo.contexts.game.domain.game.DrawPile
|
||||||
|
import eventDemo.contexts.game.domain.game.GameId
|
||||||
|
import eventDemo.contexts.game.domain.game.Player
|
||||||
|
import eventDemo.contexts.game.domain.game.PlayerHand
|
||||||
|
import eventDemo.contexts.game.domain.game.PlayerList
|
||||||
|
import eventDemo.contexts.game.domain.game.gameState.GameCreated
|
||||||
|
import eventDemo.contexts.game.domain.game.gameState.GameStarted
|
||||||
|
import eventDemo.sharedKernel.UserId
|
||||||
|
import io.kotest.core.spec.style.FunSpec
|
||||||
|
import io.kotest.matchers.shouldBe
|
||||||
|
import org.junit.jupiter.api.assertInstanceOf
|
||||||
|
|
||||||
|
class ToNotificationTest :
|
||||||
|
FunSpec({
|
||||||
|
val player1 =
|
||||||
|
Player(
|
||||||
|
name = "Bob",
|
||||||
|
userId = UserId(),
|
||||||
|
hand = PlayerHand(setOf(Card.NumericCard(1, Card.Color.Red))),
|
||||||
|
id = Player.PlayerId(),
|
||||||
|
)
|
||||||
|
val player2 =
|
||||||
|
Player(
|
||||||
|
name = "John",
|
||||||
|
userId = UserId(),
|
||||||
|
hand = PlayerHand(setOf(Card.NumericCard(1, Card.Color.Red))),
|
||||||
|
id = Player.PlayerId(),
|
||||||
|
)
|
||||||
|
|
||||||
|
test("NewPlayerEvent") {
|
||||||
|
val game =
|
||||||
|
GameCreated(
|
||||||
|
aggregateId = GameId(),
|
||||||
|
version = 1,
|
||||||
|
players = PlayerList(setOf(player1)),
|
||||||
|
recordedEvents = setOf(),
|
||||||
|
)
|
||||||
|
NewPlayerEvent(
|
||||||
|
game.aggregateId,
|
||||||
|
version = 2,
|
||||||
|
player = player1,
|
||||||
|
).toNotification(
|
||||||
|
game = game,
|
||||||
|
currentUserId = player1.userId,
|
||||||
|
).let {
|
||||||
|
it.toList().size shouldBe 1
|
||||||
|
// Check if the user is
|
||||||
|
assertInstanceOf<WelcomeToTheGameNotification>(it.first()).run {
|
||||||
|
players.size shouldBe 1
|
||||||
|
players.first().name shouldBe "Bob"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("PlayerReadyEvent") {
|
||||||
|
val game =
|
||||||
|
GameCreated(
|
||||||
|
aggregateId = GameId(),
|
||||||
|
version = 1,
|
||||||
|
players = PlayerList(setOf(player1)),
|
||||||
|
recordedEvents = setOf(),
|
||||||
|
)
|
||||||
|
PlayerReadyEvent(
|
||||||
|
game.aggregateId,
|
||||||
|
version = 2,
|
||||||
|
playerId = player1.id,
|
||||||
|
).toNotification(
|
||||||
|
game = game,
|
||||||
|
currentUserId = player1.userId,
|
||||||
|
).let {
|
||||||
|
it.toList().size shouldBe 1
|
||||||
|
assertInstanceOf<PlayerWasReadyNotification>(it.first()).let {
|
||||||
|
it.playerId shouldBe player1.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("PlayerHaveDrawCardEvent on current player") {
|
||||||
|
val game =
|
||||||
|
GameStarted(
|
||||||
|
aggregateId = GameId(),
|
||||||
|
players = PlayerList(setOf(player1, player2)),
|
||||||
|
drawPile = DrawPile(),
|
||||||
|
discardPile = DiscardPile(),
|
||||||
|
lastPlayerId = player1.id,
|
||||||
|
nextPlayerId = player2.id,
|
||||||
|
currentColor = Card.Color.Red,
|
||||||
|
version = 1,
|
||||||
|
recordedEvents = setOf(),
|
||||||
|
)
|
||||||
|
val card = Card.NumericCard(1, Card.Color.Blue)
|
||||||
|
PlayerHaveDrawCardEvent(
|
||||||
|
game.aggregateId,
|
||||||
|
version = 2,
|
||||||
|
playerId = player1.id,
|
||||||
|
takenCards = setOf(card),
|
||||||
|
).toNotification(
|
||||||
|
game = game,
|
||||||
|
currentUserId = player1.userId,
|
||||||
|
).let {
|
||||||
|
it.toList().size shouldBe 2
|
||||||
|
it.toList().let { notifications ->
|
||||||
|
assertInstanceOf<YourNewCardNotification>(notifications.first()).let {
|
||||||
|
it.cards.first() shouldBe card
|
||||||
|
}
|
||||||
|
assertInstanceOf<ItsTheTurnOfNotification>(notifications[1]).let {
|
||||||
|
it.player.id shouldBe player2.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
test("PlayerHaveDrawCardEvent on other player") {
|
||||||
|
val game =
|
||||||
|
GameStarted(
|
||||||
|
aggregateId = GameId(),
|
||||||
|
players = PlayerList(setOf(player1, player2)),
|
||||||
|
drawPile = DrawPile(),
|
||||||
|
discardPile = DiscardPile(),
|
||||||
|
lastPlayerId = player1.id,
|
||||||
|
nextPlayerId = player2.id,
|
||||||
|
currentColor = Card.Color.Red,
|
||||||
|
version = 1,
|
||||||
|
recordedEvents = setOf(),
|
||||||
|
)
|
||||||
|
val card = Card.NumericCard(1, Card.Color.Blue)
|
||||||
|
PlayerHaveDrawCardEvent(
|
||||||
|
game.aggregateId,
|
||||||
|
version = 2,
|
||||||
|
playerId = player1.id,
|
||||||
|
takenCards = setOf(card),
|
||||||
|
).toNotification(
|
||||||
|
game = game,
|
||||||
|
currentUserId = player2.userId,
|
||||||
|
).let {
|
||||||
|
it.toList().size shouldBe 2
|
||||||
|
it.toList().let { notifications ->
|
||||||
|
assertInstanceOf<PlayerHavePassNotification>(notifications.first()).let {
|
||||||
|
it.playerId shouldBe player1.id
|
||||||
|
}
|
||||||
|
assertInstanceOf<ItsTheTurnOfNotification>(notifications[1]).let {
|
||||||
|
it.player.id shouldBe player2.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("CardIsPlayedEvent on current player") {
|
||||||
|
val game =
|
||||||
|
GameStarted(
|
||||||
|
aggregateId = GameId(),
|
||||||
|
players = PlayerList(setOf(player1, player2)),
|
||||||
|
drawPile = DrawPile(),
|
||||||
|
discardPile = DiscardPile(),
|
||||||
|
lastPlayerId = player2.id,
|
||||||
|
nextPlayerId = player1.id,
|
||||||
|
currentColor = Card.Color.Red,
|
||||||
|
version = 1,
|
||||||
|
recordedEvents = setOf(),
|
||||||
|
)
|
||||||
|
val card = Card.NumericCard(1, Card.Color.Blue)
|
||||||
|
CardIsPlayedEvent(
|
||||||
|
game.aggregateId,
|
||||||
|
version = 2,
|
||||||
|
playerId = player1.id,
|
||||||
|
card = card,
|
||||||
|
).toNotification(
|
||||||
|
game = game,
|
||||||
|
currentUserId = player1.userId,
|
||||||
|
).toList()
|
||||||
|
.let { notifications ->
|
||||||
|
notifications.size shouldBe 2
|
||||||
|
assertInstanceOf<PlayerAsPlayACardNotification>(notifications.first()).let {
|
||||||
|
it.playerId shouldBe player1.id
|
||||||
|
it.card shouldBe card
|
||||||
|
}
|
||||||
|
assertInstanceOf<ItsTheTurnOfNotification>(notifications[1]).let {
|
||||||
|
it.player.id shouldBe player1.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
test("CardIsPlayedEvent on other player") {
|
||||||
|
val game =
|
||||||
|
GameStarted(
|
||||||
|
aggregateId = GameId(),
|
||||||
|
players = PlayerList(setOf(player1, player2)),
|
||||||
|
drawPile = DrawPile(),
|
||||||
|
discardPile = DiscardPile(),
|
||||||
|
lastPlayerId = player1.id,
|
||||||
|
nextPlayerId = player2.id,
|
||||||
|
currentColor = Card.Color.Red,
|
||||||
|
version = 1,
|
||||||
|
recordedEvents = setOf(),
|
||||||
|
)
|
||||||
|
val card = Card.NumericCard(1, Card.Color.Blue)
|
||||||
|
CardIsPlayedEvent(
|
||||||
|
game.aggregateId,
|
||||||
|
version = 2,
|
||||||
|
playerId = player2.id,
|
||||||
|
card = card,
|
||||||
|
).toNotification(
|
||||||
|
game = game,
|
||||||
|
currentUserId = player1.userId,
|
||||||
|
).let {
|
||||||
|
it.toList().size shouldBe 2
|
||||||
|
it.toList().let { notifications ->
|
||||||
|
assertInstanceOf<PlayerAsPlayACardNotification>(notifications.first()).let {
|
||||||
|
it.playerId shouldBe player2.id
|
||||||
|
it.card shouldBe card
|
||||||
|
}
|
||||||
|
assertInstanceOf<ItsTheTurnOfNotification>(notifications[1]).let {
|
||||||
|
it.player.id shouldBe player2.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("DrawFilledWithDiscardEvent") {
|
||||||
|
val game =
|
||||||
|
GameStarted(
|
||||||
|
aggregateId = GameId(),
|
||||||
|
players = PlayerList(setOf(player1, player2)),
|
||||||
|
drawPile = DrawPile(),
|
||||||
|
discardPile = DiscardPile(),
|
||||||
|
lastPlayerId = player1.id,
|
||||||
|
nextPlayerId = player2.id,
|
||||||
|
currentColor = Card.Color.Red,
|
||||||
|
version = 1,
|
||||||
|
recordedEvents = setOf(),
|
||||||
|
)
|
||||||
|
DrawFilledWithDiscardEvent(
|
||||||
|
game.aggregateId,
|
||||||
|
version = 2,
|
||||||
|
newDrawPile = DrawPile(),
|
||||||
|
newDiscardPile = DiscardPile(),
|
||||||
|
).toNotification(
|
||||||
|
game = game,
|
||||||
|
currentUserId = player1.userId,
|
||||||
|
).let {
|
||||||
|
it.toList().size shouldBe 1
|
||||||
|
assertInstanceOf<PilesShuffledNotification>(it.first())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("GameStartedEvent") {
|
||||||
|
val game =
|
||||||
|
GameStarted(
|
||||||
|
aggregateId = GameId(),
|
||||||
|
players = PlayerList(setOf(player1, player2)),
|
||||||
|
drawPile = DrawPile(),
|
||||||
|
discardPile = DiscardPile(),
|
||||||
|
lastPlayerId = player1.id,
|
||||||
|
nextPlayerId = player2.id,
|
||||||
|
currentColor = Card.Color.Red,
|
||||||
|
version = 1,
|
||||||
|
recordedEvents = setOf(),
|
||||||
|
)
|
||||||
|
GameStartedEvent(
|
||||||
|
game.aggregateId,
|
||||||
|
version = 2,
|
||||||
|
firstPlayer = player1.id,
|
||||||
|
playersHans =
|
||||||
|
mapOf(
|
||||||
|
player1.id to player1.hand,
|
||||||
|
player2.id to player2.hand,
|
||||||
|
),
|
||||||
|
drawPile = DrawPile(),
|
||||||
|
discardPile = DiscardPile(),
|
||||||
|
).toNotification(
|
||||||
|
game = game,
|
||||||
|
currentUserId = player1.userId,
|
||||||
|
).toList()
|
||||||
|
.let { notifications ->
|
||||||
|
notifications.size shouldBe 2
|
||||||
|
assertInstanceOf<TheGameWasStartedNotification>(notifications.first()).let {
|
||||||
|
it.hand.size shouldBe 1
|
||||||
|
it.hand.first() shouldBe player1.hand.cards.first()
|
||||||
|
}
|
||||||
|
assertInstanceOf<ItsTheTurnOfNotification>(notifications[1]).let {
|
||||||
|
it.player.id shouldBe player2.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -11,6 +11,7 @@ import eventDemo.contexts.game.domain.game.GameId
|
|||||||
import eventDemo.contexts.game.domain.game.Player
|
import eventDemo.contexts.game.domain.game.Player
|
||||||
import eventDemo.contexts.game.domain.game.PlayerHand
|
import eventDemo.contexts.game.domain.game.PlayerHand
|
||||||
import eventDemo.contexts.game.domain.game.PlayerList
|
import eventDemo.contexts.game.domain.game.PlayerList
|
||||||
|
import eventDemo.contexts.game.domain.game.gameState.Game.Direction
|
||||||
import eventDemo.sharedKernel.UserId
|
import eventDemo.sharedKernel.UserId
|
||||||
import eventDemo.testHelpers.act
|
import eventDemo.testHelpers.act
|
||||||
import eventDemo.testHelpers.assert
|
import eventDemo.testHelpers.assert
|
||||||
@@ -195,10 +196,12 @@ class GameStartedTest :
|
|||||||
val card3 = Card.NumericCard(3, Card.Color.Red)
|
val card3 = Card.NumericCard(3, Card.Color.Red)
|
||||||
assert {
|
assert {
|
||||||
val player1 = Player("Player 1", UserId())
|
val player1 = Player("Player 1", UserId())
|
||||||
|
val player2 = Player("Player 2", UserId())
|
||||||
GameStarted(
|
GameStarted(
|
||||||
aggregateId = GameId(),
|
aggregateId = GameId(),
|
||||||
players = PlayerList(setOf(player1)),
|
players = PlayerList(setOf(player1)),
|
||||||
lastPlayerId = player1.id,
|
lastPlayerId = player1.id,
|
||||||
|
nextPlayerId = player2.id,
|
||||||
drawPile = DrawPile(),
|
drawPile = DrawPile(),
|
||||||
discardPile =
|
discardPile =
|
||||||
DiscardPile(
|
DiscardPile(
|
||||||
@@ -244,17 +247,14 @@ class GameStartedTest :
|
|||||||
val card3 = Card.NumericCard(3, Card.Color.Red)
|
val card3 = Card.NumericCard(3, Card.Color.Red)
|
||||||
val card4 = Card.NumericCard(4, Card.Color.Red)
|
val card4 = Card.NumericCard(4, Card.Color.Red)
|
||||||
|
|
||||||
val player1 =
|
val player1 = Player("Jo", UserId())
|
||||||
Player(
|
val player2 = Player("Bob", UserId())
|
||||||
"Jo",
|
|
||||||
UserId(),
|
|
||||||
hand = PlayerHand(cards = setOf()),
|
|
||||||
)
|
|
||||||
assert {
|
assert {
|
||||||
GameStarted(
|
GameStarted(
|
||||||
aggregateId = GameId(),
|
aggregateId = GameId(),
|
||||||
players = PlayerList(setOf(player1)),
|
players = PlayerList(setOf(player1)),
|
||||||
lastPlayerId = player1.id,
|
lastPlayerId = player1.id,
|
||||||
|
nextPlayerId = player2.id,
|
||||||
drawPile =
|
drawPile =
|
||||||
DrawPile(
|
DrawPile(
|
||||||
setOf(
|
setOf(
|
||||||
@@ -303,6 +303,7 @@ class GameStartedTest :
|
|||||||
aggregateId = GameId(),
|
aggregateId = GameId(),
|
||||||
players = PlayerList(setOf(player1, player2)),
|
players = PlayerList(setOf(player1, player2)),
|
||||||
lastPlayerId = player1.id,
|
lastPlayerId = player1.id,
|
||||||
|
nextPlayerId = player2.id,
|
||||||
drawPile = DrawPile(),
|
drawPile = DrawPile(),
|
||||||
discardPile = DiscardPile(),
|
discardPile = DiscardPile(),
|
||||||
currentColor = Card.Color.Yellow,
|
currentColor = Card.Color.Yellow,
|
||||||
@@ -373,10 +374,12 @@ private fun gameWithCard(
|
|||||||
val player1 = Player("Tesla", UserId(), hand = played1Hand)
|
val player1 = Player("Tesla", UserId(), hand = played1Hand)
|
||||||
val player2 = Player("Einstein", UserId(), hand = played2Hand)
|
val player2 = Player("Einstein", UserId(), hand = played2Hand)
|
||||||
val player3 = Player("Curie", UserId(), hand = PlayerHand(setOf(Card.NumericCard(8, Card.Color.Yellow))))
|
val player3 = Player("Curie", UserId(), hand = PlayerHand(setOf(Card.NumericCard(8, Card.Color.Yellow))))
|
||||||
|
val players = PlayerList(setOf(player1, player2, player3))
|
||||||
return GameStarted(
|
return GameStarted(
|
||||||
aggregateId = GameId(),
|
aggregateId = GameId(),
|
||||||
players = PlayerList(setOf(player1, player2, player3)),
|
players = players,
|
||||||
lastPlayerId = player3.id,
|
lastPlayerId = player3.id,
|
||||||
|
nextPlayerId = players.nextPlayerTurn(player3.id, Direction.CLOCKWISE),
|
||||||
discardPile = DiscardPile(setOf(onTheDiscardPile)),
|
discardPile = DiscardPile(setOf(onTheDiscardPile)),
|
||||||
drawPile = DrawPile(),
|
drawPile = DrawPile(),
|
||||||
currentColor = (onTheDiscardPile as? Card.CardWithColor)?.color ?: chosenColor ?: error("no color"),
|
currentColor = (onTheDiscardPile as? Card.CardWithColor)?.color ?: chosenColor ?: error("no color"),
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package eventDemo.contexts.game.domain.game.gameState
|
||||||
|
|
||||||
|
import eventDemo.contexts.game.domain.game.Card
|
||||||
|
import io.kotest.assertions.retry
|
||||||
|
import io.kotest.core.spec.style.FunSpec
|
||||||
|
import io.kotest.matchers.collections.shouldHaveSize
|
||||||
|
import io.kotest.matchers.should
|
||||||
|
import io.kotest.matchers.shouldBe
|
||||||
|
import io.kotest.matchers.shouldNotBe
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
class NewDeckTest :
|
||||||
|
FunSpec({
|
||||||
|
|
||||||
|
test("newDeck") {
|
||||||
|
newDeck().let {
|
||||||
|
it shouldNotBe null
|
||||||
|
it.filterIsInstance<Card.NumericCard>() shouldHaveSize 76
|
||||||
|
it.filterIsInstance<Card.Plus2Card>() shouldHaveSize 8
|
||||||
|
it.filterIsInstance<Card.ReverseCard>() shouldHaveSize 8
|
||||||
|
it.filterIsInstance<Card.PassCard>() shouldHaveSize 8
|
||||||
|
it.filterIsInstance<Card.Plus4Card>() shouldHaveSize 4
|
||||||
|
it.filterIsInstance<Card.ChangeColorCard>() shouldHaveSize 4
|
||||||
|
it shouldHaveSize 108
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("shuffleDeck") {
|
||||||
|
val deck = (0..9).map { Card.NumericCard(it, Card.Color.Red) }
|
||||||
|
deck.run {
|
||||||
|
this[3].number shouldBe 3
|
||||||
|
}
|
||||||
|
should {
|
||||||
|
retry(maxRetry = 4, timeout = 1.seconds) {
|
||||||
|
deck.shuffled().run {
|
||||||
|
this[3].number shouldNotBe 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -117,8 +117,8 @@ class GameListRouteTest :
|
|||||||
call.body<List<GameList>>().first().let {
|
call.body<List<GameList>>().first().let {
|
||||||
it.status shouldBeEqual GameList.Status.IS_STARTED
|
it.status shouldBeEqual GameList.Status.IS_STARTED
|
||||||
it.players shouldHaveSize 2
|
it.players shouldHaveSize 2
|
||||||
it.players.map { it.userId } shouldContain user1
|
it.players.map { it.userId } shouldContain user1.id
|
||||||
it.players.map { it.userId } shouldContain user2
|
it.players.map { it.userId } shouldContain user2.id
|
||||||
it.winners shouldHaveSize 0
|
it.winners shouldHaveSize 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,9 +33,10 @@ class EventStreamTest :
|
|||||||
block(aggregateId)
|
block(aggregateId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Koin.eventStreams(): List<EventStream<EventXTest, IdTest>> =
|
fun Koin.eventStreams(): Map<String, EventStream<EventXTest, IdTest>> =
|
||||||
listOf(
|
mapOf(
|
||||||
EventStreamInMemory(IdTest()),
|
EventStreamInMemory::class.simpleName.toString() to EventStreamInMemory(IdTest()),
|
||||||
|
EventStreamInPostgresql::class.simpleName.toString() to
|
||||||
EventStreamInPostgresql(
|
EventStreamInPostgresql(
|
||||||
IdTest(),
|
IdTest(),
|
||||||
dataSource = get(),
|
dataSource = get(),
|
||||||
|
|||||||
@@ -9,51 +9,70 @@ import eventDemo.contexts.game.application.eventStores.GameRepository
|
|||||||
import eventDemo.contexts.game.domain.game.Card
|
import eventDemo.contexts.game.domain.game.Card
|
||||||
import eventDemo.contexts.game.domain.game.GameId
|
import eventDemo.contexts.game.domain.game.GameId
|
||||||
import eventDemo.contexts.game.domain.game.Player
|
import eventDemo.contexts.game.domain.game.Player
|
||||||
|
import eventDemo.contexts.game.domain.game.gameState.Game
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import org.koin.core.Koin
|
import org.koin.core.Koin
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
object CreateGameWithCommandsInChannelsHelpers {
|
object CreateGameWithCommandsInChannelsHelpers {
|
||||||
|
class Data(
|
||||||
|
private val repo: GameRepository,
|
||||||
|
val gameId: GameId,
|
||||||
|
val currentUser: User,
|
||||||
|
) {
|
||||||
|
fun getPlayer(user: User): Player =
|
||||||
|
game.players.get(user.id)
|
||||||
|
|
||||||
|
val currentPlayer: Player get() = getPlayer(currentUser)
|
||||||
|
|
||||||
|
val game: Game
|
||||||
|
get() = repo.get(gameId)!!
|
||||||
|
}
|
||||||
|
|
||||||
context(koin: Koin)
|
context(koin: Koin)
|
||||||
suspend fun <T> createGameWithCommandsInChannels(
|
suspend fun <T> createGameWithCommandsInChannels(
|
||||||
channelCommand: Channel<GameCommand>,
|
channelCommand: Channel<GameCommand>,
|
||||||
gameName: String = "testGame${UUID.randomUUID()}",
|
gameId: GameId,
|
||||||
block: suspend context(Channel<GameCommand>, GameId) ((User) -> Player) -> T,
|
user: User,
|
||||||
|
block:
|
||||||
|
suspend context(
|
||||||
|
CreateGameWithCommandsInChannelsHelpers,
|
||||||
|
Channel<GameCommand>,
|
||||||
|
User,
|
||||||
|
) Data.() -> T,
|
||||||
): T {
|
): T {
|
||||||
val gameId = GameId(UUID.nameUUIDFromBytes(gameName.encodeToByteArray()))
|
|
||||||
val repo = koin.get<GameRepository>()
|
val repo = koin.get<GameRepository>()
|
||||||
repo.getOrCreate(gameId)
|
repo.getOrCreate(gameId)
|
||||||
|
|
||||||
return with(gameId) {
|
return with(channelCommand) {
|
||||||
with(channelCommand) {
|
with(user) {
|
||||||
with(CreateGameWithCommandsInChannelsHelpers) {
|
with(CreateGameWithCommandsInChannelsHelpers) {
|
||||||
block { user -> repo.get(gameId)!!.players.get(user.id) }
|
Data(repo, gameId, user).block()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context(channelCommand: Channel<GameCommand>, gameId: GameId)
|
context(channelCommand: Channel<GameCommand>, data: Data)
|
||||||
suspend fun User.joinTheGame(): JoinTheGameCommand =
|
suspend fun joinTheGame(): JoinTheGameCommand =
|
||||||
JoinTheGameCommand(
|
JoinTheGameCommand(
|
||||||
id,
|
data.currentUser.id,
|
||||||
JoinTheGameCommand.Payload(gameId),
|
JoinTheGameCommand.Payload(data.gameId),
|
||||||
).also { channelCommand.send(it) }
|
).also { channelCommand.send(it) }
|
||||||
|
|
||||||
context(channelCommand: Channel<GameCommand>, gameId: GameId)
|
context(channelCommand: Channel<GameCommand>, data: Data)
|
||||||
suspend fun Player.readyToPlay(): ReadyToPlayCommand =
|
suspend fun readyToPlay(): ReadyToPlayCommand =
|
||||||
ReadyToPlayCommand(
|
ReadyToPlayCommand(
|
||||||
userId,
|
data.currentUser.id,
|
||||||
ReadyToPlayCommand.Payload(gameId, id),
|
ReadyToPlayCommand.Payload(data.gameId, data.currentPlayer.id),
|
||||||
).also { channelCommand.send(it) }
|
).also { channelCommand.send(it) }
|
||||||
|
|
||||||
context(channelCommand: Channel<GameCommand>, gameId: GameId)
|
context(channelCommand: Channel<GameCommand>, data: Data)
|
||||||
suspend fun Player.playCard(
|
suspend fun playCard(
|
||||||
card: Card,
|
card: Card,
|
||||||
chosenColor: Card.Color? = null,
|
chosenColor: Card.Color? = null,
|
||||||
): PlayCardCommand =
|
): PlayCardCommand =
|
||||||
PlayCardCommand(
|
PlayCardCommand(
|
||||||
userId,
|
data.currentUser.id,
|
||||||
PlayCardCommand.Payload(gameId, id, card, chosenColor),
|
PlayCardCommand.Payload(data.gameId, data.currentPlayer.id, card, chosenColor),
|
||||||
).also { channelCommand.send(it) }
|
).also { channelCommand.send(it) }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user