-
Notifications
You must be signed in to change notification settings - Fork 0
/
Main.hs
230 lines (207 loc) · 7.4 KB
/
Main.hs
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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
{-# LANGUAGE ExistentialQuantification,
FlexibleContexts,
RecordWildCards #-}
module Main where
import Control.Comonad
import Control.Monad (forM, forM_, void)
import Data.Time
import Graphics.UI.WX hiding (Event, space)
import System.Exit
import Conway
import ListZipper
import Plane
import Torus
import Universe (Universe, Matrix(..))
import qualified Universe
data Creation = forall a. (Comonad a, Matrix a Bool, Universe a Bool) =>
Creation (a Bool)
evolve' (Creation u) = Creation (evolve u)
up' (Creation u) = Creation (Universe.up u)
down' (Creation u) = Creation (Universe.down u)
left' (Creation u) = Creation (Universe.left u)
right' (Creation u) = Creation (Universe.right u)
god :: Int -> Int -> (Creation -> Creation) -> Creation -> Creation
god 0 0 backtrack (Creation u) =
backtrack $
if Universe.get u
then
Creation $ Universe.set False u
else
Creation $ Universe.set True u
god 0 y b c = god 0 (y-1) (b . up') (down' c)
god x y b c = god (x-1) y (b . left') (right' c)
ankh :: Int -> Int -> (Creation -> Creation) -> Creation -> Creation
ankh 0 0 backtrack (Creation u) =
backtrack $ Creation $ Universe.set True u
ankh 0 y b c = ankh 0 (y-1) (b . up') (down' c)
ankh x y b c = ankh (x-1) y (b . left') (right' c)
maxXDim = 200
maxYDim = 200
minXDim = 3
minYDim = 3
maxSpeed = 2
data State =
State
{ stateUniverse :: Creation
, stateBigBang :: Creation
, stateFrame :: Frame ()
, statePanel :: Panel ()
, stateTimer :: Timer
, stateRunning :: Bool
, stateSpeed :: Int
, stateXDim :: Int
, stateYDim :: Int
, stateLastFrame :: UTCTime }
main = do
let universe = space 100 100 :: Torus Bool
start $ do
f <- frame [ text := "Game of life"
, outerSize := Size 500 500
, closeable := True
, on closing := exitSuccess ]
p <- panel f [ bgcolor := black ]
t <- timer f [ interval := 100 ]
s <- getCurrentTime
var <- varCreate $
State
(Creation universe)
(Creation universe)
f p t True 100 50 50 s
set t [ on command := evolveUniverse var ]
set p [ on paint := paintGrid var
, on mouse := clickGrid var
, on keyboard := keyboardGrid var ]
paintGrid :: Var State -> DC b -> Rect -> IO ()
paintGrid var dc area = do
State {..} <- varGet var
now <- getCurrentTime
let diff = diffUTCTime now stateLastFrame
varUpdate var (\s -> s { stateLastFrame = now })
drawText dc (show diff) (Point 0 0) [ textColor := white ]
Creation universe <- return stateUniverse
let xDim = stateXDim
yDim = stateYDim
height = rectHeight area `quot` yDim
width = rectWidth area `quot` xDim
pVar <- varCreate (Rect 0 0 width height)
forM_ (take yDim . iterate Universe.down $ universe) $ \universe' -> do
forM_ (take xDim . iterate Universe.right $ universe') $ \universe'' -> do
let state = extract universe''
pos <- varGet pVar
when state $ do
drawRect dc pos [ bgcolor := white ]
varUpdate pVar (\p -> p { rectLeft = rectLeft p + width })
varUpdate pVar (\p -> p { rectTop = rectTop p + height,
rectLeft = 0 })
keyboardGrid :: Var State -> EventKey -> IO ()
keyboardGrid var (EventKey key Modifiers {..} _) = do
case key of
KeySpace -> do
State {..} <- varGet var
if stateRunning
then do
set stateTimer [ enabled := False ]
varUpdate var (\s@(State {..}) -> s { stateRunning = False })
return ()
else do
set stateTimer [ enabled := True ]
varUpdate var (\s@(State {..}) -> s { stateRunning = True })
return ()
KeyUp -> do
varUpdate var (\s@(State {..}) -> s { stateUniverse = down' stateUniverse })
State {..} <- varGet var
repaint statePanel
return ()
KeyDown -> do
varUpdate var (\s@(State {..}) -> s { stateUniverse = up' stateUniverse })
State {..} <- varGet var
repaint statePanel
return ()
KeyLeft -> do
varUpdate var (\s@(State {..}) -> s { stateUniverse = right' stateUniverse })
State {..} <- varGet var
repaint statePanel
return ()
KeyRight -> do
varUpdate var (\s@(State {..}) -> s { stateUniverse = left' stateUniverse })
State {..} <- varGet var
repaint statePanel
return ()
KeyChar 'z' -> do
State {..} <- varGet var
if altDown
then do
let newXDim = min maxXDim $ stateXDim + 2
newYDim = min maxYDim $ stateYDim + 2
when (newXDim /= stateXDim && newYDim /= stateYDim) $ do
varUpdate var (\s@(State {..}) ->
s { stateUniverse = up' . left' $ stateUniverse
, stateXDim = newXDim
, stateYDim = newYDim })
repaint statePanel
return ()
else do
let newXDim = max minXDim $ stateXDim - 2
newYDim = max minYDim $ stateYDim - 2
when (newXDim /= stateXDim && newYDim /= stateYDim) $ do
varUpdate var (\s@(State {..}) ->
s { stateUniverse = down' . right' $ stateUniverse
, stateXDim = newXDim
, stateYDim = newYDim })
repaint statePanel
return ()
KeyChar '+' -> do
State {..} <- varGet var
let newSpeed = max maxSpeed $ stateSpeed - 10
set stateTimer [ interval := newSpeed ]
varUpdate var (\s@(State {..}) -> s { stateSpeed = newSpeed })
return ()
KeyChar '-' -> do
State {..} <- varGet var
let newSpeed = stateSpeed + 10
set stateTimer [ interval := newSpeed ]
varUpdate var (\s@(State {..}) -> s { stateSpeed = newSpeed })
return ()
KeyChar 'c' -> do
State {..} <- varGet var
varUpdate var (\s@(State {..}) -> s { stateUniverse = stateBigBang })
repaint statePanel
_ -> return ()
clickGrid :: Var State -> EventMouse -> IO ()
clickGrid var event = do
case event of
MouseLeftDown Point {..} _ -> do
State {..} <- varGet var
Creation universe <- return stateUniverse
area <- get statePanel outerSize
let grid = toMatrix universe
xDim = min stateXDim (head . fmap length $ grid)
yDim = min stateYDim (length grid)
height = sizeH area `quot` yDim
width = sizeW area `quot` xDim
x = max 0 $ pointX `quot` width
y = max 0 $ pointY `quot` height
varUpdate var (\s@(State {..}) ->
s { stateUniverse = god x y id stateUniverse })
repaint statePanel
MouseLeftDrag Point {..} _ -> do
State {..} <- varGet var
Creation universe <- return stateUniverse
area <- get statePanel outerSize
let grid = toMatrix universe
xDim = min stateXDim (head . fmap length $ grid)
yDim = min stateYDim (length grid)
height = sizeH area `quot` yDim
width = sizeW area `quot` xDim
x = max 0 $ pointX `quot` width
y = max 0 $ pointY `quot` height
varUpdate var (\s@(State {..}) ->
s { stateUniverse = ankh x y id stateUniverse })
repaint statePanel
_ -> return ()
evolveUniverse :: Var State -> IO ()
evolveUniverse var = do
State {..} <- varGet var
varUpdate var (\s@(State {..}) ->
s { stateUniverse = evolve' stateUniverse })
repaint statePanel