diff --git a/.idea/scala_settings.xml b/.idea/scala_settings.xml new file mode 100644 index 0000000..4608fe0 --- /dev/null +++ b/.idea/scala_settings.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/modules/core/src/main/scala/de/nowchess/chess/config/JacksonConfig.scala b/modules/core/src/main/scala/de/nowchess/chess/config/JacksonConfig.scala index b252cec..188a328 100644 --- a/modules/core/src/main/scala/de/nowchess/chess/config/JacksonConfig.scala +++ b/modules/core/src/main/scala/de/nowchess/chess/config/JacksonConfig.scala @@ -1,6 +1,5 @@ package de.nowchess.chess.config -import com.fasterxml.jackson.core.Version import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.scala.DefaultScalaModule import io.quarkus.jackson.ObjectMapperCustomizer @@ -9,9 +8,4 @@ import jakarta.inject.Singleton @Singleton class JacksonConfig extends ObjectMapperCustomizer: def customize(mapper: ObjectMapper): Unit = - mapper.registerModule(new DefaultScalaModule() { - override def version(): Version = - // scalafix:off DisableSyntax.null - new Version(2, 21, 1, null, "com.fasterxml.jackson.module", "jackson-module-scala") - // scalafix:on DisableSyntax.null - }) + mapper.registerModule(DefaultScalaModule) diff --git a/modules/core/src/main/scala/de/nowchess/chess/registry/GameEntry.scala b/modules/core/src/main/scala/de/nowchess/chess/registry/GameEntry.scala index 7dd09fb..74c240e 100644 --- a/modules/core/src/main/scala/de/nowchess/chess/registry/GameEntry.scala +++ b/modules/core/src/main/scala/de/nowchess/chess/registry/GameEntry.scala @@ -9,5 +9,6 @@ final case class GameEntry( engine: GameEngine, white: PlayerInfo, black: PlayerInfo, + drawOfferedBy: Option[Color] = None, resigned: Boolean = false, ) diff --git a/modules/core/src/main/scala/de/nowchess/chess/resource/GameResource.scala b/modules/core/src/main/scala/de/nowchess/chess/resource/GameResource.scala index e8258bb..7520810 100644 --- a/modules/core/src/main/scala/de/nowchess/chess/resource/GameResource.scala +++ b/modules/core/src/main/scala/de/nowchess/chess/resource/GameResource.scala @@ -20,19 +20,10 @@ import jakarta.ws.rs.* import jakarta.ws.rs.core.{MediaType, Response} import java.util.concurrent.atomic.AtomicReference -import scala.compiletime.uninitialized @Path("/api/board/game") @ApplicationScoped -class GameResource: - - // scalafix:off DisableSyntax.var - @Inject - var registry: GameRegistry = uninitialized - - @Inject - var objectMapper: ObjectMapper = uninitialized - // scalafix:on DisableSyntax.var +class GameResource(@Inject val registry: GameRegistry, @Inject val objectMapper: ObjectMapper): private val DefaultWhite = PlayerInfo(PlayerId("p1"), "Player 1") private val DefaultBlack = PlayerInfo(PlayerId("p2"), "Player 2") @@ -40,7 +31,7 @@ class GameResource: // ── mapping ────────────────────────────────────────────────────────────── private def statusOf(entry: GameEntry): String = - if entry.engine.pendingDrawOfferBy.isDefined then "drawOffered" + if entry.drawOfferedBy.isDefined then "drawOffered" else val ctx = entry.engine.context ctx.result match @@ -113,7 +104,7 @@ class GameResource: val error = new AtomicReference[Option[String]](None) val obs = new Observer: def onGameEvent(e: GameEvent): Unit = e match - case InvalidMoveEvent(_, reason) => error.set(Some(reason.toString)) + case InvalidMoveEvent(_, reason) => error.set(Some(reason)) case _ => () engine.subscribe(obs) engine.processUserInput(uci) @@ -142,7 +133,6 @@ class GameResource: val black = playerInfoFrom(req.black, DefaultBlack) val entry = newEntry(GameContext.initial, white, black) registry.store(entry) - println(s"Created game ${entry.gameId}") created(toGameFullDto(entry)) @GET @@ -245,16 +235,26 @@ class GameResource: assertGameNotOver(entry) action match case "offer" => - entry.engine.offerDraw(entry.engine.context.turn) + registry.update(entry.copy(drawOfferedBy = Some(entry.engine.context.turn))) ok(OkResponseDto()) case "accept" => - entry.engine.acceptDraw(entry.engine.context.turn) - ok(OkResponseDto()) + entry.drawOfferedBy match + case None => + throw BadRequestException("NO_DRAW_OFFER", "No draw offer to accept") + case Some(offerer) if offerer == entry.engine.context.turn => + throw BadRequestException("CANNOT_ACCEPT_OWN_OFFER", "Cannot accept your own draw offer") + case _ => + entry.engine.applyDraw(DrawReason.Agreement) + registry.update(entry.copy(drawOfferedBy = None)) + ok(OkResponseDto()) case "decline" => - entry.engine.declineDraw(entry.engine.context.turn) + if entry.drawOfferedBy.isEmpty then throw BadRequestException("NO_DRAW_OFFER", "No draw offer to decline") + registry.update(entry.copy(drawOfferedBy = None)) ok(OkResponseDto()) case "claim" => - entry.engine.claimDraw() + if entry.engine.context.halfMoveClock < 100 then + throw BadRequestException("CLAIM_NOT_AVAILABLE", "Fifty-move rule draw is not available") + entry.engine.applyDraw(DrawReason.FiftyMoveRule) ok(OkResponseDto()) case _ => throw BadRequestException("INVALID_ACTION", s"Unknown draw action: $action", Some("action"))