feat(grpc): implement gRPC services for game context management and move handling
This commit is contained in:
@@ -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
|
||||
+29
-28
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user