@@ -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