Add IDs handling directly in library + add a couple useful functions on Hanafuda cards

This commit is contained in:
Tissevert 2019-08-24 23:04:37 +02:00
parent f1ee562809
commit 61eeac8bc6
10 changed files with 129 additions and 68 deletions

View File

@ -1,5 +1,10 @@
# Revision history for hanafuda # Revision history for hanafuda
## 0.3.3.0 -- 2019-08-24
* Add IDs handling directly in library : it's a newtype phantom type to generate IDs for any type and it's used to make IDs for players and KoiKoi games which allows to decrease the arity of the Player constructor
* Some more useful function were added to the Hanafuda module : they simplify Yakus a bit and will be useful for Hannah, the bot to come soon — Yakus were extended further by defining a notion of distance to a Yaku that will hopefully help a bot determine how close it is from scoring a Yaku
## 0.3.2.0 -- 2019-08-12 ## 0.3.2.0 -- 2019-08-12
* Handle the end of games * Handle the end of games

View File

@ -2,7 +2,7 @@
-- documentation, see http://haskell.org/cabal/users-guide/ -- documentation, see http://haskell.org/cabal/users-guide/
name: hanafuda name: hanafuda
version: 0.3.2.0 version: 0.3.3.0
synopsis: A game of Hanafuda (a family of japanese card games) synopsis: A game of Hanafuda (a family of japanese card games)
description: This is a library to represent the cards and the players description: This is a library to represent the cards and the players
of games in this family. It also implements one such game of games in this family. It also implements one such game
@ -13,11 +13,11 @@ description: This is a library to represent the cards and the players
new game and playing moves. Its approach is move-by-move : new game and playing moves. Its approach is move-by-move :
a game's state is computed from the previous state and a a game's state is computed from the previous state and a
move. move.
homepage: https://framagit.org/hanafuda homepage: https://git.marvid.fr/hanafuda
license: BSD3 license: BSD3
license-file: LICENSE license-file: LICENSE
author: Sasha author: Tissevert
maintainer: sasha+frama@marvid.fr maintainer: tissevert+devel@marvid.fr
-- copyright: -- copyright:
category: Game category: Game
build-type: Simple build-type: Simple
@ -25,12 +25,13 @@ extra-source-files: ChangeLog.md
cabal-version: >=1.10 cabal-version: >=1.10
source-repository head source-repository head
type: git type: git
location: https://framagit.org/hanafuda/lib location: https://git.marvid.fr/hanafuda/lib
library library
exposed-modules: Hanafuda exposed-modules: Hanafuda
, Hanafuda.KoiKoi , Hanafuda.KoiKoi
, Hanafuda.Player , Hanafuda.Player
, Hanafuda.ID
other-modules: Hanafuda.KoiKoi.Game other-modules: Hanafuda.KoiKoi.Game
, Hanafuda.KoiKoi.Round , Hanafuda.KoiKoi.Round
, Hanafuda.KoiKoi.Turn , Hanafuda.KoiKoi.Turn

View File

@ -5,16 +5,20 @@ module Hanafuda (
, Pack , Pack
, add , add
, cards , cards
, cardsOf
, cardsOfPack , cardsOfPack
, contains , contains
, difference
, empty , empty
, flower , flower
, intersection , intersection
, match , match
, packOfCards , packOfCards
, remove , remove
, sameMonth
, shuffle , shuffle
, size , size
, union
) where ) where
import Data.Word (Word64) import Data.Word (Word64)
@ -26,6 +30,7 @@ import Data.Bits (
, testBit , testBit
, xor , xor
, (.&.) , (.&.)
, (.|.)
, countTrailingZeros , countTrailingZeros
) )
import System.Random (randomRIO) import System.Random (randomRIO)
@ -111,8 +116,14 @@ intersection = portBinary (.&.)
difference :: Pack -> Pack -> Pack difference :: Pack -> Pack -> Pack
difference = portBinary (\a b -> a `xor` (a .&. b)) difference = portBinary (\a b -> a `xor` (a .&. b))
sameMonth :: Card -> Pack union :: Pack -> Pack -> Pack
sameMonth card = Pack $ 0xf `shift` (fromEnum card .&. 0xfc) union = portBinary (.|.)
cardsOf :: Flower -> Pack
cardsOf = Pack . shift 0xf . (* 4) . fromEnum
sameMonth :: Card -> Pack -> Pack
sameMonth card (Pack p) = Pack $ (0xf `shift` (fromEnum card .&. 0xfc)) .&. p
cards :: [Card] cards :: [Card]
cards = [Pine0 .. Phoenix] cards = [Pine0 .. Phoenix]
@ -130,7 +141,7 @@ shuffle l =
match :: Card -> Pack -> Either String (Pack, [Card]) match :: Card -> Pack -> Either String (Pack, [Card])
match card pack = match card pack =
let sameMonthCards = sameMonth card `intersection` pack in let sameMonthCards = sameMonth card pack in
case size sameMonthCards of case size sameMonthCards of
0 -> Right (add pack card, []) 0 -> Right (add pack card, [])
1 -> Right (difference pack sameMonthCards, card : cardsOfPack sameMonthCards) 1 -> Right (difference pack sameMonthCards, card : cardsOfPack sameMonthCards)

10
src/Hanafuda/ID.hs Normal file
View File

@ -0,0 +1,10 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Hanafuda.ID (
ID(..)
, getID
) where
newtype ID a = ID Int deriving (Eq, Ord, Enum, Read, Show)
getID :: ID a -> String
getID (ID n) = show n

View File

@ -3,10 +3,13 @@
module Hanafuda.KoiKoi ( module Hanafuda.KoiKoi (
Action(..) Action(..)
, Card(..) , Card(..)
, Game(..)
, Environment , Environment
, Game
, GameBlueprint(..)
, GameID
, Mode(..) , Mode(..)
, Move(..) , Move(..)
, PlayerID
, Score , Score
, Source(..) , Source(..)
, Step(..) , Step(..)
@ -18,13 +21,16 @@ module Hanafuda.KoiKoi (
import Hanafuda (Card(..), Flower(Pine), contains, flower, match, remove) import Hanafuda (Card(..), Flower(Pine), contains, flower, match, remove)
import qualified Hanafuda.Player as Player (players, random, scores) import qualified Hanafuda.Player as Player (players, random, scores)
import Hanafuda.KoiKoi.Yaku (Yaku(..), Score) import Hanafuda.KoiKoi.Yaku (Yaku(..), Score)
import Hanafuda.KoiKoi.Game (Action(..), Environment, Game(..), Mode(..), Move(..), Source(..), Step(..)) import Hanafuda.KoiKoi.Game (
Action(..), Environment, Game, GameBlueprint(..), GameID, Mode(..), Move(..), PlayerID
, Source(..), Step(..)
)
import qualified Hanafuda.KoiKoi.Round as Round (deal, next) import qualified Hanafuda.KoiKoi.Round as Round (deal, next)
import qualified Hanafuda.KoiKoi.Turn as Turn (catch, end, next) import qualified Hanafuda.KoiKoi.Turn as Turn (catch, end, next)
import Control.Monad.Except (MonadError(..)) import Control.Monad.Except (MonadError(..))
import Control.Monad.IO.Class (MonadIO) import Control.Monad.IO.Class (MonadIO)
play :: (Environment m, Ord player) => Move -> Game player -> m (Game player) play :: Environment m => Move -> Game -> m Game
play move game@(Game {river, step}) = play move game@(Game {river, step}) =
case (step, move) of case (step, move) of
(ToPlay, Play card) -> (ToPlay, Play card) ->
@ -43,8 +49,8 @@ play move game@(Game {river, step}) =
where where
canCatch card1 card2 = flower card1 == flower card2 && river `contains` card2 canCatch card1 card2 = flower card1 == flower card2 && river `contains` card2
new :: (MonadIO m, Ord player) => [player] -> Mode -> m (Game player) new :: MonadIO m => (PlayerID, PlayerID) -> Mode -> m Game
new playersList mode = do new (playerA, playerB) mode = do
playing <- Player.random players playing <- Player.random players
Round.deal $ Game { Round.deal $ Game {
mode mode
@ -61,4 +67,4 @@ new playersList mode = do
, rounds = [] , rounds = []
} }
where where
players = Player.players playersList players = Player.players [playerA, playerB]

View File

@ -3,10 +3,14 @@
{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE ConstraintKinds #-}
module Hanafuda.KoiKoi.Game ( module Hanafuda.KoiKoi.Game (
Action(..) Action(..)
, Game(..)
, Environment , Environment
, Game
, GameBlueprint(..)
, GameID
, ID
, Mode(..) , Mode(..)
, Move(..) , Move(..)
, PlayerID
, Source(..) , Source(..)
, Step(..) , Step(..)
, end , end
@ -17,8 +21,10 @@ import Control.Monad.Except (MonadError)
import Control.Monad.IO.Class (MonadIO) import Control.Monad.IO.Class (MonadIO)
import Control.Monad.Writer (MonadWriter) import Control.Monad.Writer (MonadWriter)
import Hanafuda (Card, Flower, Pack) import Hanafuda (Card, Flower, Pack)
import Hanafuda.ID (ID)
import qualified Hanafuda.KoiKoi.Yaku as KoiKoi (Score)
import Hanafuda.Player (Players, Player, Scores, set) import Hanafuda.Player (Players, Player, Scores, set)
import Hanafuda.KoiKoi.Yaku (Score) import qualified Hanafuda.Player as Player (ID)
data Mode = FirstAt Int | WholeYear deriving (Show) data Mode = FirstAt Int | WholeYear deriving (Show)
data Move = Play Card | Capture (Card, Card) | Choose Card | KoiKoi Bool data Move = Play Card | Capture (Card, Card) | Choose Card | KoiKoi Bool
@ -31,24 +37,27 @@ data Action = Action {
} deriving (Show) } deriving (Show)
type Environment m = (MonadIO m, MonadError String m, MonadWriter [Action] m) type Environment m = (MonadIO m, MonadError String m, MonadWriter [Action] m)
type PlayerID = Player.ID KoiKoi.Score
type GameID = ID Game
data Game player = Game { data GameBlueprint deckType = Game {
mode :: Mode mode :: Mode
, scores :: Scores player , scores :: Scores KoiKoi.Score
, month :: Flower , month :: Flower
, players :: Players player Score , players :: Players KoiKoi.Score
, playing :: player , playing :: PlayerID
, winning :: player , winning :: PlayerID
, oyake :: player , oyake :: PlayerID
, deck :: [Card] , deck :: deckType
, river :: Pack , river :: Pack
, step :: Step , step :: Step
, trick :: [Card] , trick :: [Card]
, rounds :: [(player, Score)] , rounds :: [(PlayerID, KoiKoi.Score)]
} deriving (Show) } deriving (Show)
type Game = GameBlueprint [Card]
setPlayer :: Ord player => Game player -> Player player Score -> Game player setPlayer :: Game -> Player KoiKoi.Score -> Game
setPlayer game@(Game {players, playing}) player = game {players = set playing player players} setPlayer game@(Game {players, playing}) player = game {players = set playing player players}
end :: Monad m => Game player -> m (Game player) end :: Monad m => Game -> m Game
end game = return $ game {step = Over} end game = return $ game {step = Over}

View File

@ -6,13 +6,13 @@ module Hanafuda.KoiKoi.Round (
import Hanafuda (Flower(Paulownia), cards, shuffle, packOfCards) import Hanafuda (Flower(Paulownia), cards, shuffle, packOfCards)
import Hanafuda.KoiKoi.Yaku (sumYakus) import Hanafuda.KoiKoi.Yaku (sumYakus)
import Hanafuda.KoiKoi.Game (Game(..), Mode(..), Step(..), end) import Hanafuda.KoiKoi.Game (Game, GameBlueprint(..), Mode(..), Step(..), end)
import qualified Hanafuda.Player as Player (deal, get, next, score, yakus) import qualified Hanafuda.Player as Player (deal, get, next, score, yakus)
import Data.Map ((!), insert) import Data.Map ((!), insert)
import Control.Monad.IO.Class (MonadIO) import Control.Monad.IO.Class (MonadIO)
import Control.Monad.State (runState, state) import Control.Monad.State (runState, state)
deal :: (MonadIO m, Ord player) => Game player -> m (Game player) deal :: MonadIO m => Game -> m Game
deal game@(Game {players}) = do deal game@(Game {players}) = do
((hand1, hand2, river), deck) <- runState getTriple <$> shuffle cards ((hand1, hand2, river), deck) <- runState getTriple <$> shuffle cards
return game { return game {
@ -24,7 +24,7 @@ deal game@(Game {players}) = do
take8 = state $ splitAt 8 take8 = state $ splitAt 8
getTriple = (,,) <$> take8 <*> take8 <*> take8 getTriple = (,,) <$> take8 <*> take8 <*> take8
next :: (MonadIO m, Ord player) => Game player -> m (Game player) next :: MonadIO m => Game -> m Game
next game@(Game {mode, scores, month, players, oyake, winning, rounds}) = next game@(Game {mode, scores, month, players, oyake, winning, rounds}) =
case mode of case mode of
FirstAt n | n <= newScore -> end scored FirstAt n | n <= newScore -> end scored

View File

@ -11,7 +11,7 @@ import Hanafuda (Card, Pack, empty, match)
import Hanafuda.Player (Player(..), plays) import Hanafuda.Player (Player(..), plays)
import qualified Hanafuda.Player as Player (get, next) import qualified Hanafuda.Player as Player (get, next)
import Hanafuda.KoiKoi.Yaku (meldInto) import Hanafuda.KoiKoi.Yaku (meldInto)
import Hanafuda.KoiKoi.Game (Action(..), Environment, Game(..), Source(..), Step(..), setPlayer) import Hanafuda.KoiKoi.Game (Action(..), Environment, Game, GameBlueprint(..), Source(..), Step(..), setPlayer)
import qualified Hanafuda.KoiKoi.Round as Round (next) import qualified Hanafuda.KoiKoi.Round as Round (next)
import Control.Monad.Except (MonadError(..)) import Control.Monad.Except (MonadError(..))
import Control.Monad.IO.Class (MonadIO) import Control.Monad.IO.Class (MonadIO)
@ -27,14 +27,14 @@ log source played cards = tell [
captures [_, captured] = Just captured captures [_, captured] = Just captured
captures _ = Nothing captures _ = Nothing
catch :: (Environment m, Ord player) => Game player -> Card -> (Pack, [Card]) -> m (Game player) catch :: Environment m => Game -> Card -> (Pack, [Card]) -> m Game
catch game@(Game {players, playing}) card (river, trick) = do catch game@(Game {players, playing}) card (river, trick) = do
log Hand card trick log Hand card trick
(setPlayer (game {river, trick})) <$> played >>= popNextCard (setPlayer (game {river, trick})) <$> played >>= popNextCard
where where
played = (Player.get playing players) `plays` card played = (Player.get playing players) `plays` card
popNextCard :: (Environment m, Ord player) => Game player -> m (Game player) popNextCard :: Environment m => Game -> m Game
popNextCard (Game {deck = []}) = throwError "No more cards in the stack" popNextCard (Game {deck = []}) = throwError "No more cards in the stack"
popNextCard game@(Game {river, deck = turned : others}) = popNextCard game@(Game {river, deck = turned : others}) =
let pop = game {deck = others} in let pop = game {deck = others} in
@ -42,7 +42,7 @@ popNextCard game@(Game {river, deck = turned : others}) =
Right (newRiver, newCaptured) -> end pop turned (newRiver, newCaptured) Right (newRiver, newCaptured) -> end pop turned (newRiver, newCaptured)
Left _ -> return $ pop {step = Turned turned} Left _ -> return $ pop {step = Turned turned}
end :: (MonadWriter [Action] m, MonadIO m, Ord player) => Game player -> Card -> (Pack, [Card]) -> m (Game player) end :: (MonadWriter [Action] m, MonadIO m) => Game -> Card -> (Pack, [Card]) -> m Game
end game@(Game {month, trick, playing, players}) card (river, newCaptured) = do end game@(Game {month, trick, playing, players}) card (river, newCaptured) = do
log Deck card newCaptured log Deck card newCaptured
let updatedGame = setPlayer (game {river, trick = []}) updatedPlayer let updatedGame = setPlayer (game {river, trick = []}) updatedPlayer
@ -55,7 +55,7 @@ end game@(Game {month, trick, playing, players}) card (river, newCaptured) = do
(scored, newMeld) = runReader (newTrick `meldInto` (meld player)) month (scored, newMeld) = runReader (newTrick `meldInto` (meld player)) month
updatedPlayer = player {meld = newMeld, yakus = scored `mappend` (yakus player)} updatedPlayer = player {meld = newMeld, yakus = scored `mappend` (yakus player)}
next :: (MonadIO m, Ord player) => Game player -> m (Game player) next :: MonadIO m => Game -> m Game
next game@(Game {players, playing}) = next game@(Game {players, playing}) =
let newPlaying = Player.next players playing in let newPlaying = Player.next players playing in
if hand (Player.get newPlaying players) == empty if hand (Player.get newPlaying players) == empty

View File

@ -1,11 +1,20 @@
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
module Hanafuda.KoiKoi.Yaku where module Hanafuda.KoiKoi.Yaku (
Score
, Yaku(..)
, meldInto
, sumYakus
) where
import Hanafuda (Card(..), Monthly, Pack, add, contains, intersection, packOfCards, size) import Hanafuda (
Card(..), Monthly, Pack
, add, cardsOf, cardsOfPack, contains, difference, intersection, packOfCards
, size
)
import Hanafuda.Player (Points) import Hanafuda.Player (Points)
import qualified Data.Map as M (Map, empty, insert, unionWith, (!)) import qualified Data.Map as M (Map, empty, insert, unionWith, (!))
import qualified Data.Set as S (Set, empty, singleton, union) import qualified Data.Set as S (Set, empty, singleton, union)
import Control.Monad.Reader (reader) import Control.Monad.Reader (asks)
data Yaku = data Yaku =
Lights Lights
@ -18,13 +27,15 @@ data Yaku =
| TsukimiZake | TsukimiZake
| HanamiZake | HanamiZake
| TsukiFuda | TsukiFuda
deriving (Eq, Ord, Show) deriving (Eq, Ord, Read, Show)
type YakuRater = Pack -> Maybe Points type YakuRater = Pack -> Maybe Points
type YakuDistance = Pack -> Int
type Score = M.Map Yaku Points type Score = M.Map Yaku Points
data YakuFinder = YakuFinder { data YakuFinder = YakuFinder {
yaku :: Yaku yaku :: Yaku
, rater :: YakuRater , rater :: YakuRater
, distance :: YakuDistance
} }
instance Eq YakuFinder where instance Eq YakuFinder where
@ -57,12 +68,16 @@ moreThan count _ pack =
index :: (Yaku, [Card], (Pack -> YakuRater)) -> YakusByCard index :: (Yaku, [Card], (Pack -> YakuRater)) -> YakusByCard
index (yaku, cards, scorer) = index (yaku, cards, scorer) =
let pack = packOfCards cards in let pack = packOfCards cards in
let yakuFinder = YakuFinder {yaku, rater = scorer pack . intersection pack} in let yakuFinder = YakuFinder {
yaku
, rater = scorer pack . intersection pack
, distance = size . difference pack
} in
foldl (\yakusByCard card -> M.insert card (S.singleton yakuFinder) yakusByCard) M.empty cards foldl (\yakusByCard card -> M.insert card (S.singleton yakuFinder) yakusByCard) M.empty cards
finders :: Monthly YakusByCard finders :: Monthly YakusByCard
finders = do finders = do
monthCardPlus <- reader $ (+) . (4*) . fromEnum monthCards <- cardsOfPack <$> asks cardsOf
return $ foldl (\yakusByCard -> M.unionWith S.union yakusByCard . index) M.empty [ return $ foldl (\yakusByCard -> M.unionWith S.union yakusByCard . index) M.empty [
(Lights, [Crane, CampCurtain, FullMoon, RainMan, Phoenix], lights) (Lights, [Crane, CampCurtain, FullMoon, RainMan, Phoenix], lights)
, (InoShikaCho, inoshikacho, fixed 5) , (InoShikaCho, inoshikacho, fixed 5)
@ -73,7 +88,7 @@ finders = do
, (Kasu, plains, moreThan 9) , (Kasu, plains, moreThan 9)
, (TsukimiZake, [SakeCup, FullMoon], fixed 3) , (TsukimiZake, [SakeCup, FullMoon], fixed 3)
, (HanamiZake, [SakeCup, CampCurtain], fixed 3) , (HanamiZake, [SakeCup, CampCurtain], fixed 3)
, (TsukiFuda, map (toEnum . monthCardPlus) [0..3], fixed 5) , (TsukiFuda, monthCards, fixed 5)
] ]
where where
inoshikacho = [Butterflies, Boar, Deer] inoshikacho = [Butterflies, Boar, Deer]

View File

@ -3,21 +3,23 @@
module Hanafuda.Player where module Hanafuda.Player where
import Hanafuda (Card, Pack, contains, packOfCards, remove) import Hanafuda (Card, Pack, contains, packOfCards, remove)
import qualified Hanafuda.ID as Hanafuda (ID)
import Data.Map ((!), Map, adjust, elemAt, empty, findMin, fromList, insert, keys, singleton, size) import Data.Map ((!), Map, adjust, elemAt, empty, findMin, fromList, insert, keys, singleton, size)
import qualified Data.Map as Map (filter) import qualified Data.Map as Map (filter)
import Control.Monad.IO.Class (MonadIO(..)) import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad.Except (MonadError(..)) import Control.Monad.Except (MonadError(..))
import System.Random (Random(..)) import System.Random (Random(..))
data Player key yakus = Player { type ID yakus = Hanafuda.ID (Player yakus)
data Player yakus = Player {
hand :: Pack hand :: Pack
, meld :: Pack , meld :: Pack
, nextPlayer :: key , nextPlayer :: ID yakus
, yakus :: yakus , yakus :: yakus
} deriving (Show) } deriving (Show)
newtype Players key yakus = Players (Map key (Player key yakus)) deriving (Show) newtype Players yakus = Players (Map (ID yakus) (Player yakus)) deriving (Show)
new :: Monoid yakus => key -> Player key yakus new :: Monoid yakus => (ID yakus) -> Player yakus
new nextPlayer = Player { new nextPlayer = Player {
hand = packOfCards [] hand = packOfCards []
, meld = packOfCards [] , meld = packOfCards []
@ -25,49 +27,51 @@ new nextPlayer = Player {
, yakus = mempty , yakus = mempty
} }
players :: (Ord key, Monoid yakus) => [key] -> Players key yakus players :: Monoid yakus => [ID yakus] -> Players yakus
players [] = Players empty players [] = Players empty
players [player] = Players $ singleton player $ new player players [player] = Players $ singleton player $ new player
players (alice:others@(bob:_)) = players (alice:others@(bob:_)) =
let Players playersByKey = players others in let Players playersByID = players others in
let (before, _) = findMin $ Map.filter ((== bob) . nextPlayer) playersByKey in let (before, _) = findMin $ Map.filter ((== bob) . nextPlayer) playersByID in
Players $ insert alice (new bob) $ adjust (setNextPlayer alice) before playersByKey Players $ insert alice (new bob) $ adjust (setNextPlayer alice) before playersByID
where where
setNextPlayer nextPlayer player = player {nextPlayer} setNextPlayer nextPlayer player = player {nextPlayer}
next :: Ord key => Players key yakus -> key -> key next :: Players yakus -> (ID yakus) -> (ID yakus)
next (Players playersByKey) = nextPlayer . (playersByKey !) next (Players playersByID) = nextPlayer . (playersByID !)
random :: MonadIO m => Players key yakus -> m key random :: MonadIO m => Players yakus -> m (ID yakus)
random (Players playersByKey) = random (Players playersByID) =
fst . ($ playersByKey) . elemAt <$> randomIndex fst . ($ playersByID) . elemAt <$> randomIndex
where where
randomIndex = liftIO $ randomRIO (0, size playersByKey - 1) randomIndex = liftIO $ randomRIO (0, size playersByID - 1)
get :: Ord key => key -> Players key yakus -> Player key yakus get :: (ID yakus) -> Players yakus -> Player yakus
get key (Players playersByKey) = playersByKey ! key get playerID (Players playersByID) = playersByID ! playerID
set :: Ord key => key -> Player key yakus -> Players key yakus -> Players key yakus set :: (ID yakus) -> Player yakus -> Players yakus -> Players yakus
set key player (Players playersByKey) = Players $ insert key player playersByKey set playerID player (Players playersByID) = Players $ insert playerID player playersByID
deal :: (Ord key, Monoid yakus) => Players key yakus -> [[Card]] -> Players key yakus deal :: Monoid yakus => Players yakus -> [[Card]] -> Players yakus
deal (Players playersByKey) hands = deal (Players playersByID) hands =
Players $ snd $ foldl dealTo (fst $ findMin playersByKey, playersByKey) hands Players $ snd $ foldl dealTo (fst $ findMin playersByID, playersByID) hands
where where
setHand cards (Player {nextPlayer}) = (new nextPlayer) {hand = packOfCards cards} setHand cards (Player {nextPlayer}) =
dealTo (key, m) hand = (nextPlayer $ m ! key, adjust (setHand hand) key m) (new nextPlayer) {hand = packOfCards cards}
dealTo (playerID, m) hand =
(nextPlayer $ m ! playerID, adjust (setHand hand) playerID m)
plays :: MonadError String m => Player key yakus -> Card -> m (Player key yakus) plays :: MonadError String m => Player yakus -> Card -> m (Player yakus)
plays player@(Player {hand}) card = plays player@(Player {hand}) card =
if hand `contains` card if hand `contains` card
then return $ player {hand = remove hand card} then return $ player {hand = remove hand card}
else throwError "You don't have this card" else throwError "You don't have this card"
type Points = Int type Points = Int
type Scores key = Map key Points type Scores yakus = Map (ID yakus) Points
score :: (yakus -> Points) -> Player key yakus -> Points score :: (yakus -> Points) -> Player yakus -> Points
score rater = rater . yakus score rater = rater . yakus
scores :: Ord key => Players key yakus -> [Points] -> Scores key scores :: Players yakus -> [Points] -> Scores yakus
scores (Players playersByKey) = fromList . zip (keys playersByKey) scores (Players playersByKey) = fromList . zip (keys playersByKey)