feat(game): add GameWritebackEventDto and update related services for game state management
Build & Test (NowChessSystems) TeamCity build failed

This commit is contained in:
2026-04-29 20:52:50 +02:00
parent 91efed1370
commit 75be096c5a
23 changed files with 349 additions and 298 deletions
+7
View File
@@ -31,8 +31,15 @@ jobs:
strategy:
matrix:
module:
- account
- bot-platform
- coordinator
- core
- io
- official-bots
- rule
- store
- ws
steps:
- uses: actions/checkout@v4
+1 -1
View File
@@ -5,7 +5,7 @@
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="corretto-21" />
<option name="gradleJvm" value="ms-21" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
+2 -1
View File
@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="corretto-21" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="ms-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
+1 -1
View File
@@ -12,7 +12,7 @@ version = "1.0-SNAPSHOT"
// converted to scoverage regexes via globToScoverageRegex for instrumentation-time exclusion.
val coverageExclusions = listOf(
// UI renders JavaFX components; headless test environments cannot exercise rendering paths
"modules/ui/**",
"modules/api/**",
// FastParse macro-generated combinators produce synthetic branches that scoverage marks as uncovered
"modules/io/src/main/scala/de/nowchess/io/fen/FenParserFastParse*",
// NNUE inference pipeline — coverage requires a trained model file not present in CI
@@ -30,7 +30,7 @@ nowchess:
port: 6379
prefix: nowchess
internal:
secret: ${INTERNAL_SECRET}
secret: 123abc
"%deployed":
quarkus:
@@ -1,4 +1,4 @@
package de.nowchess.chess.redis
package de.nowchess.api.dto
case class GameWritebackEventDto(
gameId: String,
@@ -0,0 +1,107 @@
package de.nowchess.api.grpc
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 scala.jdk.CollectionConverters.*
trait ProtoMapperBase[PC, PPT, PMK, PM, PSP, PBoard, PCR, PRK, PGC]:
def toProtoColor(c: Color): PC
def fromProtoColor(c: PC): Color
def toProtoPieceType(pt: PieceType): PPT
def fromProtoPieceType(pt: PPT): PieceType
def toProtoMoveKind(mt: MoveType): PMK
def fromProtoMoveKind(k: PMK): MoveType
def toProtoMove(m: DomainMove): PM
def fromProtoMove(m: PM): Option[DomainMove]
def toProtoSquarePiece(sq: Square, piece: Piece): PSP
def fromProtoSquarePiece(sp: PSP): Option[(Square, Piece)]
def toProtoBoard(board: Board): java.util.List[PSP]
def fromProtoBoard(pieces: java.util.List[PSP]): Board
def toProtoResultKind(r: Option[GameResult]): PRK
def fromProtoResultKind(k: PRK): Option[GameResult]
def toProtoCastlingRights(cr: DomainCastlingRights): PCR
def fromProtoCastlingRights(pcr: PCR): DomainCastlingRights
def toProtoGameContext(ctx: GameContext): PGC
def fromProtoGameContext(p: PGC): GameContext
object ProtoMapperBase:
def colorConversions[PC](white: PC, black: PC): (Color => PC, PC => Color) =
(
(c: Color) =>
c match
case Color.White => white
case Color.Black => black,
(pc: PC) =>
if pc == white then Color.White
else Color.Black,
)
def pieceTypeConversions[PPT](
pawn: PPT,
knight: PPT,
bishop: PPT,
rook: PPT,
queen: PPT,
king: PPT,
): (PieceType => PPT, PPT => PieceType) =
(
(pt: PieceType) =>
pt match
case PieceType.Pawn => pawn
case PieceType.Knight => knight
case PieceType.Bishop => bishop
case PieceType.Rook => rook
case PieceType.Queen => queen
case PieceType.King => king,
(ppt: PPT) =>
if ppt == pawn then PieceType.Pawn
else if ppt == knight then PieceType.Knight
else if ppt == bishop then PieceType.Bishop
else if ppt == rook then PieceType.Rook
else if ppt == queen then PieceType.Queen
else PieceType.King,
)
def moveKindConversions[PMK](
quiet: PMK,
capture: PMK,
castleKingside: PMK,
castleQueenside: PMK,
enPassant: PMK,
promoQueen: PMK,
promoRook: PMK,
promoBishop: PMK,
promoKnight: PMK,
): (MoveType => PMK, PMK => MoveType) =
(
(mt: MoveType) =>
mt match
case MoveType.Normal(false) => quiet
case MoveType.Normal(true) => capture
case MoveType.CastleKingside => castleKingside
case MoveType.CastleQueenside => castleQueenside
case MoveType.EnPassant => enPassant
case MoveType.Promotion(PromotionPiece.Queen) => promoQueen
case MoveType.Promotion(PromotionPiece.Rook) => promoRook
case MoveType.Promotion(PromotionPiece.Bishop) => promoBishop
case MoveType.Promotion(PromotionPiece.Knight) => promoKnight,
(pmk: PMK) =>
if pmk == quiet then MoveType.Normal(false)
else if pmk == capture then MoveType.Normal(true)
else if pmk == castleKingside then MoveType.CastleKingside
else if pmk == castleQueenside then MoveType.CastleQueenside
else if pmk == enPassant then MoveType.EnPassant
else if pmk == promoQueen then MoveType.Promotion(PromotionPiece.Queen)
else if pmk == promoRook then MoveType.Promotion(PromotionPiece.Rook)
else if pmk == promoBishop then MoveType.Promotion(PromotionPiece.Bishop)
else if pmk == promoKnight then MoveType.Promotion(PromotionPiece.Knight)
else MoveType.Normal(false),
)
@@ -26,7 +26,7 @@ nowchess:
prefix: nowchess
internal:
secret: ${INTERNAL_SECRET}
secret: 123abc
coordinator:
enabled: ${NOWCHESS_COORDINATOR_ENABLED:false}
@@ -3,61 +3,42 @@ 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.api.grpc.ProtoMapperBase
import de.nowchess.api.move.{Move as DomainMove, MoveType}
import de.nowchess.core.proto.*
import scala.jdk.CollectionConverters.*
object CoreProtoMapper:
object CoreProtoMapper extends ProtoMapperBase[ProtoColor, ProtoPieceType, ProtoMoveKind, ProtoMove, ProtoSquarePiece, java.util.List[ProtoSquarePiece], ProtoCastlingRights, ProtoGameResultKind, ProtoGameContext]:
private val (colorTo, colorFrom) = ProtoMapperBase.colorConversions(ProtoColor.WHITE, ProtoColor.BLACK)
private val (pieceTypeTo, pieceTypeFrom) = ProtoMapperBase.pieceTypeConversions(
ProtoPieceType.PAWN,
ProtoPieceType.KNIGHT,
ProtoPieceType.BISHOP,
ProtoPieceType.ROOK,
ProtoPieceType.QUEEN,
ProtoPieceType.KING,
)
private val (moveKindTo, moveKindFrom) = ProtoMapperBase.moveKindConversions(
ProtoMoveKind.QUIET,
ProtoMoveKind.CAPTURE,
ProtoMoveKind.CASTLE_KINGSIDE,
ProtoMoveKind.CASTLE_QUEENSIDE,
ProtoMoveKind.EN_PASSANT,
ProtoMoveKind.PROMO_QUEEN,
ProtoMoveKind.PROMO_ROOK,
ProtoMoveKind.PROMO_BISHOP,
ProtoMoveKind.PROMO_KNIGHT,
)
def toProtoColor(c: Color): ProtoColor = c match
case Color.White => ProtoColor.WHITE
case Color.Black => ProtoColor.BLACK
override def toProtoColor(c: Color): ProtoColor = colorTo(c)
override def fromProtoColor(c: ProtoColor): Color = colorFrom(c)
override def toProtoPieceType(pt: PieceType): ProtoPieceType = pieceTypeTo(pt)
override def fromProtoPieceType(pt: ProtoPieceType): PieceType = pieceTypeFrom(pt)
override def toProtoMoveKind(mt: MoveType): ProtoMoveKind = moveKindTo(mt)
override def fromProtoMoveKind(k: ProtoMoveKind): MoveType = moveKindFrom(k)
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 =
override def toProtoMove(m: DomainMove): ProtoMove =
ProtoMove
.newBuilder()
.setFrom(m.from.toString)
@@ -65,42 +46,44 @@ object CoreProtoMapper:
.setMoveKind(toProtoMoveKind(m.moveType))
.build()
def fromProtoMove(m: ProtoMove): Option[DomainMove] =
override 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
override def toProtoSquarePiece(sq: Square, piece: Piece): ProtoSquarePiece =
ProtoSquarePiece
.newBuilder()
.setSquare(sq.toString)
.setPiece(
ProtoPiece
.newBuilder()
.setSquare(sq.toString)
.setPiece(
ProtoPiece
.newBuilder()
.setColor(toProtoColor(piece.color))
.setPieceType(toProtoPieceType(piece.pieceType))
.build(),
)
.build()
}
.setColor(toProtoColor(piece.color))
.setPieceType(toProtoPieceType(piece.pieceType))
.build(),
)
.build()
override def fromProtoSquarePiece(sp: ProtoSquarePiece): Option[(Square, Piece)] =
Square
.fromAlgebraic(sp.getSquare)
.map(_ -> Piece(fromProtoColor(sp.getPiece.getColor), fromProtoPieceType(sp.getPiece.getPieceType)))
override def toProtoBoard(board: Board): java.util.List[ProtoSquarePiece] =
board.pieces
.map { (sq, piece) => toProtoSquarePiece(sq, piece) }
.toSeq
.asJava
def fromProtoBoard(pieces: java.util.List[ProtoSquarePiece]): Board =
override 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))),
)
.flatMap(fromProtoSquarePiece)
.toMap,
)
def toProtoResultKind(r: Option[GameResult]): ProtoGameResultKind = r match
override 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
@@ -114,7 +97,7 @@ object CoreProtoMapper:
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
override 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))
@@ -129,20 +112,24 @@ object CoreProtoMapper:
case ProtoGameResultKind.DRAW_AGREEMENT => Some(GameResult.Draw(DrawReason.Agreement))
case _ => None
def toProtoGameContext(ctx: GameContext): ProtoGameContext =
override def toProtoCastlingRights(cr: DomainCastlingRights): ProtoCastlingRights =
ProtoCastlingRights
.newBuilder()
.setWhiteKingSide(cr.whiteKingSide)
.setWhiteQueenSide(cr.whiteQueenSide)
.setBlackKingSide(cr.blackKingSide)
.setBlackQueenSide(cr.blackQueenSide)
.build()
override def fromProtoCastlingRights(pcr: ProtoCastlingRights): DomainCastlingRights =
DomainCastlingRights(pcr.getWhiteKingSide, pcr.getWhiteQueenSide, pcr.getBlackKingSide, pcr.getBlackQueenSide)
override 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(),
)
.setCastlingRights(toProtoCastlingRights(ctx.castlingRights))
.setEnPassantSquare(ctx.enPassantSquare.map(_.toString).getOrElse(""))
.setHalfMoveClock(ctx.halfMoveClock)
.addAllMoves(ctx.moves.map(toProtoMove).asJava)
@@ -150,13 +137,11 @@ object CoreProtoMapper:
.addAllInitialBoard(toProtoBoard(ctx.initialBoard))
.build()
def fromProtoGameContext(p: ProtoGameContext): GameContext =
val cr = p.getCastlingRights
override def fromProtoGameContext(p: ProtoGameContext): GameContext =
GameContext(
board = fromProtoBoard(p.getBoardList),
turn = fromProtoColor(p.getTurn),
castlingRights =
DomainCastlingRights(cr.getWhiteKingSide, cr.getWhiteQueenSide, cr.getBlackKingSide, cr.getBlackQueenSide),
castlingRights = fromProtoCastlingRights(p.getCastlingRights),
enPassantSquare = Option(p.getEnPassantSquare).filter(_.nonEmpty).flatMap(Square.fromAlgebraic),
halfMoveClock = p.getHalfMoveClock,
moves = p.getMovesList.asScala.flatMap(fromProtoMove).toList,
@@ -1,7 +1,7 @@
package de.nowchess.chess.redis
import com.fasterxml.jackson.databind.ObjectMapper
import de.nowchess.api.dto.GameStateEventDto
import de.nowchess.api.dto.{GameStateEventDto, GameWritebackEventDto}
import de.nowchess.api.game.{CorrespondenceClockState, LiveClockState}
import de.nowchess.chess.grpc.IoGrpcClientWrapper
import de.nowchess.api.game.{DrawReason, GameResult, WinReason}
@@ -9,7 +9,7 @@ quarkus:
nowchess:
internal:
secret: ${INTERNAL_SECRET}
secret: 123abc
smallrye-openapi:
info-title: NowChess IO Service
info-version: 1.0.0
@@ -3,61 +3,42 @@ package de.nowchess.io.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.api.grpc.ProtoMapperBase
import de.nowchess.api.move.{Move as DomainMove, MoveType}
import de.nowchess.io.proto.*
import scala.jdk.CollectionConverters.*
object IoProtoMapper:
object IoProtoMapper extends ProtoMapperBase[ProtoColor, ProtoPieceType, ProtoMoveKind, ProtoMove, ProtoSquarePiece, java.util.List[ProtoSquarePiece], ProtoCastlingRights, ProtoGameResultKind, ProtoGameContext]:
private val (colorTo, colorFrom) = ProtoMapperBase.colorConversions(ProtoColor.WHITE, ProtoColor.BLACK)
private val (pieceTypeTo, pieceTypeFrom) = ProtoMapperBase.pieceTypeConversions(
ProtoPieceType.PAWN,
ProtoPieceType.KNIGHT,
ProtoPieceType.BISHOP,
ProtoPieceType.ROOK,
ProtoPieceType.QUEEN,
ProtoPieceType.KING,
)
private val (moveKindTo, moveKindFrom) = ProtoMapperBase.moveKindConversions(
ProtoMoveKind.QUIET,
ProtoMoveKind.CAPTURE,
ProtoMoveKind.CASTLE_KINGSIDE,
ProtoMoveKind.CASTLE_QUEENSIDE,
ProtoMoveKind.EN_PASSANT,
ProtoMoveKind.PROMO_QUEEN,
ProtoMoveKind.PROMO_ROOK,
ProtoMoveKind.PROMO_BISHOP,
ProtoMoveKind.PROMO_KNIGHT,
)
def toProtoColor(c: Color): ProtoColor = c match
case Color.White => ProtoColor.WHITE
case Color.Black => ProtoColor.BLACK
override def toProtoColor(c: Color): ProtoColor = colorTo(c)
override def fromProtoColor(c: ProtoColor): Color = colorFrom(c)
override def toProtoPieceType(pt: PieceType): ProtoPieceType = pieceTypeTo(pt)
override def fromProtoPieceType(pt: ProtoPieceType): PieceType = pieceTypeFrom(pt)
override def toProtoMoveKind(mt: MoveType): ProtoMoveKind = moveKindTo(mt)
override def fromProtoMoveKind(k: ProtoMoveKind): MoveType = moveKindFrom(k)
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 =
override def toProtoMove(m: DomainMove): ProtoMove =
ProtoMove
.newBuilder()
.setFrom(m.from.toString)
@@ -65,42 +46,44 @@ object IoProtoMapper:
.setMoveKind(toProtoMoveKind(m.moveType))
.build()
def fromProtoMove(m: ProtoMove): Option[DomainMove] =
override 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
override def toProtoSquarePiece(sq: Square, piece: Piece): ProtoSquarePiece =
ProtoSquarePiece
.newBuilder()
.setSquare(sq.toString)
.setPiece(
ProtoPiece
.newBuilder()
.setSquare(sq.toString)
.setPiece(
ProtoPiece
.newBuilder()
.setColor(toProtoColor(piece.color))
.setPieceType(toProtoPieceType(piece.pieceType))
.build(),
)
.build()
}
.setColor(toProtoColor(piece.color))
.setPieceType(toProtoPieceType(piece.pieceType))
.build(),
)
.build()
override def fromProtoSquarePiece(sp: ProtoSquarePiece): Option[(Square, Piece)] =
Square
.fromAlgebraic(sp.getSquare)
.map(_ -> Piece(fromProtoColor(sp.getPiece.getColor), fromProtoPieceType(sp.getPiece.getPieceType)))
override def toProtoBoard(board: Board): java.util.List[ProtoSquarePiece] =
board.pieces
.map { (sq, piece) => toProtoSquarePiece(sq, piece) }
.toSeq
.asJava
def fromProtoBoard(pieces: java.util.List[ProtoSquarePiece]): Board =
override 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))),
)
.flatMap(fromProtoSquarePiece)
.toMap,
)
def toProtoResultKind(r: Option[GameResult]): ProtoGameResultKind = r match
override 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
@@ -114,7 +97,7 @@ object IoProtoMapper:
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
override 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))
@@ -129,20 +112,24 @@ object IoProtoMapper:
case ProtoGameResultKind.DRAW_AGREEMENT => Some(GameResult.Draw(DrawReason.Agreement))
case _ => None
def toProtoGameContext(ctx: GameContext): ProtoGameContext =
override def toProtoCastlingRights(cr: DomainCastlingRights): ProtoCastlingRights =
ProtoCastlingRights
.newBuilder()
.setWhiteKingSide(cr.whiteKingSide)
.setWhiteQueenSide(cr.whiteQueenSide)
.setBlackKingSide(cr.blackKingSide)
.setBlackQueenSide(cr.blackQueenSide)
.build()
override def fromProtoCastlingRights(pcr: ProtoCastlingRights): DomainCastlingRights =
DomainCastlingRights(pcr.getWhiteKingSide, pcr.getWhiteQueenSide, pcr.getBlackKingSide, pcr.getBlackQueenSide)
override 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(),
)
.setCastlingRights(toProtoCastlingRights(ctx.castlingRights))
.setEnPassantSquare(ctx.enPassantSquare.map(_.toString).getOrElse(""))
.setHalfMoveClock(ctx.halfMoveClock)
.addAllMoves(ctx.moves.map(toProtoMove).asJava)
@@ -150,13 +137,11 @@ object IoProtoMapper:
.addAllInitialBoard(toProtoBoard(ctx.initialBoard))
.build()
def fromProtoGameContext(p: ProtoGameContext): GameContext =
val cr = p.getCastlingRights
override def fromProtoGameContext(p: ProtoGameContext): GameContext =
GameContext(
board = fromProtoBoard(p.getBoardList),
turn = fromProtoColor(p.getTurn),
castlingRights =
DomainCastlingRights(cr.getWhiteKingSide, cr.getWhiteQueenSide, cr.getBlackKingSide, cr.getBlackQueenSide),
castlingRights = fromProtoCastlingRights(p.getCastlingRights),
enPassantSquare = Option(p.getEnPassantSquare).filter(_.nonEmpty).flatMap(Square.fromAlgebraic),
halfMoveClock = p.getHalfMoveClock,
moves = p.getMovesList.asScala.flatMap(fromProtoMove).toList,
@@ -9,4 +9,4 @@ quarkus:
nowchess:
internal:
secret: ${INTERNAL_SECRET}
secret: 123abc
@@ -2,61 +2,42 @@ package de.nowchess.rules.grpc
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.api.grpc.ProtoMapperBase
import de.nowchess.api.move.{Move as DomainMove, MoveType}
import de.nowchess.rules.proto.*
import scala.jdk.CollectionConverters.*
object ProtoMapper:
object ProtoMapper extends ProtoMapperBase[ProtoColor, ProtoPieceType, ProtoMoveKind, ProtoMove, ProtoSquarePiece, java.util.List[ProtoSquarePiece], ProtoCastlingRights, ProtoGameResultKind, ProtoGameContext]:
private val (colorTo, colorFrom) = ProtoMapperBase.colorConversions(ProtoColor.WHITE, ProtoColor.BLACK)
private val (pieceTypeTo, pieceTypeFrom) = ProtoMapperBase.pieceTypeConversions(
ProtoPieceType.PAWN,
ProtoPieceType.KNIGHT,
ProtoPieceType.BISHOP,
ProtoPieceType.ROOK,
ProtoPieceType.QUEEN,
ProtoPieceType.KING,
)
private val (moveKindTo, moveKindFrom) = ProtoMapperBase.moveKindConversions(
ProtoMoveKind.QUIET,
ProtoMoveKind.CAPTURE,
ProtoMoveKind.CASTLE_KINGSIDE,
ProtoMoveKind.CASTLE_QUEENSIDE,
ProtoMoveKind.EN_PASSANT,
ProtoMoveKind.PROMO_QUEEN,
ProtoMoveKind.PROMO_ROOK,
ProtoMoveKind.PROMO_BISHOP,
ProtoMoveKind.PROMO_KNIGHT,
)
def toProtoColor(c: Color): ProtoColor = c match
case Color.White => ProtoColor.WHITE
case Color.Black => ProtoColor.BLACK
override def toProtoColor(c: Color): ProtoColor = colorTo(c)
override def fromProtoColor(c: ProtoColor): Color = colorFrom(c)
override def toProtoPieceType(pt: PieceType): ProtoPieceType = pieceTypeTo(pt)
override def fromProtoPieceType(pt: ProtoPieceType): PieceType = pieceTypeFrom(pt)
override def toProtoMoveKind(mt: MoveType): ProtoMoveKind = moveKindTo(mt)
override def fromProtoMoveKind(k: ProtoMoveKind): MoveType = moveKindFrom(k)
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 =
override def toProtoMove(m: DomainMove): ProtoMove =
ProtoMove
.newBuilder()
.setFrom(m.from.toString)
@@ -64,42 +45,44 @@ object ProtoMapper:
.setMoveKind(toProtoMoveKind(m.moveType))
.build()
def fromProtoMove(m: ProtoMove): Option[DomainMove] =
override 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
override def toProtoSquarePiece(sq: Square, piece: Piece): ProtoSquarePiece =
ProtoSquarePiece
.newBuilder()
.setSquare(sq.toString)
.setPiece(
ProtoPiece
.newBuilder()
.setSquare(sq.toString)
.setPiece(
ProtoPiece
.newBuilder()
.setColor(toProtoColor(piece.color))
.setPieceType(toProtoPieceType(piece.pieceType))
.build(),
)
.build()
}
.setColor(toProtoColor(piece.color))
.setPieceType(toProtoPieceType(piece.pieceType))
.build(),
)
.build()
override def fromProtoSquarePiece(sp: ProtoSquarePiece): Option[(Square, Piece)] =
Square
.fromAlgebraic(sp.getSquare)
.map(_ -> Piece(fromProtoColor(sp.getPiece.getColor), fromProtoPieceType(sp.getPiece.getPieceType)))
override def toProtoBoard(board: Board): java.util.List[ProtoSquarePiece] =
board.pieces
.map { (sq, piece) => toProtoSquarePiece(sq, piece) }
.toSeq
.asJava
def fromProtoBoard(pieces: java.util.List[ProtoSquarePiece]): Board =
override 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))),
)
.flatMap(fromProtoSquarePiece)
.toMap,
)
def toProtoResultKind(r: Option[GameResult]): ProtoGameResultKind = r match
override 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
@@ -113,7 +96,7 @@ object ProtoMapper:
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
override 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))
@@ -128,20 +111,24 @@ object ProtoMapper:
case ProtoGameResultKind.DRAW_AGREEMENT => Some(GameResult.Draw(DrawReason.Agreement))
case _ => None
def toProtoGameContext(ctx: GameContext): ProtoGameContext =
override def toProtoCastlingRights(cr: DomainCastlingRights): ProtoCastlingRights =
ProtoCastlingRights
.newBuilder()
.setWhiteKingSide(cr.whiteKingSide)
.setWhiteQueenSide(cr.whiteQueenSide)
.setBlackKingSide(cr.blackKingSide)
.setBlackQueenSide(cr.blackQueenSide)
.build()
override def fromProtoCastlingRights(pcr: ProtoCastlingRights): DomainCastlingRights =
DomainCastlingRights(pcr.getWhiteKingSide, pcr.getWhiteQueenSide, pcr.getBlackKingSide, pcr.getBlackQueenSide)
override 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(),
)
.setCastlingRights(toProtoCastlingRights(ctx.castlingRights))
.setEnPassantSquare(ctx.enPassantSquare.map(_.toString).getOrElse(""))
.setHalfMoveClock(ctx.halfMoveClock)
.addAllMoves(ctx.moves.map(toProtoMove).asJava)
@@ -149,13 +136,11 @@ object ProtoMapper:
.addAllInitialBoard(toProtoBoard(ctx.initialBoard))
.build()
def fromProtoGameContext(p: ProtoGameContext): GameContext =
val cr = p.getCastlingRights
override def fromProtoGameContext(p: ProtoGameContext): GameContext =
GameContext(
board = fromProtoBoard(p.getBoardList),
turn = fromProtoColor(p.getTurn),
castlingRights =
DomainCastlingRights(cr.getWhiteKingSide, cr.getWhiteQueenSide, cr.getBlackKingSide, cr.getBlackQueenSide),
castlingRights = fromProtoCastlingRights(p.getCastlingRights),
enPassantSquare = Option(p.getEnPassantSquare).filter(_.nonEmpty).flatMap(Square.fromAlgebraic),
halfMoveClock = p.getHalfMoveClock,
moves = p.getMovesList.asScala.flatMap(fromProtoMove).toList,
@@ -12,7 +12,7 @@ class InternalGrpcSecretClientInterceptor extends ClientInterceptor:
private val secretKey = Metadata.Key.of("x-internal-secret", Metadata.ASCII_STRING_MARSHALLER)
@ConfigProperty(name = "nowchess.internal.secret")
@ConfigProperty(name = "nowchess.internal.secret", defaultValue = "")
// scalafix:off DisableSyntax.var
var secret: String = uninitialized
// scalafix:on DisableSyntax.var
@@ -8,7 +8,7 @@ import scala.compiletime.uninitialized
@ApplicationScoped
class InternalSecretClientFilter extends ClientRequestFilter:
@ConfigProperty(name = "nowchess.internal.secret")
@ConfigProperty(name = "nowchess.internal.secret", defaultValue = "")
// scalafix:off DisableSyntax.var
var secret: String = uninitialized
// scalafix:on DisableSyntax.var
+1
View File
@@ -31,6 +31,7 @@ val quarkusPlatformArtifactId: String by project
val quarkusPlatformVersion: String by project
dependencies {
implementation(project(":modules:api"))
runtimeOnly("io.quarkus:quarkus-jdbc-h2")
@@ -1,7 +1,7 @@
package de.nowchess.store.config
import de.nowchess.api.dto.{GameWritebackEventDto}
import de.nowchess.store.domain.GameRecord
import de.nowchess.store.redis.GameWritebackEventDto
import io.quarkus.runtime.annotations.RegisterForReflection
@RegisterForReflection(
@@ -86,6 +86,9 @@ class GameRecord extends PanacheEntityBase:
@Column
var pendingDrawOffer: String = uninitialized
@Column
var pendingTakebackOffer: String = uninitialized
// Game result
@Column
var result: String = uninitialized
@@ -1,26 +0,0 @@
package de.nowchess.store.redis
case class GameWritebackEventDto(
gameId: String,
fen: String,
pgn: String,
moveCount: Int,
whiteId: String,
whiteName: String,
blackId: String,
blackName: String,
mode: String,
resigned: Boolean,
limitSeconds: Option[Int],
incrementSeconds: Option[Int],
daysPerMove: Option[Int],
whiteRemainingMs: Option[Long],
blackRemainingMs: Option[Long],
incrementMs: Option[Long],
clockLastTickAt: Option[Long],
clockMoveDeadline: Option[Long],
clockActiveColor: Option[String],
pendingDrawOffer: Option[String],
result: Option[String] = None,
terminationReason: Option[String] = None,
)
@@ -1,6 +1,7 @@
package de.nowchess.store.redis
import com.fasterxml.jackson.databind.ObjectMapper
import de.nowchess.api.dto.GameWritebackEventDto
import de.nowchess.store.service.GameWritebackService
import io.quarkus.redis.datasource.RedisDataSource
import jakarta.annotation.PostConstruct
@@ -25,7 +25,7 @@ class GameRecordRepository:
def findByPlayerId(playerId: String, offset: Int, limit: Int): List[GameRecord] =
em.createQuery(
"SELECT g FROM GameRecord g WHERE g.whiteId = :id OR g.blackId = :id ORDER BY g.updatedAt DESC",
"SELECT g FROM GameRecord g WHERE g.whiteId = :id OR g.blackId = :id AND g.result != null ORDER BY g.updatedAt DESC",
classOf[GameRecord],
).setParameter("id", playerId)
.setFirstResult(offset)
@@ -1,11 +1,12 @@
package de.nowchess.store.service
import de.nowchess.api.dto.GameWritebackEventDto
import de.nowchess.store.domain.GameRecord
import de.nowchess.store.redis.GameWritebackEventDto
import de.nowchess.store.repository.GameRecordRepository
import jakarta.enterprise.context.ApplicationScoped
import jakarta.inject.Inject
import jakarta.transaction.Transactional
import scala.compiletime.uninitialized
import java.time.Instant
@@ -66,6 +67,7 @@ class GameWritebackService:
r.clockMoveDeadline = event.clockMoveDeadline.map(java.lang.Long.valueOf).orNull
r.clockActiveColor = event.clockActiveColor.orNull
r.pendingDrawOffer = event.pendingDrawOffer.orNull
r.pendingTakebackOffer = event.pendingTakebackRequest.orNull
r.result = event.result.orNull
r.terminationReason = event.terminationReason.orNull
r.updatedAt = Instant.now()