@@ -1,119 +0,0 @@
|
||||
package de.nowchess.chess.monad
|
||||
|
||||
import de.nowchess.api.board.{Board, Color, Piece, Square}
|
||||
import de.nowchess.chess.logic.{GameHistory, GameRules, MoveValidator}
|
||||
import de.nowchess.chess.monad.Validated
|
||||
|
||||
/** Example usage of the Validated monad for chess move validation.
|
||||
*
|
||||
* Instead of returning early on the first error (like current code),
|
||||
* this accumulates ALL validation errors and reports them together.
|
||||
*
|
||||
* Benefits:
|
||||
* - Better user feedback: "The square is empty AND it's not your turn"
|
||||
* - Composable validations
|
||||
* - Type-safe error handling
|
||||
*/
|
||||
object MoveValidationExample:
|
||||
|
||||
/** Validation error types for chess moves. */
|
||||
enum ValidationError:
|
||||
case EmptySquare(square: Square)
|
||||
case WrongColor(piece: Piece, expectedColor: Color)
|
||||
case IllegalMove(from: Square, to: Square, piece: Piece)
|
||||
case MoveLeavesKingInCheck(color: Color)
|
||||
case InvalidSquare(input: String)
|
||||
|
||||
def message: String = this match
|
||||
case EmptySquare(sq) => s"No piece on square $sq"
|
||||
case WrongColor(piece, expected) => s"Wrong color: $piece belongs to ${piece.color}, but it's ${expected}'s turn"
|
||||
case IllegalMove(from, to, piece) => s"Illegal move: ${piece.pieceType} cannot move from $from to $to"
|
||||
case MoveLeavesKingInCheck(color) => s"This move would leave ${color}'s king in check"
|
||||
case InvalidSquare(input) => s"Invalid square notation: $input"
|
||||
|
||||
import ValidationError.*
|
||||
|
||||
/** Validate that a piece exists at the given square. */
|
||||
def validatePieceExists(board: Board, square: Square): Validated[ValidationError, Piece] =
|
||||
Validated.fromOption(
|
||||
board.pieceAt(square),
|
||||
EmptySquare(square)
|
||||
)
|
||||
|
||||
/** Validate that the piece belongs to the current player. */
|
||||
def validatePieceColor(piece: Piece, expectedColor: Color): Validated[ValidationError, Piece] =
|
||||
Validated.cond(
|
||||
piece.color == expectedColor,
|
||||
piece,
|
||||
WrongColor(piece, expectedColor)
|
||||
)
|
||||
|
||||
/** Validate that the move is geometrically legal. */
|
||||
def validateMoveLegality(board: Board, from: Square, to: Square, piece: Piece): Validated[ValidationError, Unit] =
|
||||
Validated.cond(
|
||||
MoveValidator.isLegal(board, from, to),
|
||||
(),
|
||||
IllegalMove(from, to, piece)
|
||||
)
|
||||
|
||||
/** Validate that the move doesn't leave the king in check. */
|
||||
def validateNoCheck(board: Board, history: GameHistory, from: Square, to: Square, color: Color): Validated[ValidationError, Unit] =
|
||||
val (newBoard, _) = board.withMove(from, to)
|
||||
Validated.cond(
|
||||
!GameRules.isInCheck(newBoard, color),
|
||||
(),
|
||||
MoveLeavesKingInCheck(color)
|
||||
)
|
||||
|
||||
/** EXAMPLE 1: Chain validations sequentially (monadic - stops on first error). */
|
||||
def validateMoveSequential(
|
||||
board: Board,
|
||||
history: GameHistory,
|
||||
from: Square,
|
||||
to: Square,
|
||||
turn: Color
|
||||
): Validated[ValidationError, Unit] =
|
||||
for
|
||||
piece <- validatePieceExists(board, from)
|
||||
_ <- validatePieceColor(piece, turn)
|
||||
_ <- validateMoveLegality(board, from, to, piece)
|
||||
_ <- validateNoCheck(board, history, from, to, turn)
|
||||
yield ()
|
||||
|
||||
/** EXAMPLE 2: Accumulate ALL errors at once (applicative). */
|
||||
def validateMoveParallel(
|
||||
board: Board,
|
||||
history: GameHistory,
|
||||
from: Square,
|
||||
to: Square,
|
||||
turn: Color
|
||||
): Validated[ValidationError, Unit] =
|
||||
val pieceCheck = validatePieceExists(board, from)
|
||||
val colorCheck = pieceCheck.flatMap(p => validatePieceColor(p, turn))
|
||||
val legalCheck = pieceCheck.flatMap(p => validateMoveLegality(board, from, to, p))
|
||||
val checkCheck = validateNoCheck(board, history, from, to, turn)
|
||||
|
||||
// Combine all validations - this will accumulate ALL errors
|
||||
Validated.validateAll(colorCheck, legalCheck, checkCheck).map(_ => ())
|
||||
|
||||
/** EXAMPLE 3: Use the validation in practice. */
|
||||
def exampleUsage(board: Board, history: GameHistory, from: Square, to: Square, turn: Color): String =
|
||||
validateMoveParallel(board, history, from, to, turn) match
|
||||
case Validated.Valid(_) =>
|
||||
"Move is valid!"
|
||||
|
||||
case Validated.Invalid(errors) =>
|
||||
val errorMessages = errors.map(_.message).mkString("\n- ")
|
||||
s"Move is invalid for the following reasons:\n- $errorMessages"
|
||||
|
||||
/** EXAMPLE 4: Validate multiple moves at once. */
|
||||
def validateMultipleMoves(
|
||||
board: Board,
|
||||
history: GameHistory,
|
||||
moves: List[(Square, Square)],
|
||||
turn: Color
|
||||
): Validated[ValidationError, List[Unit]] =
|
||||
val validations = moves.map { case (from, to) =>
|
||||
validateMoveSequential(board, history, from, to, turn)
|
||||
}
|
||||
Validated.validateAll(validations*)
|
||||
Reference in New Issue
Block a user