-
Notifications
You must be signed in to change notification settings - Fork 2
/
Main.elm
217 lines (181 loc) · 5.13 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
import Window
import Random
import Time
import Keyboard
import Touch
import Mouse
-- Input
delta : Signal Float
delta = (\t -> t/20) <~ fps 60
type Inputs = {time: Float, tap: Bool, rand: Int}
inputSig : Signal Inputs
inputSig = sampleOn delta (Inputs <~ delta
~ Keyboard.space
~ Random.range -250 250 (every 1000))
-- Model
-- Constants
gameWidth = 768
groundHeight = 128
groundY = (toFloat -backgroundHeight) / 2 - groundHeight
groundTop = groundY + (toFloat groundHeight) / 2 - 1
backgroundHeight = 896
gameHeight = backgroundHeight + groundHeight
pipeWindow = 900
birdX = -120
birdWidth = 92
birdHeight = 64
pipeWidth = 138
pipeHeight = 793
overflowX = 100
gravity = -1.2
flapVelocity = 12
pipeInterval = 70
gameScale = 0.8
data State = Waiting | Playing | Dead
type Object a =
{ a | x: Float
, y: Float
, vx: Float
, vy: Float
, width: Int
, height: Int }
type Bird = Object {}
data Position = Top | Bottom
type Pipe = Object { pos : Position }
type Ground = Object {}
type Model =
{ pipes: [Pipe]
, countdown: Float
, bird: Bird
, state: State }
bird : Bird
bird = { x=birdX, y=0, vx=0, vy=0, width=birdWidth, height=birdHeight }
pipe : Pipe
pipe =
{ x = 400
, y = 0
, vx = -6
, vy = 0
, width = 138
, height = 793
, pos = Top }
initial : Model
initial =
{ pipes = []
, countdown = 100
, bird = bird
, state = Waiting }
-- Update
moving {time} m =
{ m | x <- m.x + m.vx * time
, y <- m.y + m.vy * time }
falling {time} f = { f | vy <- f.vy + gravity * time^2 }
gap : Float
gap = pipeHeight / 2 + 150
createPipe : Int -> Position -> Pipe
createPipe rand pos =
let y = case pos of
Top -> toFloat rand + gap
Bottom -> toFloat rand - gap
in
{ pipe | y <- y
, pos <- pos }
addPipes : Inputs -> Model -> [Pipe]
addPipes {rand} {countdown, pipes} =
if countdown <= 0
then [createPipe rand Top, createPipe rand Bottom] ++ pipes
else pipes
filterPipe : Pipe -> Bool
filterPipe pipe = pipe.x > (toFloat -gameWidth) / 2 - overflowX
updatePipes : Inputs -> Model -> [Pipe]
updatePipes inputs game =
addPipes inputs game
|> map (moving inputs)
|> filter filterPipe
flapping : Inputs -> Bird -> Bird
flapping {tap} bird = { bird | vy <- if tap then flapVelocity else bird.vy }
moveBird : Inputs -> Bird -> Bird
moveBird inputs bird =
let bird' = moving inputs bird
in
{ bird | y <- max (groundTop + toFloat bird.height/2 - 3) bird'.y }
updateBird : Inputs -> Model -> Bird
updateBird inputs = falling inputs . moveBird inputs . flapping inputs . .bird
nearing : Float -> Float -> Float -> Bool
nearing n c m = m >= n - c && m <= n + c
colliding obj1 obj2 =
let width1 = toFloat obj1.width
width2 = toFloat obj2.width
height1 = toFloat obj1.height
height2 = toFloat obj2.height
in
nearing obj1.x (width1/2 + width2/2 - 2) obj2.x &&
nearing obj1.y (height1/2 + height2/2 - 2) obj2.y
grounded {y} = y >= (-gameHeight/2)
updateState : Model -> State
updateState {bird, pipes} =
if any (colliding bird) pipes
then Dead
else Playing
updateCountdown : Inputs -> Model -> Float
updateCountdown {time} game =
if game.countdown > 0
then game.countdown - time
else pipeInterval
play : Inputs -> Model -> Model
play inputs game =
{ game | state <- updateState game
, countdown <- updateCountdown inputs game
, bird <- updateBird inputs game
, pipes <- updatePipes inputs game }
dead : Inputs -> Model -> Model
dead ({tap} as inputs) game =
if tap
then initial
else { game | bird <- (falling inputs . moveBird inputs) game.bird }
wait : Inputs -> Model -> Model
wait ({tap} as inputs) game =
if tap
then play inputs game
else game
step : Inputs -> Model -> Model
step inputs game =
case game.state of
Playing -> play inputs game
Waiting -> wait inputs game
Dead -> dead inputs game
gameSig : Signal Model
gameSig = foldp step initial inputSig
-- Render
birdTilt vy =
clamp (degrees -90) (degrees 30) (8 * degrees vy)
drawGround =
tiledImage gameWidth groundHeight "assets/ground.png"
|> toForm
|> moveY groundY
drawBackground =
image gameWidth gameHeight "assets/background.png"
|> toForm
drawBird bird =
let asset = if bird.vy >= -5
then "assets/flapping.gif"
else "assets/falling.png"
in
image bird.width bird.height asset
|> toForm
|> move (bird.x, bird.y)
|> rotate (birdTilt bird.vy)
drawPipe pipe =
image pipe.width pipe.height "assets/pipe.png"
|> toForm
|> move (pipe.x, pipe.y)
|> rotate (case pipe.pos of
Top -> pi
Bottom -> 0)
drawPipes pipes = []
draw (w, h) game =
(container w h middle
. collage gameWidth gameHeight) <|
[scale 0.6 <|group ([drawBackground,drawBird game.bird,
(toForm . asText) game.state, drawGround ] ++ (map drawPipe game.pipes))]
main = lift2 draw Window.dimensions gameSig