summaryrefslogtreecommitdiff
path: root/Game.hs
blob: e6baf6742225b7f58af57150e70970c7e471f300 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
module Game where

import Data.Char (isDigit)
import Control.Monad (mapM_)
import System.Random (RandomGen, getStdGen, newStdGen)
import Player (Player(..), shed, draw)
import qualified Player as P
import Card (Card(..))
import qualified Card as C

data Setup = Setup { playerN :: Int
                   , roundN :: Int
                   , deckN :: Int
                   , cardN :: Int      -- cards dealt to each player
                   , autoMode :: Bool
                   }

data Game = Game { players :: [Player]
                 , playerIdx :: Int
                 , attack :: Int
                 , direction :: Int      -- ^ 1 when CCW, -1 when CW
                 , prevCard :: Card
                 , stockPile :: [Card]
                 , discardPile :: [Card]
                 }

-- | Set up game.
beginGame :: Setup -> IO Game
beginGame setup@(Setup n r d c a) = do
    gen <- getStdGen
    let decks = C.shuffle gen $ C.fullDecks d
    let defaultPlayers = [ Player "Alice" 0 []
                         , Player "Bob"   0 []
                         , Player "Carol" 0 []
                         ]
    let game = Game { players     = defaultPlayers
                    , playerIdx   = 0  -- TODO: determine first player
                    , attack      = 1
                    , direction   = 1
                    , prevCard    = head decks
                    , stockPile   = tail decks
                    , discardPile = []
                    }
    beginRound setup game

-- | Begin round.
beginRound :: Setup -> Game -> IO Game
beginRound (Setup _ 0 _ _ _) game = return game
beginRound (Setup n r d c a) game = do
    game' <- beginTurn a $ dealCards c game
    beginRound (Setup n (r - 1) d c a) game'

-- | Deal c cards to each player in game.
dealCards :: Int -> Game -> Game
dealCards c game@(Game plyrs _ _ _ _ stock disc)
  | (length $ P.cards $ head plyrs) < c =
      dealCards c game { players = newPlyrs, stockPile = newStock }
  | otherwise = game
    where newPlyrs = giveOneCardEach stock plyrs
          newStock = drop c stock
          giveOneCardEach =
              zipWith (\card player -> player { P.cards = card:(P.cards player) })

-- | Let current player take their turn.
beginTurn :: Bool -> Game -> IO Game
beginTurn auto game@(Game plyrs pidx att dir prev stock disc) = do
    let player = plyrs !! pidx

    putStrLn $ replicate 80 '-'
    putStrLn $ P.name player ++ "'s turn (input 0 to skip turn and draw card)"
    -- putStrLn $ P.showCards player prev att
    putStrLn $ "Stock: " ++ (show $ length stock)
            ++ ", Discard: " ++ (show $ length disc)
    putStrLn $ "Current attack: " ++ show att
    putStrLn $ "Prev card: " ++ C.showCard prev

    decision <- if auto
                   then return $ automate game
                   else prompt game
    game' <- case decision of
               Nothing -> drawAndSkip game
               Just card -> shedAndContinue card game

    if playerIdx game' == pidx  -- shedAndContinue does this when player wins
       then endRound game'
       else beginTurn auto game'

-- | Game state after player draws card(s) and skips turn.
drawAndSkip :: Game -> IO Game
drawAndSkip game@(Game plyrs pidx att dir prev stock disc) = do
    gen <- newStdGen
    let stockLongEnough = if length stock < att
                             then stock ++ (C.shuffle gen disc)
                             else stock
    let stock' = drop att stockLongEnough
    let disc' = if length stock < att
                   then []
                   else disc
    let cardsToDraw = take att stockLongEnough
    let player = plyrs !! pidx
    let player' = player `draw` cardsToDraw
    let plyrs' = P.update plyrs pidx player'
    let pidx' = (pidx + dir) `mod` length plyrs
    putStrLn $ (P.name player) ++ " draws " ++ (C.showCards cardsToDraw)
    return $ Game plyrs' pidx' 1 dir prev stock' disc'

-- | Game state after player sheds card.
shedAndContinue :: Card -> Game -> IO Game
shedAndContinue card game@(Game plyrs pidx att dir prev stock disc) = do
    let player = plyrs !! pidx
    let player' = player `shed` card
    let disc' = card:disc
    let plyrs' = P.update plyrs pidx player'
    let dir' = case card of
                 Card _ C.Queen -> negate dir
                 _ -> dir
    let pidx' = if null $ P.cards player'
                   then pidx  -- round ends
                   else case card of
                          Card _ C.Jack -> (pidx + 2 * dir') `mod` length plyrs
                          _ -> (pidx + dir') `mod` length plyrs
    let att' = case card of
                 Card _ C.Two   -> if att == 1 then 2 else att + 2
                 Card _ C.Three -> if att == 1 then 3 else att + 3
                 Card _ C.Seven -> 1
                 _ -> att
    putStrLn $ (P.name player) ++ " plays " ++ (C.showCard card)
    return $ Game plyrs' pidx' att' dir' card stock disc'

-- | Prompt player to play a card (or draw card and skip turn).
prompt :: Game -> IO (Maybe Card)
prompt game@(Game plyrs pidx att _ prev _ _) = do
    let player = plyrs !! pidx
    let cards = P.cards player
    cardIdxStr <- getLine
    if any (== False) $ map isDigit cardIdxStr
       then do
           putStrLn "Please input card #"
           prompt game
       else do
           let cardIdx = read cardIdxStr - 1
           if cardIdx == -1
              then do
                 return Nothing
              else do
                  if cardIdx < 0 || cardIdx >= length cards
                     then do
                         putStrLn "This card does not exist, try again"
                         prompt game
                     else do
                         let card = cards !! cardIdx
                         if C.isValid prev att card
                            then return $ Just card
                            else do
                                putStrLn "You cannot play this card, try again"
                                prompt game

-- | Make an automated decision to draw/shed card.
automate :: Game -> Maybe Card
automate game@(Game plyrs pidx att _ prev _ _) =
    if null validCards
       then Nothing
       else Just (head validCards)
           where validCards = filter (C.isValid prev att) $ P.cards (plyrs !! pidx)

-- | Keep penalties and reset game for next round.
endRound :: Game -> IO Game
endRound game@(Game plyrs pidx _ _ _ stock disc) = do
    putStrLn $ (P.name $ plyrs !! pidx) ++ " wins this round!"
    let plyrs' = P.calcPenalties plyrs
    putStrLn "Penalties:"
    putStrLn $ P.showPenalties plyrs'
    return $ Game plyrs' pidx 1 1 (head stock) (tail stock) disc

wait :: a -> IO a
wait x = getLine >> return x