packages feed

chessica-0.1.0.0: src/Chess/Rulebook/Standard/Threat.hs

{-# LANGUAGE GADTs           #-}
{-# LANGUAGE OverloadedLists #-}
-----------------------------------------------------------------------------
-- |
-- Module      : Chess.Rulebook.Standard.Threat
-- Copyright   : (c) Michael Szvetits, 2023
-- License     : BSD-3-Clause (see the file LICENSE)
-- Maintainer  : typedbyte@qualified.name
-- Stability   : stable
-- Portability : portable
--
-- Implementation of chess piece threats, according to the standard rulebook.
-----------------------------------------------------------------------------
module Chess.Rulebook.Standard.Threat
  ( threats
  , threatCommands
  ) where

-- base
import Prelude hiding (lookup)

import Chess.Board             (Board, lookup)
import Chess.Board.Direction   (Direction(..), diagonals, jumps, orthogonals, principals)
import Chess.Board.PlacedPiece (PlacedPiece(..))
import Chess.Board.Position    (Position, offset)
import Chess.Color             (Color(White))
import Chess.Game.Command      (Command, move, destroy)
import Chess.Piece             (PieceType(..))
import Chess.Some              (Some(Some))

-- | Determines the positions threatened by a given chess piece.
threats :: PlacedPiece t -> Board -> [Position]
threats piece board =
  case piece.type' of
    Pawn   -> threat pawnDirections 1
    Knight -> threat jumps 1
    Bishop -> threat diagonals maxBound
    Rook   -> threat orthogonals maxBound
    Queen  -> threat principals maxBound
    King   -> threat principals 1
  where
    threat = reach piece board
    pawnDelta = if piece.color == White then 1 else (-1)
    pawnDirections = filter (\d -> d.rowDelta == pawnDelta) diagonals

-- | Determines all possible threat-based movements (including captures) for a
-- given chess piece.
threatCommands :: PlacedPiece t -> Board -> [Command]
threatCommands piece board = do
  target <- threats piece board
  case lookup target board of
    Nothing ->
      pure (move target piece)
    Just (Some enemy) ->
      pure [destroy enemy, move target piece]

-- | Starting at a specified position, provides a list of all the positions
--   that are reachable by a chess piece in the given directions.
reach
  :: PlacedPiece t -- ^ The analyzed chess piece.
  -> Board         -- ^ The current chess board state.
  -> [Direction]   -- ^ The directions to iterate, beginning by the starting position.
  -> Int           -- ^ The maximum count of steps to take in a specific direction.
  -> [Position]    -- ^ A list of all the reachable positions.
reach piece board directions maxSteps = do
  direction <- directions
  takeUntilPiece
    $ take maxSteps
    $ collect (offset direction) piece.position
  where
    -- takeUntilPiece iterates until a friend or enemy piece is hit
    takeUntilPiece [] = []
    takeUntilPiece (p:ps) =
      case lookup p board of
        Nothing -> p : takeUntilPiece ps
        Just (Some other)
          | piece.color == other.color -> []
          | otherwise                  -> [p]
    -- collect is catMaybes, but only until the first Nothing
    collect f start = go (f start)
      where
        go (Just p) = p : go (f p)
        go Nothing  = []