Handle the end of games
This commit is contained in:
parent
14d58d002e
commit
f1ee562809
6 changed files with 51 additions and 56 deletions
|
@ -1,5 +1,9 @@
|
||||||
# Revision history for hanafuda
|
# Revision history for hanafuda
|
||||||
|
|
||||||
|
## 0.3.2.0 -- 2019-08-12
|
||||||
|
|
||||||
|
* Handle the end of games
|
||||||
|
|
||||||
## 0.3.1.0 -- 2019-01-08
|
## 0.3.1.0 -- 2019-01-08
|
||||||
|
|
||||||
* Generalize the output monad of basic functions by only expressing constraints on it
|
* Generalize the output monad of basic functions by only expressing constraints on it
|
||||||
|
|
|
@ -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.1.0
|
version: 0.3.2.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
|
||||||
|
|
|
@ -7,8 +7,6 @@ module Hanafuda.KoiKoi (
|
||||||
, Environment
|
, Environment
|
||||||
, Mode(..)
|
, Mode(..)
|
||||||
, Move(..)
|
, Move(..)
|
||||||
, On(..)
|
|
||||||
, Over(..)
|
|
||||||
, Score
|
, Score
|
||||||
, Source(..)
|
, Source(..)
|
||||||
, Step(..)
|
, Step(..)
|
||||||
|
@ -20,34 +18,35 @@ 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(..), On(..), Over(..), Source(..), Step(..))
|
import Hanafuda.KoiKoi.Game (Action(..), Environment, Game(..), Mode(..), Move(..), 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 -> On player -> m (Game player)
|
play :: (Environment m, Ord player) => Move -> Game player -> m (Game player)
|
||||||
play move on@(On_ {river, step}) =
|
play move game@(Game {river, step}) =
|
||||||
case (step, move) of
|
case (step, move) of
|
||||||
(ToPlay, Play card) ->
|
(ToPlay, Play card) ->
|
||||||
either throwError (Turn.catch on card) $ match card river
|
either throwError (Turn.catch game card) $ match card river
|
||||||
(ToPlay, Capture (card, caught)) ->
|
(ToPlay, Capture (card, caught)) ->
|
||||||
if card `canCatch` caught
|
if card `canCatch` caught
|
||||||
then Turn.catch on card (remove river caught, [card, caught])
|
then Turn.catch game card (remove river caught, [card, caught])
|
||||||
else throwError "You can't choose that card"
|
else throwError "You can't choose that card"
|
||||||
(Turned card, Choose caught) ->
|
(Turned card, Choose caught) ->
|
||||||
if card `canCatch` caught
|
if card `canCatch` caught
|
||||||
then Turn.end on card (remove river caught, [card, caught])
|
then Turn.end game card (remove river caught, [card, caught])
|
||||||
else throwError "You can't choose that card"
|
else throwError "You can't choose that card"
|
||||||
(Scored, KoiKoi keepOn) -> (if keepOn then Turn.next else Round.next) on
|
(Scored, KoiKoi keepGame) -> (if keepGame then Turn.next else Round.next) game
|
||||||
|
(Over, _) -> throwError "This game is over"
|
||||||
(_, _) -> throwError "You can't play this move in that state"
|
(_, _) -> throwError "You can't play this move in that state"
|
||||||
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 (On player)
|
new :: (MonadIO m, Ord player) => [player] -> Mode -> m (Game player)
|
||||||
new playersList mode = do
|
new playersList mode = do
|
||||||
playing <- Player.random players
|
playing <- Player.random players
|
||||||
Round.deal $ On_ {
|
Round.deal $ Game {
|
||||||
mode
|
mode
|
||||||
, scores = Player.scores players [0, 0]
|
, scores = Player.scores players [0, 0]
|
||||||
, month = Pine
|
, month = Pine
|
||||||
|
@ -59,6 +58,7 @@ new playersList mode = do
|
||||||
, river = undefined
|
, river = undefined
|
||||||
, step = ToPlay
|
, step = ToPlay
|
||||||
, trick = []
|
, trick = []
|
||||||
|
, rounds = []
|
||||||
}
|
}
|
||||||
where
|
where
|
||||||
players = Player.players playersList
|
players = Player.players playersList
|
||||||
|
|
|
@ -7,13 +7,10 @@ module Hanafuda.KoiKoi.Game (
|
||||||
, Environment
|
, Environment
|
||||||
, Mode(..)
|
, Mode(..)
|
||||||
, Move(..)
|
, Move(..)
|
||||||
, On(..)
|
|
||||||
, Over(..)
|
|
||||||
, Source(..)
|
, Source(..)
|
||||||
, Step(..)
|
, Step(..)
|
||||||
, end
|
, end
|
||||||
, setPlayer
|
, setPlayer
|
||||||
, stop
|
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Monad.Except (MonadError)
|
import Control.Monad.Except (MonadError)
|
||||||
|
@ -25,7 +22,7 @@ import Hanafuda.KoiKoi.Yaku (Score)
|
||||||
|
|
||||||
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
|
||||||
data Step = ToPlay | Turned Card | Scored 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 {
|
||||||
source :: Source
|
source :: Source
|
||||||
|
@ -35,7 +32,7 @@ data Action = Action {
|
||||||
|
|
||||||
type Environment m = (MonadIO m, MonadError String m, MonadWriter [Action] m)
|
type Environment m = (MonadIO m, MonadError String m, MonadWriter [Action] m)
|
||||||
|
|
||||||
data On player = On_ {
|
data Game player = Game {
|
||||||
mode :: Mode
|
mode :: Mode
|
||||||
, scores :: Scores player
|
, scores :: Scores player
|
||||||
, month :: Flower
|
, month :: Flower
|
||||||
|
@ -47,18 +44,11 @@ data On player = On_ {
|
||||||
, river :: Pack
|
, river :: Pack
|
||||||
, step :: Step
|
, step :: Step
|
||||||
, trick :: [Card]
|
, trick :: [Card]
|
||||||
} deriving (Show)
|
, rounds :: [(player, Score)]
|
||||||
data Over player = Over_ {
|
|
||||||
finalScores :: Scores player
|
|
||||||
} deriving (Show)
|
} deriving (Show)
|
||||||
|
|
||||||
data Game player = Over (Over player) | On (On player) deriving (Show)
|
setPlayer :: Ord player => Game player -> Player player Score -> Game player
|
||||||
|
setPlayer game@(Game {players, playing}) player = game {players = set playing player players}
|
||||||
|
|
||||||
setPlayer :: Ord player => On player -> Player player Score -> On player
|
end :: Monad m => Game player -> m (Game player)
|
||||||
setPlayer on@(On_ {players, playing}) player = on {players = set playing player players}
|
end game = return $ game {step = Over}
|
||||||
|
|
||||||
end :: Monad m => On player -> m (Game player)
|
|
||||||
end (On_ {scores}) = return . Over $ Over_ {finalScores = scores}
|
|
||||||
|
|
||||||
stop :: Monad m => On player -> m (Game player)
|
|
||||||
stop = return . On
|
|
||||||
|
|
|
@ -6,16 +6,16 @@ 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(..), On(..), Step(..), end, stop)
|
import Hanafuda.KoiKoi.Game (Game(..), Mode(..), Step(..), end)
|
||||||
import qualified Hanafuda.Player as Player (deal, get, next, score)
|
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) => On player -> m (On player)
|
deal :: (MonadIO m, Ord player) => Game player -> m (Game player)
|
||||||
deal on@(On_ {players}) = do
|
deal game@(Game {players}) = do
|
||||||
((hand1, hand2, river), deck) <- runState getTriple <$> shuffle cards
|
((hand1, hand2, river), deck) <- runState getTriple <$> shuffle cards
|
||||||
return on {
|
return game {
|
||||||
players = Player.deal players [hand1, hand2]
|
players = Player.deal players [hand1, hand2]
|
||||||
, deck
|
, deck
|
||||||
, river = packOfCards river
|
, river = packOfCards river
|
||||||
|
@ -24,8 +24,8 @@ deal on@(On_ {players}) = do
|
||||||
take8 = state $ splitAt 8
|
take8 = state $ splitAt 8
|
||||||
getTriple = (,,) <$> take8 <*> take8 <*> take8
|
getTriple = (,,) <$> take8 <*> take8 <*> take8
|
||||||
|
|
||||||
next :: (MonadIO m, Ord player) => On player -> m (Game player)
|
next :: (MonadIO m, Ord player) => Game player -> m (Game player)
|
||||||
next on@(On_ {mode, scores, month, players, oyake, winning}) =
|
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
|
||||||
FirstAt _ -> continue
|
FirstAt _ -> continue
|
||||||
|
@ -33,8 +33,9 @@ next on@(On_ {mode, scores, month, players, oyake, winning}) =
|
||||||
WholeYear -> continue
|
WholeYear -> continue
|
||||||
where
|
where
|
||||||
playing = Player.next players oyake
|
playing = Player.next players oyake
|
||||||
newScore = (scores ! winning) + Player.score sumYakus (Player.get winning players)
|
winner = Player.get winning players
|
||||||
scored = on {scores = insert winning newScore scores}
|
newScore = (scores ! winning) + Player.score sumYakus winner
|
||||||
|
scored = game {scores = insert winning newScore scores, rounds = (winning, Player.yakus winner): rounds}
|
||||||
continue =
|
continue =
|
||||||
deal (scored {
|
deal (scored {
|
||||||
month = succ month
|
month = succ month
|
||||||
|
@ -42,4 +43,4 @@ next on@(On_ {mode, scores, month, players, oyake, winning}) =
|
||||||
, winning = playing
|
, winning = playing
|
||||||
, oyake = playing
|
, oyake = playing
|
||||||
, step = ToPlay
|
, step = ToPlay
|
||||||
}) >>= stop
|
})
|
||||||
|
|
|
@ -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, On(..), Source(..), Step(..), setPlayer, stop)
|
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)
|
||||||
|
@ -27,37 +27,37 @@ log source played cards = tell [
|
||||||
captures [_, captured] = Just captured
|
captures [_, captured] = Just captured
|
||||||
captures _ = Nothing
|
captures _ = Nothing
|
||||||
|
|
||||||
catch :: (Environment m, Ord player) => On player -> Card -> (Pack, [Card]) -> m (Game player)
|
catch :: (Environment m, Ord player) => Game player -> Card -> (Pack, [Card]) -> m (Game player)
|
||||||
catch on@(On_ {players, playing}) card (river, trick) = do
|
catch game@(Game {players, playing}) card (river, trick) = do
|
||||||
log Hand card trick
|
log Hand card trick
|
||||||
(setPlayer (on {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) => On player -> m (Game player)
|
popNextCard :: (Environment m, Ord player) => Game player -> m (Game player)
|
||||||
popNextCard (On_ {deck = []}) = throwError "No more cards in the stack"
|
popNextCard (Game {deck = []}) = throwError "No more cards in the stack"
|
||||||
popNextCard on@(On_ {river, deck = turned : others}) =
|
popNextCard game@(Game {river, deck = turned : others}) =
|
||||||
let pop = on {deck = others} in
|
let pop = game {deck = others} in
|
||||||
case match turned river of
|
case match turned river of
|
||||||
Right (newRiver, newCaptured) -> end pop turned (newRiver, newCaptured)
|
Right (newRiver, newCaptured) -> end pop turned (newRiver, newCaptured)
|
||||||
Left _ -> stop $ pop {step = Turned turned}
|
Left _ -> return $ pop {step = Turned turned}
|
||||||
|
|
||||||
end :: (MonadWriter [Action] m, MonadIO m, Ord player) => On player -> Card -> (Pack, [Card]) -> m (Game player)
|
end :: (MonadWriter [Action] m, MonadIO m, Ord player) => Game player -> Card -> (Pack, [Card]) -> m (Game player)
|
||||||
end on@(On_ {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 (on {river, trick = []}) updatedPlayer
|
let updatedGame = setPlayer (game {river, trick = []}) updatedPlayer
|
||||||
if null scored
|
if null scored
|
||||||
then next updatedGame
|
then next updatedGame
|
||||||
else stop $ updatedGame {step = Scored, winning = playing}
|
else return $ updatedGame {step = Scored, winning = playing}
|
||||||
where
|
where
|
||||||
newTrick = newCaptured ++ trick
|
newTrick = newCaptured ++ trick
|
||||||
player = Player.get playing players
|
player = Player.get playing players
|
||||||
(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) => On player -> m (Game player)
|
next :: (MonadIO m, Ord player) => Game player -> m (Game player)
|
||||||
next on@(On_ {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
|
||||||
then Round.next $ on
|
then Round.next $ game
|
||||||
else stop $ on {playing = newPlaying, step = ToPlay}
|
else return $ game {playing = newPlaying, step = ToPlay}
|
||||||
|
|
Loading…
Reference in a new issue