1 {-# LANGUAGE GADTs, FlexibleContexts, Rank2Types, GeneralizedNewtypeDeriving #-}
28 import Control
.Monad
.Prompt
29 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 beginner
, intermediate
, expert
:: Config
68 beginner
= Config
{ cSize
= Pos
9 9, cMines
= 10 }
69 intermediate
= Config
{ cSize
= Pos
16 16, cMines
= 40 }
70 expert
= Config
{ cSize
= Pos
30 16, cMines
= 99 }
72 mkBoard
:: Config
-> StdGen -> Board
73 mkBoard cfg
@Config
{ cSize
= sz
@(Pos sX sY
), cMines
= m
} gen
74 | sX
< 2 || sY
< 2 || m
< 1 || m
>= sX
* sY
= error "invalid mine config"
77 bView
= listArray (Pos
1 1, sz
) (repeat Hidden
),
78 bMines
= listArray (Pos
1 1, sz
) (pick gen
(sX
* sY
) m
),
82 pick gen n m | r
<= m
= True : pick gen
' (n
-1) (m
-1)
83 |
otherwise = False : pick gen
' (n
-1) m
85 (r
, gen
') = randomR (1, n
) gen
87 neighbours
:: Config
-> Pos
-> [Pos
]
88 neighbours Config
{ cSize
= Pos sX sY
} (Pos x y
) =
89 [ Pos
(x
+ dx
) (y
+ dy
)
90 | dx
<- if x
== 1 then [0..1] else if x
== sX
then [-1..0] else [-1..1],
91 dy
<- if y
== 1 then [0..1] else if y
== sY
then [-1..0] else [-1..1],
94 mines
:: Board
-> Pos
-> Int
95 mines Board
{ bConfig
= cfg
, bMines
= m
} =
96 length . filter (m
!) . neighbours cfg
99 Move
:: Pos
-> Request
Int
100 Mark
:: Pos
-> Request
()
101 GetView
:: Request View
102 GetConfig
:: Request Config
103 TraceMine
:: String -> Request
()
105 move
:: Pos
-> StrategyM
Int
106 move
= StrategyM
. prompt
. Move
108 move_
:: Pos
-> StrategyM
()
109 move_
= (>> return ()) . move
111 mark
:: Pos
-> StrategyM
()
112 mark
= StrategyM
. prompt
. Mark
114 getView
:: StrategyM View
115 getView
= StrategyM
(prompt GetView
)
117 getConfig
:: StrategyM Config
118 getConfig
= StrategyM
(prompt GetConfig
)
120 traceMine
:: String -> StrategyM
()
121 traceMine
= StrategyM
. prompt
. TraceMine
123 data Result a
= Won | Unfinished a | Lost
deriving (Show, Eq
)
125 newtype StrategyM a
= StrategyM
{
126 runStrategyM
:: Prompt Request a
129 data Strategy
= Strategy
{
132 sDescription
:: String,
133 sRun
:: Config
-> StdGen -> StrategyM
String
136 defaultStrategy
:: Strategy
137 defaultStrategy
= Strategy
{
138 sName
= "<unknown strategy>",
139 sAuthor
= "<unknown author>",
140 sDescription
= "This strategy has no description.",
141 sRun
= \_ _
-> return "<unimplemented strategy>"
145 Start
:: Board
-> Play
()
146 Update
:: Pos
-> Board
-> Play
()
147 Trace
:: String -> Board
-> Play
()
149 type PlayM a
= StateT Board
(Prompt Play
) (Result a
)
151 playGameP
:: Config
-> StdGen -> StrategyM a
-> Prompt Play
(Result a
, Board
)
152 playGameP cfg gen strategy
= runStateT
(game strategy
) (mkBoard cfg gen
)
154 game
:: StrategyM a
-> PlayM a
156 get
>>= lift
. prompt
. Start
157 runPromptC
(return . Unfinished
) handle
(runStrategyM strategy
)
159 handle
:: Request p
-> (p
-> PlayM a
) -> PlayM a
160 handle GetView cont
= gets bView
>>= cont
161 handle GetConfig cont
= gets bConfig
>>= cont
162 handle
(Move p
) cont
= do
163 b
@Board
{ bMines
= bm
, bView
= bv
, bTodo
= bt
} <- get
164 if bm
! p
then do put b
{ bView
= bv
// [(p
, Exploded
)] }
165 get
>>= lift
. prompt
. Update p
169 _
-> do let n
= mines b p
170 put b
{ bView
= bv
// [(p
, Exposed n
)],
172 get
>>= lift
. prompt
. Update p
173 if bt
== 1 then return Won
else cont n
174 handle
(Mark p
) cont
= do
175 b
@Board
{ bMines
= bm
, bView
= bv
} <- get
176 when (bv
! p
== Hidden
) $ do
177 put b
{ bView
= bv
// [(p
, Marked
)] }
178 get
>>= lift
. prompt
. Update p
183 handle
(TraceMine s
) cont
= get
>>= lift
. prompt
. Trace s
>>= (cont
$!)
185 playGame
:: Config
-> StdGen -> StrategyM a
-> (Result a
, Board
)
186 playGame cfg gen strat
= runPrompt handle
(playGameP cfg gen strat
) where
187 handle
:: Play a
-> a
189 handle Update
{} = ()
190 handle
(Trace s b
) = trace
(s
++ show b
) ()
194 playGame 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])