-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathBreakout.elm
182 lines (138 loc) · 5.31 KB
/
Breakout.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
import Color (..)
import Graphics.Collage (..)
import Graphics.Element (..)
import Keyboard
import Signal (..)
import Time (fps, inSeconds, Time)
import Window
import Text
import List
{-- Part 1: Model the user input ----------------------------------------------
What information do you need to represent all relevant user input?
Task: Redefine `UserInput` to include all of the information you need.
Redefine `userInput` to be a signal that correctly models the user
input as described by `UserInput`.
------------------------------------------------------------------------------}
type alias UserInput = { dir : Int }
userInput : Signal UserInput
userInput = UserInput <~ (.x <~ Keyboard.arrows)
type alias Input =
{ delta : Float
, userInput : UserInput
}
{-- Part 2: Model the game ----------------------------------------------------
What information do you need to represent the entire game?
Tasks: Redefine `GameState` to represent your particular game.
Redefine `defaultGame` to represent your initial game state.
For example, if you want to represent many objects that just have a position,
your GameState might just be a list of coordinates and your default game might
be an empty list (no objects at the start):
type GameState = { objects : [(Float,Float)] }
defaultGame = { objects = [] }
------------------------------------------------------------------------------}
(gameWidth,gameHeight) = (400, 600)
(halfWidth,halfHeight) = (200, 300)
type alias Ball = { x:Float, y:Float, vx:Float, vy:Float, r:Float }
type alias Player = { x:Float, y:Float, vx:Float, vy:Float, w:Float, h:Float }
type alias Block = { x:Float, y:Float, w:Float, h:Float }
block x y w h = { x=x, y=y, w=w, h=h }
(blockWidth, blockHeight) = (40,20)
type alias GameState =
{ ball : Ball
, player : Player
, blocks : List Block
}
defaultGame : GameState
defaultGame =
{ ball = { x=0, y=0, vx=200, vy=200, r=8 }
, player = { x=0, y=20-halfHeight, vx=0, vy=0, w=40, h=10 }
, blocks = List.map (\x -> block (blockWidth * 2 * x) (halfHeight / 2) blockWidth blockHeight) [-2..2]
}
{-- Part 3: Update the game ---------------------------------------------------
How does the game step from one state to another based on user input?
Task: redefine `stepGame` to use the UserInput and GameState
you defined in parts 1 and 2. Maybe use some helper functions
to break up the work, stepping smaller parts of the game.
------------------------------------------------------------------------------}
stepGame : Input -> GameState -> GameState
stepGame {delta,userInput} ({ball,player,blocks} as gameState) =
let
(ball', blocks') = stepBall delta ball player blocks
player' = stepPlayer delta userInput.dir player
in
{ gameState | ball <- ball'
, blocks <- blocks'
, player <- player'
}
stepPlayer delta dir player =
let player' = stepObj delta { player | vx <- toFloat dir * 200 }
in
{ player' |
x <- clamp (22-halfWidth) (halfWidth-22) player'.x
}
stepBall : Time -> Ball -> Player -> List Block -> (Ball, List Block)
stepBall delta ({x,y,vx,vy} as ball) player blocks =
let
hitPlayer = (ball `within` player)
hitCeiling = (y > halfHeight - ball.r)
ball' = stepObj delta
{ ball | vx <- stepV vx (x < ball.r - halfWidth) (x > halfWidth - ball.r)
, vy <- stepV vy hitPlayer hitCeiling
}
in
(List.foldr goBlockHits (ball',[]) blocks)
goBlockHits : Block -> (Ball,List Block) -> (Ball,List Block)
goBlockHits block (ball,blocks) =
let
hit = ball `within` block
blocks' = if hit then blocks else block::blocks
ball' = if hit then { ball | vy <- -ball.vy } else ball
in
(ball', blocks')
near k c n =
n >= k-c && n <= k+c
within ball box = (ball.x |> near box.x (ball.r + box.w / 2))
&& (ball.y |> near box.y (ball.r + box.h / 2))
stepV v lowerCollision upperCollision =
if | lowerCollision -> abs v
| upperCollision -> 0 - abs v
| otherwise -> v
stepObj delta ({x,y,vx,vy} as obj) =
{ obj |
x <- x + vx * delta,
y <- y + vy * delta
}
{-- Part 4: Display the game --------------------------------------------------
How should the GameState be displayed to the user?
Task: redefine `display` to use the GameState you defined in part 2.
------------------------------------------------------------------------------}
display : (Int,Int) -> GameState -> Element
display (w,h) ({ball,player,blocks} as gameState) =
container w h middle <|
collage gameWidth gameHeight <|
[ rect gameWidth gameHeight
|> filled green
, circle ball.r
|> make ball
, rect player.w player.h
|> make player
, group <| List.map (\b -> rect b.w b.h |> make b) blocks
]
make obj shape =
shape
|> filled white
|> move (obj.x, obj.y)
{-- That's all folks! ---------------------------------------------------------
The following code puts it all together and shows it on screen.
------------------------------------------------------------------------------}
input : Signal Input
input =
let
delta = inSeconds <~ fps 30
in
sampleOn delta (Input <~ delta ~ userInput)
gameState : Signal GameState
gameState =
foldp stepGame defaultGame input
main : Signal Element
main = display <~ Window.dimensions ~ gameState