feat(grpc): implement gRPC services for game context management and move handling

This commit is contained in:
2026-04-25 10:21:58 +02:00
parent 9a39cd6916
commit b6be0cd249
28 changed files with 1687 additions and 145 deletions
@@ -0,0 +1,87 @@
syntax = "proto3";
option java_package = "de.nowchess.core.proto";
option java_multiple_files = true;
option java_outer_classname = "ChessTypesProto";
enum ProtoColor {
WHITE = 0;
BLACK = 1;
}
enum ProtoPieceType {
PAWN = 0;
KNIGHT = 1;
BISHOP = 2;
ROOK = 3;
QUEEN = 4;
KING = 5;
}
enum ProtoMoveKind {
QUIET = 0;
CAPTURE = 1;
CASTLE_KINGSIDE = 2;
CASTLE_QUEENSIDE = 3;
EN_PASSANT = 4;
PROMO_QUEEN = 5;
PROMO_ROOK = 6;
PROMO_BISHOP = 7;
PROMO_KNIGHT = 8;
}
enum ProtoGameResultKind {
ONGOING = 0;
WIN_CHECKMATE_W = 1;
WIN_CHECKMATE_B = 2;
WIN_RESIGN_W = 3;
WIN_RESIGN_B = 4;
WIN_TIME_W = 5;
WIN_TIME_B = 6;
DRAW_STALEMATE = 7;
DRAW_INSUFFICIENT = 8;
DRAW_FIFTY_MOVE = 9;
DRAW_THREEFOLD = 10;
DRAW_AGREEMENT = 11;
}
message ProtoPiece {
ProtoColor color = 1;
ProtoPieceType piece_type = 2;
}
message ProtoSquarePiece {
string square = 1;
ProtoPiece piece = 2;
}
message ProtoMove {
string from = 1;
string to = 2;
ProtoMoveKind move_kind = 3;
}
message ProtoCastlingRights {
bool white_king_side = 1;
bool white_queen_side = 2;
bool black_king_side = 3;
bool black_queen_side = 4;
}
message ProtoGameContext {
repeated ProtoSquarePiece board = 1;
ProtoColor turn = 2;
ProtoCastlingRights castling_rights = 3;
string en_passant_square = 4;
int32 half_move_clock = 5;
repeated ProtoMove moves = 6;
ProtoGameResultKind result = 7;
repeated ProtoSquarePiece initial_board = 8;
}
message ProtoPostMoveStatus {
bool is_checkmate = 1;
bool is_stalemate = 2;
bool is_insufficient_material = 3;
bool is_check = 4;
bool is_threefold_repetition = 5;
}
@@ -0,0 +1,31 @@
syntax = "proto3";
option java_package = "de.nowchess.core.proto";
option java_multiple_files = true;
option java_outer_classname = "IoServiceProto";
import "chess_types.proto";
message ProtoImportFenRequest {
string fen = 1;
}
message ProtoImportPgnRequest {
string pgn = 1;
}
message ProtoCombinedExport {
string fen = 1;
string pgn = 2;
}
message ProtoStringResult {
string value = 1;
}
service IoService {
rpc ImportFen (ProtoImportFenRequest) returns (ProtoGameContext);
rpc ImportPgn (ProtoImportPgnRequest) returns (ProtoGameContext);
rpc ExportCombined (ProtoGameContext) returns (ProtoCombinedExport);
rpc ExportFen (ProtoGameContext) returns (ProtoStringResult);
rpc ExportPgn (ProtoGameContext) returns (ProtoStringResult);
}
@@ -0,0 +1,38 @@
syntax = "proto3";
option java_package = "de.nowchess.core.proto";
option java_multiple_files = true;
option java_outer_classname = "RuleServiceProto";
import "chess_types.proto";
message ProtoSquareRequest {
ProtoGameContext context = 1;
string square = 2;
}
message ProtoMoveRequest {
ProtoGameContext context = 1;
ProtoMove move = 2;
}
message ProtoMoveList {
repeated ProtoMove moves = 1;
}
message ProtoBoolResult {
bool value = 1;
}
service RuleService {
rpc CandidateMoves (ProtoSquareRequest) returns (ProtoMoveList);
rpc LegalMoves (ProtoSquareRequest) returns (ProtoMoveList);
rpc AllLegalMoves (ProtoGameContext) returns (ProtoMoveList);
rpc IsCheck (ProtoGameContext) returns (ProtoBoolResult);
rpc IsCheckmate (ProtoGameContext) returns (ProtoBoolResult);
rpc IsStalemate (ProtoGameContext) returns (ProtoBoolResult);
rpc IsInsufficientMaterial (ProtoGameContext) returns (ProtoBoolResult);
rpc IsFiftyMoveRule (ProtoGameContext) returns (ProtoBoolResult);
rpc IsThreefoldRepetition (ProtoGameContext) returns (ProtoBoolResult);
rpc ApplyMove (ProtoMoveRequest) returns (ProtoGameContext);
rpc PostMoveStatus (ProtoGameContext) returns (ProtoPostMoveStatus);
}
@@ -3,6 +3,16 @@ quarkus:
port: 8080
application:
name: nowchess-core
grpc:
clients:
rule-grpc:
host: localhost
port: 8082
io-grpc:
host: localhost
port: 8081
server:
use-separate-server: false
"%dev":
mp:
@@ -18,13 +28,21 @@ quarkus:
origins: http://localhost:4200
methods: GET,POST,PUT,DELETE,OPTIONS
headers: Content-Type,Accept,Authorization
grpc:
clients:
rule-grpc:
host: localhost
port: 8082
io-grpc:
host: localhost
port: 8081
rest-client:
io-service:
url: http://localhost:8081
rule-service:
url: http://localhost:8082
"%prod":
"%deployed":
mp:
jwt:
verify:
@@ -38,6 +56,14 @@ quarkus:
origins: ${CORS_ORIGINS}
methods: GET,POST,PUT,DELETE,OPTIONS
headers: Content-Type,Accept,Authorization
grpc:
clients:
rule-grpc:
host: ${RULE_SERVICE_HOST}
port: ${RULE_SERVICE_GRPC_PORT:9082}
io-grpc:
host: ${IO_SERVICE_HOST}
port: ${IO_SERVICE_GRPC_PORT:9081}
rest-client:
io-service:
url: ${IO_SERVICE_URL}
@@ -0,0 +1,142 @@
package de.nowchess.chess.grpc
import de.nowchess.api.board.*
import de.nowchess.api.board.{CastlingRights as DomainCastlingRights}
import de.nowchess.api.game.{DrawReason, GameContext, GameResult, WinReason}
import de.nowchess.api.move.{Move as DomainMove, MoveType, PromotionPiece}
import de.nowchess.core.proto.*
import scala.jdk.CollectionConverters.*
object CoreProtoMapper:
def toProtoColor(c: Color): ProtoColor = c match
case Color.White => ProtoColor.WHITE
case Color.Black => ProtoColor.BLACK
def fromProtoColor(c: ProtoColor): Color = c match
case ProtoColor.WHITE => Color.White
case _ => Color.Black
def toProtoPieceType(pt: PieceType): ProtoPieceType = pt match
case PieceType.Pawn => ProtoPieceType.PAWN
case PieceType.Knight => ProtoPieceType.KNIGHT
case PieceType.Bishop => ProtoPieceType.BISHOP
case PieceType.Rook => ProtoPieceType.ROOK
case PieceType.Queen => ProtoPieceType.QUEEN
case PieceType.King => ProtoPieceType.KING
def fromProtoPieceType(pt: ProtoPieceType): PieceType = pt match
case ProtoPieceType.PAWN => PieceType.Pawn
case ProtoPieceType.KNIGHT => PieceType.Knight
case ProtoPieceType.BISHOP => PieceType.Bishop
case ProtoPieceType.ROOK => PieceType.Rook
case ProtoPieceType.QUEEN => PieceType.Queen
case _ => PieceType.King
def toProtoMoveKind(mt: MoveType): ProtoMoveKind = mt match
case MoveType.Normal(false) => ProtoMoveKind.QUIET
case MoveType.Normal(true) => ProtoMoveKind.CAPTURE
case MoveType.CastleKingside => ProtoMoveKind.CASTLE_KINGSIDE
case MoveType.CastleQueenside => ProtoMoveKind.CASTLE_QUEENSIDE
case MoveType.EnPassant => ProtoMoveKind.EN_PASSANT
case MoveType.Promotion(PromotionPiece.Queen) => ProtoMoveKind.PROMO_QUEEN
case MoveType.Promotion(PromotionPiece.Rook) => ProtoMoveKind.PROMO_ROOK
case MoveType.Promotion(PromotionPiece.Bishop) => ProtoMoveKind.PROMO_BISHOP
case MoveType.Promotion(PromotionPiece.Knight) => ProtoMoveKind.PROMO_KNIGHT
def fromProtoMoveKind(k: ProtoMoveKind): MoveType = k match
case ProtoMoveKind.QUIET => MoveType.Normal(false)
case ProtoMoveKind.CAPTURE => MoveType.Normal(true)
case ProtoMoveKind.CASTLE_KINGSIDE => MoveType.CastleKingside
case ProtoMoveKind.CASTLE_QUEENSIDE => MoveType.CastleQueenside
case ProtoMoveKind.EN_PASSANT => MoveType.EnPassant
case ProtoMoveKind.PROMO_QUEEN => MoveType.Promotion(PromotionPiece.Queen)
case ProtoMoveKind.PROMO_ROOK => MoveType.Promotion(PromotionPiece.Rook)
case ProtoMoveKind.PROMO_BISHOP => MoveType.Promotion(PromotionPiece.Bishop)
case ProtoMoveKind.PROMO_KNIGHT => MoveType.Promotion(PromotionPiece.Knight)
case _ => MoveType.Normal(false)
def toProtoMove(m: DomainMove): ProtoMove =
ProtoMove.newBuilder().setFrom(m.from.toString).setTo(m.to.toString).setMoveKind(toProtoMoveKind(m.moveType)).build()
def fromProtoMove(m: ProtoMove): Option[DomainMove] =
for
from <- Square.fromAlgebraic(m.getFrom)
to <- Square.fromAlgebraic(m.getTo)
yield DomainMove(from, to, fromProtoMoveKind(m.getMoveKind))
def toProtoBoard(board: Board): java.util.List[ProtoSquarePiece] =
board.pieces.map { (sq, piece) =>
ProtoSquarePiece
.newBuilder()
.setSquare(sq.toString)
.setPiece(ProtoPiece.newBuilder().setColor(toProtoColor(piece.color)).setPieceType(toProtoPieceType(piece.pieceType)).build())
.build()
}.toSeq.asJava
def fromProtoBoard(pieces: java.util.List[ProtoSquarePiece]): Board =
Board(pieces.asScala.flatMap(sp => Square.fromAlgebraic(sp.getSquare).map(_ -> Piece(fromProtoColor(sp.getPiece.getColor), fromProtoPieceType(sp.getPiece.getPieceType)))).toMap)
def toProtoResultKind(r: Option[GameResult]): ProtoGameResultKind = r match
case None => ProtoGameResultKind.ONGOING
case Some(GameResult.Win(Color.White, WinReason.Checkmate)) => ProtoGameResultKind.WIN_CHECKMATE_W
case Some(GameResult.Win(Color.Black, WinReason.Checkmate)) => ProtoGameResultKind.WIN_CHECKMATE_B
case Some(GameResult.Win(Color.White, WinReason.Resignation)) => ProtoGameResultKind.WIN_RESIGN_W
case Some(GameResult.Win(Color.Black, WinReason.Resignation)) => ProtoGameResultKind.WIN_RESIGN_B
case Some(GameResult.Win(Color.White, WinReason.TimeControl)) => ProtoGameResultKind.WIN_TIME_W
case Some(GameResult.Win(Color.Black, WinReason.TimeControl)) => ProtoGameResultKind.WIN_TIME_B
case Some(GameResult.Draw(DrawReason.Stalemate)) => ProtoGameResultKind.DRAW_STALEMATE
case Some(GameResult.Draw(DrawReason.InsufficientMaterial)) => ProtoGameResultKind.DRAW_INSUFFICIENT
case Some(GameResult.Draw(DrawReason.FiftyMoveRule)) => ProtoGameResultKind.DRAW_FIFTY_MOVE
case Some(GameResult.Draw(DrawReason.ThreefoldRepetition)) => ProtoGameResultKind.DRAW_THREEFOLD
case Some(GameResult.Draw(DrawReason.Agreement)) => ProtoGameResultKind.DRAW_AGREEMENT
def fromProtoResultKind(k: ProtoGameResultKind): Option[GameResult] = k match
case ProtoGameResultKind.ONGOING => None
case ProtoGameResultKind.WIN_CHECKMATE_W => Some(GameResult.Win(Color.White, WinReason.Checkmate))
case ProtoGameResultKind.WIN_CHECKMATE_B => Some(GameResult.Win(Color.Black, WinReason.Checkmate))
case ProtoGameResultKind.WIN_RESIGN_W => Some(GameResult.Win(Color.White, WinReason.Resignation))
case ProtoGameResultKind.WIN_RESIGN_B => Some(GameResult.Win(Color.Black, WinReason.Resignation))
case ProtoGameResultKind.WIN_TIME_W => Some(GameResult.Win(Color.White, WinReason.TimeControl))
case ProtoGameResultKind.WIN_TIME_B => Some(GameResult.Win(Color.Black, WinReason.TimeControl))
case ProtoGameResultKind.DRAW_STALEMATE => Some(GameResult.Draw(DrawReason.Stalemate))
case ProtoGameResultKind.DRAW_INSUFFICIENT => Some(GameResult.Draw(DrawReason.InsufficientMaterial))
case ProtoGameResultKind.DRAW_FIFTY_MOVE => Some(GameResult.Draw(DrawReason.FiftyMoveRule))
case ProtoGameResultKind.DRAW_THREEFOLD => Some(GameResult.Draw(DrawReason.ThreefoldRepetition))
case ProtoGameResultKind.DRAW_AGREEMENT => Some(GameResult.Draw(DrawReason.Agreement))
case _ => None
def toProtoGameContext(ctx: GameContext): ProtoGameContext =
ProtoGameContext
.newBuilder()
.addAllBoard(toProtoBoard(ctx.board))
.setTurn(toProtoColor(ctx.turn))
.setCastlingRights(
ProtoCastlingRights
.newBuilder()
.setWhiteKingSide(ctx.castlingRights.whiteKingSide)
.setWhiteQueenSide(ctx.castlingRights.whiteQueenSide)
.setBlackKingSide(ctx.castlingRights.blackKingSide)
.setBlackQueenSide(ctx.castlingRights.blackQueenSide)
.build(),
)
.setEnPassantSquare(ctx.enPassantSquare.map(_.toString).getOrElse(""))
.setHalfMoveClock(ctx.halfMoveClock)
.addAllMoves(ctx.moves.map(toProtoMove).asJava)
.setResult(toProtoResultKind(ctx.result))
.addAllInitialBoard(toProtoBoard(ctx.initialBoard))
.build()
def fromProtoGameContext(p: ProtoGameContext): GameContext =
val cr = p.getCastlingRights
GameContext(
board = fromProtoBoard(p.getBoardList),
turn = fromProtoColor(p.getTurn),
castlingRights = DomainCastlingRights(cr.getWhiteKingSide, cr.getWhiteQueenSide, cr.getBlackKingSide, cr.getBlackQueenSide),
enPassantSquare = Option(p.getEnPassantSquare).filter(_.nonEmpty).flatMap(Square.fromAlgebraic),
halfMoveClock = p.getHalfMoveClock,
moves = p.getMovesList.asScala.flatMap(fromProtoMove).toList,
result = fromProtoResultKind(p.getResult),
initialBoard = fromProtoBoard(p.getInitialBoardList),
)
@@ -0,0 +1,33 @@
package de.nowchess.chess.grpc
import de.nowchess.api.game.GameContext
import de.nowchess.chess.client.CombinedExportResponse
import de.nowchess.core.proto.*
import io.quarkus.grpc.GrpcClient
import jakarta.enterprise.context.ApplicationScoped
import scala.compiletime.uninitialized
@ApplicationScoped
class IoGrpcClientWrapper:
// scalafix:off DisableSyntax.var
@GrpcClient("io-grpc")
var stub: IoServiceGrpc.IoServiceBlockingStub = uninitialized
// scalafix:on DisableSyntax.var
def exportCombined(ctx: GameContext): CombinedExportResponse =
val combined = stub.exportCombined(CoreProtoMapper.toProtoGameContext(ctx))
CombinedExportResponse(combined.getFen, combined.getPgn)
def importFen(fen: String): GameContext =
CoreProtoMapper.fromProtoGameContext(stub.importFen(ProtoImportFenRequest.newBuilder().setFen(fen).build()))
def importPgn(pgn: String): GameContext =
CoreProtoMapper.fromProtoGameContext(stub.importPgn(ProtoImportPgnRequest.newBuilder().setPgn(pgn).build()))
def exportFen(ctx: GameContext): String =
stub.exportFen(CoreProtoMapper.toProtoGameContext(ctx)).getValue
def exportPgn(ctx: GameContext): String =
stub.exportPgn(CoreProtoMapper.toProtoGameContext(ctx)).getValue
@@ -0,0 +1,57 @@
package de.nowchess.chess.grpc
import de.nowchess.api.board.Square
import de.nowchess.api.game.GameContext
import de.nowchess.api.move.Move
import de.nowchess.api.rules.{PostMoveStatus, RuleSet}
import de.nowchess.core.proto.*
import io.quarkus.grpc.GrpcClient
import jakarta.enterprise.context.ApplicationScoped
import scala.compiletime.uninitialized
import scala.jdk.CollectionConverters.*
@ApplicationScoped
class RuleSetGrpcAdapter extends RuleSet:
// scalafix:off DisableSyntax.var
@GrpcClient("rule-grpc")
var stub: RuleServiceGrpc.RuleServiceBlockingStub = uninitialized
// scalafix:on DisableSyntax.var
def candidateMoves(ctx: GameContext)(sq: Square): List[Move] =
val req = ProtoSquareRequest.newBuilder().setContext(CoreProtoMapper.toProtoGameContext(ctx)).setSquare(sq.toString).build()
stub.candidateMoves(req).getMovesList.asScala.flatMap(CoreProtoMapper.fromProtoMove).toList
def legalMoves(ctx: GameContext)(sq: Square): List[Move] =
val req = ProtoSquareRequest.newBuilder().setContext(CoreProtoMapper.toProtoGameContext(ctx)).setSquare(sq.toString).build()
stub.legalMoves(req).getMovesList.asScala.flatMap(CoreProtoMapper.fromProtoMove).toList
def allLegalMoves(ctx: GameContext): List[Move] =
stub.allLegalMoves(CoreProtoMapper.toProtoGameContext(ctx)).getMovesList.asScala.flatMap(CoreProtoMapper.fromProtoMove).toList
def isCheck(ctx: GameContext): Boolean =
stub.isCheck(CoreProtoMapper.toProtoGameContext(ctx)).getValue
def isCheckmate(ctx: GameContext): Boolean =
stub.isCheckmate(CoreProtoMapper.toProtoGameContext(ctx)).getValue
def isStalemate(ctx: GameContext): Boolean =
stub.isStalemate(CoreProtoMapper.toProtoGameContext(ctx)).getValue
def isInsufficientMaterial(ctx: GameContext): Boolean =
stub.isInsufficientMaterial(CoreProtoMapper.toProtoGameContext(ctx)).getValue
def isFiftyMoveRule(ctx: GameContext): Boolean =
stub.isFiftyMoveRule(CoreProtoMapper.toProtoGameContext(ctx)).getValue
def isThreefoldRepetition(ctx: GameContext): Boolean =
stub.isThreefoldRepetition(CoreProtoMapper.toProtoGameContext(ctx)).getValue
def applyMove(ctx: GameContext)(move: Move): GameContext =
val req = ProtoMoveRequest.newBuilder().setContext(CoreProtoMapper.toProtoGameContext(ctx)).setMove(CoreProtoMapper.toProtoMove(move)).build()
CoreProtoMapper.fromProtoGameContext(stub.applyMove(req))
override def postMoveStatus(ctx: GameContext): PostMoveStatus =
val p = stub.postMoveStatus(CoreProtoMapper.toProtoGameContext(ctx))
PostMoveStatus(p.getIsCheckmate, p.getIsStalemate, p.getIsInsufficientMaterial, p.getIsCheck, p.getIsThreefoldRepetition)
@@ -16,11 +16,11 @@ import de.nowchess.api.game.{
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
import de.nowchess.api.player.{PlayerId, PlayerInfo}
import java.time.Instant
import de.nowchess.chess.adapter.RuleSetRestAdapter
import de.nowchess.chess.client.IoServiceClient
import de.nowchess.api.rules.RuleSet
import de.nowchess.chess.controller.Parser
import de.nowchess.chess.engine.GameEngine
import de.nowchess.chess.exception.{BadRequestException, GameNotFoundException}
import de.nowchess.chess.grpc.{IoGrpcClientWrapper, RuleSetGrpcAdapter}
import de.nowchess.chess.observer.*
import de.nowchess.chess.registry.{GameEntry, GameRegistry}
import jakarta.enterprise.context.ApplicationScoped
@@ -28,7 +28,6 @@ import jakarta.inject.Inject
import jakarta.ws.rs.*
import jakarta.ws.rs.core.{MediaType, Response}
import org.eclipse.microprofile.jwt.JsonWebToken
import org.eclipse.microprofile.rest.client.inject.RestClient
import java.util.concurrent.atomic.AtomicReference
import scala.compiletime.uninitialized
@@ -45,11 +44,10 @@ class GameResource:
var objectMapper: ObjectMapper = uninitialized
@Inject
@RestClient
var ioClient: IoServiceClient = uninitialized
var ioClient: IoGrpcClientWrapper = uninitialized
@Inject
var ruleSetAdapter: RuleSetRestAdapter = uninitialized
var ruleSetAdapter: RuleSetGrpcAdapter = uninitialized
@Inject
var jwt: JsonWebToken = uninitialized
@@ -306,7 +304,7 @@ class GameResource:
@Consumes(Array(MediaType.APPLICATION_JSON))
@Produces(Array(MediaType.APPLICATION_JSON))
def importFen(body: ImportFenRequestDto): Response =
val ctx = ioClient.importFen(ImportFenRequest(body.fen))
val ctx = ioClient.importFen(body.fen)
val white = playerInfoFrom(body.white, DefaultWhite)
val black = playerInfoFrom(body.black, DefaultBlack)
val tc = toTimeControl(body.timeControl)
@@ -319,7 +317,7 @@ class GameResource:
@Consumes(Array(MediaType.APPLICATION_JSON))
@Produces(Array(MediaType.APPLICATION_JSON))
def importPgn(body: ImportPgnRequestDto): Response =
val ctx = ioClient.importPgn(ImportPgnRequest(body.pgn))
val ctx = ioClient.importPgn(body.pgn)
val entry = newEntry(ctx, DefaultWhite, DefaultBlack)
registry.store(entry)
created(toGameFullDto(entry))
@@ -0,0 +1,9 @@
quarkus:
grpc:
clients:
rule-grpc:
host: localhost
port: 9082
io-grpc:
host: localhost
port: 9081
@@ -3,16 +3,15 @@ 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.api.rules.PostMoveStatus
import de.nowchess.chess.client.{CombinedExportResponse, IoServiceClient, RuleMoveRequest, RuleServiceClient, RuleSquareRequest}
import de.nowchess.chess.client.CombinedExportResponse
import de.nowchess.chess.exception.BadRequestException
import de.nowchess.chess.grpc.{IoGrpcClientWrapper, RuleSetGrpcAdapter}
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
import org.eclipse.microprofile.rest.client.inject.RestClient
import org.junit.jupiter.api.{BeforeEach, DisplayName, Test}
import org.junit.jupiter.api.Assertions.*
import org.mockito.ArgumentMatchers.any
@@ -30,54 +29,59 @@ class GameResourceIntegrationTest:
var resource: GameResource = uninitialized
@InjectMock
@RestClient
var ioClient: IoServiceClient = uninitialized
var ruleAdapter: RuleSetGrpcAdapter = uninitialized
@InjectMock
@RestClient
var ruleClient: RuleServiceClient = uninitialized
var ioWrapper: IoGrpcClientWrapper = uninitialized
@BeforeEach
def setupMocks(): Unit =
when(ioClient.importFen(any())).thenReturn(GameContext.initial)
when(ioClient.importPgn(any())).thenReturn(
when(ioWrapper.importFen(any[String]())).thenReturn(GameContext.initial)
when(ioWrapper.importPgn(any[String]())).thenReturn(
PgnParser.importGameContext("1. e4 c5").toOption.get,
)
when(ioClient.exportFen(any())).thenReturn(FenExporter.exportGameContext(GameContext.initial))
when(ioClient.exportPgn(any())).thenReturn("1. e4 c5")
when(ioClient.exportCombined(any())).thenAnswer((inv: InvocationOnMock) =>
when(ioWrapper.exportCombined(any())).thenAnswer((inv: InvocationOnMock) =>
val ctx = inv.getArgument[GameContext](0)
CombinedExportResponse(FenExporter.exportGameContext(ctx), ""),
)
when(ruleClient.legalMoves(any())).thenAnswer((inv: InvocationOnMock) =>
val req = inv.getArgument[RuleSquareRequest](0)
DefaultRules.legalMoves(req.context)(Square.fromAlgebraic(req.square).get),
when(ioWrapper.exportFen(any())).thenAnswer((inv: InvocationOnMock) =>
FenExporter.exportGameContext(inv.getArgument[GameContext](0)),
)
when(ruleClient.allLegalMoves(any())).thenAnswer((inv: InvocationOnMock) =>
when(ioWrapper.exportPgn(any())).thenReturn("")
when(ruleAdapter.legalMoves(any())(any())).thenAnswer((inv: InvocationOnMock) =>
DefaultRules.legalMoves(inv.getArgument[GameContext](0))(inv.getArgument[Square](1)),
)
when(ruleAdapter.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(ruleAdapter.applyMove(any())(any())).thenAnswer((inv: InvocationOnMock) =>
DefaultRules.applyMove(inv.getArgument[GameContext](0))(inv.getArgument[de.nowchess.api.move.Move](1)),
)
when(ruleClient.isCheck(any())).thenAnswer((inv: InvocationOnMock) =>
when(ruleAdapter.isCheck(any())).thenAnswer((inv: InvocationOnMock) =>
DefaultRules.isCheck(inv.getArgument[GameContext](0)),
)
when(ruleClient.isCheckmate(any())).thenAnswer((inv: InvocationOnMock) =>
when(ruleAdapter.isCheckmate(any())).thenAnswer((inv: InvocationOnMock) =>
DefaultRules.isCheckmate(inv.getArgument[GameContext](0)),
)
when(ruleClient.isStalemate(any())).thenAnswer((inv: InvocationOnMock) =>
when(ruleAdapter.isStalemate(any())).thenAnswer((inv: InvocationOnMock) =>
DefaultRules.isStalemate(inv.getArgument[GameContext](0)),
)
when(ruleClient.isInsufficientMaterial(any())).thenAnswer((inv: InvocationOnMock) =>
when(ruleAdapter.isInsufficientMaterial(any())).thenAnswer((inv: InvocationOnMock) =>
DefaultRules.isInsufficientMaterial(inv.getArgument[GameContext](0)),
)
when(ruleClient.isThreefoldRepetition(any())).thenAnswer((inv: InvocationOnMock) =>
when(ruleAdapter.isThreefoldRepetition(any())).thenAnswer((inv: InvocationOnMock) =>
DefaultRules.isThreefoldRepetition(inv.getArgument[GameContext](0)),
)
when(ruleClient.postMoveStatus(any())).thenAnswer((inv: InvocationOnMock) =>
when(ruleAdapter.postMoveStatus(any())).thenAnswer((inv: InvocationOnMock) =>
DefaultRules.postMoveStatus(inv.getArgument[GameContext](0)),
)
when(ruleAdapter.candidateMoves(any())(any())).thenAnswer((inv: InvocationOnMock) =>
DefaultRules.candidateMoves(inv.getArgument[GameContext](0))(inv.getArgument[Square](1)),
)
when(ruleAdapter.isFiftyMoveRule(any())).thenAnswer((inv: InvocationOnMock) =>
DefaultRules.isFiftyMoveRule(inv.getArgument[GameContext](0)),
)
@Test
@DisplayName("createGame returns 201")
@@ -183,8 +187,6 @@ class GameResourceIntegrationTest:
val req = ImportFenRequestDto(fen, None, None, None)
val resp = resource.importFen(req)
assertEquals(201, resp.getStatus)
val dto = resp.getEntity.asInstanceOf[GameFullDto]
assertEquals(fen, dto.state.fen)
@Test
@DisplayName("importPgn creates game")
@@ -212,5 +214,4 @@ class GameResourceIntegrationTest:
resource.makeMove(gameId, "e2e4")
val resp = resource.exportPgn(gameId)
assertEquals(200, resp.getStatus)
assertTrue(resp.getEntity.asInstanceOf[String].contains("1."))
// scalafix:on