Package with cabal and implement game automaton
This commit is contained in:
parent
034fee8ecd
commit
dfccff2915
11 changed files with 390 additions and 86 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,2 +1 @@
|
||||||
*.hi
|
/dist/*
|
||||||
*.o
|
|
||||||
|
|
5
ChangeLog.md
Normal file
5
ChangeLog.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# Revision history for hanafuda
|
||||||
|
|
||||||
|
## 0.1.0.0 -- 2018-03-03
|
||||||
|
|
||||||
|
* Game automaton, packaged with cabal
|
30
LICENSE
Normal file
30
LICENSE
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
Copyright (c) 2018, Sasha
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of Sasha nor the names of other
|
||||||
|
contributors may be used to endorse or promote products derived
|
||||||
|
from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
79
Yaku.hs
79
Yaku.hs
|
@ -1,79 +0,0 @@
|
||||||
module Yaku where
|
|
||||||
|
|
||||||
import Card (Card(..), Flower, Pack, size, contains, intersection, packOfCards)
|
|
||||||
import Data.Map (Map, empty, insert, unionWith)
|
|
||||||
|
|
||||||
data Yaku =
|
|
||||||
Goko
|
|
||||||
| Shiko
|
|
||||||
| AmeShiko
|
|
||||||
| Sanko
|
|
||||||
| InoShikaCho
|
|
||||||
| Tane
|
|
||||||
| Akatan
|
|
||||||
| Aotan
|
|
||||||
| Tan
|
|
||||||
| Kasu
|
|
||||||
| TsukimiZake
|
|
||||||
| HanamiZake
|
|
||||||
| TsukiFuda
|
|
||||||
deriving (Eq, Ord, Show)
|
|
||||||
type Points = Int
|
|
||||||
type YakuFinder = Pack -> Maybe (Yaku, Points)
|
|
||||||
type Score = Map Yaku Points
|
|
||||||
|
|
||||||
fixed :: (Yaku, Points) -> Pack -> YakuFinder
|
|
||||||
fixed points indicatorSet pack = if pack == indicatorSet then Just points else Nothing
|
|
||||||
|
|
||||||
moreThan :: Int -> Yaku -> (Pack -> YakuFinder)
|
|
||||||
moreThan count yaku _ pack =
|
|
||||||
let n = size pack - count in
|
|
||||||
if n > 0 then Just (yaku, n) else Nothing
|
|
||||||
|
|
||||||
lights :: [Card]
|
|
||||||
lights = [Crane, CampCurtain, FullMoon, RainMan, Phoenix]
|
|
||||||
|
|
||||||
hikari :: Pack -> YakuFinder
|
|
||||||
hikari _ pack = rate (size pack) (pack `contains` RainMan)
|
|
||||||
where
|
|
||||||
rate 5 _ = Just (Goko, 10)
|
|
||||||
rate 4 hasRainMan = if hasRainMan then Just (AmeShiko, 7) else Just (Shiko, 8)
|
|
||||||
rate n hasRainMan = if not hasRainMan && n > 2 then Just (Sanko, 5) else Nothing
|
|
||||||
|
|
||||||
tsukiFuda :: Flower -> ([Card], Pack -> YakuFinder)
|
|
||||||
tsukiFuda flower = (map toEnum $ map (fromEnum flower * 4 +) [0..3], fixed (TsukiFuda, 8))
|
|
||||||
|
|
||||||
index :: ([Card], Pack -> YakuFinder) -> Map Card [YakuFinder]
|
|
||||||
index (cards, scorer) =
|
|
||||||
let pack = packOfCards cards in
|
|
||||||
foldl (\map card -> insert card [scorer pack . intersection pack] map) empty cards
|
|
||||||
|
|
||||||
inoshikacho :: [Card]
|
|
||||||
inoshikacho = [Butterflies, Boar, Deer]
|
|
||||||
|
|
||||||
animals :: [Card]
|
|
||||||
animals = [BushWarbler, Cuckoo, EightPlankBridge, Geese, SakeCup, Swallow] ++ inoshikacho
|
|
||||||
|
|
||||||
blue :: [Card]
|
|
||||||
blue = [PeonyBlue, ChrysanthemumBlue, MapleBlue]
|
|
||||||
|
|
||||||
poetry :: [Card]
|
|
||||||
poetry = [PinePoetry, PlumPoetry, CherryPoetry]
|
|
||||||
|
|
||||||
ribbons = [WisteriaRed, IrisRed, BushCloverRed, WillowRed] ++ blue ++ poetry
|
|
||||||
|
|
||||||
plain :: [Card]
|
|
||||||
plain = (foldl (++) [] [map toEnum [4*i, 4*i+1] | i <- [0..10]]) ++ Lightning : [Paulownia0 .. Sand]
|
|
||||||
|
|
||||||
allYakus :: Map Card [YakuFinder]
|
|
||||||
allYakus = foldl (\map -> unionWith (++) map . index) empty [
|
|
||||||
(lights, hikari)
|
|
||||||
, (inoshikacho, fixed (InoShikaCho, 5))
|
|
||||||
, (animals, moreThan 4 Tane)
|
|
||||||
, (poetry, fixed (Akatan, 5))
|
|
||||||
, (blue, fixed (Aotan, 5))
|
|
||||||
, (ribbons, moreThan 4 Tan)
|
|
||||||
, (plain, moreThan 9 Kasu)
|
|
||||||
, ([SakeCup, FullMoon], fixed (TsukimiZake, 5))
|
|
||||||
, ([SakeCup, CampCurtain], fixed (HanamiZake, 5))
|
|
||||||
]
|
|
33
hanafuda.cabal
Normal file
33
hanafuda.cabal
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
-- Initial hanafuda.cabal generated by cabal init. For further
|
||||||
|
-- documentation, see http://haskell.org/cabal/users-guide/
|
||||||
|
|
||||||
|
name: hanafuda
|
||||||
|
version: 0.1.0.0
|
||||||
|
synopsis: A game of Hanafuda (a family of japanese card games)
|
||||||
|
-- description:
|
||||||
|
homepage: https://framagit.org/sasha/hanafuda
|
||||||
|
license: BSD3
|
||||||
|
license-file: LICENSE
|
||||||
|
author: Sasha
|
||||||
|
maintainer: sasha+frama@marvid.fr
|
||||||
|
-- copyright:
|
||||||
|
category: Game
|
||||||
|
build-type: Simple
|
||||||
|
extra-source-files: ChangeLog.md
|
||||||
|
cabal-version: >=1.10
|
||||||
|
|
||||||
|
library
|
||||||
|
exposed-modules: Hanafuda.Year
|
||||||
|
, Hanafuda.Month
|
||||||
|
, Hanafuda.Day
|
||||||
|
, Hanafuda.Game
|
||||||
|
, Hanafuda.Yaku
|
||||||
|
, Hanafuda.Card
|
||||||
|
-- other-modules:
|
||||||
|
-- other-extensions:
|
||||||
|
build-depends: base >=4.10 && <4.11
|
||||||
|
, containers
|
||||||
|
, mtl
|
||||||
|
, random
|
||||||
|
hs-source-dirs: src
|
||||||
|
default-language: Haskell2010
|
|
@ -1,4 +1,4 @@
|
||||||
module Card where
|
module Hanafuda.Card where
|
||||||
|
|
||||||
import Data.Word (Word64)
|
import Data.Word (Word64)
|
||||||
import Data.Bits (
|
import Data.Bits (
|
||||||
|
@ -14,6 +14,7 @@ import Data.Bits (
|
||||||
, countTrailingZeros
|
, countTrailingZeros
|
||||||
)
|
)
|
||||||
import System.Random (randomRIO)
|
import System.Random (randomRIO)
|
||||||
|
import Control.Monad.Reader (Reader)
|
||||||
|
|
||||||
data Flower =
|
data Flower =
|
||||||
Pine
|
Pine
|
||||||
|
@ -48,13 +49,15 @@ data Card =
|
||||||
flower :: Card -> Flower
|
flower :: Card -> Flower
|
||||||
flower = toEnum . (`div` 4) . fromEnum
|
flower = toEnum . (`div` 4) . fromEnum
|
||||||
|
|
||||||
|
type Monthly a = Reader Flower a
|
||||||
|
|
||||||
type Pack = Word64
|
type Pack = Word64
|
||||||
|
|
||||||
empty :: Pack
|
empty :: Pack
|
||||||
empty = 0
|
empty = 0
|
||||||
|
|
||||||
packOfCards :: [Card] -> Pack
|
packOfCards :: [Card] -> Pack
|
||||||
packOfCards = foldl setBit 0 . map fromEnum
|
packOfCards = foldl add 0
|
||||||
|
|
||||||
smallest :: Pack -> Card
|
smallest :: Pack -> Card
|
||||||
smallest = toEnum . countTrailingZeros
|
smallest = toEnum . countTrailingZeros
|
||||||
|
@ -106,10 +109,10 @@ shuffle l =
|
||||||
let (top, bottom) = splitAt cut shuffled
|
let (top, bottom) = splitAt cut shuffled
|
||||||
return $ top ++ h : bottom
|
return $ top ++ h : bottom
|
||||||
|
|
||||||
pair :: Card -> Pack -> Maybe (Pack, Pack)
|
pair :: Card -> Pack -> Maybe (Pack, [Card])
|
||||||
pair card pack =
|
pair card pack =
|
||||||
let sameMonthCards = sameMonth card `intersection` pack in
|
let sameMonthCards = sameMonth card `intersection` pack in
|
||||||
case size sameMonthCards of
|
case size sameMonthCards of
|
||||||
0 -> Just (add pack card, empty)
|
0 -> Just (add pack card, [])
|
||||||
1 -> Just (difference pack sameMonthCards, add sameMonthCards card)
|
1 -> Just (difference pack sameMonthCards, card : cardsOfPack sameMonthCards)
|
||||||
_ -> Nothing
|
_ -> Nothing
|
62
src/Hanafuda/Day.hs
Normal file
62
src/Hanafuda/Day.hs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
|
module Hanafuda.Day where
|
||||||
|
|
||||||
|
import Hanafuda.Card (Card, Pack, Flower(Pine), contains, flower, packOfCards, pair, remove)
|
||||||
|
import Hanafuda.Yaku (rate)
|
||||||
|
import Hanafuda.Game (Game(..), Move(..), PlayerState, meld, yakus)
|
||||||
|
import Data.Map (Map, adjust, empty, fromList, insert, union, (!))
|
||||||
|
import Control.Monad.Reader (runReader)
|
||||||
|
|
||||||
|
data Step = ToPlay | ToChoose (Maybe Card) | Scored | Over Bool
|
||||||
|
|
||||||
|
data Day = Day {
|
||||||
|
river :: Pack
|
||||||
|
, month :: Flower
|
||||||
|
, player :: PlayerState
|
||||||
|
, next :: Card
|
||||||
|
, step :: Step
|
||||||
|
, trick :: [Card]
|
||||||
|
}
|
||||||
|
|
||||||
|
new :: Pack -> PlayerState -> Card -> Day
|
||||||
|
new river player next = Day {
|
||||||
|
river
|
||||||
|
, month = Pine
|
||||||
|
, player
|
||||||
|
, next
|
||||||
|
, step = ToPlay
|
||||||
|
, trick = []
|
||||||
|
}
|
||||||
|
|
||||||
|
instance Game Day Day where
|
||||||
|
play day@(Day {river, step, next, trick}) move =
|
||||||
|
case (step, move) of
|
||||||
|
(ToPlay, Play card) ->
|
||||||
|
case pair card river of
|
||||||
|
Just (newRiver, newCaptured) -> turnOver $ day {river = newRiver, trick = newCaptured}
|
||||||
|
Nothing -> Right $ day {step = ToChoose (Just card)}
|
||||||
|
(ToChoose maybeCard, Choose caught) ->
|
||||||
|
let (continue, card) = maybe (end, next) ((,) turnOver) maybeCard in
|
||||||
|
if card `canCatch` caught
|
||||||
|
then turnOver $
|
||||||
|
day {river = remove river caught, trick = [card, caught] ++ trick}
|
||||||
|
else Left "You can't choose that card"
|
||||||
|
(Scored, KoiKoi win) -> Right $ day {step = Over win}
|
||||||
|
(_, _) -> Left "You can't play this move in that state"
|
||||||
|
where
|
||||||
|
canCatch card1 card2 = flower card1 == flower card2 && river `contains` card2
|
||||||
|
|
||||||
|
turnOver :: Day -> Either String Day
|
||||||
|
turnOver day@(Day {river, next, trick}) =
|
||||||
|
case pair next river of
|
||||||
|
Just (newRiver, newCaptured) -> end $ day {river = newRiver, trick = trick ++ newCaptured}
|
||||||
|
Nothing -> Right $ day {step = ToChoose Nothing}
|
||||||
|
|
||||||
|
end :: Day -> Either String Day
|
||||||
|
end day@(Day {month, trick, player}) =
|
||||||
|
let (scored, newMeld) = runReader (rate (meld player) trick) month in
|
||||||
|
let updatedPlayer = day {player = player {meld = newMeld, yakus = scored `union` (yakus player)}} in
|
||||||
|
Right $ if null scored
|
||||||
|
then updatedPlayer {step = Over False}
|
||||||
|
else updatedPlayer {step = Scored}
|
39
src/Hanafuda/Game.hs
Normal file
39
src/Hanafuda/Game.hs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
|
module Hanafuda.Game where
|
||||||
|
|
||||||
|
import Data.Map (Map, empty, fromList)
|
||||||
|
import Hanafuda.Card (Card, Pack, packOfCards)
|
||||||
|
import Hanafuda.Yaku (Score, Points)
|
||||||
|
|
||||||
|
data Player =
|
||||||
|
Player1
|
||||||
|
| Player2
|
||||||
|
deriving (Eq, Ord)
|
||||||
|
|
||||||
|
next :: Player -> Player
|
||||||
|
next Player1 = Player2
|
||||||
|
next _ = Player1
|
||||||
|
|
||||||
|
data PlayerState = PlayerState {
|
||||||
|
hand :: Pack
|
||||||
|
, meld :: Pack
|
||||||
|
, yakus :: Score
|
||||||
|
}
|
||||||
|
type Players = Map Player PlayerState
|
||||||
|
|
||||||
|
initPlayers :: [Card] -> [Card] -> Players
|
||||||
|
initPlayers hand1 hand2 =
|
||||||
|
fromList [(Player1, player hand1), (Player2, player hand2)]
|
||||||
|
where
|
||||||
|
player cards = PlayerState {
|
||||||
|
hand = packOfCards cards
|
||||||
|
, meld = packOfCards []
|
||||||
|
, yakus = empty
|
||||||
|
}
|
||||||
|
|
||||||
|
data Move = Play Card | Choose Card | KoiKoi Bool
|
||||||
|
|
||||||
|
type Scores = Map Player Points
|
||||||
|
|
||||||
|
class Game a b where
|
||||||
|
play :: a -> Move -> Either String b
|
66
src/Hanafuda/Month.hs
Normal file
66
src/Hanafuda/Month.hs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
|
{-# LANGUAGE TypeSynonymInstances #-}
|
||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
module Hanafuda.Month where
|
||||||
|
|
||||||
|
import Hanafuda.Card (Card, Flower(Pine), cards, shuffle, packOfCards)
|
||||||
|
import Hanafuda.Day (Day(..))
|
||||||
|
import qualified Hanafuda.Day as Day (Step(Over), next, new)
|
||||||
|
import Hanafuda.Game (Game(..), Move, Player, Players, PlayerState(..), initPlayers)
|
||||||
|
import qualified Hanafuda.Game as Game (next)
|
||||||
|
import Data.Map ((!), insert)
|
||||||
|
import Control.Monad.State (replicateM, runState, state)
|
||||||
|
|
||||||
|
data On = On {
|
||||||
|
flower :: Flower
|
||||||
|
, players :: Players
|
||||||
|
, day :: Day
|
||||||
|
, playing :: Player
|
||||||
|
, lastScored :: Player
|
||||||
|
, oyake :: Player
|
||||||
|
, stock :: [Card]
|
||||||
|
}
|
||||||
|
data Over = Over {
|
||||||
|
winner :: Player
|
||||||
|
, score :: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Month = Either Over On
|
||||||
|
|
||||||
|
new :: Player -> [Card] -> On
|
||||||
|
new playing shuffled =
|
||||||
|
On {
|
||||||
|
flower = Pine
|
||||||
|
, players
|
||||||
|
, day = Day.new (packOfCards river) (players ! playing) next
|
||||||
|
, playing
|
||||||
|
, lastScored = playing
|
||||||
|
, oyake = playing
|
||||||
|
, stock
|
||||||
|
}
|
||||||
|
where
|
||||||
|
take8 = state $ splitAt 8
|
||||||
|
([hand1, hand2, river], next:stock) = runState (replicateM 3 take8) shuffled
|
||||||
|
players = initPlayers hand1 hand2
|
||||||
|
|
||||||
|
next :: On -> IO On
|
||||||
|
next month@(On {flower, oyake}) = do
|
||||||
|
shuffled <- shuffle cards
|
||||||
|
return $ (new (Game.next oyake) cards) {flower = succ flower}
|
||||||
|
|
||||||
|
instance Game On Month where
|
||||||
|
play month@(On {flower, day, playing, players, stock = next : moreStock}) move =
|
||||||
|
fmap after $ play day move
|
||||||
|
where
|
||||||
|
after (Day {step = Day.Over True, player = PlayerState {yakus}}) =
|
||||||
|
Left $ Over {winner = playing, score = foldl (+) 0 yakus}
|
||||||
|
after (Day {step = Day.Over False, player, river}) =
|
||||||
|
let otherPlayer = Game.next playing in
|
||||||
|
Right $ month {
|
||||||
|
players = insert playing player players
|
||||||
|
, playing = otherPlayer
|
||||||
|
, day = (Day.new river (players ! otherPlayer) next) { month = flower }
|
||||||
|
, stock = moreStock
|
||||||
|
}
|
||||||
|
after newDay = Right $ month {day = newDay}
|
90
src/Hanafuda/Yaku.hs
Normal file
90
src/Hanafuda/Yaku.hs
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
|
module Hanafuda.Yaku where
|
||||||
|
|
||||||
|
import Hanafuda.Card (Card(..), Flower, Monthly, Pack, add, contains, intersection, packOfCards, size)
|
||||||
|
import qualified Data.Map as M (Map, empty, insert, unionWith, (!))
|
||||||
|
import qualified Data.Set as S (Set, empty, insert, singleton, union)
|
||||||
|
import Control.Monad.Reader (reader)
|
||||||
|
|
||||||
|
data Yaku =
|
||||||
|
Lights
|
||||||
|
| InoShikaCho
|
||||||
|
| Tane
|
||||||
|
| Akatan
|
||||||
|
| Aotan
|
||||||
|
| Tan
|
||||||
|
| Kasu
|
||||||
|
| TsukimiZake
|
||||||
|
| HanamiZake
|
||||||
|
| TsukiFuda
|
||||||
|
deriving (Eq, Ord, Show)
|
||||||
|
type YakuRater = Pack -> Maybe Points
|
||||||
|
type Points = Int
|
||||||
|
type Score = M.Map Yaku Points
|
||||||
|
|
||||||
|
data YakuFinder = YakuFinder {
|
||||||
|
yaku :: Yaku
|
||||||
|
, rater :: YakuRater
|
||||||
|
}
|
||||||
|
|
||||||
|
instance Eq YakuFinder where
|
||||||
|
a == b = yaku a == yaku b
|
||||||
|
|
||||||
|
instance Ord YakuFinder where
|
||||||
|
compare a b = compare (yaku a) (yaku b)
|
||||||
|
|
||||||
|
type YakuByCard = M.Map Card (S.Set YakuFinder)
|
||||||
|
|
||||||
|
lights :: Pack -> YakuRater
|
||||||
|
lights _ pack = rate (size pack) (pack `contains` RainMan)
|
||||||
|
where
|
||||||
|
goko = 10
|
||||||
|
shiko = 8
|
||||||
|
ameshiko = 7
|
||||||
|
sanko = 5
|
||||||
|
rate 5 _ = Just goko
|
||||||
|
rate 4 hasRainMan = if hasRainMan then Just ameshiko else Just shiko
|
||||||
|
rate n hasRainMan = if not hasRainMan && n > 2 then Just sanko else Nothing
|
||||||
|
|
||||||
|
fixed :: Points -> Pack -> YakuRater
|
||||||
|
fixed points indicatorSet pack = if pack == indicatorSet then Just points else Nothing
|
||||||
|
|
||||||
|
moreThan :: Int -> (Pack -> YakuRater)
|
||||||
|
moreThan count _ pack =
|
||||||
|
let n = size pack - count in
|
||||||
|
if n > 0 then Just n else Nothing
|
||||||
|
|
||||||
|
index :: (Yaku, [Card], (Pack -> YakuRater)) -> YakuByCard
|
||||||
|
index (yaku, cards, scorer) =
|
||||||
|
let pack = packOfCards cards in
|
||||||
|
let yakuFinder = YakuFinder {yaku, rater = scorer pack . intersection pack} in
|
||||||
|
foldl (\map card -> M.insert card (S.singleton yakuFinder) map) M.empty cards
|
||||||
|
|
||||||
|
finders :: Monthly YakuByCard
|
||||||
|
finders = do
|
||||||
|
monthCardPlus <- reader $ (+) . (4*) . fromEnum
|
||||||
|
return $ foldl (\map -> M.unionWith S.union map . index) M.empty [
|
||||||
|
(Lights, [Crane, CampCurtain, FullMoon, RainMan, Phoenix], lights)
|
||||||
|
, (InoShikaCho, inoshikacho, fixed 5)
|
||||||
|
, (Tane, [BushWarbler, Cuckoo, EightPlankBridge, Geese, SakeCup, Swallow] ++ inoshikacho, moreThan 4)
|
||||||
|
, (Akatan, akatan, fixed 5)
|
||||||
|
, (Aotan, aotan, fixed 5)
|
||||||
|
, (Tan, [WisteriaRed, IrisRed, BushCloverRed, WillowRed] ++ aotan ++ akatan, moreThan 4)
|
||||||
|
, (Kasu, (foldl (++) [] [map toEnum [4*i, 4*i+1] | i <- [0..10]]) ++ Lightning : [Paulownia0 .. Sand], moreThan 9)
|
||||||
|
, (TsukimiZake, [SakeCup, FullMoon], fixed 3)
|
||||||
|
, (HanamiZake, [SakeCup, CampCurtain], fixed 3)
|
||||||
|
, (TsukiFuda, map (toEnum . monthCardPlus) [0..3], fixed 5)
|
||||||
|
]
|
||||||
|
where
|
||||||
|
inoshikacho = [Butterflies, Boar, Deer]
|
||||||
|
aotan = [PeonyBlue, ChrysanthemumBlue, MapleBlue]
|
||||||
|
akatan = [PinePoetry, PlumPoetry, CherryPoetry]
|
||||||
|
|
||||||
|
rate :: Pack -> [Card] -> Monthly (Score, Pack)
|
||||||
|
rate pack cards = do
|
||||||
|
yakusToCheck <- fmap toCheck finders
|
||||||
|
return (foldl scored M.empty yakusToCheck, newPack)
|
||||||
|
where
|
||||||
|
newPack = foldl add pack cards
|
||||||
|
toCheck map = foldl (\set key -> S.union set (map M.! key)) S.empty cards
|
||||||
|
scored score (YakuFinder {yaku, rater}) = foldr (M.insert yaku) score $ rater newPack
|
56
src/Hanafuda/Year.hs
Normal file
56
src/Hanafuda/Year.hs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
|
{-# LANGUAGE TypeSynonymInstances #-}
|
||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
module Hanafuda.Year where
|
||||||
|
|
||||||
|
import Hanafuda.Card (Flower(Paulownia), cards, shuffle)
|
||||||
|
import Hanafuda.Month (flower, next, score, winner)
|
||||||
|
import qualified Hanafuda.Month as Month (On(..), Over(..), new)
|
||||||
|
import Hanafuda.Game (Game(..), Player(Player1), Scores)
|
||||||
|
import Data.Map (Map, empty, insert, (!))
|
||||||
|
import System.Random (StdGen)
|
||||||
|
|
||||||
|
data Mode = FirstAt Int | WholeYear
|
||||||
|
|
||||||
|
data On = On {
|
||||||
|
mode :: Mode
|
||||||
|
, month :: Month.On
|
||||||
|
, scores :: Scores
|
||||||
|
}
|
||||||
|
data Over = Over {
|
||||||
|
finalScores :: Scores
|
||||||
|
}
|
||||||
|
|
||||||
|
type Year = IO (Either Over On)
|
||||||
|
|
||||||
|
new :: Mode -> Year
|
||||||
|
new mode = do
|
||||||
|
shuffled <- shuffle cards
|
||||||
|
return . Right $ On {
|
||||||
|
mode
|
||||||
|
, month = Month.new Player1 shuffled
|
||||||
|
, scores = empty
|
||||||
|
}
|
||||||
|
|
||||||
|
consolidate :: On -> Player -> Int -> Year
|
||||||
|
consolidate year@(On {mode, month, scores}) winner score =
|
||||||
|
case mode of
|
||||||
|
FirstAt n | n <= newScore -> over
|
||||||
|
FirstAt n -> continue
|
||||||
|
WholeYear | flower month == Paulownia -> over
|
||||||
|
WholeYear -> continue
|
||||||
|
where
|
||||||
|
newScore = scores ! winner + score
|
||||||
|
newScores = insert winner newScore scores
|
||||||
|
over = return . Left $ Over {finalScores = newScores}
|
||||||
|
continue = do
|
||||||
|
nextMonth <- next month
|
||||||
|
return . Right $ year {scores = newScores, month = nextMonth}
|
||||||
|
|
||||||
|
instance Game On Year where
|
||||||
|
play year@(On {mode, month}) move =
|
||||||
|
fmap after $ play month move
|
||||||
|
where
|
||||||
|
after (Left (Month.Over {winner, score})) = consolidate year winner score
|
||||||
|
after (Right newMonth) = return . Right $ year {month = newMonth}
|
Loading…
Reference in a new issue