packages feed

halma-telegram-bot-0.1.0.0: src/Game/Halma/TelegramBot/Model.hs

module Game.Halma.TelegramBot.Model
  ( module Game.Halma.TelegramBot.Model.MoveCmd
  , module Game.Halma.TelegramBot.Model.Types
  , AfterMove (..)
  , doMove
  , undoLastMove
  , newMatch
  , newRound
  , initialHalmaChat
  , newRound
  ) where

import Game.Halma.Board
import Game.Halma.Configuration
import Game.Halma.Rules
import Game.Halma.TelegramBot.Model.Types
import Game.Halma.TelegramBot.Model.MoveCmd
import Game.TurnCounter

import Data.Foldable (toList)
import Data.Maybe (fromMaybe)
import qualified Data.Map as M

initialHalmaChat :: ChatId -> HalmaChat
initialHalmaChat chatId =
  HalmaChat
    { hcId = chatId
    , hcLocale = En
    , hcMatchState = NoMatch
    }

initialHalmaState :: Configuration Player -> HalmaState
initialHalmaState config =
  let
    (board, turnCounter) = newGame config
  in
    HalmaState
      { hsBoard = board
      , hsTurnCounter = toParty <$> turnCounter
      , hsLastMove = Nothing
      , hsFinished = []
      }
  where
    toParty (dir, player) = Party { partyPlayer = player, partyHomeCorner = dir }

data AfterMove
  = GameContinues (Maybe ExtendedPartyResult) HalmaState -- ^ result if a player finished in the last turn
  | GameEnded ExtendedPartyResult -- ^ result of the last player to finish
  deriving (Show, Eq)

doMove
  :: Move
  -> HalmaState
  -> Either String AfterMove
     -- ^ an error or the new state and whether the party has just won
doMove move state =
  case movePiece move (hsBoard state) of
    Left err -> Left err
    Right board' ->
      let
        wasLastMove = hasFinished board' (partyHomeCorner party)
        mExtendedPartyResult =
          if wasLastMove then
            Just $ getExtendedPartyResult (hsTurnCounter state) (hsFinished state)
          else
            Nothing
        finished' =
          hsFinished state ++
          toList (eprPartyResult <$> mExtendedPartyResult)
        numberOfPlayers = length $ tcPlayers $ hsTurnCounter state
        isGameEnd = length finished' == numberOfPlayers
        turnCounter' =
          fromMaybe (hsTurnCounter state) $
          nextTurnWith (`notElem` map prParty finished') $
          hsTurnCounter state
        state' =
          HalmaState
            { hsBoard = board'
            , hsTurnCounter = turnCounter'
            , hsLastMove = Just move
            , hsFinished = finished'
            }
      in
      Right $
      case mExtendedPartyResult of
        Just extendedPartyResult | isGameEnd ->
          GameEnded extendedPartyResult
        _ ->
          GameContinues mExtendedPartyResult state'
  where
    party = currentPlayer (hsTurnCounter state)
    getExtendedPartyResult turnCounter finishedBefore =
      let
        numberOfTurns = currentRound turnCounter + 1
        place =
          length $ filter ((< numberOfTurns) . prNumberOfTurns) finishedBefore
      in
      ExtendedPartyResult
        { eprPartyResult =
            PartyResult
              { prParty = party
              , prNumberOfTurns = numberOfTurns
              }
        , eprPlace = place
        , eprPlaceShared = place < length finishedBefore
        , eprLag =
            case finishedBefore of
              [] -> 0
              winner:_ -> numberOfTurns - prNumberOfTurns winner
        , eprNumberOfPlayers = length (tcPlayers turnCounter)
      }

undoLastMove :: HalmaState -> Maybe HalmaState
undoLastMove state = do
  move <- hsLastMove state
  board' <- eitherToMaybe $ movePiece (invertMove move) (hsBoard state)
  let
    finished' = 
      filter (hasFinished board' . partyHomeCorner . prParty) $
      hsFinished state
  Just
    HalmaState
      { hsBoard = board'
      , hsTurnCounter =
          fromMaybe (hsTurnCounter state) $
          nextTurnWith (`notElem` map prParty finished') $
          hsTurnCounter state
      , hsLastMove = Nothing
      , hsFinished = finished'
      }
  where
    eitherToMaybe = either (const Nothing) Just
    invertMove Move { moveFrom = from, moveTo = to } =
      Move { moveFrom = to, moveTo = from }

newMatch :: Configuration Player -> Match
newMatch config =
  Match
    { matchConfig = config
    , matchRules = rules
    , matchHistory = []
    , matchCurrentGame = Just (initialHalmaState config)
    }
  where
    rules =
      RuleOptions
        { movingBackwards = Temporarily
        , invading = Allowed
        }

newRound :: Match -> Match
newRound match = 
  match
    { matchHistory = matchHistory match ++ toList mGameResult
    , matchCurrentGame = Just game'
    }
  where
    game' = initialHalmaState (matchConfig match)
    mGameResult = GameResult . hsFinished <$> matchCurrentGame match