module Chess.Internal.Move (GameState(..), Move(..), CastlingType(..),
MoveError(..), isRightPlayerMove,
isMoveError, initialState,
isCorrectStartPiece, areCoordinatesValid,
generateAllRookMoves, iterateMovementSquares,
iterateDirection, generateAllBishopMoves,
generateAllQueenMoves, generateAllKnightMoves,
generateAllKingMoves, generateAllPawnMoves,
generateAllPotentialMoves, boardAfterMove,
generateAllMoves) where
import Chess.Internal.Piece
import Chess.Internal.Board
import Data.Maybe
data GameState = State {
stateBoard :: Board
, currentPlayer :: Color
, whiteCastlingsPossible :: [CastlingType]
, blackCastlingsPossible :: [CastlingType]
, enPassantSquare :: Maybe Coordinates
, halfmoveClock :: Integer
, moveNumber :: Integer
} deriving (Eq, Show)
data Move = Movement Piece Coordinates Coordinates
| Capture Piece Coordinates Coordinates
| Castling Color CastlingType
| EnPassant Piece Coordinates Coordinates
| Promotion Piece Coordinates Coordinates PieceType
| PawnDoubleMove Piece Coordinates Coordinates
deriving (Show, Eq)
data CastlingType = Short | Long
deriving (Show, Eq, Ord)
data MoveError = WrongPlayer | WrongPiece | InvalidMove | InCheck | InvalidCoordinates
deriving (Show, Eq)
initialState :: GameState
initialState = State initialBoard White [Short, Long] [Short, Long] Nothing 0 1
generateAllMoves :: GameState -> [Move]
generateAllMoves game = filter isLegalMove $ generateAllPotentialMoves game
where isLegalMove move = isNothing (isMoveError game move)
isInCheckAfterMove :: GameState -> Move -> Bool
isInCheckAfterMove game move = case newBoard of
Nothing -> False
Just b -> isCheck b (currentPlayer game)
where board = stateBoard game
newBoard = boardAfterMove board move
isCorrectPiece :: GameState -> Move -> Bool
isCorrectPiece (State board _ _ _ _ _ _) (Movement piece start _) = isCorrectStartPiece board piece start
isCorrectPiece (State board _ _ _ _ _ _) (Capture piece start _) = isCorrectStartPiece board piece start
isCorrectPiece (State board _ _ _ _ _ _) (EnPassant piece start _) = isCorrectStartPiece board piece start
isCorrectPiece (State board _ _ _ _ _ _) (Promotion piece start _ _) = isCorrectStartPiece board piece start
isCorrectPiece (State board _ _ _ _ _ _) (PawnDoubleMove piece start _) = isCorrectStartPiece board piece start
isCorrectPiece _ (Castling _ _) = True
isMoveError :: GameState -> Move -> Maybe MoveError
isMoveError game move | not (isCorrectPlayer game move) = Just WrongPlayer
| not (isCorrectPiece game move) = Just WrongPiece
| not (isCorrectBoardMove game move) = Just InvalidCoordinates
| isInCheckAfterMove game move = Just InCheck
| move `notElem` generateAllPotentialMoves game = Just InvalidMove
| otherwise = Nothing
isCorrectBoardMove :: GameState -> Move -> Bool
isCorrectBoardMove game move = isJust (boardAfterMove (stateBoard game) move)
isCorrectStartPiece :: Board -> Piece -> Coordinates -> Bool
isCorrectStartPiece board (Piece color pieceType) coordinates
= case boardPiece of
Nothing -> False
Just (Piece color' pieceType') -> color == color' && pieceType == pieceType'
where boardPiece = getPiece board coordinates
isCorrectPlayer :: GameState -> Move -> Bool
isCorrectPlayer game = isRightPlayerMove (currentPlayer game)
isRightPlayerMove :: Color -> Move -> Bool
isRightPlayerMove player (Movement (Piece color _) _ _) = player == color
isRightPlayerMove player (Capture (Piece color _) _ _) = player == color
isRightPlayerMove player (Castling color _) = player == color
isRightPlayerMove player (EnPassant (Piece color _) _ _) = player == color
isRightPlayerMove player (Promotion (Piece color _) _ _ _) = player == color
isRightPlayerMove player (PawnDoubleMove (Piece color _) _ _) = player == color
areCoordinatesValid :: Coordinates -> Coordinates -> Maybe MoveError
areCoordinatesValid start end | start == end = Just InvalidCoordinates
| not (isInsideBoard start) || not (isInsideBoard end) = Just InvalidCoordinates
| otherwise = Nothing
generateAllRookMoves :: GameState -> Coordinates -> [Move]
generateAllRookMoves game coords = patternMoves game coords rookPattern
generateAllBishopMoves :: GameState -> Coordinates -> [Move]
generateAllBishopMoves game coords = patternMoves game coords bishopPattern
generateAllQueenMoves :: GameState -> Coordinates -> [Move]
generateAllQueenMoves game coords = patternMoves game coords queenPattern
generateAllKnightMoves :: GameState -> Coordinates -> [Move]
generateAllKnightMoves game coords = map (Movement piece coords) emptySquares
++ map (Capture piece coords) opponentSquares
where squares = filter isInsideBoard [sumSquares coords jump | jump <- knightPattern]
board = stateBoard game
emptySquares = filter (isEmpty board) squares
piece@(Piece player _) = fromJust $ getPiece board coords
opponentSquares = filter (\square -> isOpponentSquare board square player) squares
generateAllKingMoves :: GameState -> Coordinates -> [Move]
generateAllKingMoves game coords = map (Movement piece coords) moveSquares
++ map (Capture piece coords) captureSquares
++ kingCastlingMoves game coords
where board = stateBoard game
piece = fromJust $ getPiece board coords
moveSquares = kingMoveSquares game coords
captureSquares = kingCaptureSquares game coords
generateAllPawnMoves :: GameState -> Coordinates -> [Move]
generateAllPawnMoves game coords@(row, _) = move ++ doubleMove ++ captures ++ promotions ++ enpassant
where board = stateBoard game
(Piece player _) = fromJust $ getPiece board coords
isOnStartRow = case player of
White -> row == 6
Black -> row == 1
isNextToPromotionRow = case player of
White -> row == 1
Black -> row == 6
moveDirection = case player of
White -> 1
Black -> 1
moveSquare = sumSquares coords (moveDirection, 0)
doubleMoveSquare = sumSquares coords (moveDirection * 2, 0)
captureSquares = map (sumSquares coords) [(moveDirection, 1), (moveDirection, 1)]
move = [Movement (Piece player Pawn) coords moveSquare | isEmpty board moveSquare && not isNextToPromotionRow]
doubleMove = [PawnDoubleMove (Piece player Pawn) coords doubleMoveSquare | isEmpty board moveSquare && isEmpty board doubleMoveSquare && isOnStartRow]
capture square = [Capture (Piece player Pawn) coords square | isOpponentSquare board square player && not isNextToPromotionRow]
captures = concatMap capture captureSquares
promotionCapture square = if isOpponentSquare board square player
then map (Promotion (Piece player Pawn) coords square) [Rook, Bishop, Knight, Queen]
else []
promotionMove square = if isEmpty board square
then map (Promotion (Piece player Pawn) coords square) [Rook, Bishop, Knight, Queen]
else []
promotions = if isNextToPromotionRow
then concatMap promotionCapture captureSquares ++ promotionMove moveSquare
else []
epSquare = enPassantSquare game
enpassant = case epSquare of
Just square -> [EnPassant (Piece player Pawn) coords square | square `elem` captureSquares]
Nothing -> []
kingMoveSquares :: GameState -> Coordinates -> [Coordinates]
kingMoveSquares game start = emptySquares
where allSquares = map (sumSquares start) queenPattern
board = stateBoard game
emptySquares = filter (isEmpty board) $ filter isInsideBoard allSquares
kingCaptureSquares :: GameState -> Coordinates -> [Coordinates]
kingCaptureSquares game start = opponentSquares
where allSquares = map (sumSquares start) queenPattern
board = stateBoard game
(Piece player _) = fromJust $ getPiece board start
opponentSquares = filter (\square -> isOpponentSquare board square player) $ filter isInsideBoard allSquares
kingCastlingMoves :: GameState -> Coordinates -> [Move]
kingCastlingMoves game coords = map (Castling player) $ filter (isCastlingPossible game player) [Long, Short]
where board = stateBoard game
(Piece player _) = fromJust $ getPiece board coords
isCastlingPossible :: GameState -> Color -> CastlingType -> Bool
isCastlingPossible game player castling = castling `elem` possibleCastlings player && all (isEmpty board) squares && traverseSquaresNotThreatened
where squares = castlingSquares player castling
board = stateBoard game
possibleCastlings White = whiteCastlingsPossible game
possibleCastlings Black = blackCastlingsPossible game
kingSquare = if player == White then (7, 4) else (0, 4)
traverseSquaresNotThreatened = not $ any (isSquareThreatened board (opponent player)) (kingSquare : squares)
castlingSquares :: Color -> CastlingType -> [Coordinates]
castlingSquares White Long = [(7, 1), (7, 2), (7, 3)]
castlingSquares White Short = [(7, 5), (7, 6)]
castlingSquares Black Long = [(0, 1), (0, 2), (0, 3)]
castlingSquares Black Short = [(0, 5), (0, 6)]
patternMoves :: GameState -> Coordinates -> [(Int, Int)] -> [Move]
patternMoves game start pattern
= concat [movementsInDirection game start dir | dir <- pattern]
++ concat [capturesInDirection game start dir | dir <- pattern]
movementsInDirection :: GameState -> Coordinates -> (Int, Int) -> [Move]
movementsInDirection game start direction = map (Movement piece start) squares
where piece = fromJust $ getPiece (stateBoard game) start
squares = iterateMovementSquares game start direction
capturesInDirection :: GameState -> Coordinates -> (Int, Int) -> [Move]
capturesInDirection game start direction = map (Capture piece start) squares
where piece = fromJust $ getPiece (stateBoard game) start
squares = iterateCaptureSquares game start direction
iterateMovementSquares :: GameState -> Coordinates -> (Int, Int) -> [Coordinates]
iterateMovementSquares = iterateDirection isEmptySquare
where isEmptySquare game' = isEmpty (stateBoard game')
iterateCaptureSquares :: GameState -> Coordinates -> (Int, Int) -> [Coordinates]
iterateCaptureSquares game start direction = case squaresNotEmpty of
[] -> []
(first:_) -> [first | isOpponentSquare board first player]
where squares = iterateDirectionInsideBoard start direction
board = stateBoard game
(Piece player _) = fromJust $ getPiece board start
squaresNotEmpty = dropWhile (isEmpty board) squares
iterateDirection :: (GameState -> Coordinates -> Bool) -> GameState -> Coordinates -> (Int, Int) -> [Coordinates]
iterateDirection condition game start direction = takeWhile (condition game) squares
where squares = iterateDirectionInsideBoard start direction
generateAllPotentialMoves :: GameState -> [Move]
generateAllPotentialMoves game = concatMap (generateSquareMoves game) squares
where player = currentPlayer game
board = stateBoard game
squares = getSquaresWithOwner board player
generateSquareMoves :: GameState -> Coordinates -> [Move]
generateSquareMoves game coordinates = case getPiece board coordinates of
Nothing -> []
Just (Piece _ Pawn) -> generateAllPawnMoves game coordinates
Just (Piece _ Rook) -> generateAllRookMoves game coordinates
Just (Piece _ Bishop) -> generateAllBishopMoves game coordinates
Just (Piece _ Queen) -> generateAllQueenMoves game coordinates
Just (Piece _ King) -> generateAllKingMoves game coordinates
Just (Piece _ Knight) -> generateAllKnightMoves game coordinates
where board = stateBoard game
boardAfterMove :: Board -> Move -> Maybe Board
boardAfterMove board (Movement _ from to) = movePiece from to board
boardAfterMove board (Capture _ from to) = movePiece from to board
boardAfterMove board (PawnDoubleMove _ from to) = movePiece from to board
boardAfterMove board (Castling White Long) = movePiece (7, 0) (7, 3) board >>= movePiece (7, 4) (7, 2)
boardAfterMove board (Castling White Short) = movePiece (7, 7) (7, 5) board >>= movePiece (7, 4) (7, 6)
boardAfterMove board (Castling Black Long) = movePiece (0, 0) (0, 3) board >>= movePiece (0, 4) (0, 2)
boardAfterMove board (Castling Black Short) = movePiece (0, 7) (0, 5) board >>= movePiece (0, 4) (0, 6)
boardAfterMove board (EnPassant (Piece player _) from to@(row, col)) = movePiece from to (removePiece board (epSquare player))
where epSquare White = (row + 1, col)
epSquare Black = (row 1, col)
boardAfterMove board (Promotion (Piece player _) from to promotiontype) =
case movePiece from to board of
Just newboard -> Just $ addPiece newboard to (Piece player promotiontype)
Nothing -> Nothing