diff --git a/modules/rule/src/main/scala/de/nowchess/rules/RuleSet.scala b/modules/api/src/main/scala/de/nowchess/api/rules/RuleSet.scala similarity index 98% rename from modules/rule/src/main/scala/de/nowchess/rules/RuleSet.scala rename to modules/api/src/main/scala/de/nowchess/api/rules/RuleSet.scala index f2622bd..535b655 100644 --- a/modules/rule/src/main/scala/de/nowchess/rules/RuleSet.scala +++ b/modules/api/src/main/scala/de/nowchess/api/rules/RuleSet.scala @@ -1,7 +1,7 @@ -package de.nowchess.rules +package de.nowchess.api.rules -import de.nowchess.api.game.GameContext import de.nowchess.api.board.Square +import de.nowchess.api.game.GameContext import de.nowchess.api.move.Move /** Extension point for chess rule variants (standard, Chess960, etc.). All rule queries are stateless: given a diff --git a/modules/bot/src/main/scala/de/nowchess/bot/bots/ClassicalBot.scala b/modules/bot/src/main/scala/de/nowchess/bot/bots/ClassicalBot.scala index 0573110..a52e5b9 100644 --- a/modules/bot/src/main/scala/de/nowchess/bot/bots/ClassicalBot.scala +++ b/modules/bot/src/main/scala/de/nowchess/bot/bots/ClassicalBot.scala @@ -7,7 +7,7 @@ import de.nowchess.bot.bots.classic.EvaluationClassic import de.nowchess.bot.logic.AlphaBetaSearch import de.nowchess.bot.util.PolyglotBook import de.nowchess.bot.{BotDifficulty, BotMoveRepetition} -import de.nowchess.rules.RuleSet +import de.nowchess.api.rules.RuleSet import de.nowchess.rules.sets.DefaultRules final class ClassicalBot( diff --git a/modules/bot/src/main/scala/de/nowchess/bot/bots/HybridBot.scala b/modules/bot/src/main/scala/de/nowchess/bot/bots/HybridBot.scala index 3a9147a..fd95d0d 100644 --- a/modules/bot/src/main/scala/de/nowchess/bot/bots/HybridBot.scala +++ b/modules/bot/src/main/scala/de/nowchess/bot/bots/HybridBot.scala @@ -9,7 +9,7 @@ import de.nowchess.bot.bots.nnue.EvaluationNNUE import de.nowchess.bot.logic.{AlphaBetaSearch, TranspositionTable} import de.nowchess.bot.util.PolyglotBook import de.nowchess.bot.{BotDifficulty, BotMoveRepetition, Config} -import de.nowchess.rules.RuleSet +import de.nowchess.api.rules.RuleSet import de.nowchess.rules.sets.DefaultRules final class HybridBot( diff --git a/modules/bot/src/main/scala/de/nowchess/bot/bots/NNUEBot.scala b/modules/bot/src/main/scala/de/nowchess/bot/bots/NNUEBot.scala index 094989d..bfa2c6f 100644 --- a/modules/bot/src/main/scala/de/nowchess/bot/bots/NNUEBot.scala +++ b/modules/bot/src/main/scala/de/nowchess/bot/bots/NNUEBot.scala @@ -7,7 +7,7 @@ import de.nowchess.bot.bots.nnue.EvaluationNNUE import de.nowchess.bot.logic.AlphaBetaSearch import de.nowchess.bot.util.{PolyglotBook, ZobristHash} import de.nowchess.bot.{BotDifficulty, BotMoveRepetition} -import de.nowchess.rules.RuleSet +import de.nowchess.api.rules.RuleSet import de.nowchess.rules.sets.DefaultRules final class NNUEBot( diff --git a/modules/bot/src/main/scala/de/nowchess/bot/logic/AlphaBetaSearch.scala b/modules/bot/src/main/scala/de/nowchess/bot/logic/AlphaBetaSearch.scala index 8a58b21..cf41d70 100644 --- a/modules/bot/src/main/scala/de/nowchess/bot/logic/AlphaBetaSearch.scala +++ b/modules/bot/src/main/scala/de/nowchess/bot/logic/AlphaBetaSearch.scala @@ -5,7 +5,7 @@ import de.nowchess.api.game.GameContext import de.nowchess.api.move.{Move, MoveType} import de.nowchess.bot.ai.Evaluation import de.nowchess.bot.util.ZobristHash -import de.nowchess.rules.RuleSet +import de.nowchess.api.rules.RuleSet import de.nowchess.rules.sets.DefaultRules import java.util.concurrent.atomic.{AtomicInteger, AtomicLong} diff --git a/modules/bot/src/test/scala/de/nowchess/bot/AlphaBetaSearchTest.scala b/modules/bot/src/test/scala/de/nowchess/bot/AlphaBetaSearchTest.scala index 609f977..da826bd 100644 --- a/modules/bot/src/test/scala/de/nowchess/bot/AlphaBetaSearchTest.scala +++ b/modules/bot/src/test/scala/de/nowchess/bot/AlphaBetaSearchTest.scala @@ -6,7 +6,7 @@ import de.nowchess.api.move.{Move, MoveType, PromotionPiece} import de.nowchess.bot.ai.Evaluation import de.nowchess.bot.bots.classic.EvaluationClassic import de.nowchess.bot.logic.AlphaBetaSearch -import de.nowchess.rules.RuleSet +import de.nowchess.api.rules.RuleSet import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers import de.nowchess.rules.sets.DefaultRules diff --git a/modules/bot/src/test/scala/de/nowchess/bot/ClassicalBotTest.scala b/modules/bot/src/test/scala/de/nowchess/bot/ClassicalBotTest.scala index 30b0214..bf53263 100644 --- a/modules/bot/src/test/scala/de/nowchess/bot/ClassicalBotTest.scala +++ b/modules/bot/src/test/scala/de/nowchess/bot/ClassicalBotTest.scala @@ -5,7 +5,7 @@ import de.nowchess.api.game.GameContext import de.nowchess.api.move.Move import de.nowchess.api.move.MoveType import de.nowchess.bot.bots.ClassicalBot -import de.nowchess.rules.RuleSet +import de.nowchess.api.rules.RuleSet import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers import de.nowchess.rules.sets.DefaultRules diff --git a/modules/bot/src/test/scala/de/nowchess/bot/HybridBotTest.scala b/modules/bot/src/test/scala/de/nowchess/bot/HybridBotTest.scala index b1e7e81..bf806c2 100644 --- a/modules/bot/src/test/scala/de/nowchess/bot/HybridBotTest.scala +++ b/modules/bot/src/test/scala/de/nowchess/bot/HybridBotTest.scala @@ -6,7 +6,7 @@ import de.nowchess.api.move.{Move, MoveType} import de.nowchess.bot.ai.Evaluation import de.nowchess.bot.bots.HybridBot import de.nowchess.bot.util.{PolyglotBook, PolyglotHash} -import de.nowchess.rules.RuleSet +import de.nowchess.api.rules.RuleSet import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers diff --git a/modules/core/build.gradle.kts b/modules/core/build.gradle.kts index d572500..f6b2c8b 100644 --- a/modules/core/build.gradle.kts +++ b/modules/core/build.gradle.kts @@ -49,7 +49,7 @@ dependencies { implementation(project(":modules:api")) implementation(project(":modules:io")) - implementation(project(":modules:rule")) + testImplementation(project(":modules:rule")) implementation(project(":modules:bot")) diff --git a/modules/core/src/main/resources/application.yml b/modules/core/src/main/resources/application.yml index 1663bc0..bbc7bff 100644 --- a/modules/core/src/main/resources/application.yml +++ b/modules/core/src/main/resources/application.yml @@ -6,3 +6,5 @@ quarkus: rest-client: io-service: url: http://localhost:8081 + rule-service: + url: http://localhost:8082 diff --git a/modules/core/src/main/scala/de/nowchess/chess/adapter/RuleSetRestAdapter.scala b/modules/core/src/main/scala/de/nowchess/chess/adapter/RuleSetRestAdapter.scala new file mode 100644 index 0000000..a281095 --- /dev/null +++ b/modules/core/src/main/scala/de/nowchess/chess/adapter/RuleSetRestAdapter.scala @@ -0,0 +1,51 @@ +package de.nowchess.chess.adapter + +import de.nowchess.api.board.Square +import de.nowchess.api.game.GameContext +import de.nowchess.api.move.Move +import de.nowchess.chess.client.{RuleMoveRequest, RuleServiceClient, RuleSquareRequest} +import de.nowchess.api.rules.RuleSet +import jakarta.enterprise.context.ApplicationScoped +import jakarta.inject.Inject +import org.eclipse.microprofile.rest.client.inject.RestClient + +import scala.compiletime.uninitialized + +@ApplicationScoped +class RuleSetRestAdapter extends RuleSet: + + // scalafix:off DisableSyntax.var + @Inject + @RestClient + var client: RuleServiceClient = uninitialized + // scalafix:on DisableSyntax.var + + def candidateMoves(ctx: GameContext)(sq: Square): List[Move] = + client.candidateMoves(RuleSquareRequest(ctx, sq.toString)) + + def legalMoves(ctx: GameContext)(sq: Square): List[Move] = + client.legalMoves(RuleSquareRequest(ctx, sq.toString)) + + def allLegalMoves(ctx: GameContext): List[Move] = + client.allLegalMoves(ctx) + + def isCheck(ctx: GameContext): Boolean = + client.isCheck(ctx) + + def isCheckmate(ctx: GameContext): Boolean = + client.isCheckmate(ctx) + + def isStalemate(ctx: GameContext): Boolean = + client.isStalemate(ctx) + + def isInsufficientMaterial(ctx: GameContext): Boolean = + client.isInsufficientMaterial(ctx) + + def isFiftyMoveRule(ctx: GameContext): Boolean = + client.isFiftyMoveRule(ctx) + + def isThreefoldRepetition(ctx: GameContext): Boolean = + client.isThreefoldRepetition(ctx) + + def applyMove(ctx: GameContext)(move: Move): GameContext = + client.applyMove(RuleMoveRequest(ctx, move)) diff --git a/modules/core/src/main/scala/de/nowchess/chess/client/RuleServiceClient.scala b/modules/core/src/main/scala/de/nowchess/chess/client/RuleServiceClient.scala new file mode 100644 index 0000000..b213ef4 --- /dev/null +++ b/modules/core/src/main/scala/de/nowchess/chess/client/RuleServiceClient.scala @@ -0,0 +1,74 @@ +package de.nowchess.chess.client + +import de.nowchess.api.game.GameContext +import de.nowchess.api.move.Move +import jakarta.ws.rs.* +import jakarta.ws.rs.core.MediaType +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient + +case class RuleSquareRequest(context: GameContext, square: String) +case class RuleMoveRequest(context: GameContext, move: Move) + +@Path("/api/rules") +@RegisterRestClient(configKey = "rule-service") +trait RuleServiceClient: + + @POST + @Path("/candidate-moves") + @Consumes(Array(MediaType.APPLICATION_JSON)) + @Produces(Array(MediaType.APPLICATION_JSON)) + def candidateMoves(req: RuleSquareRequest): List[Move] + + @POST + @Path("/legal-moves") + @Consumes(Array(MediaType.APPLICATION_JSON)) + @Produces(Array(MediaType.APPLICATION_JSON)) + def legalMoves(req: RuleSquareRequest): List[Move] + + @POST + @Path("/all-legal-moves") + @Consumes(Array(MediaType.APPLICATION_JSON)) + @Produces(Array(MediaType.APPLICATION_JSON)) + def allLegalMoves(ctx: GameContext): List[Move] + + @POST + @Path("/is-check") + @Consumes(Array(MediaType.APPLICATION_JSON)) + @Produces(Array(MediaType.APPLICATION_JSON)) + def isCheck(ctx: GameContext): Boolean + + @POST + @Path("/is-checkmate") + @Consumes(Array(MediaType.APPLICATION_JSON)) + @Produces(Array(MediaType.APPLICATION_JSON)) + def isCheckmate(ctx: GameContext): Boolean + + @POST + @Path("/is-stalemate") + @Consumes(Array(MediaType.APPLICATION_JSON)) + @Produces(Array(MediaType.APPLICATION_JSON)) + def isStalemate(ctx: GameContext): Boolean + + @POST + @Path("/is-insufficient-material") + @Consumes(Array(MediaType.APPLICATION_JSON)) + @Produces(Array(MediaType.APPLICATION_JSON)) + def isInsufficientMaterial(ctx: GameContext): Boolean + + @POST + @Path("/is-fifty-move-rule") + @Consumes(Array(MediaType.APPLICATION_JSON)) + @Produces(Array(MediaType.APPLICATION_JSON)) + def isFiftyMoveRule(ctx: GameContext): Boolean + + @POST + @Path("/is-threefold-repetition") + @Consumes(Array(MediaType.APPLICATION_JSON)) + @Produces(Array(MediaType.APPLICATION_JSON)) + def isThreefoldRepetition(ctx: GameContext): Boolean + + @POST + @Path("/apply-move") + @Consumes(Array(MediaType.APPLICATION_JSON)) + @Produces(Array(MediaType.APPLICATION_JSON)) + def applyMove(req: RuleMoveRequest): GameContext 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 816cb0b..254368d 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 @@ -5,6 +5,8 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.module.scala.DefaultScalaModule import de.nowchess.api.board.Square +import de.nowchess.api.move.MoveType +import de.nowchess.chess.json.{MoveTypeDeserializer, MoveTypeSerializer, SquareDeserializer, SquareSerializer} import de.nowchess.io.json.{SquareKeyDeserializer, SquareKeySerializer} import io.quarkus.jackson.ObjectMapperCustomizer import jakarta.inject.Singleton @@ -18,7 +20,11 @@ class JacksonConfig extends ObjectMapperCustomizer: new Version(2, 21, 1, null, "com.fasterxml.jackson.module", "jackson-module-scala") // scalafix:on DisableSyntax.null }) - val squareModule = new SimpleModule() - squareModule.addKeyDeserializer(classOf[Square], new SquareKeyDeserializer()) - squareModule.addKeySerializer(classOf[Square], new SquareKeySerializer()) - mapper.registerModule(squareModule) + val mod = new SimpleModule() + mod.addKeySerializer(classOf[Square], new SquareKeySerializer()) + mod.addKeyDeserializer(classOf[Square], new SquareKeyDeserializer()) + mod.addSerializer(classOf[Square], new SquareSerializer()) + mod.addDeserializer(classOf[Square], new SquareDeserializer()) + mod.addSerializer(classOf[MoveType], new MoveTypeSerializer()) + mod.addDeserializer(classOf[MoveType], new MoveTypeDeserializer()) + mapper.registerModule(mod) diff --git a/modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala b/modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala index 43f099b..43ac4fa 100644 --- a/modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala +++ b/modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala @@ -8,8 +8,7 @@ import de.nowchess.chess.controller.Parser import de.nowchess.chess.observer.* import de.nowchess.chess.command.{CommandInvoker, MoveCommand, MoveResult} import de.nowchess.io.{GameContextExport, GameContextImport} -import de.nowchess.rules.RuleSet -import de.nowchess.rules.sets.DefaultRules +import de.nowchess.api.rules.RuleSet import scala.concurrent.{ExecutionContext, Future} @@ -18,7 +17,7 @@ import scala.concurrent.{ExecutionContext, Future} */ class GameEngine( val initialContext: GameContext = GameContext.initial, - val ruleSet: RuleSet = DefaultRules, + val ruleSet: RuleSet, val participants: Map[Color, Participant] = Map( Color.White -> Human(PlayerInfo(PlayerId("p1"), "Player 1")), Color.Black -> Human(PlayerInfo(PlayerId("p2"), "Player 2")), diff --git a/modules/core/src/main/scala/de/nowchess/chess/json/MoveTypeDeserializer.scala b/modules/core/src/main/scala/de/nowchess/chess/json/MoveTypeDeserializer.scala new file mode 100644 index 0000000..969d614 --- /dev/null +++ b/modules/core/src/main/scala/de/nowchess/chess/json/MoveTypeDeserializer.scala @@ -0,0 +1,19 @@ +package de.nowchess.chess.json + +import com.fasterxml.jackson.core.{JsonParseException, JsonParser} +import com.fasterxml.jackson.databind.node.ObjectNode +import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer} +import de.nowchess.api.move.{MoveType, PromotionPiece} + +class MoveTypeDeserializer extends JsonDeserializer[MoveType]: + // scalafix:off DisableSyntax.throw + override def deserialize(p: JsonParser, ctx: DeserializationContext): MoveType = + val node = p.getCodec.readTree[ObjectNode](p) + node.get("type").asText() match + case "normal" => MoveType.Normal(node.get("isCapture").asBoolean(false)) + case "castleKingside" => MoveType.CastleKingside + case "castleQueenside" => MoveType.CastleQueenside + case "enPassant" => MoveType.EnPassant + case "promotion" => MoveType.Promotion(PromotionPiece.valueOf(node.get("piece").asText())) + case t => throw new JsonParseException(p, s"Unknown move type: $t") + // scalafix:on DisableSyntax.throw diff --git a/modules/core/src/main/scala/de/nowchess/chess/json/MoveTypeSerializer.scala b/modules/core/src/main/scala/de/nowchess/chess/json/MoveTypeSerializer.scala new file mode 100644 index 0000000..8d90eba --- /dev/null +++ b/modules/core/src/main/scala/de/nowchess/chess/json/MoveTypeSerializer.scala @@ -0,0 +1,23 @@ +package de.nowchess.chess.json + +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.databind.{JsonSerializer, SerializerProvider} +import de.nowchess.api.move.MoveType + +class MoveTypeSerializer extends JsonSerializer[MoveType]: + override def serialize(value: MoveType, gen: JsonGenerator, provider: SerializerProvider): Unit = + gen.writeStartObject() + value match + case MoveType.Normal(isCapture) => + gen.writeStringField("type", "normal") + gen.writeBooleanField("isCapture", isCapture) + case MoveType.CastleKingside => + gen.writeStringField("type", "castleKingside") + case MoveType.CastleQueenside => + gen.writeStringField("type", "castleQueenside") + case MoveType.EnPassant => + gen.writeStringField("type", "enPassant") + case MoveType.Promotion(piece) => + gen.writeStringField("type", "promotion") + gen.writeStringField("piece", piece.toString) + gen.writeEndObject() diff --git a/modules/core/src/main/scala/de/nowchess/chess/json/SquareDeserializer.scala b/modules/core/src/main/scala/de/nowchess/chess/json/SquareDeserializer.scala new file mode 100644 index 0000000..a14b84f --- /dev/null +++ b/modules/core/src/main/scala/de/nowchess/chess/json/SquareDeserializer.scala @@ -0,0 +1,11 @@ +package de.nowchess.chess.json + +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer} +import de.nowchess.api.board.Square + +class SquareDeserializer extends JsonDeserializer[Square]: + // scalafix:off DisableSyntax.null + override def deserialize(p: JsonParser, ctx: DeserializationContext): Square = + Square.fromAlgebraic(p.getText).orNull + // scalafix:on DisableSyntax.null diff --git a/modules/core/src/main/scala/de/nowchess/chess/json/SquareSerializer.scala b/modules/core/src/main/scala/de/nowchess/chess/json/SquareSerializer.scala new file mode 100644 index 0000000..98240ac --- /dev/null +++ b/modules/core/src/main/scala/de/nowchess/chess/json/SquareSerializer.scala @@ -0,0 +1,9 @@ +package de.nowchess.chess.json + +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.databind.{JsonSerializer, SerializerProvider} +import de.nowchess.api.board.Square + +class SquareSerializer extends JsonSerializer[Square]: + override def serialize(value: Square, gen: JsonGenerator, provider: SerializerProvider): Unit = + gen.writeString(value.toString) 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 654e303..e050fb7 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 @@ -6,6 +6,7 @@ import de.nowchess.api.dto.* import de.nowchess.api.game.{DrawReason, GameContext, GameResult} import de.nowchess.api.move.{Move, MoveType, PromotionPiece} import de.nowchess.api.player.{PlayerId, PlayerInfo} +import de.nowchess.chess.adapter.RuleSetRestAdapter import de.nowchess.chess.client.IoServiceClient import de.nowchess.chess.controller.Parser import de.nowchess.chess.engine.GameEngine @@ -39,6 +40,9 @@ class GameResource: @Inject @RestClient var ioClient: IoServiceClient = uninitialized + + @Inject + var ruleSetAdapter: RuleSetRestAdapter = uninitialized // scalafix:on DisableSyntax.var private val DefaultWhite = PlayerInfo(PlayerId("p1"), "Player 1") @@ -114,7 +118,7 @@ class GameResource: dto.fold(default)(d => PlayerInfo(PlayerId(d.id), d.displayName)) private def newEntry(ctx: GameContext, white: PlayerInfo, black: PlayerInfo): GameEntry = - GameEntry(registry.generateId(), GameEngine(initialContext = ctx), white, black) + GameEntry(registry.generateId(), GameEngine(initialContext = ctx, ruleSet = ruleSetAdapter), white, black) private def applyMoveInput(engine: GameEngine, uci: String): Option[String] = val error = new AtomicReference[Option[String]](None) diff --git a/modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala b/modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala index 3a32668..7c49e73 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/engine/EngineTestHelpers.scala @@ -13,7 +13,7 @@ object EngineTestHelpers: new GameEngine(ruleSet = DefaultRules) def makeEngineWithBoard(board: Board, turn: Color = Color.White): GameEngine = - GameEngine(initialContext = GameContext.initial.withBoard(board).withTurn(turn)) + GameEngine(initialContext = GameContext.initial.withBoard(board).withTurn(turn), ruleSet = DefaultRules) def loadFen(engine: GameEngine, fen: String): Unit = engine.loadGame(FenParser, fen) diff --git a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineDrawOfferTest.scala b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineDrawOfferTest.scala index 03b39a6..872ce56 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineDrawOfferTest.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineDrawOfferTest.scala @@ -1,5 +1,6 @@ package de.nowchess.chess.engine +import de.nowchess.rules.sets.DefaultRules import scala.collection.mutable import de.nowchess.api.board.Color import de.nowchess.api.game.{DrawReason, GameResult} @@ -18,7 +19,7 @@ import org.scalatest.matchers.should.Matchers class GameEngineDrawOfferTest extends AnyFunSuite with Matchers: test("White offers draw"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new DrawOfferMockObserver() engine.subscribe(observer) @@ -32,7 +33,7 @@ class GameEngineDrawOfferTest extends AnyFunSuite with Matchers: fail(s"Expected DrawOfferEvent, but got $other") test("Black accepts White's draw offer"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new DrawOfferMockObserver() engine.subscribe(observer) @@ -49,7 +50,7 @@ class GameEngineDrawOfferTest extends AnyFunSuite with Matchers: fail(s"Expected DrawEvent, but got $other") test("Black declines White's draw offer"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new DrawOfferMockObserver() engine.subscribe(observer) @@ -65,7 +66,7 @@ class GameEngineDrawOfferTest extends AnyFunSuite with Matchers: fail(s"Expected DrawOfferDeclinedEvent, but got $other") test("Black offers draw"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new DrawOfferMockObserver() engine.subscribe(observer) @@ -79,7 +80,7 @@ class GameEngineDrawOfferTest extends AnyFunSuite with Matchers: fail(s"Expected DrawOfferEvent, but got $other") test("White accepts Black's draw offer"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new DrawOfferMockObserver() engine.subscribe(observer) @@ -96,7 +97,7 @@ class GameEngineDrawOfferTest extends AnyFunSuite with Matchers: fail(s"Expected DrawEvent, but got $other") test("Cannot accept draw when no offer pending"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new DrawOfferMockObserver() engine.subscribe(observer) @@ -110,7 +111,7 @@ class GameEngineDrawOfferTest extends AnyFunSuite with Matchers: fail(s"Expected InvalidMoveEvent, but got $other") test("Cannot decline draw when no offer pending"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new DrawOfferMockObserver() engine.subscribe(observer) @@ -124,7 +125,7 @@ class GameEngineDrawOfferTest extends AnyFunSuite with Matchers: fail(s"Expected InvalidMoveEvent, but got $other") test("Cannot offer draw when game is already over"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new DrawOfferMockObserver() engine.subscribe(observer) @@ -147,7 +148,7 @@ class GameEngineDrawOfferTest extends AnyFunSuite with Matchers: fail(s"Expected InvalidMoveEvent, but got $other") test("Cannot accept your own draw offer"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new DrawOfferMockObserver() engine.subscribe(observer) @@ -163,7 +164,7 @@ class GameEngineDrawOfferTest extends AnyFunSuite with Matchers: fail(s"Expected InvalidMoveEvent, but got $other") test("Cannot decline your own draw offer"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new DrawOfferMockObserver() engine.subscribe(observer) @@ -179,7 +180,7 @@ class GameEngineDrawOfferTest extends AnyFunSuite with Matchers: fail(s"Expected InvalidMoveEvent, but got $other") test("Cannot make second draw offer when one is already pending"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new DrawOfferMockObserver() engine.subscribe(observer) @@ -195,7 +196,7 @@ class GameEngineDrawOfferTest extends AnyFunSuite with Matchers: fail(s"Expected InvalidMoveEvent, but got $other") test("Draw offer is cleared when game ends by resignation (accept)"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new DrawOfferMockObserver() engine.subscribe(observer) @@ -215,7 +216,7 @@ class GameEngineDrawOfferTest extends AnyFunSuite with Matchers: fail(s"Expected InvalidMoveEvent, but got $other") test("Draw offer is cleared when game ends by resignation (decline)"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new DrawOfferMockObserver() engine.subscribe(observer) @@ -235,22 +236,22 @@ class GameEngineDrawOfferTest extends AnyFunSuite with Matchers: fail(s"Expected InvalidMoveEvent, but got $other") test("pendingDrawOfferBy returns None initially"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) engine.pendingDrawOfferBy shouldBe None test("pendingDrawOfferBy returns White after White offers"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) engine.offerDraw(Color.White) engine.pendingDrawOfferBy shouldBe Some(Color.White) test("pendingDrawOfferBy returns None after draw is accepted"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) engine.offerDraw(Color.White) engine.acceptDraw(Color.Black) engine.pendingDrawOfferBy shouldBe None test("applyDraw sets draw result when game not over"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new DrawOfferMockObserver() engine.subscribe(observer) engine.applyDraw(DrawReason.Agreement) @@ -263,7 +264,7 @@ class GameEngineDrawOfferTest extends AnyFunSuite with Matchers: fail(s"Expected DrawEvent, but got $other") test("applyDraw does nothing when game already over"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new DrawOfferMockObserver() engine.subscribe(observer) // End the game with checkmate @@ -276,7 +277,7 @@ class GameEngineDrawOfferTest extends AnyFunSuite with Matchers: observer.events should have length 0 test("claimDraw with fifty-move rule when at half-move 100"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new DrawOfferMockObserver() engine.subscribe(observer) // Play moves to reach fifty-move rule claim @@ -288,7 +289,7 @@ class GameEngineDrawOfferTest extends AnyFunSuite with Matchers: // This is hard to do naturally; skip for now if not critical test("claimDraw when game already over"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new DrawOfferMockObserver() engine.subscribe(observer) // End the game with checkmate diff --git a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineGameEndingTest.scala b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineGameEndingTest.scala index 9e491e8..4bb9207 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineGameEndingTest.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineGameEndingTest.scala @@ -1,5 +1,6 @@ package de.nowchess.chess.engine +import de.nowchess.rules.sets.DefaultRules import scala.collection.mutable import de.nowchess.api.board.Color import de.nowchess.api.game.DrawReason @@ -11,7 +12,7 @@ import org.scalatest.matchers.should.Matchers class GameEngineGameEndingTest extends AnyFunSuite with Matchers: test("GameEngine handles Checkmate (Fool's Mate)"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new EndingMockObserver() engine.subscribe(observer) @@ -31,7 +32,7 @@ class GameEngineGameEndingTest extends AnyFunSuite with Matchers: fail(s"Expected CheckmateEvent, but got $other") test("GameEngine handles check detection"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new EndingMockObserver() engine.subscribe(observer) @@ -53,7 +54,7 @@ class GameEngineGameEndingTest extends AnyFunSuite with Matchers: // Wait, let's just use Sam Loyd's 10-move stalemate: // 1. e3 a5 2. Qh5 Ra6 3. Qxa5 h5 4. h4 Rah6 5. Qxc7 f6 6. Qxd7+ Kf7 7. Qxb7 Qd3 8. Qxb8 Qh7 9. Qxc8 Kg6 10. Qe6 test("GameEngine handles Stalemate via 10-move known sequence"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new EndingMockObserver() engine.subscribe(observer) diff --git a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala index 371f2dd..d4586f6 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala @@ -5,7 +5,7 @@ import de.nowchess.api.game.GameContext import de.nowchess.api.move.{Move, MoveType, PromotionPiece} import de.nowchess.chess.observer.{GameEvent, InvalidMoveEvent, InvalidMoveReason, MoveRedoneEvent, Observer} import de.nowchess.io.GameContextImport -import de.nowchess.rules.RuleSet +import de.nowchess.api.rules.RuleSet import de.nowchess.rules.sets.DefaultRules import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers @@ -21,7 +21,7 @@ class GameEngineIntegrationTest extends AnyFunSuite with Matchers: events test("accessors expose redo availability and command history"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) engine.canRedo shouldBe false engine.commandHistory shouldBe empty @@ -30,7 +30,7 @@ class GameEngineIntegrationTest extends AnyFunSuite with Matchers: engine.commandHistory.nonEmpty shouldBe true test("processUserInput handles undo redo empty and malformed commands"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val events = captureEvents(engine) engine.processUserInput("") @@ -44,7 +44,7 @@ class GameEngineIntegrationTest extends AnyFunSuite with Matchers: } should be >= 3 test("processUserInput emits Illegal move for syntactically valid but illegal target"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val events = captureEvents(engine) engine.processUserInput("e2e5") @@ -56,14 +56,14 @@ class GameEngineIntegrationTest extends AnyFunSuite with Matchers: test("loadGame returns Left when importer fails"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val failingImporter = new GameContextImport: def importGameContext(input: String): Either[String, GameContext] = Left("boom") engine.loadGame(failingImporter, "ignored") shouldBe Left("boom") test("loadPosition replaces context clears history and notifies reset"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val events = captureEvents(engine) engine.processUserInput("e2e4") @@ -78,7 +78,7 @@ class GameEngineIntegrationTest extends AnyFunSuite with Matchers: } shouldBe true test("redo event includes captured piece description when replaying a capture"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val events = captureEvents(engine) EngineTestHelpers.loadFen(engine, "4k3/8/8/8/8/8/4K3/R6r w - - 0 1") @@ -145,13 +145,13 @@ class GameEngineIntegrationTest extends AnyFunSuite with Matchers: test("loadGame replay executes non-promotion moves through default replay branch"): val normalMove = Move(sq("e2"), sq("e4"), MoveType.Normal()) - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) engine.replayMoves(List(normalMove), engine.context) shouldBe Right(()) engine.context.moves.lastOption shouldBe Some(normalMove) test("replayMoves skips later moves after the first move triggers an error"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val saved = engine.context val illegalPromotion = Move(sq("e2"), sq("e1"), MoveType.Promotion(PromotionPiece.Queen)) val trailingMove = Move(sq("e2"), sq("e4")) @@ -160,19 +160,19 @@ class GameEngineIntegrationTest extends AnyFunSuite with Matchers: engine.context shouldBe saved test("normalMoveNotation handles missing source piece"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val result = engine.normalMoveNotation(Move(sq("e3"), sq("e4")), Board.initial, isCapture = false) result shouldBe "e4" test("pieceNotation default branch returns empty string"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val result = engine.pieceNotation(PieceType.Pawn) result shouldBe "" test("observerCount reflects subscribe and unsubscribe operations"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new Observer: def onGameEvent(event: GameEvent): Unit = () diff --git a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineLoadGameTest.scala b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineLoadGameTest.scala index d926147..c3b6a50 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineLoadGameTest.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineLoadGameTest.scala @@ -1,5 +1,6 @@ package de.nowchess.chess.engine +import de.nowchess.rules.sets.DefaultRules import scala.collection.mutable import de.nowchess.api.board.{Board, Color} import de.nowchess.api.game.GameContext @@ -14,7 +15,7 @@ import org.scalatest.matchers.should.Matchers class GameEngineLoadGameTest extends AnyFunSuite with Matchers: test("loadGame with PgnParser: loads valid PGN and enables undo/redo"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val pgn = "[Event \"Test\"]\n\n1. e4 e5\n" val result = engine.loadGame(PgnParser, pgn) result shouldBe Right(()) @@ -22,7 +23,7 @@ class GameEngineLoadGameTest extends AnyFunSuite with Matchers: engine.canUndo shouldBe true test("loadGame with FenParser: loads position without replaying moves"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val fen = "8/4P3/4k3/8/8/8/8/8 w - - 0 1" val result = engine.loadGame(FenParser, fen) result shouldBe Right(()) @@ -30,7 +31,7 @@ class GameEngineLoadGameTest extends AnyFunSuite with Matchers: engine.canUndo shouldBe false test("exportGame with PgnExporter: exports current game as PGN"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) engine.processUserInput("e2e4") engine.processUserInput("e7e5") val pgn = engine.exportGame(PgnExporter) diff --git a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineNotationTest.scala b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineNotationTest.scala index e1c1877..6482b35 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineNotationTest.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineNotationTest.scala @@ -1,6 +1,7 @@ package de.nowchess.chess.engine import de.nowchess.api.board.{Board, Color, File, Rank, Square} +import de.nowchess.rules.sets.DefaultRules import de.nowchess.api.game.GameContext import de.nowchess.io.fen.FenParser import de.nowchess.chess.observer.* @@ -37,7 +38,7 @@ class GameEngineNotationTest extends AnyFunSuite with Matchers: .withTurn(Color.White) .withCastlingRights(castlingRights) - val engine = new GameEngine(ctx) + val engine = new GameEngine(ctx, DefaultRules) val events = captureEvents(engine) // White castles queenside: e1c1 @@ -65,7 +66,7 @@ class GameEngineNotationTest extends AnyFunSuite with Matchers: .withEnPassantSquare(epSquare) .withCastlingRights(de.nowchess.api.board.CastlingRights(false, false, false, false)) - val engine = new GameEngine(ctx) + val engine = new GameEngine(ctx, DefaultRules) val events = captureEvents(engine) // White pawn on e5 captures en passant to d6 @@ -96,7 +97,7 @@ class GameEngineNotationTest extends AnyFunSuite with Matchers: .withTurn(Color.White) .withCastlingRights(de.nowchess.api.board.CastlingRights(false, false, false, false)) - val engine = new GameEngine(ctx) + val engine = new GameEngine(ctx, DefaultRules) val events = captureEvents(engine) engine.processUserInput("e7e8b") @@ -117,7 +118,7 @@ class GameEngineNotationTest extends AnyFunSuite with Matchers: .withTurn(Color.White) .withCastlingRights(de.nowchess.api.board.CastlingRights(false, false, false, false)) - val engine = new GameEngine(ctx) + val engine = new GameEngine(ctx, DefaultRules) val events = captureEvents(engine) // King moves e1 -> f1 diff --git a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala index 4c86f17..edc74d7 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala @@ -14,7 +14,7 @@ import de.nowchess.chess.observer.{ MoveExecutedEvent, Observer, } -import de.nowchess.rules.RuleSet +import de.nowchess.api.rules.RuleSet import de.nowchess.rules.sets.DefaultRules import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers @@ -29,7 +29,7 @@ class GameEnginePromotionTest extends AnyFunSuite with Matchers: events private def engineWith(board: Board, turn: Color = Color.White): GameEngine = - new GameEngine(initialContext = GameContext.initial.withBoard(board).withTurn(turn)) + new GameEngine(initialContext = GameContext.initial.withBoard(board).withTurn(turn), ruleSet = DefaultRules) test("processUserInput without promotion suffix fires InvalidMoveEvent when pawn reaches back rank") { val promotionBoard = FenParser.parseBoard("8/4P3/4k3/8/8/8/8/8").get diff --git a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineResignTest.scala b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineResignTest.scala index cf96cdf..0ee4395 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineResignTest.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineResignTest.scala @@ -1,5 +1,6 @@ package de.nowchess.chess.engine +import de.nowchess.rules.sets.DefaultRules import scala.collection.mutable import de.nowchess.api.board.Color import de.nowchess.api.game.GameResult @@ -10,7 +11,7 @@ import org.scalatest.matchers.should.Matchers class GameEngineResignTest extends AnyFunSuite with Matchers: test("White resigns"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new ResignMockObserver() engine.subscribe(observer) @@ -25,7 +26,7 @@ class GameEngineResignTest extends AnyFunSuite with Matchers: fail(s"Expected ResignEvent, but got $other") test("Black resigns"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new ResignMockObserver() engine.subscribe(observer) @@ -40,7 +41,7 @@ class GameEngineResignTest extends AnyFunSuite with Matchers: fail(s"Expected ResignEvent, but got $other") test("Cannot resign when game is already over"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new ResignMockObserver() engine.subscribe(observer) @@ -64,7 +65,7 @@ class GameEngineResignTest extends AnyFunSuite with Matchers: fail(s"Expected InvalidMoveEvent, but got $other") test("resign() without color resigns side to move"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new ResignMockObserver() engine.subscribe(observer) @@ -73,7 +74,7 @@ class GameEngineResignTest extends AnyFunSuite with Matchers: engine.context.result shouldBe Some(GameResult.Win(Color.Black)) test("resign() without color does nothing when game already over"): - val engine = new GameEngine() + val engine = new GameEngine(ruleSet = DefaultRules) val observer = new ResignMockObserver() engine.subscribe(observer) diff --git a/modules/core/src/test/scala/de/nowchess/chess/registry/GameRegistryImplTest.scala b/modules/core/src/test/scala/de/nowchess/chess/registry/GameRegistryImplTest.scala index 6bea0c0..f027e53 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/registry/GameRegistryImplTest.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/registry/GameRegistryImplTest.scala @@ -1,6 +1,7 @@ package de.nowchess.chess.registry import de.nowchess.api.player.{PlayerId, PlayerInfo} +import de.nowchess.rules.sets.DefaultRules import de.nowchess.chess.engine.GameEngine import io.quarkus.test.junit.QuarkusTest import jakarta.inject.Inject @@ -20,14 +21,14 @@ class GameRegistryImplTest: @Test @DisplayName("store saves entry") def testStore(): Unit = - val entry = GameEntry("g1", GameEngine(), PlayerInfo(PlayerId("p1"), "P1"), PlayerInfo(PlayerId("p2"), "P2")) + val entry = GameEntry("g1", GameEngine(ruleSet = DefaultRules), PlayerInfo(PlayerId("p1"), "P1"), PlayerInfo(PlayerId("p2"), "P2")) registry.store(entry) assertTrue(registry.get("g1").isDefined) @Test @DisplayName("get returns stored entry") def testGet(): Unit = - val entry = GameEntry("g2", GameEngine(), PlayerInfo(PlayerId("p1"), "P1"), PlayerInfo(PlayerId("p2"), "P2")) + val entry = GameEntry("g2", GameEngine(ruleSet = DefaultRules), PlayerInfo(PlayerId("p1"), "P1"), PlayerInfo(PlayerId("p2"), "P2")) registry.store(entry) val retrieved = registry.get("g2") assertTrue(retrieved.isDefined) @@ -41,7 +42,7 @@ class GameRegistryImplTest: @Test @DisplayName("update modifies existing entry") def testUpdate(): Unit = - val entry = GameEntry("g3", GameEngine(), PlayerInfo(PlayerId("p1"), "P1"), PlayerInfo(PlayerId("p2"), "P2")) + val entry = GameEntry("g3", GameEngine(ruleSet = DefaultRules), PlayerInfo(PlayerId("p1"), "P1"), PlayerInfo(PlayerId("p2"), "P2")) registry.store(entry) val updated = entry.copy(resigned = true) registry.update(updated) diff --git a/modules/core/src/test/scala/de/nowchess/chess/resource/GameResourceIntegrationTest.scala b/modules/core/src/test/scala/de/nowchess/chess/resource/GameResourceIntegrationTest.scala index 1ee458e..abea233 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/resource/GameResourceIntegrationTest.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/resource/GameResourceIntegrationTest.scala @@ -1,11 +1,13 @@ package de.nowchess.chess.resource +import de.nowchess.api.board.Square import de.nowchess.api.dto.* import de.nowchess.api.game.GameContext -import de.nowchess.chess.client.IoServiceClient +import de.nowchess.chess.client.{IoServiceClient, RuleMoveRequest, RuleServiceClient, RuleSquareRequest} import de.nowchess.chess.exception.BadRequestException import de.nowchess.io.fen.FenExporter import de.nowchess.io.pgn.PgnParser +import de.nowchess.rules.sets.DefaultRules import io.quarkus.test.InjectMock import io.quarkus.test.junit.QuarkusTest import jakarta.inject.Inject @@ -14,6 +16,7 @@ import org.junit.jupiter.api.{BeforeEach, DisplayName, Test} import org.junit.jupiter.api.Assertions.* import org.mockito.ArgumentMatchers.any import org.mockito.Mockito.when +import org.mockito.invocation.InvocationOnMock import scala.compiletime.uninitialized @@ -29,6 +32,10 @@ class GameResourceIntegrationTest: @RestClient var ioClient: IoServiceClient = uninitialized + @InjectMock + @RestClient + var ruleClient: RuleServiceClient = uninitialized + @BeforeEach def setupMocks(): Unit = when(ioClient.importFen(any())).thenReturn(GameContext.initial) @@ -37,6 +44,32 @@ class GameResourceIntegrationTest: ) when(ioClient.exportFen(any())).thenReturn(FenExporter.exportGameContext(GameContext.initial)) when(ioClient.exportPgn(any())).thenReturn("1. e4 c5") + when(ruleClient.legalMoves(any())).thenAnswer((inv: InvocationOnMock) => + val req = inv.getArgument[RuleSquareRequest](0) + DefaultRules.legalMoves(req.context)(Square.fromAlgebraic(req.square).get), + ) + when(ruleClient.allLegalMoves(any())).thenAnswer((inv: InvocationOnMock) => + DefaultRules.allLegalMoves(inv.getArgument[GameContext](0)), + ) + when(ruleClient.applyMove(any())).thenAnswer((inv: InvocationOnMock) => + val req = inv.getArgument[RuleMoveRequest](0) + DefaultRules.applyMove(req.context)(req.move), + ) + when(ruleClient.isCheck(any())).thenAnswer((inv: InvocationOnMock) => + DefaultRules.isCheck(inv.getArgument[GameContext](0)), + ) + when(ruleClient.isCheckmate(any())).thenAnswer((inv: InvocationOnMock) => + DefaultRules.isCheckmate(inv.getArgument[GameContext](0)), + ) + when(ruleClient.isStalemate(any())).thenAnswer((inv: InvocationOnMock) => + DefaultRules.isStalemate(inv.getArgument[GameContext](0)), + ) + when(ruleClient.isInsufficientMaterial(any())).thenAnswer((inv: InvocationOnMock) => + DefaultRules.isInsufficientMaterial(inv.getArgument[GameContext](0)), + ) + when(ruleClient.isThreefoldRepetition(any())).thenAnswer((inv: InvocationOnMock) => + DefaultRules.isThreefoldRepetition(inv.getArgument[GameContext](0)), + ) @Test @DisplayName("createGame returns 201") diff --git a/modules/rule/src/main/resources/application.yml b/modules/rule/src/main/resources/application.yml index 2c5486b..98b81ce 100644 --- a/modules/rule/src/main/resources/application.yml +++ b/modules/rule/src/main/resources/application.yml @@ -1,5 +1,5 @@ quarkus: http: - port: 8081 + port: 8082 application: name: rule-service diff --git a/modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala b/modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala index 036f00d..ed3bde2 100644 --- a/modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala +++ b/modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala @@ -3,7 +3,7 @@ package de.nowchess.rules.sets import de.nowchess.api.board.* import de.nowchess.api.game.GameContext import de.nowchess.api.move.{Move, MoveType, PromotionPiece} -import de.nowchess.rules.RuleSet +import de.nowchess.api.rules.RuleSet import scala.annotation.tailrec