I'm creating a card game in Haskell where I need to know the color of the card for each suit.
Right now I construct all possible combinations of suit, rank, and color using cardCombinations
and then filter the result with cardWithColors
to construct the Deck
The requirement is I want the suits to have the correct associated color: Diamonds, Hearts are Red and Spades, Clubs are Black.
Is there possibly a more concise way to express this logic rather than first constructing all combinations and then filtering them?
module Deck (Suit,Color,Rank,Card,Deck) where
data Color = Red | Black deriving (Eq,Enum,Show,Ord,Bounded)
data Suit = Clubs | Diamonds | Hearts | Spades deriving (Eq,Ord,Enum,Bounded,Show)
data Rank = Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten
| Jack | Queen | King | Ace deriving (Eq,Ord,Enum,Bounded,Show)
data Card = Card { rank :: Rank
, suit :: Suit
, color :: Color } deriving (Eq,Ord,Bounded,Show)
type Deck = [Card]
findColorForSuit :: Suit -> Color
findColorForSuit suit
| any (suit==) [Diamonds, Hearts] = Red
| any (suit==) [Spades, Clubs] = Black
cardCombinations = [(ranks, suits, colors) | ranks <- [minBound :: Rank .. maxBound :: Rank], suits <- [minBound :: Suit .. maxBound :: Suit], colors <- [minBound :: Color .. maxBound :: Color]]
cardsWithColors = filter (\(_,suit,color) -> (findColorForSuit suit) == color) cardCombinations
makeCard :: (Rank, Suit, Color) -> Card
makeCard (rank, suit, color) = Card rank suit color
makeCards :: Deck
makeCards = map makeCard cardsWithColors
I tried creating all combinations and then filtering the result based off a filter predicate to determine how to associate the color to the suit.
CodePudding user response:
These requirement are also known as an invariant and the idiomatic way is to avoid them if possible (make invalid states impossible). Sometimes it is really hard and/or not practical to avoid all the invariants, however in your case it is really easy because the Color
of each card is uniquely determined by the Suit
. Instead of the color
field you could just define it as a function.
data Card = Card { rank :: Rank, suit :: Suit }
deriving (..)
color :: Card -> Color
color card = findColorForSuit (suit card)
This means that each Card
is a valid Card by construction and you can just construct all possible combinations of ranks and suits.
CodePudding user response:
I would make Card
only have the rank and suit, and then write a function that calculates the colour (by using your findColourForSuit
, although this will be even simpler if you use a single pattern match, instead of using any
, ==
, and lists).
You don't need to store the colour of every card, it's implied by the suit that you're already storing. In much the same way as we don't put a field even :: Bool
on every Integer
; we just store the integer's value, and when we need to know whether it's odd or even we calculate that.