1 {-# LANGUAGE GADTs, FlexibleContexts, Rank2Types, GeneralizedNewtypeDeriving #-}
29 import Control
.Monad
.Prompt
30 import Control
.Monad
.State
34 data Pos
= Pos
{ pX
:: Int, pY
:: Int } deriving (Show, Ord
, Eq
, Ix
)
36 data Cell
= Hidden | Marked | Exploded | Exposed
Int deriving Eq
38 type View
= Array Pos Cell
42 bMines
:: Array Pos
Bool,
47 instance Show Board
where
48 show Board
{ bConfig
= Config
{ cSize
= p
}, bMines
= b
, bView
= v
} =
49 '\n' : unlines [ "|" ++ concat [cell
(Pos x y
) | x
<- [1..pX p
]] ++ " |"
52 cell p | b
! p
= case v
! p
of
56 |
otherwise = case v
! p
of
60 Exposed i
-> ' ' : toEnum (48 + i
) : ""
62 data Config
= Config
{
67 validConfig
:: Config
-> Bool
68 validConfig Config
{ cSize
= sz
@(Pos sX sY
), cMines
= m
} =
69 sX
>= 2 && sY
>= 2 && m
>= 1 && m
< sX
* sY
71 beginner
, intermediate
, expert
:: Config
72 beginner
= Config
{ cSize
= Pos
9 9, cMines
= 10 }
73 intermediate
= Config
{ cSize
= Pos
16 16, cMines
= 40 }
74 expert
= Config
{ cSize
= Pos
30 16, cMines
= 99 }
76 mkBoard
:: Config
-> StdGen -> Board
77 mkBoard cfg
@Config
{ cSize
= sz
@(Pos sX sY
), cMines
= m
} gen
78 |
not (validConfig cfg
) = error "invalid mine config"
81 bView
= listArray (Pos
1 1, sz
) (repeat Hidden
),
82 bMines
= listArray (Pos
1 1, sz
) (pick gen
(sX
* sY
) m
),
86 pick gen n m | r
<= m
= True : pick gen
' (n
-1) (m
-1)
87 |
otherwise = False : pick gen
' (n
-1) m
89 (r
, gen
') = randomR (1, n
) gen
91 neighbours
:: Config
-> Pos
-> [Pos
]
92 neighbours Config
{ cSize
= Pos sX sY
} (Pos x y
) =
93 [ Pos
(x
+ dx
) (y
+ dy
)
94 | dx
<- if x
== 1 then [0..1] else if x
== sX
then [-1..0] else [-1..1],
95 dy
<- if y
== 1 then [0..1] else if y
== sY
then [-1..0] else [-1..1],
98 mines
:: Board
-> Pos
-> Int
99 mines Board
{ bConfig
= cfg
, bMines
= m
} =
100 length . filter (m
!) . neighbours cfg
103 Move
:: Pos
-> Request
Int
104 Mark
:: Pos
-> Request
()
105 GetView
:: Request View
106 GetConfig
:: Request Config
107 TraceMine
:: String -> Request
()
109 move
:: Pos
-> StrategyM
Int
110 move
= StrategyM
. prompt
. Move
112 move_
:: Pos
-> StrategyM
()
113 move_
= (>> return ()) . move
115 mark
:: Pos
-> StrategyM
()
116 mark
= StrategyM
. prompt
. Mark
118 getView
:: StrategyM View
119 getView
= StrategyM
(prompt GetView
)
121 getConfig
:: StrategyM Config
122 getConfig
= StrategyM
(prompt GetConfig
)
124 traceMine
:: String -> StrategyM
()
125 traceMine
= StrategyM
. prompt
. TraceMine
127 data Result a
= Won | Unfinished a | Lost
deriving (Show, Eq
)
129 newtype StrategyM a
= StrategyM
{
130 runStrategyM
:: Prompt Request a
133 data Strategy
= Strategy
{
136 sDescription
:: String,
137 sRun
:: StdGen -> StrategyM
String
140 defaultStrategy
:: Strategy
141 defaultStrategy
= Strategy
{
142 sName
= "<unknown strategy>",
143 sAuthor
= "<unknown author>",
144 sDescription
= "This strategy has no description.",
145 sRun
= \_
-> return "<unimplemented strategy>"
149 Start
:: Board
-> Play
()
150 Update
:: Pos
-> Board
-> Play
()
151 Trace
:: String -> Board
-> Play
()
153 type PlayM a
= StateT Board
(Prompt Play
) (Result a
)
155 playGameP
:: Config
-> StdGen -> StrategyM a
-> Prompt Play
(Result a
, Board
)
156 playGameP cfg gen strategy
= runStateT
(game strategy
) (mkBoard cfg gen
)
158 game
:: StrategyM a
-> PlayM a
160 get
>>= lift
. prompt
. Start
161 runPromptC
(return . Unfinished
) handle
(runStrategyM strategy
)
163 handle
:: Request p
-> (p
-> PlayM a
) -> PlayM a
164 handle GetView cont
= gets bView
>>= cont
165 handle GetConfig cont
= gets bConfig
>>= cont
166 handle
(Move p
) cont
= do
167 b
@Board
{ bMines
= bm
, bView
= bv
, bTodo
= bt
} <- get
168 if bm
! p
then do put b
{ bView
= bv
// [(p
, Exploded
)] }
169 get
>>= lift
. prompt
. Update p
173 _
-> do let n
= mines b p
174 put b
{ bView
= bv
// [(p
, Exposed n
)],
176 get
>>= lift
. prompt
. Update p
177 if bt
== 1 then return Won
else cont n
178 handle
(Mark p
) cont
= do
179 b
@Board
{ bMines
= bm
, bView
= bv
} <- get
180 when (bv
! p
== Hidden
) $ do
181 put b
{ bView
= bv
// [(p
, Marked
)] }
182 get
>>= lift
. prompt
. Update p
187 handle
(TraceMine s
) cont
= get
>>= lift
. prompt
. Trace s
>> cont
()
189 playGame
:: Config
-> StdGen -> StrategyM a
-> (Result a
, Board
)
190 playGame cfg gen strat
= runPrompt handle
(playGameP cfg gen strat
) where
191 handle
:: Play a
-> a
193 handle Update
{} = ()
194 handle
(Trace s b
) = ()
198 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])