Purify types into newtypes to ease writing instances

This commit is contained in:
Sasha 2018-03-07 17:32:28 +01:00
parent dfccff2915
commit 95b2132a10
4 changed files with 62 additions and 50 deletions

View file

@ -51,49 +51,52 @@ flower = toEnum . (`div` 4) . fromEnum
type Monthly a = Reader Flower a type Monthly a = Reader Flower a
type Pack = Word64 newtype Pack = Pack { unpack :: Word64 } deriving (Eq)
empty :: Pack empty :: Pack
empty = 0 empty = Pack 0
packOfCards :: [Card] -> Pack packOfCards :: [Card] -> Pack
packOfCards = foldl add 0 packOfCards = foldl add empty
smallest :: Pack -> Card smallest :: Pack -> Card
smallest = toEnum . countTrailingZeros smallest = toEnum . countTrailingZeros . unpack
cardsOfPack :: Pack -> [Card] cardsOfPack :: Pack -> [Card]
cardsOfPack 0 = [] cardsOfPack (Pack 0) = []
cardsOfPack pack = cardsOfPack p =
let n = countTrailingZeros pack in let c = smallest p in
toEnum n : cardsOfPack (clearBit pack n) c : cardsOfPack (remove p c)
port :: (Bits a, Enum e) => (a -> Int -> b) -> a -> e -> b portEnum :: Enum e => (Word64 -> Int -> b) -> Pack -> e -> b
port f bits = f bits . fromEnum portEnum f (Pack p) = f p . fromEnum
contains :: Pack -> Card -> Bool contains :: Pack -> Card -> Bool
contains = port testBit contains = portEnum testBit
size :: Pack -> Int size :: Pack -> Int
size = popCount size (Pack p) = popCount p
add :: Pack -> Card -> Pack add :: Pack -> Card -> Pack
add = port setBit add p = Pack . portEnum setBit p
remove :: Pack -> Card -> Pack remove :: Pack -> Card -> Pack
remove = port clearBit remove p = Pack . portEnum clearBit p
portBinary :: (Word64 -> Word64 -> Word64) -> Pack -> Pack -> Pack
portBinary operator (Pack a) (Pack b) = Pack $ operator a b
union :: Pack -> Pack -> Pack union :: Pack -> Pack -> Pack
union = (.|.) union = portBinary (.|.)
intersection :: Pack -> Pack -> Pack intersection :: Pack -> Pack -> Pack
intersection = (.&.) intersection = portBinary (.&.)
difference :: Pack -> Pack -> Pack difference :: Pack -> Pack -> Pack
difference a b = a `xor` (a .&. b) difference = portBinary (\a b -> a `xor` (a .&. b))
sameMonth :: Card -> Pack sameMonth :: Card -> Pack
sameMonth card = 0xf `shift` (fromEnum card .&. 0xfc) sameMonth card = Pack $ 0xf `shift` (fromEnum card .&. 0xfc)
cards :: [Card] cards :: [Card]
cards = [Pine0 .. Phoenix] cards = [Pine0 .. Phoenix]

View file

@ -1,7 +1,7 @@
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
module Hanafuda.Game where module Hanafuda.Game where
import Data.Map (Map, empty, fromList) import Data.Map (Map, empty, fromList, (!))
import Hanafuda.Card (Card, Pack, packOfCards) import Hanafuda.Card (Card, Pack, packOfCards)
import Hanafuda.Yaku (Score, Points) import Hanafuda.Yaku (Score, Points)
@ -21,9 +21,15 @@ data PlayerState = PlayerState {
} }
type Players = Map Player PlayerState type Players = Map Player PlayerState
initPlayers :: [Card] -> [Card] -> Players players :: [Player]
initPlayers hand1 hand2 = players = [Player1, Player2]
fromList [(Player1, player hand1), (Player2, player hand2)]
deal :: [a] -> Map Player a
deal = fromList . zip players
initPlayers :: [[Card]] -> Players
initPlayers =
deal . map player
where where
player cards = PlayerState { player cards = PlayerState {
hand = packOfCards cards hand = packOfCards cards

View file

@ -1,7 +1,5 @@
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
module Hanafuda.Month where module Hanafuda.Month where
import Hanafuda.Card (Card, Flower(Pine), cards, shuffle, packOfCards) import Hanafuda.Card (Card, Flower(Pine), cards, shuffle, packOfCards)
@ -26,7 +24,10 @@ data Over = Over {
, score :: Int , score :: Int
} }
type Month = Either Over On newtype Month = Month (Either Over On)
go :: On -> Month
go = Month . Right
new :: Player -> [Card] -> On new :: Player -> [Card] -> On
new playing shuffled = new playing shuffled =
@ -42,25 +43,25 @@ new playing shuffled =
where where
take8 = state $ splitAt 8 take8 = state $ splitAt 8
([hand1, hand2, river], next:stock) = runState (replicateM 3 take8) shuffled ([hand1, hand2, river], next:stock) = runState (replicateM 3 take8) shuffled
players = initPlayers hand1 hand2 players = initPlayers [hand1, hand2]
next :: On -> IO On next :: On -> IO On
next month@(On {flower, oyake}) = do next (On {flower, oyake}) = do
shuffled <- shuffle cards shuffled <- shuffle cards
return $ (new (Game.next oyake) cards) {flower = succ flower} return $ (new (Game.next oyake) shuffled) {flower = succ flower}
instance Game On Month where instance Game On Month where
play month@(On {flower, day, playing, players, stock = next : moreStock}) move = play on@(On {flower, day, playing, players, stock = next : moreStock}) move =
fmap after $ play day move fmap after $ play day move
where where
after (Day {step = Day.Over True, player = PlayerState {yakus}}) = after (Day {step = Day.Over True, player = PlayerState {yakus}}) =
Left $ Over {winner = playing, score = foldl (+) 0 yakus} Month . Left $ Over {winner = playing, score = foldl (+) 0 yakus}
after (Day {step = Day.Over False, player, river}) = after (Day {step = Day.Over False, player, river}) =
let otherPlayer = Game.next playing in let otherPlayer = Game.next playing in
Right $ month { go $ on {
players = insert playing player players players = insert playing player players
, playing = otherPlayer , playing = otherPlayer
, day = (Day.new river (players ! otherPlayer) next) { month = flower } , day = (Day.new river (players ! otherPlayer) next) { month = flower }
, stock = moreStock , stock = moreStock
} }
after newDay = Right $ month {day = newDay} after newDay = go $ on {day = newDay}

View file

@ -1,14 +1,13 @@
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
module Hanafuda.Year where module Hanafuda.Year where
import Hanafuda.Card (Flower(Paulownia), cards, shuffle) import Hanafuda.Card (Flower(Paulownia), cards, shuffle)
import Hanafuda.Month (flower, next, score, winner) import Hanafuda.Month (Month(..), flower, next, score, winner)
import qualified Hanafuda.Month as Month (On(..), Over(..), new) import qualified Hanafuda.Month as Month (On(..), Over(..), new)
import Hanafuda.Game (Game(..), Player(Player1), Scores) import Hanafuda.Game (Game(..), Player(Player1), Scores, deal)
import Data.Map (Map, empty, insert, (!)) import Data.Map (Map, insert, (!))
import System.Random (StdGen) import System.Random (StdGen)
data Mode = FirstAt Int | WholeYear data Mode = FirstAt Int | WholeYear
@ -22,35 +21,38 @@ data Over = Over {
finalScores :: Scores finalScores :: Scores
} }
type Year = IO (Either Over On) newtype Year = Year (Either Over On)
new :: Mode -> Year go :: On -> IO Year
go = return . Year . Right
new :: Mode -> IO Year
new mode = do new mode = do
shuffled <- shuffle cards shuffled <- shuffle cards
return . Right $ On { go $ On {
mode mode
, month = Month.new Player1 shuffled , month = Month.new Player1 shuffled
, scores = empty , scores = deal $ cycle [0]
} }
consolidate :: On -> Player -> Int -> Year consolidate :: On -> Player -> Int -> IO Year
consolidate year@(On {mode, month, scores}) winner score = consolidate on@(On {mode, month, scores}) winner score =
case mode of case mode of
FirstAt n | n <= newScore -> over FirstAt n | n <= newScore -> stop
FirstAt n -> continue FirstAt n -> continue
WholeYear | flower month == Paulownia -> over WholeYear | flower month == Paulownia -> stop
WholeYear -> continue WholeYear -> continue
where where
newScore = scores ! winner + score newScore = scores ! winner + score
newScores = insert winner newScore scores newScores = insert winner newScore scores
over = return . Left $ Over {finalScores = newScores} stop = return . Year . Left $ Over {finalScores = newScores}
continue = do continue = do
nextMonth <- next month nextMonth <- next month
return . Right $ year {scores = newScores, month = nextMonth} go $ on {scores = newScores, month = nextMonth}
instance Game On Year where instance Game On (IO Year) where
play year@(On {mode, month}) move = play on@(On {mode, month}) move =
fmap after $ play month move fmap after $ play month move
where where
after (Left (Month.Over {winner, score})) = consolidate year winner score after (Month (Left (Month.Over {winner, score}))) = consolidate on winner score
after (Right newMonth) = return . Right $ year {month = newMonth} after (Month (Right newMonth)) = go $ on {month = newMonth}