packages feed

chessica-0.1.0.0: src/Chess/Game.hs

{-# LANGUAGE OverloadedLists #-}
-----------------------------------------------------------------------------
-- |
-- Module      : Chess.Game
-- Copyright   : (c) Michael Szvetits, 2023
-- License     : BSD-3-Clause (see the file LICENSE)
-- Maintainer  : typedbyte@qualified.name
-- Stability   : stable
-- Portability : portable
--
-- Types and functions to create, manipulate and query chess game states.
-----------------------------------------------------------------------------
module Chess.Game
  ( -- * Representing Games
    Game(..)
  , Update(..)
    -- * Manipulating Games
  , execute
    -- * Querying Games
  , history
  , spawnCommands
    -- * Re-exports
  , module Chess.Game.Command
  , module Chess.Game.Status
  ) where

import Chess.Board             (Board, pieces, place, remove)
import Chess.Board.PlacedPiece (PlacedPiece(..))
import Chess.Exception         (ChessException)
import Chess.Game.Command
import Chess.Game.Status
import Chess.Player            (Player)
import Chess.Some              (Some(Some))

-- | Represents an update of a chess game state.
data Update = Update
  { game :: Game
    -- ^ Represents the chess game state before or after executing the corresponding command,
    -- depending on the context.
  , command :: Command
    -- ^ Represents the command that is involved in the game state update.
  }

-- | Represents a chess game state.
data Game = Game
  { board :: Board
    -- ^ The state of the chess board.
  , activePlayer :: Player
    -- ^ The player who is currently playing.
  , passivePlayer :: Player
    -- ^ The player who is currently waiting.
  , lastUpdate :: Maybe Update
    -- ^ The last update which led to this game state.
  }

-- | Executes a command on a game state in order to obtain a new game state.
execute :: Command -> Game -> Either ChessException Game
execute command game =
  case command of
    EndTurn ->
      pure
        game
          { activePlayer  = game.passivePlayer
          , passivePlayer = game.activePlayer
          }
    Move dst (Some placed) -> do
      tempBoard <- remove placed game.board
      newBoard <- place dst placed.piece tempBoard
      pure game { board = newBoard }
    Destroy (Some piece) -> do
      newBoard <- remove piece game.board
      pure game { board = newBoard }
    Spawn position (Some piece) -> do
      newBoard <- place position piece game.board
      pure game { board = newBoard }
    Sequence cmd1 cmd2 ->
      execute cmd1 game >>= execute cmd2
    Promote piece@(Some old) new ->
      execute [Destroy piece, Spawn old.position new] game
    Atomic cmd -> do
      newGame <- execute cmd game
      pure newGame { lastUpdate = Just (Update game command) }

-- | Gets the history of updates that led to the specified game state.
-- The most recent update is at the head of the list.
history :: Game -> [Update]
history game =
  case game.lastUpdate of
    Just update -> update : history update.game
    Nothing     -> []

-- | Gets a list of 'Spawn' commands which can be used to reconstruct
-- the board of the specified game state.
spawnCommands :: Game -> [Command]
spawnCommands game =
  flip fmap (pieces game.board) $ \(Some placedPiece) ->
    spawn placedPiece.position placedPiece.piece