-- Copyright (c) 2014-2015 Jonathan M. Lange -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. -- You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- See the License for the specific language governing permissions and -- limitations under the License. {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NoImplicitPrelude #-} module Haverer.Action ( BadPlay, Play(..), Action, bustingHand, getTarget, getValidPlays, playToAction, viewAction ) where import BasicPrelude import Control.Monad.Except import Haverer.Deck (Card(..)) -- | A thing that can be done with a card. data Play target = NoEffect | Attack target | Guess target Card deriving (Eq, Show) -- | A validated card + play combination. -- Only guarantees that such a thing makes sense according to the rules, -- rather than the current state of the round. data Action target = Action target Card (Play target) deriving (Eq, Show) data BadPlay target = BadActionForCard (Play target) Card -- ^ If that play and card are forbidden by the rules | BadGuess -- ^ If they try to guess a soldier | SelfTarget -- ^ If they try to target themselves when forbidden deriving Show viewAction :: Action target -> (target, Card, Play target) viewAction (Action pid card play) = (pid, card, play) getTarget :: Play target -> Maybe target getTarget NoEffect = Nothing getTarget (Attack target) = Just target getTarget (Guess target _) = Just target -- | Given a player, a card, and a choice of play, decide whether it's a valid -- action. playToAction :: (Eq target, MonadError (BadPlay target) m) => target -> Card -> Play target -> m (Action target) playToAction pid card play = Action pid card <$> _validatePlay pid card play _validatePlay :: (Eq target, MonadError (BadPlay target) m) => target -> Card -> Play target -> m (Play target) _validatePlay _ Soldier (Guess _ Soldier) = throwError BadGuess _validatePlay player Soldier play@(Guess target _) | player == target = throwError SelfTarget | otherwise = return play _validatePlay player Clown play@(Attack target) | player == target = throwError SelfTarget | otherwise = return play _validatePlay player Knight play@(Attack target) | player == target = throwError SelfTarget | otherwise = return play _validatePlay _ Priestess NoEffect = return NoEffect _validatePlay _ Wizard play@(Attack _) = return play _validatePlay player General play@(Attack target) | player == target = throwError SelfTarget | otherwise = return play _validatePlay _ Minister NoEffect = return NoEffect _validatePlay _ Prince NoEffect = return NoEffect _validatePlay _ card play = throwError (BadActionForCard play card) -- | Return all valid plays for a particular card. getValidPlaysForCard :: target -- ^ The current player -> [target] -- ^ All other active players in the round -> Card -- ^ The card they wish to play -> [Play target] -- ^ All valid plays for that card getValidPlaysForCard self others card = case card of Soldier -> [Guess tgt c | tgt <- others, c <- [Clown ..]] Clown -> fmap Attack others Knight -> fmap Attack others Priestess -> [NoEffect] Wizard -> fmap Attack (self:others) General -> fmap Attack others Minister -> [NoEffect] Prince -> [NoEffect] -- | Return all valid plays for a hand. -- -- If the hand is one that would bust out (see 'bustingHand') then returns an -- empty list. getValidPlays :: player -- ^ The current player -> [player] -- ^ All other active players in the round -> Card -- ^ The card they were dealt -> Card -- ^ The card that was in their hand -> [(Card, Play player)] -- ^ All valid plays getValidPlays self others dealt hand = do guard $ not $ bustingHand dealt hand [(dealt, play) | play <- playsForCard dealt] ++ [(hand, play) | play <-playsForCard hand] where playsForCard = getValidPlaysForCard self others -- | If you're holding the Minister, there's a potential to "bust out" -- to -- have to immediately leave the round because you're holding another high -- card. bustingHand :: Card -> Card -> Bool bustingHand Minister card = card >= Wizard bustingHand card Minister = bustingHand Minister card bustingHand _ _ = False