fix(rules): Serializers
Added serializers like in IO
This commit is contained in:
@@ -1,11 +1,29 @@
|
|||||||
package de.nowchess.rules.config
|
package de.nowchess.rules.config
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.Version
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||||
import com.fasterxml.jackson.module.scala.DefaultScalaModule
|
import com.fasterxml.jackson.module.scala.DefaultScalaModule
|
||||||
|
import de.nowchess.api.board.Square
|
||||||
|
import de.nowchess.api.move.MoveType
|
||||||
|
import de.nowchess.rules.json.*
|
||||||
import io.quarkus.jackson.ObjectMapperCustomizer
|
import io.quarkus.jackson.ObjectMapperCustomizer
|
||||||
import jakarta.inject.Singleton
|
import jakarta.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class JacksonConfig extends ObjectMapperCustomizer:
|
class JacksonConfig extends ObjectMapperCustomizer:
|
||||||
def customize(mapper: ObjectMapper): Unit =
|
def customize(mapper: ObjectMapper): Unit =
|
||||||
mapper.registerModule(DefaultScalaModule)
|
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
|
||||||
|
})
|
||||||
|
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)
|
||||||
|
|||||||
@@ -1,128 +0,0 @@
|
|||||||
package de.nowchess.rules.dto
|
|
||||||
|
|
||||||
import de.nowchess.api.board.{Board, CastlingRights, Color, Piece, PieceType, Square}
|
|
||||||
import de.nowchess.api.game.GameContext
|
|
||||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
|
||||||
|
|
||||||
object DtoMapper:
|
|
||||||
|
|
||||||
def toColor(s: String): Either[String, Color] = s match
|
|
||||||
case "White" => Right(Color.White)
|
|
||||||
case "Black" => Right(Color.Black)
|
|
||||||
case other => Left(s"Unknown color: $other")
|
|
||||||
|
|
||||||
def toPieceType(s: String): Either[String, PieceType] = s match
|
|
||||||
case "Pawn" => Right(PieceType.Pawn)
|
|
||||||
case "Knight" => Right(PieceType.Knight)
|
|
||||||
case "Bishop" => Right(PieceType.Bishop)
|
|
||||||
case "Rook" => Right(PieceType.Rook)
|
|
||||||
case "Queen" => Right(PieceType.Queen)
|
|
||||||
case "King" => Right(PieceType.King)
|
|
||||||
case other => Left(s"Unknown piece type: $other")
|
|
||||||
|
|
||||||
def toSquare(s: String): Either[String, Square] =
|
|
||||||
Square.fromAlgebraic(s).toRight(s"Invalid square: $s")
|
|
||||||
|
|
||||||
def toMoveType(dto: MoveDto): Either[String, MoveType] = dto.moveType match
|
|
||||||
case "normal" => Right(MoveType.Normal(isCapture = false))
|
|
||||||
case "capture" => Right(MoveType.Normal(isCapture = true))
|
|
||||||
case "castleKingside" => Right(MoveType.CastleKingside)
|
|
||||||
case "castleQueenside" => Right(MoveType.CastleQueenside)
|
|
||||||
case "enPassant" => Right(MoveType.EnPassant)
|
|
||||||
case "promotion" =>
|
|
||||||
dto.promotionPiece.toRight("Missing promotion piece").flatMap(toPromotionPiece).map(MoveType.Promotion(_))
|
|
||||||
case other => Left(s"Unknown move type: $other")
|
|
||||||
|
|
||||||
def toMove(dto: MoveDto): Either[String, Move] =
|
|
||||||
for
|
|
||||||
from <- toSquare(dto.from)
|
|
||||||
to <- toSquare(dto.to)
|
|
||||||
moveType <- toMoveType(dto)
|
|
||||||
yield Move(from, to, moveType)
|
|
||||||
|
|
||||||
def toBoard(pieces: List[PieceOnSquareDto]): Either[String, Board] =
|
|
||||||
sequenceList(pieces.map(toPieceOnSquare)).map(entries => Board(entries.toMap))
|
|
||||||
|
|
||||||
def toGameContext(dto: GameContextDto): Either[String, GameContext] =
|
|
||||||
for
|
|
||||||
board <- toBoard(dto.board)
|
|
||||||
turn <- toColor(dto.turn)
|
|
||||||
epSquare <- sequenceOpt(dto.enPassantSquare.map(toSquare))
|
|
||||||
moves <- sequenceList(dto.moves.map(toMove))
|
|
||||||
initialBoard <- toBoard(dto.initialBoard)
|
|
||||||
yield GameContext(
|
|
||||||
board = board,
|
|
||||||
turn = turn,
|
|
||||||
castlingRights = toCastlingRights(dto.castlingRights),
|
|
||||||
enPassantSquare = epSquare,
|
|
||||||
halfMoveClock = dto.halfMoveClock,
|
|
||||||
moves = moves,
|
|
||||||
initialBoard = initialBoard,
|
|
||||||
)
|
|
||||||
|
|
||||||
def fromMove(move: Move): MoveDto =
|
|
||||||
val (moveType, promotionPiece) = fromMoveType(move.moveType)
|
|
||||||
MoveDto(move.from.toString, move.to.toString, moveType, promotionPiece)
|
|
||||||
|
|
||||||
def fromBoard(board: Board): List[PieceOnSquareDto] =
|
|
||||||
board.pieces.toList.map { case (sq, p) =>
|
|
||||||
PieceOnSquareDto(sq.toString, p.color.label, p.pieceType.label)
|
|
||||||
}
|
|
||||||
|
|
||||||
def fromGameContext(ctx: GameContext): GameContextDto =
|
|
||||||
GameContextDto(
|
|
||||||
board = fromBoard(ctx.board),
|
|
||||||
turn = ctx.turn.label,
|
|
||||||
castlingRights = fromCastlingRights(ctx.castlingRights),
|
|
||||||
enPassantSquare = ctx.enPassantSquare.map(_.toString),
|
|
||||||
halfMoveClock = ctx.halfMoveClock,
|
|
||||||
moves = ctx.moves.map(fromMove),
|
|
||||||
initialBoard = fromBoard(ctx.initialBoard),
|
|
||||||
)
|
|
||||||
|
|
||||||
private def toPromotionPiece(s: String): Either[String, PromotionPiece] = s match
|
|
||||||
case "Queen" => Right(PromotionPiece.Queen)
|
|
||||||
case "Rook" => Right(PromotionPiece.Rook)
|
|
||||||
case "Bishop" => Right(PromotionPiece.Bishop)
|
|
||||||
case "Knight" => Right(PromotionPiece.Knight)
|
|
||||||
case other => Left(s"Unknown promotion piece: $other")
|
|
||||||
|
|
||||||
private def fromMoveType(mt: MoveType): (String, Option[String]) = mt match
|
|
||||||
case MoveType.Normal(false) => ("normal", None)
|
|
||||||
case MoveType.Normal(true) => ("capture", None)
|
|
||||||
case MoveType.CastleKingside => ("castleKingside", None)
|
|
||||||
case MoveType.CastleQueenside => ("castleQueenside", None)
|
|
||||||
case MoveType.EnPassant => ("enPassant", None)
|
|
||||||
case MoveType.Promotion(pp) => ("promotion", Some(fromPromotionPiece(pp)))
|
|
||||||
|
|
||||||
private def fromPromotionPiece(pp: PromotionPiece): String = pp match
|
|
||||||
case PromotionPiece.Queen => "Queen"
|
|
||||||
case PromotionPiece.Rook => "Rook"
|
|
||||||
case PromotionPiece.Bishop => "Bishop"
|
|
||||||
case PromotionPiece.Knight => "Knight"
|
|
||||||
|
|
||||||
private def toCastlingRights(dto: CastlingRightsDto): CastlingRights =
|
|
||||||
CastlingRights(dto.whiteKingSide, dto.whiteQueenSide, dto.blackKingSide, dto.blackQueenSide)
|
|
||||||
|
|
||||||
private def fromCastlingRights(cr: CastlingRights): CastlingRightsDto =
|
|
||||||
CastlingRightsDto(cr.whiteKingSide, cr.whiteQueenSide, cr.blackKingSide, cr.blackQueenSide)
|
|
||||||
|
|
||||||
private def toPieceOnSquare(dto: PieceOnSquareDto): Either[String, (Square, Piece)] =
|
|
||||||
for
|
|
||||||
sq <- toSquare(dto.square)
|
|
||||||
color <- toColor(dto.color)
|
|
||||||
pieceType <- toPieceType(dto.pieceType)
|
|
||||||
yield sq -> Piece(color, pieceType)
|
|
||||||
|
|
||||||
private def sequenceList[R](list: List[Either[String, R]]): Either[String, List[R]] =
|
|
||||||
list.foldLeft[Either[String, List[R]]](Right(List.empty)) {
|
|
||||||
case (Right(acc), Right(v)) => Right(acc :+ v)
|
|
||||||
case (Left(e), _) => Left(e)
|
|
||||||
case (_, Left(e)) => Left(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def sequenceOpt[R](opt: Option[Either[String, R]]): Either[String, Option[R]] =
|
|
||||||
opt match
|
|
||||||
case None => Right(None)
|
|
||||||
case Some(Right(v)) => Right(Some(v))
|
|
||||||
case Some(Left(e)) => Left(e)
|
|
||||||
@@ -1,37 +1,14 @@
|
|||||||
package de.nowchess.rules.dto
|
package de.nowchess.rules.dto
|
||||||
|
|
||||||
case class PieceOnSquareDto(square: String, color: String, pieceType: String)
|
import de.nowchess.api.game.GameContext
|
||||||
|
import de.nowchess.api.move.Move
|
||||||
|
|
||||||
case class CastlingRightsDto(
|
case class ContextRequest(context: GameContext)
|
||||||
whiteKingSide: Boolean,
|
|
||||||
whiteQueenSide: Boolean,
|
|
||||||
blackKingSide: Boolean,
|
|
||||||
blackQueenSide: Boolean,
|
|
||||||
)
|
|
||||||
|
|
||||||
case class MoveDto(
|
case class ContextSquareRequest(context: GameContext, square: String)
|
||||||
from: String,
|
|
||||||
to: String,
|
|
||||||
moveType: String,
|
|
||||||
promotionPiece: Option[String],
|
|
||||||
)
|
|
||||||
|
|
||||||
case class GameContextDto(
|
case class ContextMoveRequest(context: GameContext, move: Move)
|
||||||
board: List[PieceOnSquareDto],
|
|
||||||
turn: String,
|
|
||||||
castlingRights: CastlingRightsDto,
|
|
||||||
enPassantSquare: Option[String],
|
|
||||||
halfMoveClock: Int,
|
|
||||||
moves: List[MoveDto],
|
|
||||||
initialBoard: List[PieceOnSquareDto],
|
|
||||||
)
|
|
||||||
|
|
||||||
case class ContextRequest(context: GameContextDto)
|
case class MovesResponse(moves: List[Move])
|
||||||
|
|
||||||
case class ContextSquareRequest(context: GameContextDto, square: String)
|
|
||||||
|
|
||||||
case class ContextMoveRequest(context: GameContextDto, move: MoveDto)
|
|
||||||
|
|
||||||
case class MovesResponse(moves: List[MoveDto])
|
|
||||||
|
|
||||||
case class BooleanResponse(result: Boolean)
|
case class BooleanResponse(result: Boolean)
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package de.nowchess.rules.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]:
|
||||||
|
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")
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package de.nowchess.rules.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()
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package de.nowchess.rules.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]:
|
||||||
|
override def deserialize(p: JsonParser, ctx: DeserializationContext): Square =
|
||||||
|
Square.fromAlgebraic(p.getText).orNull
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package de.nowchess.rules.json
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.{DeserializationContext, KeyDeserializer}
|
||||||
|
import de.nowchess.api.board.Square
|
||||||
|
|
||||||
|
class SquareKeyDeserializer extends KeyDeserializer:
|
||||||
|
override def deserializeKey(key: String, ctx: DeserializationContext): AnyRef =
|
||||||
|
Square.fromAlgebraic(key).orNull
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package de.nowchess.rules.json
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator
|
||||||
|
import com.fasterxml.jackson.databind.{JsonSerializer, SerializerProvider}
|
||||||
|
import de.nowchess.api.board.Square
|
||||||
|
|
||||||
|
class SquareKeySerializer extends JsonSerializer[Square]:
|
||||||
|
override def serialize(value: Square, gen: JsonGenerator, provider: SerializerProvider): Unit =
|
||||||
|
gen.writeFieldName(value.toString)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package de.nowchess.rules.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)
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package de.nowchess.rules.resource
|
package de.nowchess.rules.resource
|
||||||
|
|
||||||
|
import de.nowchess.api.board.Square
|
||||||
|
import de.nowchess.api.game.GameContext
|
||||||
import de.nowchess.rules.dto.*
|
import de.nowchess.rules.dto.*
|
||||||
import de.nowchess.rules.sets.DefaultRules
|
import de.nowchess.rules.sets.DefaultRules
|
||||||
import jakarta.enterprise.context.ApplicationScoped
|
import jakarta.enterprise.context.ApplicationScoped
|
||||||
@@ -12,9 +14,8 @@ class RuleSetResource:
|
|||||||
private val rules = DefaultRules
|
private val rules = DefaultRules
|
||||||
|
|
||||||
// scalafix:off DisableSyntax.throw
|
// scalafix:off DisableSyntax.throw
|
||||||
private def parse[T](e: Either[String, T]): T = e match
|
private def parseSquare(s: String): Square =
|
||||||
case Right(v) => v
|
Square.fromAlgebraic(s).getOrElse(throw new BadRequestException(s"Invalid square: $s"))
|
||||||
case Left(msg) => throw BadRequestException(msg)
|
|
||||||
// scalafix:on DisableSyntax.throw
|
// scalafix:on DisableSyntax.throw
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@@ -22,73 +23,67 @@ class RuleSetResource:
|
|||||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||||
def candidateMoves(req: ContextSquareRequest): MovesResponse =
|
def candidateMoves(req: ContextSquareRequest): MovesResponse =
|
||||||
val ctx = parse(DtoMapper.toGameContext(req.context))
|
MovesResponse(rules.candidateMoves(req.context)(parseSquare(req.square)))
|
||||||
val sq = parse(DtoMapper.toSquare(req.square))
|
|
||||||
MovesResponse(rules.candidateMoves(ctx)(sq).map(DtoMapper.fromMove))
|
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/legal-moves")
|
@Path("/legal-moves")
|
||||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||||
def legalMoves(req: ContextSquareRequest): MovesResponse =
|
def legalMoves(req: ContextSquareRequest): MovesResponse =
|
||||||
val ctx = parse(DtoMapper.toGameContext(req.context))
|
MovesResponse(rules.legalMoves(req.context)(parseSquare(req.square)))
|
||||||
val sq = parse(DtoMapper.toSquare(req.square))
|
|
||||||
MovesResponse(rules.legalMoves(ctx)(sq).map(DtoMapper.fromMove))
|
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/all-legal-moves")
|
@Path("/all-legal-moves")
|
||||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||||
def allLegalMoves(req: ContextRequest): MovesResponse =
|
def allLegalMoves(req: ContextRequest): MovesResponse =
|
||||||
MovesResponse(rules.allLegalMoves(parse(DtoMapper.toGameContext(req.context))).map(DtoMapper.fromMove))
|
MovesResponse(rules.allLegalMoves(req.context))
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/is-check")
|
@Path("/is-check")
|
||||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||||
def isCheck(req: ContextRequest): BooleanResponse =
|
def isCheck(req: ContextRequest): BooleanResponse =
|
||||||
BooleanResponse(rules.isCheck(parse(DtoMapper.toGameContext(req.context))))
|
BooleanResponse(rules.isCheck(req.context))
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/is-checkmate")
|
@Path("/is-checkmate")
|
||||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||||
def isCheckmate(req: ContextRequest): BooleanResponse =
|
def isCheckmate(req: ContextRequest): BooleanResponse =
|
||||||
BooleanResponse(rules.isCheckmate(parse(DtoMapper.toGameContext(req.context))))
|
BooleanResponse(rules.isCheckmate(req.context))
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/is-stalemate")
|
@Path("/is-stalemate")
|
||||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||||
def isStalemate(req: ContextRequest): BooleanResponse =
|
def isStalemate(req: ContextRequest): BooleanResponse =
|
||||||
BooleanResponse(rules.isStalemate(parse(DtoMapper.toGameContext(req.context))))
|
BooleanResponse(rules.isStalemate(req.context))
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/is-insufficient-material")
|
@Path("/is-insufficient-material")
|
||||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||||
def isInsufficientMaterial(req: ContextRequest): BooleanResponse =
|
def isInsufficientMaterial(req: ContextRequest): BooleanResponse =
|
||||||
BooleanResponse(rules.isInsufficientMaterial(parse(DtoMapper.toGameContext(req.context))))
|
BooleanResponse(rules.isInsufficientMaterial(req.context))
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/is-fifty-move-rule")
|
@Path("/is-fifty-move-rule")
|
||||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||||
def isFiftyMoveRule(req: ContextRequest): BooleanResponse =
|
def isFiftyMoveRule(req: ContextRequest): BooleanResponse =
|
||||||
BooleanResponse(rules.isFiftyMoveRule(parse(DtoMapper.toGameContext(req.context))))
|
BooleanResponse(rules.isFiftyMoveRule(req.context))
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/is-threefold-repetition")
|
@Path("/is-threefold-repetition")
|
||||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||||
def isThreefoldRepetition(req: ContextRequest): BooleanResponse =
|
def isThreefoldRepetition(req: ContextRequest): BooleanResponse =
|
||||||
BooleanResponse(rules.isThreefoldRepetition(parse(DtoMapper.toGameContext(req.context))))
|
BooleanResponse(rules.isThreefoldRepetition(req.context))
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/apply-move")
|
@Path("/apply-move")
|
||||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||||
def applyMove(req: ContextMoveRequest): GameContextDto =
|
def applyMove(req: ContextMoveRequest): GameContext =
|
||||||
val ctx = parse(DtoMapper.toGameContext(req.context))
|
rules.applyMove(req.context)(req.move)
|
||||||
val move = parse(DtoMapper.toMove(req.move))
|
|
||||||
DtoMapper.fromGameContext(rules.applyMove(ctx)(move))
|
|
||||||
|
|||||||
@@ -1,14 +1,30 @@
|
|||||||
package de.nowchess.rules.config
|
package de.nowchess.rules.config
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import de.nowchess.api.board.{File, Rank, Square}
|
||||||
|
import de.nowchess.api.move.MoveType
|
||||||
import org.scalatest.funsuite.AnyFunSuite
|
import org.scalatest.funsuite.AnyFunSuite
|
||||||
import org.scalatest.matchers.should.Matchers
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
|
||||||
class JacksonConfigTest extends AnyFunSuite with Matchers:
|
class JacksonConfigTest extends AnyFunSuite with Matchers:
|
||||||
|
|
||||||
test("customize registers DefaultScalaModule enabling Option serialization"):
|
private val mapper: ObjectMapper =
|
||||||
val config = new JacksonConfig()
|
val m = new ObjectMapper()
|
||||||
val mapper = new ObjectMapper()
|
new JacksonConfig().customize(m)
|
||||||
config.customize(mapper)
|
m
|
||||||
|
|
||||||
|
test("customize enables Option serialization via DefaultScalaModule"):
|
||||||
mapper.writeValueAsString(None) shouldBe "null"
|
mapper.writeValueAsString(None) shouldBe "null"
|
||||||
mapper.writeValueAsString(Some("hello")) shouldBe """"hello""""
|
mapper.writeValueAsString(Some("hello")) shouldBe """"hello""""
|
||||||
|
|
||||||
|
test("customize registers SquareSerializer"):
|
||||||
|
mapper.writeValueAsString(Square(File.E, Rank.R4)) shouldBe """"e4""""
|
||||||
|
|
||||||
|
test("customize registers MoveTypeSerializer"):
|
||||||
|
mapper.writeValueAsString(MoveType.CastleKingside) shouldBe """{"type":"castleKingside"}"""
|
||||||
|
|
||||||
|
test("customize registers SquareDeserializer"):
|
||||||
|
mapper.readValue(""""e4"""", classOf[Square]) shouldBe Square(File.E, Rank.R4)
|
||||||
|
|
||||||
|
test("customize registers MoveTypeDeserializer"):
|
||||||
|
mapper.readValue("""{"type":"enPassant"}""", classOf[MoveType]) shouldBe MoveType.EnPassant
|
||||||
|
|||||||
@@ -1,174 +0,0 @@
|
|||||||
package de.nowchess.rules.dto
|
|
||||||
|
|
||||||
import de.nowchess.api.board.{Color, File, Piece, PieceType, Rank, Square}
|
|
||||||
import de.nowchess.api.game.GameContext
|
|
||||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
|
||||||
import org.scalatest.funsuite.AnyFunSuite
|
|
||||||
import org.scalatest.matchers.should.Matchers
|
|
||||||
|
|
||||||
class DtoMapperTest extends AnyFunSuite with Matchers:
|
|
||||||
|
|
||||||
// ── toColor ────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
test("toColor converts White"):
|
|
||||||
DtoMapper.toColor("White") shouldBe Right(Color.White)
|
|
||||||
|
|
||||||
test("toColor converts Black"):
|
|
||||||
DtoMapper.toColor("Black") shouldBe Right(Color.Black)
|
|
||||||
|
|
||||||
test("toColor rejects unknown"):
|
|
||||||
DtoMapper.toColor("Red") shouldBe a[Left[?, ?]]
|
|
||||||
|
|
||||||
// ── toPieceType ────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
test("toPieceType converts all piece types"):
|
|
||||||
DtoMapper.toPieceType("Pawn") shouldBe Right(PieceType.Pawn)
|
|
||||||
DtoMapper.toPieceType("Knight") shouldBe Right(PieceType.Knight)
|
|
||||||
DtoMapper.toPieceType("Bishop") shouldBe Right(PieceType.Bishop)
|
|
||||||
DtoMapper.toPieceType("Rook") shouldBe Right(PieceType.Rook)
|
|
||||||
DtoMapper.toPieceType("Queen") shouldBe Right(PieceType.Queen)
|
|
||||||
DtoMapper.toPieceType("King") shouldBe Right(PieceType.King)
|
|
||||||
|
|
||||||
test("toPieceType rejects unknown"):
|
|
||||||
DtoMapper.toPieceType("Dragon") shouldBe a[Left[?, ?]]
|
|
||||||
|
|
||||||
// ── toSquare ───────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
test("toSquare converts valid algebraic"):
|
|
||||||
DtoMapper.toSquare("e4") shouldBe Right(Square(File.E, Rank.R4))
|
|
||||||
|
|
||||||
test("toSquare rejects invalid"):
|
|
||||||
DtoMapper.toSquare("z9") shouldBe a[Left[?, ?]]
|
|
||||||
|
|
||||||
// ── toMoveType ────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
test("toMoveType converts normal non-capture"):
|
|
||||||
DtoMapper.toMoveType(MoveDto("e2", "e4", "normal", None)) shouldBe Right(MoveType.Normal(false))
|
|
||||||
|
|
||||||
test("toMoveType converts capture"):
|
|
||||||
DtoMapper.toMoveType(MoveDto("e2", "d3", "capture", None)) shouldBe Right(MoveType.Normal(true))
|
|
||||||
|
|
||||||
test("toMoveType converts castleKingside"):
|
|
||||||
DtoMapper.toMoveType(MoveDto("e1", "g1", "castleKingside", None)) shouldBe Right(MoveType.CastleKingside)
|
|
||||||
|
|
||||||
test("toMoveType converts castleQueenside"):
|
|
||||||
DtoMapper.toMoveType(MoveDto("e1", "c1", "castleQueenside", None)) shouldBe Right(MoveType.CastleQueenside)
|
|
||||||
|
|
||||||
test("toMoveType converts enPassant"):
|
|
||||||
DtoMapper.toMoveType(MoveDto("e5", "d6", "enPassant", None)) shouldBe Right(MoveType.EnPassant)
|
|
||||||
|
|
||||||
test("toMoveType converts all promotion pieces"):
|
|
||||||
DtoMapper.toMoveType(MoveDto("e7", "e8", "promotion", Some("Queen"))) shouldBe Right(
|
|
||||||
MoveType.Promotion(PromotionPiece.Queen),
|
|
||||||
)
|
|
||||||
DtoMapper.toMoveType(MoveDto("e7", "e8", "promotion", Some("Rook"))) shouldBe Right(
|
|
||||||
MoveType.Promotion(PromotionPiece.Rook),
|
|
||||||
)
|
|
||||||
DtoMapper.toMoveType(MoveDto("e7", "e8", "promotion", Some("Bishop"))) shouldBe Right(
|
|
||||||
MoveType.Promotion(PromotionPiece.Bishop),
|
|
||||||
)
|
|
||||||
DtoMapper.toMoveType(MoveDto("e7", "e8", "promotion", Some("Knight"))) shouldBe Right(
|
|
||||||
MoveType.Promotion(PromotionPiece.Knight),
|
|
||||||
)
|
|
||||||
|
|
||||||
test("toMoveType rejects promotion without piece"):
|
|
||||||
DtoMapper.toMoveType(MoveDto("e7", "e8", "promotion", None)) shouldBe a[Left[?, ?]]
|
|
||||||
|
|
||||||
test("toMoveType rejects promotion with unknown piece"):
|
|
||||||
DtoMapper.toMoveType(MoveDto("e7", "e8", "promotion", Some("Pawn"))) shouldBe a[Left[?, ?]]
|
|
||||||
|
|
||||||
test("toMoveType rejects unknown type"):
|
|
||||||
DtoMapper.toMoveType(MoveDto("e2", "e4", "unknown", None)) shouldBe a[Left[?, ?]]
|
|
||||||
|
|
||||||
// ── toBoard ───────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
test("toBoard builds valid board"):
|
|
||||||
val pieces = List(
|
|
||||||
PieceOnSquareDto("e1", "White", "King"),
|
|
||||||
PieceOnSquareDto("e8", "Black", "King"),
|
|
||||||
)
|
|
||||||
val result = DtoMapper.toBoard(pieces)
|
|
||||||
result.isRight shouldBe true
|
|
||||||
result.map(_.pieceAt(Square(File.E, Rank.R1))) shouldBe Right(Some(Piece(Color.White, PieceType.King)))
|
|
||||||
|
|
||||||
test("toBoard rejects invalid square"):
|
|
||||||
DtoMapper.toBoard(List(PieceOnSquareDto("z9", "White", "King"))) shouldBe a[Left[?, ?]]
|
|
||||||
|
|
||||||
test("toBoard rejects invalid color"):
|
|
||||||
DtoMapper.toBoard(List(PieceOnSquareDto("e1", "Red", "King"))) shouldBe a[Left[?, ?]]
|
|
||||||
|
|
||||||
test("toBoard rejects invalid piece type"):
|
|
||||||
DtoMapper.toBoard(List(PieceOnSquareDto("e1", "White", "Dragon"))) shouldBe a[Left[?, ?]]
|
|
||||||
|
|
||||||
test("toBoard with multiple invalid pieces covers all sequenceList branches"):
|
|
||||||
val pieces = List(
|
|
||||||
PieceOnSquareDto("z9", "White", "King"),
|
|
||||||
PieceOnSquareDto("z8", "White", "Queen"),
|
|
||||||
)
|
|
||||||
DtoMapper.toBoard(pieces) shouldBe a[Left[?, ?]]
|
|
||||||
|
|
||||||
// ── toGameContext ─────────────────────────────────────────────────
|
|
||||||
|
|
||||||
test("toGameContext round-trips initial position"):
|
|
||||||
val ctx = GameContext.initial
|
|
||||||
val dto = DtoMapper.fromGameContext(ctx)
|
|
||||||
DtoMapper.toGameContext(dto) shouldBe Right(ctx)
|
|
||||||
|
|
||||||
test("toGameContext rejects invalid turn"):
|
|
||||||
val dto = DtoMapper.fromGameContext(GameContext.initial).copy(turn = "Red")
|
|
||||||
DtoMapper.toGameContext(dto) shouldBe a[Left[?, ?]]
|
|
||||||
|
|
||||||
test("toGameContext rejects invalid en passant square"):
|
|
||||||
val dto = DtoMapper.fromGameContext(GameContext.initial).copy(enPassantSquare = Some("z9"))
|
|
||||||
DtoMapper.toGameContext(dto) shouldBe a[Left[?, ?]]
|
|
||||||
|
|
||||||
test("toGameContext rejects invalid initial board"):
|
|
||||||
val badBoard = List(PieceOnSquareDto("z9", "White", "King"))
|
|
||||||
val dto = DtoMapper.fromGameContext(GameContext.initial).copy(initialBoard = badBoard)
|
|
||||||
DtoMapper.toGameContext(dto) shouldBe a[Left[?, ?]]
|
|
||||||
|
|
||||||
test("toGameContext rejects invalid move"):
|
|
||||||
val badMove = MoveDto("z9", "e4", "normal", None)
|
|
||||||
val dto = DtoMapper.fromGameContext(GameContext.initial).copy(moves = List(badMove))
|
|
||||||
DtoMapper.toGameContext(dto) shouldBe a[Left[?, ?]]
|
|
||||||
|
|
||||||
// ── fromGameContext ───────────────────────────────────────────────
|
|
||||||
|
|
||||||
test("fromGameContext includes en passant square when present"):
|
|
||||||
val ctx = GameContext.initial.copy(enPassantSquare = Some(Square(File.E, Rank.R3)))
|
|
||||||
DtoMapper.fromGameContext(ctx).enPassantSquare shouldBe Some("e3")
|
|
||||||
|
|
||||||
test("toGameContext round-trips a valid en passant square"):
|
|
||||||
val ctx = GameContext.initial.copy(enPassantSquare = Some(Square(File.E, Rank.R3)))
|
|
||||||
DtoMapper.toGameContext(DtoMapper.fromGameContext(ctx)).map(_.enPassantSquare) shouldBe Right(
|
|
||||||
Some(Square(File.E, Rank.R3)),
|
|
||||||
)
|
|
||||||
|
|
||||||
// ── fromMove ──────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
test("fromMove converts all move types"):
|
|
||||||
DtoMapper.fromMove(Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4))).moveType shouldBe "normal"
|
|
||||||
DtoMapper
|
|
||||||
.fromMove(Move(Square(File.E, Rank.R2), Square(File.D, Rank.R3), MoveType.Normal(true)))
|
|
||||||
.moveType shouldBe "capture"
|
|
||||||
DtoMapper
|
|
||||||
.fromMove(Move(Square(File.E, Rank.R1), Square(File.G, Rank.R1), MoveType.CastleKingside))
|
|
||||||
.moveType shouldBe "castleKingside"
|
|
||||||
DtoMapper
|
|
||||||
.fromMove(Move(Square(File.E, Rank.R1), Square(File.C, Rank.R1), MoveType.CastleQueenside))
|
|
||||||
.moveType shouldBe "castleQueenside"
|
|
||||||
DtoMapper
|
|
||||||
.fromMove(Move(Square(File.E, Rank.R5), Square(File.D, Rank.R6), MoveType.EnPassant))
|
|
||||||
.moveType shouldBe "enPassant"
|
|
||||||
DtoMapper
|
|
||||||
.fromMove(Move(Square(File.E, Rank.R7), Square(File.E, Rank.R8), MoveType.Promotion(PromotionPiece.Queen)))
|
|
||||||
.promotionPiece shouldBe Some("Queen")
|
|
||||||
DtoMapper
|
|
||||||
.fromMove(Move(Square(File.E, Rank.R7), Square(File.E, Rank.R8), MoveType.Promotion(PromotionPiece.Rook)))
|
|
||||||
.promotionPiece shouldBe Some("Rook")
|
|
||||||
DtoMapper
|
|
||||||
.fromMove(Move(Square(File.E, Rank.R7), Square(File.E, Rank.R8), MoveType.Promotion(PromotionPiece.Bishop)))
|
|
||||||
.promotionPiece shouldBe Some("Bishop")
|
|
||||||
DtoMapper
|
|
||||||
.fromMove(Move(Square(File.E, Rank.R7), Square(File.E, Rank.R8), MoveType.Promotion(PromotionPiece.Knight)))
|
|
||||||
.promotionPiece shouldBe Some("Knight")
|
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package de.nowchess.rules.json
|
||||||
|
|
||||||
|
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.{File, Rank, Square}
|
||||||
|
import de.nowchess.api.move.{MoveType, PromotionPiece}
|
||||||
|
import org.scalatest.funsuite.AnyFunSuite
|
||||||
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
|
||||||
|
class JsonSerializersTest extends AnyFunSuite with Matchers:
|
||||||
|
|
||||||
|
private val mapper: ObjectMapper =
|
||||||
|
val m = new ObjectMapper()
|
||||||
|
val mod = new SimpleModule()
|
||||||
|
m.registerModule(DefaultScalaModule)
|
||||||
|
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())
|
||||||
|
m.registerModule(mod)
|
||||||
|
m
|
||||||
|
|
||||||
|
private val e4 = Square(File.E, Rank.R4)
|
||||||
|
|
||||||
|
// ── SquareKeySerializer ───────────────────────────────────────────
|
||||||
|
|
||||||
|
test("SquareKeySerializer writes square as map field name"):
|
||||||
|
mapper.writeValueAsString(Map(e4 -> "piece")) shouldBe """{"e4":"piece"}"""
|
||||||
|
|
||||||
|
// ── SquareKeyDeserializer ─────────────────────────────────────────
|
||||||
|
|
||||||
|
test("SquareKeyDeserializer returns square for valid key"):
|
||||||
|
new SquareKeyDeserializer().deserializeKey("e4", null) shouldBe e4
|
||||||
|
|
||||||
|
test("SquareKeyDeserializer returns null for invalid key"):
|
||||||
|
new SquareKeyDeserializer().deserializeKey("z9", null) shouldBe null
|
||||||
|
|
||||||
|
// ── SquareSerializer/Deserializer ─────────────────────────────────
|
||||||
|
|
||||||
|
test("SquareSerializer writes square as string"):
|
||||||
|
mapper.writeValueAsString(e4) shouldBe """"e4""""
|
||||||
|
|
||||||
|
test("SquareDeserializer reads valid square string"):
|
||||||
|
mapper.readValue(""""e4"""", classOf[Square]) shouldBe e4
|
||||||
|
|
||||||
|
test("SquareDeserializer returns null for invalid square string"):
|
||||||
|
mapper.readValue(""""z9"""", classOf[Square]) shouldBe null
|
||||||
|
|
||||||
|
// ── MoveTypeSerializer ────────────────────────────────────────────
|
||||||
|
|
||||||
|
test("MoveTypeSerializer serializes Normal non-capture"):
|
||||||
|
mapper.writeValueAsString(MoveType.Normal(false)) shouldBe """{"type":"normal","isCapture":false}"""
|
||||||
|
|
||||||
|
test("MoveTypeSerializer serializes Normal capture"):
|
||||||
|
mapper.writeValueAsString(MoveType.Normal(true)) shouldBe """{"type":"normal","isCapture":true}"""
|
||||||
|
|
||||||
|
test("MoveTypeSerializer serializes CastleKingside"):
|
||||||
|
mapper.writeValueAsString(MoveType.CastleKingside) shouldBe """{"type":"castleKingside"}"""
|
||||||
|
|
||||||
|
test("MoveTypeSerializer serializes CastleQueenside"):
|
||||||
|
mapper.writeValueAsString(MoveType.CastleQueenside) shouldBe """{"type":"castleQueenside"}"""
|
||||||
|
|
||||||
|
test("MoveTypeSerializer serializes EnPassant"):
|
||||||
|
mapper.writeValueAsString(MoveType.EnPassant) shouldBe """{"type":"enPassant"}"""
|
||||||
|
|
||||||
|
test("MoveTypeSerializer serializes Promotion"):
|
||||||
|
mapper.writeValueAsString(MoveType.Promotion(PromotionPiece.Queen)) shouldBe
|
||||||
|
"""{"type":"promotion","piece":"Queen"}"""
|
||||||
|
|
||||||
|
// ── MoveTypeDeserializer ──────────────────────────────────────────
|
||||||
|
|
||||||
|
test("MoveTypeDeserializer deserializes normal non-capture"):
|
||||||
|
mapper.readValue("""{"type":"normal","isCapture":false}""", classOf[MoveType]) shouldBe
|
||||||
|
MoveType.Normal(false)
|
||||||
|
|
||||||
|
test("MoveTypeDeserializer deserializes normal capture"):
|
||||||
|
mapper.readValue("""{"type":"normal","isCapture":true}""", classOf[MoveType]) shouldBe
|
||||||
|
MoveType.Normal(true)
|
||||||
|
|
||||||
|
test("MoveTypeDeserializer deserializes castleKingside"):
|
||||||
|
mapper.readValue("""{"type":"castleKingside"}""", classOf[MoveType]) shouldBe MoveType.CastleKingside
|
||||||
|
|
||||||
|
test("MoveTypeDeserializer deserializes castleQueenside"):
|
||||||
|
mapper.readValue("""{"type":"castleQueenside"}""", classOf[MoveType]) shouldBe MoveType.CastleQueenside
|
||||||
|
|
||||||
|
test("MoveTypeDeserializer deserializes enPassant"):
|
||||||
|
mapper.readValue("""{"type":"enPassant"}""", classOf[MoveType]) shouldBe MoveType.EnPassant
|
||||||
|
|
||||||
|
test("MoveTypeDeserializer deserializes promotion"):
|
||||||
|
mapper.readValue("""{"type":"promotion","piece":"Rook"}""", classOf[MoveType]) shouldBe
|
||||||
|
MoveType.Promotion(PromotionPiece.Rook)
|
||||||
|
|
||||||
|
test("MoveTypeDeserializer throws for unknown type"):
|
||||||
|
an[Exception] should be thrownBy
|
||||||
|
mapper.readValue("""{"type":"unknown"}""", classOf[MoveType])
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
package de.nowchess.rules.resource
|
package de.nowchess.rules.resource
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import com.fasterxml.jackson.module.scala.DefaultScalaModule
|
|
||||||
import de.nowchess.api.board.{Board, CastlingRights, Color, File, Piece, PieceType, Rank, Square}
|
import de.nowchess.api.board.{Board, CastlingRights, Color, File, Piece, PieceType, Rank, Square}
|
||||||
import de.nowchess.api.game.GameContext
|
import de.nowchess.api.game.GameContext
|
||||||
import de.nowchess.api.move.Move
|
import de.nowchess.api.move.Move
|
||||||
import de.nowchess.rules.dto.{ContextMoveRequest, ContextRequest, ContextSquareRequest, DtoMapper}
|
import de.nowchess.rules.config.JacksonConfig
|
||||||
|
import de.nowchess.rules.dto.{ContextMoveRequest, ContextRequest, ContextSquareRequest}
|
||||||
import de.nowchess.rules.sets.DefaultRules
|
import de.nowchess.rules.sets.DefaultRules
|
||||||
import io.quarkus.test.junit.QuarkusTest
|
import io.quarkus.test.junit.QuarkusTest
|
||||||
import io.restassured.RestAssured
|
import io.restassured.RestAssured
|
||||||
@@ -16,21 +16,25 @@ import org.junit.jupiter.api.Test
|
|||||||
@QuarkusTest
|
@QuarkusTest
|
||||||
class RuleSetResourceTest:
|
class RuleSetResourceTest:
|
||||||
|
|
||||||
private val mapper = new ObjectMapper().registerModule(DefaultScalaModule)
|
private val mapper: ObjectMapper =
|
||||||
private val rules = DefaultRules
|
val m = new ObjectMapper()
|
||||||
|
new JacksonConfig().customize(m)
|
||||||
|
m
|
||||||
|
|
||||||
|
private val rules = DefaultRules
|
||||||
|
|
||||||
private def request() = RestAssured.`given`()
|
private def request() = RestAssured.`given`()
|
||||||
|
|
||||||
private def toJson(value: AnyRef): String = mapper.writeValueAsString(value)
|
private def toJson(value: AnyRef): String = mapper.writeValueAsString(value)
|
||||||
|
|
||||||
private def contextBody(ctx: GameContext): String =
|
private def contextBody(ctx: GameContext): String =
|
||||||
toJson(ContextRequest(DtoMapper.fromGameContext(ctx)))
|
toJson(ContextRequest(ctx))
|
||||||
|
|
||||||
private def contextSquareBody(ctx: GameContext, square: String): String =
|
private def contextSquareBody(ctx: GameContext, square: String): String =
|
||||||
toJson(ContextSquareRequest(DtoMapper.fromGameContext(ctx), square))
|
toJson(ContextSquareRequest(ctx, square))
|
||||||
|
|
||||||
private def contextMoveBody(ctx: GameContext, move: Move): String =
|
private def contextMoveBody(ctx: GameContext, move: Move): String =
|
||||||
toJson(ContextMoveRequest(DtoMapper.fromGameContext(ctx), DtoMapper.fromMove(move)))
|
toJson(ContextMoveRequest(ctx, move))
|
||||||
|
|
||||||
// ── all-legal-moves ───────────────────────────────────────────────
|
// ── all-legal-moves ───────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -238,7 +242,7 @@ class RuleSetResourceTest:
|
|||||||
def invalidSquare_returns400(): Unit =
|
def invalidSquare_returns400(): Unit =
|
||||||
request()
|
request()
|
||||||
.contentType(ContentType.JSON)
|
.contentType(ContentType.JSON)
|
||||||
.body(toJson(ContextSquareRequest(DtoMapper.fromGameContext(GameContext.initial), "z9")))
|
.body(toJson(ContextSquareRequest(GameContext.initial, "z9")))
|
||||||
.when()
|
.when()
|
||||||
.post("/api/rules/legal-moves")
|
.post("/api/rules/legal-moves")
|
||||||
.`then`()
|
.`then`()
|
||||||
|
|||||||
+6
-19
@@ -2,8 +2,8 @@ package de.nowchess.rules.resource
|
|||||||
|
|
||||||
import de.nowchess.api.board.{Board, CastlingRights, Color, File, Piece, PieceType, Rank, Square}
|
import de.nowchess.api.board.{Board, CastlingRights, Color, File, Piece, PieceType, Rank, Square}
|
||||||
import de.nowchess.api.game.GameContext
|
import de.nowchess.api.game.GameContext
|
||||||
import de.nowchess.api.move.{Move, MoveType}
|
import de.nowchess.api.move.Move
|
||||||
import de.nowchess.rules.dto.{ContextMoveRequest, ContextRequest, ContextSquareRequest, DtoMapper}
|
import de.nowchess.rules.dto.{ContextMoveRequest, ContextRequest, ContextSquareRequest}
|
||||||
import de.nowchess.rules.sets.DefaultRules
|
import de.nowchess.rules.sets.DefaultRules
|
||||||
import jakarta.ws.rs.BadRequestException
|
import jakarta.ws.rs.BadRequestException
|
||||||
import org.scalatest.funsuite.AnyFunSuite
|
import org.scalatest.funsuite.AnyFunSuite
|
||||||
@@ -14,9 +14,9 @@ class RuleSetResourceUnitTest extends AnyFunSuite with Matchers:
|
|||||||
private val resource = new RuleSetResource()
|
private val resource = new RuleSetResource()
|
||||||
private val rules = DefaultRules
|
private val rules = DefaultRules
|
||||||
|
|
||||||
private def ctx(g: GameContext) = ContextRequest(DtoMapper.fromGameContext(g))
|
private def ctx(g: GameContext) = ContextRequest(g)
|
||||||
private def ctxSq(g: GameContext, sq: String) = ContextSquareRequest(DtoMapper.fromGameContext(g), sq)
|
private def ctxSq(g: GameContext, sq: String) = ContextSquareRequest(g, sq)
|
||||||
private def ctxMv(g: GameContext, m: Move) = ContextMoveRequest(DtoMapper.fromGameContext(g), DtoMapper.fromMove(m))
|
private def ctxMv(g: GameContext, m: Move) = ContextMoveRequest(g, m)
|
||||||
|
|
||||||
// ── position builders ─────────────────────────────────────────────
|
// ── position builders ─────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -104,12 +104,6 @@ class RuleSetResourceUnitTest extends AnyFunSuite with Matchers:
|
|||||||
test("isCheck returns true when king is attacked"):
|
test("isCheck returns true when king is attacked"):
|
||||||
resource.isCheck(ctx(checkContext())).result shouldBe true
|
resource.isCheck(ctx(checkContext())).result shouldBe true
|
||||||
|
|
||||||
test("isCheck throws BadRequestException for invalid context"):
|
|
||||||
an[BadRequestException] should be thrownBy
|
|
||||||
resource.isCheck(
|
|
||||||
ctx(GameContext.initial).copy(context = DtoMapper.fromGameContext(GameContext.initial).copy(turn = "Red")),
|
|
||||||
)
|
|
||||||
|
|
||||||
// ── isCheckmate ───────────────────────────────────────────────────
|
// ── isCheckmate ───────────────────────────────────────────────────
|
||||||
|
|
||||||
test("isCheckmate returns false for initial position"):
|
test("isCheckmate returns false for initial position"):
|
||||||
@@ -157,11 +151,4 @@ class RuleSetResourceUnitTest extends AnyFunSuite with Matchers:
|
|||||||
.legalMoves(GameContext.initial)(Square(File.E, Rank.R2))
|
.legalMoves(GameContext.initial)(Square(File.E, Rank.R2))
|
||||||
.find(_.to == Square(File.E, Rank.R4))
|
.find(_.to == Square(File.E, Rank.R4))
|
||||||
.get
|
.get
|
||||||
resource.applyMove(ctxMv(GameContext.initial, move)).turn shouldBe "Black"
|
resource.applyMove(ctxMv(GameContext.initial, move)).turn shouldBe Color.Black
|
||||||
|
|
||||||
test("applyMove throws BadRequestException for invalid move"):
|
|
||||||
val badMove = DtoMapper
|
|
||||||
.fromMove(Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4)))
|
|
||||||
.copy(moveType = "unknown")
|
|
||||||
an[BadRequestException] should be thrownBy
|
|
||||||
resource.applyMove(ContextMoveRequest(DtoMapper.fromGameContext(GameContext.initial), badMove))
|
|
||||||
|
|||||||
Reference in New Issue
Block a user