Compare commits

...

9 Commits

6 changed files with 95 additions and 57 deletions

View File

@ -1,10 +1,38 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Hanafuda.ID ( module Hanafuda.ID (
ID(..) ID(..)
, getID , IDType(..)
, Prefix(..)
) where ) where
newtype ID a = ID Int deriving (Eq, Ord, Enum, Read, Show) import Data.Char (isDigit)
import System.Random (Random(..))
import Text.ParserCombinators.ReadP (char, munch, string)
import Text.ParserCombinators.ReadPrec (lift)
import Text.Read (readPrec)
getID :: ID a -> String newtype ID a = ID {
getID (ID n) = show n getID :: Int
} deriving (Eq, Ord)
newtype Prefix a = Prefix String
class IDType a where
prefix :: Prefix a
instance IDType a => Show (ID a) where
show (ID someID) = p ++ ('#' : show someID)
where Prefix p = (prefix :: Prefix a)
instance IDType a => Read (ID a) where
readPrec = fmap (ID . read) . lift $
string p >> char '#' >> munch (isDigit)
where Prefix p = (prefix :: Prefix a)
rIntToRID :: (Int, g) -> (ID a, g)
rIntToRID (someID, g) = (ID someID, g)
instance Random (ID a) where
random = rIntToRID . randomR (0, maxBound)
randomR (ID idA, ID idB) = rIntToRID . randomR (idA, idB)

View File

@ -4,13 +4,16 @@ module Hanafuda.KoiKoi (
Action(..) Action(..)
, Card(..) , Card(..)
, Environment , Environment
, Game , Game(..)
, GameBlueprint(..)
, GameID , GameID
, Mode(..) , Mode(..)
, Move(..) , Move(..)
, Player
, PlayerID , PlayerID
, PlayerTurn
, Players
, Score , Score
, Scores
, Source(..) , Source(..)
, Step(..) , Step(..)
, Yaku(..) , Yaku(..)
@ -18,17 +21,19 @@ module Hanafuda.KoiKoi (
, play , play
) where ) where
import qualified Data.Map as Map (fromList)
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 ( import Hanafuda.KoiKoi.Game (
Action(..), Environment, Game, GameBlueprint(..), GameID, Mode(..), Move(..), PlayerID Action(..), Environment, Game(..), GameID, Mode(..), Move(..), Player
, Source(..), Step(..) , PlayerID, PlayerTurn, Players, Scores, 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(..))
import System.Random (randomIO)
play :: Environment m => Move -> Game -> m Game play :: Environment m => Move -> Game -> m Game
play move game@(Game {river, step}) = play move game@(Game {river, step}) =
@ -52,11 +57,14 @@ play move game@(Game {river, step}) =
new :: MonadIO m => (PlayerID, PlayerID) -> Mode -> m Game new :: MonadIO m => (PlayerID, PlayerID) -> Mode -> m Game
new (playerA, playerB) mode = do new (playerA, playerB) mode = do
playing <- Player.random players playing <- Player.random players
gameID <- liftIO $ randomIO
Round.deal $ Game { Round.deal $ Game {
mode gameID
, mode
, scores = Player.scores players [0, 0] , scores = Player.scores players [0, 0]
, month = Pine , month = Pine
, players , players
, nextPlayer
, playing , playing
, winning = playing , winning = playing
, oyake = playing , oyake = playing
@ -68,3 +76,4 @@ new (playerA, playerB) mode = do
} }
where where
players = Player.players [playerA, playerB] players = Player.players [playerA, playerB]
nextPlayer = Map.fromList $ [(playerA, playerB), (playerB, playerA)]

View File

@ -4,13 +4,16 @@
module Hanafuda.KoiKoi.Game ( module Hanafuda.KoiKoi.Game (
Action(..) Action(..)
, Environment , Environment
, Game , Game(..)
, GameBlueprint(..)
, GameID , GameID
, ID , ID
, Mode(..) , Mode(..)
, Move(..) , Move(..)
, Player
, PlayerID , PlayerID
, PlayerTurn
, Players
, Scores
, Source(..) , Source(..)
, Step(..) , Step(..)
, end , end
@ -20,14 +23,14 @@ module Hanafuda.KoiKoi.Game (
import Control.Monad.Except (MonadError) 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 Data.Map (Map)
import Hanafuda (Card, Flower, Pack) import Hanafuda (Card, Flower, Pack)
import Hanafuda.ID (ID) import Hanafuda.ID (ID, IDType(..), Prefix(..))
import qualified Hanafuda.KoiKoi.Yaku as KoiKoi (Score) import qualified Hanafuda.KoiKoi.Yaku as KoiKoi (Score)
import Hanafuda.Player (Players, Player, Scores, set) import qualified Hanafuda.Player as Player (ID, Players, Player, Scores, set)
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 deriving (Show)
data Step = ToPlay | Turned Card | Scored | Over deriving (Show) data Step = ToPlay | Turned Card | Scored | Over deriving (Show)
data Source = Hand | Deck deriving (Show) data Source = Hand | Deck deriving (Show)
data Action = Action { data Action = Action {
@ -37,27 +40,36 @@ 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 Player = Player.Player KoiKoi.Score
type PlayerID = Player.ID KoiKoi.Score type PlayerID = Player.ID KoiKoi.Score
type Players = Player.Players KoiKoi.Score
type Scores = Player.Scores KoiKoi.Score
type GameID = ID Game type GameID = ID Game
instance IDType Game where
prefix = Prefix "Game"
type PlayerTurn = Map PlayerID PlayerID
data GameBlueprint deckType = Game { data Game = Game {
mode :: Mode gameID :: GameID
, scores :: Scores KoiKoi.Score , mode :: Mode
, scores :: Scores
, month :: Flower , month :: Flower
, players :: Players KoiKoi.Score , nextPlayer :: PlayerTurn
, players :: Players
, playing :: PlayerID , playing :: PlayerID
, winning :: PlayerID , winning :: PlayerID
, oyake :: PlayerID , oyake :: PlayerID
, deck :: deckType , deck :: [Card]
, river :: Pack , river :: Pack
, step :: Step , step :: Step
, trick :: [Card] , trick :: [Card]
, rounds :: [(PlayerID, KoiKoi.Score)] , rounds :: [(PlayerID, KoiKoi.Score)]
} deriving (Show) } deriving (Show)
type Game = GameBlueprint [Card]
setPlayer :: Game -> Player KoiKoi.Score -> Game setPlayer :: Game -> Player -> Game
setPlayer game@(Game {players, playing}) player = game {players = set playing player players} setPlayer game@(Game {players, playing}) player = game {
players = Player.set playing player players
}
end :: Monad m => Game -> m Game end :: Monad m => Game -> m Game
end game = return $ game {step = Over} end game = return $ game {step = Over}

View File

@ -6,8 +6,8 @@ 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, GameBlueprint(..), Mode(..), Step(..), end) import Hanafuda.KoiKoi.Game (Game(..), Mode(..), Step(..), end)
import qualified Hanafuda.Player as Player (deal, get, next, score, yakus) import qualified Hanafuda.Player as Player (deal, get, 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)
@ -25,14 +25,14 @@ deal game@(Game {players}) = do
getTriple = (,,) <$> take8 <*> take8 <*> take8 getTriple = (,,) <$> take8 <*> take8 <*> take8
next :: MonadIO m => Game -> m Game next :: MonadIO m => Game -> m Game
next game@(Game {mode, scores, month, players, oyake, winning, rounds}) = next game@(Game {mode, scores, month, players, nextPlayer, oyake, winning, rounds}) =
case mode of case mode of
FirstAt n | n <= newScore -> end scored FirstAt n | n <= newScore -> end scored
FirstAt _ -> continue FirstAt _ -> continue
WholeYear | month == Paulownia -> end scored WholeYear | month == Paulownia -> end scored
WholeYear -> continue WholeYear -> continue
where where
playing = Player.next players oyake playing = nextPlayer ! oyake
winner = Player.get winning players winner = Player.get winning players
newScore = (scores ! winning) + Player.score sumYakus winner newScore = (scores ! winning) + Player.score sumYakus winner
scored = game {scores = insert winning newScore scores, rounds = (winning, Player.yakus winner): rounds} scored = game {scores = insert winning newScore scores, rounds = (winning, Player.yakus winner): rounds}

View File

@ -7,11 +7,12 @@ module Hanafuda.KoiKoi.Turn (
, popNextCard , popNextCard
) where ) where
import Data.Map ((!))
import Hanafuda (Card, Pack, empty, match) 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)
import Hanafuda.KoiKoi.Yaku (meldInto) import Hanafuda.KoiKoi.Yaku (meldInto)
import Hanafuda.KoiKoi.Game (Action(..), Environment, Game, GameBlueprint(..), Source(..), Step(..), setPlayer) import Hanafuda.KoiKoi.Game (Action(..), Environment, Game(..), 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)
@ -56,8 +57,8 @@ end game@(Game {month, trick, playing, players}) card (river, newCaptured) = do
updatedPlayer = player {meld = newMeld, yakus = scored `mappend` (yakus player)} updatedPlayer = player {meld = newMeld, yakus = scored `mappend` (yakus player)}
next :: MonadIO m => Game -> m Game next :: MonadIO m => Game -> m Game
next game@(Game {players, playing}) = next game@(Game {players, nextPlayer, playing}) =
let newPlaying = Player.next players playing in let newPlaying = nextPlayer ! playing in
if hand (Player.get newPlaying players) == empty if hand (Player.get newPlaying players) == empty
then Round.next $ game then Round.next $ game
else return $ game {playing = newPlaying, step = ToPlay} else return $ game {playing = newPlaying, step = ToPlay}

View File

@ -3,42 +3,33 @@
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 qualified Hanafuda.ID as Hanafuda (ID, IDType(..), Prefix(..))
import Data.Map ((!), Map, adjust, elemAt, empty, findMin, fromList, insert, keys, singleton, size) import Data.Map ((!), Map, elemAt, insert, keys, size)
import qualified Data.Map as Map (filter) import qualified Data.Map as Map (fromList, keys)
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(..))
type ID yakus = Hanafuda.ID (Player yakus) type ID yakus = Hanafuda.ID (Player yakus)
instance Hanafuda.IDType (Player yakus) where
prefix = Hanafuda.Prefix "Player"
data Player yakus = Player { data Player yakus = Player {
hand :: Pack hand :: Pack
, meld :: Pack , meld :: Pack
, nextPlayer :: ID yakus
, yakus :: yakus , yakus :: yakus
} deriving (Show) } deriving (Show)
newtype Players yakus = Players (Map (ID yakus) (Player yakus)) deriving (Show) newtype Players yakus = Players (Map (ID yakus) (Player yakus)) deriving (Show)
new :: Monoid yakus => (ID yakus) -> Player yakus new :: Monoid yakus => [Card] -> Player yakus
new nextPlayer = Player { new cards = Player {
hand = packOfCards [] hand = packOfCards cards
, meld = packOfCards [] , meld = packOfCards []
, nextPlayer
, yakus = mempty , yakus = mempty
} }
players :: Monoid yakus => [ID yakus] -> Players yakus players :: Monoid yakus => [ID yakus] -> Players yakus
players [] = Players empty players = Players . Map.fromList . fmap (\playerID -> (playerID, new []))
players [player] = Players $ singleton player $ new player
players (alice:others@(bob:_)) =
let Players playersByID = players others in
let (before, _) = findMin $ Map.filter ((== bob) . nextPlayer) playersByID in
Players $ insert alice (new bob) $ adjust (setNextPlayer alice) before playersByID
where
setNextPlayer nextPlayer player = player {nextPlayer}
next :: Players yakus -> (ID yakus) -> (ID yakus)
next (Players playersByID) = nextPlayer . (playersByID !)
random :: MonadIO m => Players yakus -> m (ID yakus) random :: MonadIO m => Players yakus -> m (ID yakus)
random (Players playersByID) = random (Players playersByID) =
@ -54,12 +45,9 @@ set playerID player (Players playersByID) = Players $ insert playerID player pla
deal :: Monoid yakus => Players yakus -> [[Card]] -> Players yakus deal :: Monoid yakus => Players yakus -> [[Card]] -> Players yakus
deal (Players playersByID) hands = deal (Players playersByID) hands =
Players $ snd $ foldl dealTo (fst $ findMin playersByID, playersByID) hands Players . Map.fromList . zipWith setHand hands $ Map.keys playersByID
where where
setHand cards (Player {nextPlayer}) = setHand cards playerID = (playerID, new cards)
(new nextPlayer) {hand = packOfCards cards}
dealTo (playerID, m) hand =
(nextPlayer $ m ! playerID, adjust (setHand hand) playerID m)
plays :: MonadError String m => Player yakus -> Card -> m (Player yakus) plays :: MonadError String m => Player yakus -> Card -> m (Player yakus)
plays player@(Player {hand}) card = plays player@(Player {hand}) card =
@ -74,4 +62,4 @@ score :: (yakus -> Points) -> Player yakus -> Points
score rater = rater . yakus score rater = rater . yakus
scores :: Players yakus -> [Points] -> Scores yakus scores :: Players yakus -> [Points] -> Scores yakus
scores (Players playersByKey) = fromList . zip (keys playersByKey) scores (Players playersByKey) = Map.fromList . zip (keys playersByKey)