-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathMain.elm
322 lines (223 loc) · 7.51 KB
/
Main.elm
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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
module Main exposing (..)
-- Main program for Rocket Elm, a project by
-- Jamon Holmgren (https://github.com/jamonholmgren)
-- Learning Elm by trial and fire and lots of mistakes.
-- License is MIT.
import Mover exposing (Mover)
import Ship exposing (Ship, tickShip, shipView, initShip)
import Bullet exposing (Bullet, tickBullets, bulletViews, initBullet)
import Smoke exposing (Smoke, tickSmokes, smokeViews, initSmoke)
import Enemy exposing (Enemy, tickEnemies, enemyAI, enemyViews, initEnemy)
-- import Trig exposing (targetDistance, targetDirection, turnDirection)
import Html exposing (Html, div, p, text, a)
import Html.Attributes exposing (style, href, target)
import Svg exposing (svg, rect, image)
import Svg.Attributes exposing (x, y, viewBox, fill, width, height, xlinkHref)
import Time exposing (Time, millisecond)
import AnimationFrame exposing (diffs)
import Keyboard
import Char exposing (fromCode)
import String exposing (fromChar)
import Set exposing (Set)
main : Program Never Model Msg
main =
Html.program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
-- MODEL
-- Represents the whole "world" we're working with.
type alias Model =
{ ship : Ship
, bullets : List Bullet
, smokes : List Smoke
, enemies : List Enemy
, score : Int
, keys : Set String
}
-- Creates the initial world with default values.
init : ( Model, Cmd Msg )
init =
let
fastEnemy =
{ initEnemy | x = 900, y = 500, ts = 9 }
mediumEnemy =
{ initEnemy | x = 100, y = 100, ts = 7 }
slowEnemy =
{ initEnemy | x = 100, y = 500, ts = 4 }
insaneEnemy =
{ initEnemy | x = 500, y = 900, ts = 40 }
enemies =
[ fastEnemy, mediumEnemy, slowEnemy, insaneEnemy ]
in
( { ship = initShip
, bullets = []
, smokes = []
, enemies = enemies
, score = 0
, keys = Set.empty
}
, Cmd.none
)
-- UPDATE
-- We respond to keyboard events and tick every n milliseconds.
type Msg
= Frame Time
| AITick Time
| KeyDownMsg Keyboard.KeyCode
| KeyUpMsg Keyboard.KeyCode
-- Our all-powerful update function.
update : Msg -> Model -> ( Model, Cmd Msg )
update msg ({ ship, bullets, smokes, keys, enemies } as model) =
case msg of
Frame timeDiff ->
let
-- Normalize the time diff based on normal FPS vs current FPS
diff =
timeDiff / 30
-- Update the ship to properly represent current user commands.
-- For example, if we're holding "W", then we want ship.acc = 1.0
-- Or if we're holding "A", we want ship.turn = -1.0
newShip =
{ ship
| acc = (accKey keys)
, turn = (turnKey keys)
, firing = (firingKey keys)
}
newBullets =
bullets
++ (fireBullets ship)
++ ((List.concatMap fireBullets) enemies)
in
-- New model with updated ship and list of bullets.
-- All of the "ticks" happen as we construct this final
-- model record.
( { model
| ship = (tickShip diff newShip)
, bullets = (tickBullets diff newBullets)
, smokes = (tickSmokes diff smokes)
, enemies = (tickEnemies diff enemies)
}
, Cmd.none
)
AITick _ ->
let
-- Retarget toward our ship
newEnemies =
List.map (enemyAI ship) enemies
-- Add smoke if accelerating, either our ship or the enemies
newSmokes =
smokes ++ (List.filterMap addSmoke newEnemies) ++ (List.filterMap addSmoke [ ship ])
in
-- New model
( { model
| smokes = newSmokes
, enemies = newEnemies
}
, Cmd.none
)
KeyDownMsg k ->
-- Only thing we do here is add a key to our set of current keys.
( { model | keys = (addKey k keys) }, Cmd.none )
KeyUpMsg k ->
-- Only thing we do here is remove a key from our set of current keys.
( { model | keys = (removeKey k keys) }, Cmd.none )
-- Check if a key is being pressed
-- Usage: if keys ?? "D" then
(??) : Set String -> String -> Bool
(??) keys k =
Set.member k keys
infixr 9 ??
-- Adds a pressed key to our keys set
addKey : Int -> Set String -> Set String
addKey k keys =
Set.insert (fromChar <| fromCode <| k) keys
-- Removes a released key from our keys set
removeKey : Int -> Set String -> Set String
removeKey k keys =
Set.remove (fromChar <| fromCode <| k) keys
-- Checks if W/S are held and sets the ship acceleration to -1, 1, or 0.
accKey : Set String -> Float
accKey keys =
if keys ?? "W" then
1.0
else if keys ?? "S" then
-1.0
else
0.0
-- Checks if A/D are held and sets the ship turn to -1, 1, or 0.
turnKey : Set String -> Float
turnKey keys =
if keys ?? "A" then
-1.0
else if keys ?? "D" then
1.0
else
0.0
-- Checks if J is held and sets the ship `firing` to True.
firingKey : Set String -> Bool
firingKey keys =
keys ?? "J"
-- Checks if ship is firing and weapon cooldown has
-- happened and creates one or more bullets if so.
fireBullets : Ship -> List Bullet
fireBullets ship =
if (ship.firing && ship.cooldown == 0) then
[ { initBullet
| x = ship.x
, y = ship.y
, d = ship.d
, friendly = True
}
]
else
[]
addSmoke : Mover a -> Maybe Smoke
addSmoke { acc, x, y } =
if acc > 0 then
Just { initSmoke | x = x, y = y }
else
Nothing
-- SUBSCRIPTIONS
-- We subscribe to three types of events. One is a time tick of every 30 ms.
-- The other two are keyboard -- keys pushed and keys released.
subscriptions : Model -> Sub Msg
subscriptions _ =
Sub.batch
[ AnimationFrame.diffs Frame
, Time.every (100 * millisecond) AITick
, Keyboard.downs KeyDownMsg
, Keyboard.ups KeyUpMsg
]
-- VIEW
-- Render a div with the game box and debug info.
view : Model -> Html Msg
view model =
div [ width "1000px", height "1000px", style [ ( "margin", "50px 50px" ), ( "text-align", "center" ) ] ]
[ gameView model
, debugView model
]
-- Game box, built out of SVG nodes.
gameView : Model -> Html msg
gameView model =
svg [ viewBox "0 0 1000 1000", width "600px" ]
[ backgroundView
, bulletViews model.bullets
, smokeViews model.smokes
, shipView model.ship
, enemyViews model.enemies
]
-- Show the current model for debugging and some help text.
debugView : Model -> Html msg
debugView model =
div []
[ p [] [ text "WASD to fly, J to fire bullets" ]
, a [ href "https://github.com/jamonholmgren/rocket-elm", target "_blank" ] [ text "Github Source" ]
-- , p [] [ text (toString model) ]
]
-- Background is just a big blue box.
backgroundView : Html msg
backgroundView =
rect [ x "0", y "0", width "1000", height "1000", fill "#0B79CE" ] []