3 -- Copyright : (c) 2008 Bertram Felgenhauer
6 -- Maintainer : Bertram Felgenhauer <int-e@gmx.de>
7 -- Stability : experimental
8 -- Portability : portable
10 -- This module is part of Haskell PGMS.
13 {-# LANGUAGE GADTs, FlexibleContexts, Rank2Types, GeneralizedNewtypeDeriving #-}
41 import Control
.Monad
.Prompt
42 import Control
.Monad
.State
46 data Pos
= Pos
{ pX
:: Int, pY
:: Int } deriving (Show, Ord
, Eq
, Ix
)
48 data Cell
= Hidden | Marked | Exploded | Exposed
Int deriving Eq
50 type View
= Array Pos Cell
54 bMines
:: Array Pos
Bool,
59 instance Show Board
where
60 show Board
{ bConfig
= Config
{ cSize
= p
}, bMines
= b
, bView
= v
} =
61 '\n' : unlines [ "|" ++ concat [cell
(Pos x y
) | x
<- [1..pX p
]] ++ " |"
64 cell p | b
! p
= case v
! p
of
68 |
otherwise = case v
! p
of
72 Exposed i
-> ' ' : toEnum (48 + i
) : ""
74 data Config
= Config
{
79 validConfig
:: Config
-> Bool
80 validConfig Config
{ cSize
= sz
@(Pos sX sY
), cMines
= m
} =
81 sX
>= 2 && sY
>= 2 && m
>= 1 && m
< sX
* sY
83 beginner
, intermediate
, expert
:: Config
84 beginner
= Config
{ cSize
= Pos
9 9, cMines
= 10 }
85 intermediate
= Config
{ cSize
= Pos
16 16, cMines
= 40 }
86 expert
= Config
{ cSize
= Pos
30 16, cMines
= 99 }
88 mkBoard
:: Config
-> StdGen -> Board
89 mkBoard cfg
@Config
{ cSize
= sz
@(Pos sX sY
), cMines
= m
} gen
90 |
not (validConfig cfg
) = error "invalid mine config"
93 bView
= listArray (Pos
1 1, sz
) (repeat Hidden
),
94 bMines
= listArray (Pos
1 1, sz
) (pick gen
(sX
* sY
) m
),
98 pick gen n m | r
<= m
= True : pick gen
' (n
-1) (m
-1)
99 |
otherwise = False : pick gen
' (n
-1) m
101 (r
, gen
') = randomR (1, n
) gen
103 neighbours
:: Config
-> Pos
-> [Pos
]
104 neighbours Config
{ cSize
= Pos sX sY
} (Pos x y
) =
105 [ Pos
(x
+ dx
) (y
+ dy
)
106 | dx
<- if x
== 1 then [0..1] else if x
== sX
then [-1..0] else [-1..1],
107 dy
<- if y
== 1 then [0..1] else if y
== sY
then [-1..0] else [-1..1],
110 mines
:: Board
-> Pos
-> Int
111 mines Board
{ bConfig
= cfg
, bMines
= m
} =
112 length . filter (m
!) . neighbours cfg
115 Move
:: Pos
-> Request
Int
116 Mark
:: Pos
-> Request
()
117 GetView
:: Request View
118 GetConfig
:: Request Config
119 TraceMine
:: String -> Request
()
121 move
:: Pos
-> StrategyM
Int
122 move
= StrategyM
. prompt
. Move
124 move_
:: Pos
-> StrategyM
()
125 move_
= (>> return ()) . move
127 mark
:: Pos
-> StrategyM
()
128 mark
= StrategyM
. prompt
. Mark
130 getView
:: StrategyM View
131 getView
= StrategyM
(prompt GetView
)
133 getConfig
:: StrategyM Config
134 getConfig
= StrategyM
(prompt GetConfig
)
136 traceMine
:: String -> StrategyM
()
137 traceMine
= StrategyM
. prompt
. TraceMine
139 data Result a
= Won | Unfinished a | Lost
deriving (Show, Eq
)
141 newtype StrategyM a
= StrategyM
{
142 runStrategyM
:: Prompt Request a
145 data Strategy
= Strategy
{
148 sDescription
:: String,
149 sRun
:: StdGen -> StrategyM
String
152 defaultStrategy
:: Strategy
153 defaultStrategy
= Strategy
{
154 sName
= "<unknown strategy>",
155 sAuthor
= "<unknown author>",
156 sDescription
= "This strategy has no description.",
157 sRun
= \_
-> return "<unimplemented strategy>"
161 Start
:: Board
-> Play
()
162 Update
:: Pos
-> Board
-> Play
()
163 Trace
:: String -> Board
-> Play
()
165 type PlayM a
= StateT Board
(Prompt Play
) (Result a
)
167 playGameP
:: Config
-> StdGen -> StrategyM a
-> Prompt Play
(Result a
, Board
)
168 playGameP cfg gen strategy
= runStateT
(game strategy
) (mkBoard cfg gen
)
170 game
:: StrategyM a
-> PlayM a
172 get
>>= lift
. prompt
. Start
173 runPromptC
(return . Unfinished
) handle
(runStrategyM strategy
)
175 handle
:: Request p
-> (p
-> PlayM a
) -> PlayM a
176 handle GetView cont
= gets bView
>>= cont
177 handle GetConfig cont
= gets bConfig
>>= cont
178 handle
(Move p
) cont
= do
179 b
@Board
{ bMines
= bm
, bView
= bv
, bTodo
= bt
} <- get
180 if bm
! p
then do put b
{ bView
= bv
// [(p
, Exploded
)] }
181 get
>>= lift
. prompt
. Update p
185 _
-> do let n
= mines b p
186 put b
{ bView
= bv
// [(p
, Exposed n
)],
188 get
>>= lift
. prompt
. Update p
189 if bt
== 1 then return Won
else cont n
190 handle
(Mark p
) cont
= do
191 b
@Board
{ bMines
= bm
, bView
= bv
} <- get
192 when (bv
! p
== Hidden
) $ do
193 put b
{ bView
= bv
// [(p
, Marked
)] }
194 get
>>= lift
. prompt
. Update p
199 handle
(TraceMine s
) cont
= get
>>= lift
. prompt
. Trace s
>> cont
()
201 playGame
:: Config
-> StdGen -> StrategyM a
-> (Result a
, Board
)
202 playGame cfg gen strat
= runPrompt handle
(playGameP cfg gen strat
) where
203 handle
:: Play a
-> a
205 handle Update
{} = ()
206 handle
(Trace s b
) = ()
210 playGame True beginner (mkStdGen 164806687) (mark (Pos 9 1) >> mark (Pos 3 4) >> mark (Pos 5 4) >> mark (Pos 1 5) >> mark (Pos 5 5) >> mark (Pos 9 5) >> mark (Pos 1 8) >> mark (Pos 3 8) >> mark (Pos 8 8) >> mark (Pos 3 9) >> getView >>= \l -> sequence [move p | (p, Hidden) <- assocs l])