-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathshooter.elm
295 lines (258 loc) · 8.41 KB
/
shooter.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
module Shooter (main) where
import Color exposing (..)
import Graphics.Collage exposing (..)
import Graphics.Element exposing (..)
import Window
import Time exposing (..)
import Keyboard
import List exposing (..)
import Char
import Text
import Debug
import SpaceSim exposing (..)
import Vectors exposing (..)
type alias Ship = { object : Object
, destroyed : Bool
, energy : Float
, fuel : Float
}
type alias Game = { ship1 : Ship
, ship2 : Ship
, score1 : Int
, score2 : Int
, bullets : List Object
, drawOrbits : Bool
}
type alias Action = {x: Int, y: Int}
type Input = Simulation Action Action Time
| Shoot Bool Bool
| ToggleOrbits Bool
| Restart Bool
gameWidth = 800
gameHeight = 600
-- Game constants
rotationSpeed = -1.0 * degrees 180
accel = 100
bulletV = 100
bulletEnergy = 2
energyGain = 1
startT = 40
au = 400
gm = 4 * pi^2 * au^3 / startT^2
elementsPlayer1 =
{ sma = 0.5 * au
, e = 0.0
, omega = degrees 0
, meanL = degrees 10
}
elementsPlayer2 =
{ sma = 0.5 * au
, e = 0.0
, omega = degrees 0
, meanL = degrees 190
}
sun = { renderer = sunRenderer
, size = 20
, orbit = { sma = 0, e = 0, omega = 0, meanL = 0}
, color = yellow
, position = Vector2D 0 0
, velocity = Vector2D 0 0
, orientation = 0
}
sunRenderer = fittedImage 40 40 "sun.gif" |> toForm
shipRenderer = fittedImage 15 15 "spaceship.gif" |> toForm
shipPRenderer = fittedImage 20 20 "spaceship-propelled.gif" |> toForm
destroyedRenderer = fittedImage 15 15 "spaceship-destroyed.gif" |> toForm
bulletRenderer = oval 2 2 |> filled white
startShip1 = { object = objectFromElements gm shipRenderer 7.5 elementsPlayer1
, destroyed = False
, energy = 10
, fuel = 100}
startShip2 = { object = objectFromElements gm shipRenderer 7.5 elementsPlayer2
, destroyed = False
, energy = 10
, fuel = 100}
game = { ship1 = startShip1 , ship2 = startShip2
, score1 = 0 , score2 = 0
, bullets = []
, drawOrbits = False
}
viewEnergy : Ship -> Form
viewEnergy ship =
let
ls = {defaultLine | color = hudGreen, width = 10}
in
traced ls <| path [(0, 0), (0, 10 * ship.energy)]
hudGreen = rgb 160 200 160
viewScore : Int -> Form
viewScore s =
Text.fromString (toString s) |> Text.color hudGreen
|> Text.monospace
|> Text.height 20
|> leftAligned
|> toForm
drawShip : Bool -> Ship -> Form
drawShip do ship =
let
object = ship.object
in if ship.destroyed then
drawObject do {object | renderer = destroyedRenderer}
else
drawObject do object
view : (Int, Int) -> Game -> Element
view (w, h) game =
let
static = [ rect gameWidth gameHeight |> filled black , drawObject False sun ]
bullets = map (drawObject game.drawOrbits) game.bullets
ship1 = [drawShip game.drawOrbits game.ship1]
ship2 = [drawShip game.drawOrbits game.ship2]
energies = [ viewEnergy game.ship2 |> move (-gameWidth/2 + 10, -gameHeight/2 + 10)
, viewEnergy game.ship1 |> move (gameWidth/2 - 10, -gameHeight/2 + 10)]
scores = [ viewScore game.score2 |> move (-gameWidth/2 + 10, -gameHeight/2 + 130)
, viewScore game.score1 |> move (gameWidth/2 - 10, -gameHeight/2 + 130)]
in
container w h middle <|
collage gameWidth gameHeight <|
concat [static, bullets, ship1, ship2,
energies, scores]
shoot : Ship -> Object
shoot ship =
let
object = ship.object
dx = Vector2D (object.size * cos object.orientation)
(object.size * sin object.orientation)
in
{object |
renderer = bulletRenderer
, size = 0
, color = white
, position = object.position .+. dx
}
updateShip : (Int, Int) -> Float -> Ship -> Ship
updateShip (dir, burn) dt ship =
let
thurst = if burn > 0 then accel * (toFloat burn)
else 0
energy = ship.energy + dt * energyGain
rot = rotationSpeed * (toFloat dir)
object = updateObject gm dt rot thurst ship.object
in
{ ship |
object = {object | renderer = if burn > 0 then shipPRenderer
else shipRenderer}
, energy = if energy >= 10 then 10 else energy
}
inGameField : Object -> Bool
inGameField object =
let
r = sqrt <| object.position .*. object.position
x = xcomp <| object.position
y = ycomp <| object.position
in
r > 20 && x > -gameWidth/2 && x < gameWidth/2 &&
y > -gameHeight/2 && y < gameHeight/2
isHit: Object -> List Object -> Bool
isHit ship objects =
let
hits o = norm (ship.position .-. o.position) <= ship.size + o.size
in
any hits objects
updateSimulation : Action -> Action -> Float -> Game -> Game
updateSimulation player1 player2 dt game =
let
ship1 = updateShip (player1.x, player1.y) dt game.ship1
ship2 = updateShip (player2.x, player2.y) dt game.ship2
bullets = filter inGameField <| map (updateObject gm dt 0 0) game.bullets
hit1 = not (inGameField ship1.object)
|| isHit ship1.object (ship2.object :: bullets)
hit2 = not (inGameField ship2.object)
|| isHit ship2.object (ship1.object :: bullets)
in
{ game |
ship1 = { ship1 | destroyed = hit1 || ship1.destroyed }
, ship2 = { ship2 | destroyed = hit2 || ship2.destroyed }
, score1 = if hit2 then game.score1 + 1 else game.score1
, score2 = if hit1 then game.score2 + 1 else game.score2
, bullets = bullets
}
shootBullet : Ship -> (Ship, List Object)
shootBullet ship =
let
object = ship.object
dx = Vector2D (object.size * cos object.orientation)
(object.size * sin object.orientation)
dv = Vector2D (bulletV * cos object.orientation)
(bulletV * sin object.orientation)
canShoot = ship.energy >= bulletEnergy
bullet = if canShoot
then [{object |
renderer = bulletRenderer
, size = 0
, color = white
, position = object.position .+. dx
, velocity = object.velocity .+. dv
}]
else []
energy = if canShoot then ship.energy - bulletEnergy
else ship.energy
in
({ship | energy = energy}, bullet)
update : Input -> Game -> Game
update input game =
let
destroyed = game.ship1.destroyed || game.ship2.destroyed
in
case input of
Simulation player1 player2 delta ->
if destroyed then
game
else
updateSimulation player1 player2 delta game
Shoot player1 player2 ->
let
t = Debug.watch "p1" <| player1
u = Debug.watch "p2" <| player2
(ship1, b1) = if player1 then shootBullet game.ship1
else (game.ship1, [])
(ship2, b2) = if player2 then shootBullet game.ship2
else (game.ship2, [])
in
{ game |
bullets = concat [b1, b2, game.bullets]
, ship1 = ship1
, ship2 = ship2
}
ToggleOrbits toggle ->
{ game |
drawOrbits = if toggle then not game.drawOrbits
else game.drawOrbits
}
Restart toggle ->
if toggle && destroyed then
{ game |
ship1 = startShip1
, ship2 = startShip2
, bullets = []
}
else
game
input : Signal Input
input = Signal.mergeMany [ Signal.map ToggleOrbits <| Keyboard.isDown 79
, Signal.map Restart <| Keyboard.isDown 32
, shotInput
, simInput ]
delta = Signal.map inSeconds (fps 35)
shotInput : Signal Input
shotInput =
Signal.merge
(Signal.map (\x -> Shoot x False) (Keyboard.isDown 40))
(Signal.map (\x -> Shoot False x) (Keyboard.isDown 83))
simInput : Signal Input
simInput = Signal.sampleOn delta <|
Signal.map3 Simulation
Keyboard.arrows
Keyboard.wasd
delta
gameState : Signal Game
gameState = Signal.foldp update game input
main = Signal.map2 view Window.dimensions gameState