This repository has been archived by the owner on Mar 10, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathController.elm
306 lines (271 loc) · 10.3 KB
/
Controller.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
{- |
Module : Controller
Description : Implementation of Extreme Pong.
Copyright : (c) Jeff Smits
License : GPL-3.0
Maintainer : jeff.smits@gmail.com
Stability : experimental
Portability : portable
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-}
module Controller where
import Model
import Either
import Line
----------------
-- CONTROLLER --
----------------
-- All input from the user
-- Spacebar, left player and right player values
-- where values are -1, 0 or 1
data UserInput = UserInput Bool Int Int
defaultUserInput = UserInput False 0 0
-- All input and the time since the last input
data Input = Input Float UserInput
defaultInput = Input 0 defaultUserInput
{-
Takes the State to the next State on an Input
Sadly, multiple definitions of the same function with different
pattern-matched input just overwrite eachother in Elm, so this
function is a little long and cluttered.
-}
stepGame : Input -> State -> State
stepGame (Input delta (UserInput spacebar left right))
(State gameState (Score ls rs) b lp rp) =
let sc = Score ls rs
in if spacebar then case gameState of
Paused -> State Playing sc b lp rp
Playing -> State Paused sc b lp rp
else case gameState of
Paused -> State Paused sc b lp rp
Playing -> let
bvec = stepBall delta b
-- try to collide with the paddles, walls, and paddles again
b' = collide bvec $ [collidePaddles lp rp,
collideFieldWalls, collidePaddles lp rp]
fxlb = 0
fxub = fst fieldSize
(gs', ls', rs', b'') = case b' of Ball (bx',_) _ ->
-- sadly, the multiway if doesn't seem to be working...
if bx' < fxlb then (Paused, ls, rs+1, defaultBall) else if
bx' > fxub then (Paused, ls+1, rs, defaultBall) else
{- otherwise -} (Playing, ls, rs, b')
in State gs' (Score ls' rs') b''
(stepPaddle delta left lp) (stepPaddle delta right rp)
{-
Takes a time delta, a direction and Paddle and
returns a paddle in that direction.
-}
stepPaddle : Float -> Int -> Paddle -> Paddle
stepPaddle delta dir (Paddle y) = let
-- paddle half height, lower and upper bound
phh = snd paddleSize / 2
plb = 0 + phh
pub = (snd fieldSize) - phh
-- negate the direction because Graphics points the y-axis down
in Paddle $ clamp plb pub $ y - (toFloat dir) * delta * 100
-- Data for the ball within a computation step
-- Ball position-vector velocity
data StepBall = StepBall ((Float, Float),(Float,Float)) (Float, Float)
{-
stepBall returns the StepBall with a vector (defined by two points)
from the current position to the next position using the time
difference and the velocity of the Ball.
-}
stepBall : Float -> Ball -> StepBall
stepBall d (Ball (x, y) (vx, vy)) =
StepBall ((x, y), (x+d*vx, y+d*vy)) (vx, vy)
{-
collide takes a vector (defined by two points)
and a list of collision functions.
It calls each function until the vector doesn't collide with that
function anymore.
Then it returns the position pointed to by the vector.
-}
collide : StepBall
-> [StepBall -> Maybe StepBall]
-> (Float, Float)
collide sb l = case l of
h::t -> case h sb of
Just sb' -> collide sb' (h::t)
Nothing -> collide sb t
[] -> case sb of
StepBall (_,pos) vel -> Ball pos vel
{-
-}
collidePaddles : Paddle
-> Paddle
-> StepBall
-> Maybe StepBall
collidePaddles (Paddle lpy) (Paddle rpy) sb =
let distFromEdge = paddleDist + fst paddleSize
in case collidePaddle (Left (distFromEdge, lpy)) sb of
Just sb -> Just sb
Nothing -> collidePaddle (Right ((fst fieldSize) - distFromEdge, rpy)) sb
{-
Checks if the vector (defined by points 1 and 2) crosses the paddle
and if so returns a vector from the collision point (cp)
to the reflection point (rp).
(2) * * (rp) (rp) * * (2)
\ / \ /
|| / \ ||
||* (cp) (cp) *||
|| \ / ||
|| * (1) (1) * ||
|| ||
-}
collidePaddle : Either (Float, Float) (Float, Float)
-> StepBall
-> Maybe StepBall
collidePaddle p (StepBall (p1,(x2,y2)) (vx,vy)) =
let plb y = y - (snd paddleSize / 2) - ballRadius
pub y = y + (snd paddleSize / 2) + ballRadius
(mCp, pp) = case p of
Left (px,py) -> let
paddSegm = verLineSegment (plb py) (pub py) $ px+ballRadius
ballSegm = Segment p1 (x2,y2)
in case intersectSS paddSegm ballSegm of
Just (C CHRight2Left _,cp) -> (Just cp,(px,py))
_ -> (Nothing, (px,py))
Right (px,py) -> let
paddSegm = verLineSegment (plb py) (pub py) $ px-ballRadius
ballSegm = Segment p1 (x2,y2)
in case intersectSS paddSegm ballSegm of
Just (C CHLeft2Right _,cp) -> (Just cp, (px,py))
_ -> (Nothing, (px,py))
in case mCp of
Just (cpx,cpy) -> let rp = (cpx - (x2-cpx), y2)
sb = (StepBall (p1,(x2,y2)) (vx,vy))
bA = ballAngle sb
bA' = switchHorVer bA
pRAA = paddleReflectionAxisAngle $ cpy - (snd pp)
rA = reflectionAngle pRAA bA'
l = sqrt $ ((x2-cpx)^2)+((y2-cpy)^2)
cp = (cpx,cpy)
rp' = reflectionPoint rA cp l
vl = sqrt $ vx^2 + vy^2
v' = relReflPoint rA vl
in Just $ StepBall ((cpx,cpy),rp') v'
_ -> collidePaddleSides pp (StepBall (p1,(x2,y2)) (vx,vy))
{-
the following code it for arkenoid paddle behaviour. It's ad-hoc right now,
I will refactor it later.
-}
ballAngle : StepBall -> Float
ballAngle (StepBall ((x1,y1),(x2,y2)) _) =
let dx = x2-x1 -- both positive means going down and to the right
dy = y2-y1 -- both positive means going down and to the right
in atan2 dy dx
switchHorVer : Float -> Float
switchHorVer d = 0.5 * pi - d
paddleReflectionAxisAngle : Float -> Float
paddleReflectionAxisAngle cpyRelToPadMid = (90 - cpyRelToPadMid) / 180 * pi
reflectionAngle : Float -> Float -> Float
reflectionAngle padReflAxisAngle ballAngle =
let dAngle = padReflAxisAngle - ballAngle
in ballAngle + 2 * dAngle
reflectionPoint : Float -> (Float,Float) -> Float -> (Float,Float)
reflectionPoint reflAngle (cpx,cpy) length =
let (dx,dy) = relReflPoint reflAngle length
in (cpx+dx,cpy+dy)
relReflPoint : Float -> Float -> (Float,Float)
relReflPoint reflAngle length =
let dx = 0 - length * sin reflAngle
dy = 0 - length * cos reflAngle
in (dx,dy)
collidePaddleSides : (Float,Float)
-> StepBall
-> Maybe StepBall
collidePaddleSides p sb =
let paddletopside = collidePaddleSide (Left p) sb
in case paddletopside of
Just _ -> paddletopside
Nothing -> collidePaddleSide (Right p) sb
{-
Hitting the side in stead of the front of the paddle should bounce
the ball off to the side. Ineffective for gameplay, but a lot better
looking than having the ball go through the side of the paddle.
(rp) * * (1)
\ /
* (cp)
/||
(2) * ||
|+ (px,py)
||
||
Left (px,py) means the left paddle side when the scene is turned 90 degrees
counter-clockwise (so the upper side). Px and py still mean the same as in
collidePaddle.
-}
collidePaddleSide : Either (Float, Float) (Float, Float)
-> StepBall
-> Maybe StepBall
collidePaddleSide p (StepBall (p1,(x2,y2)) (vx,vy)) =
let (pSx, pSy) = paddleSize
plb x = x - pSx - ballRadius
prb x = x + ballRadius
mCp = case p of
-- Left means lower side, Right means upper side
Left (px,py) -> let
paddSegm = horLineSegment (plb px) (prb px) $ py - (pSy/2) - ballRadius
ballSegm = Segment p1 (x2,y2)
in case intersectSS paddSegm ballSegm of
Just (C _ CVTop2Bottom,cp) -> Just cp
_ -> Nothing
Right (px,py) -> let
paddSegm = horLineSegment (plb px) (prb px) $ py + (pSy/2) + ballRadius
ballSegm = Segment p1 (x2,y2)
in case intersectSS paddSegm ballSegm of
Just (C _ CVBottom2Top,cp) -> Just cp
_ -> Nothing
in case mCp of
Just (cpx,cpy) -> let rp = (x2, cpy - (y2-cpy))
in Just $ StepBall ((cpx,cpy),rp) (vx,0-vy)
_ -> Nothing
{-
-}
collideFieldWalls : StepBall
-> Maybe StepBall
collideFieldWalls sb =
case collideFieldWall (Left 0) sb of
Just sb' -> Just sb'
Nothing -> collideFieldWall (Right $ snd fieldSize) sb
{-
Checks if the vector (defined by points 1 and 2) crosses
a field wall and if so returns a vector
from the collision point (cp) to the reflection point (rp).
(2) *
/
___________
(cp) *
/ \
(1) * \
(rp) *
-}
collideFieldWall : Either Float Float
-> StepBall
-> Maybe StepBall
collideFieldWall wallBound (StepBall (p1,(x2,y2)) (vx,vy)) =
let segm = Segment p1 (x2,y2)
mCp = case wallBound of
Left wlb -> case intersectLS (horLine $ wlb + ballRadius) segm of
Just (C _ CVBottom2Top,cp) -> Just cp
_ -> Nothing
Right wub -> case intersectLS (horLine $ wub - ballRadius) segm of
Just (C _ CVTop2Bottom,cp) -> Just cp
_ -> Nothing
in case mCp of
Just (cpx,cpy) ->
let rp = (x2, cpy - (y2-cpy))
in Just $ StepBall ((cpx,cpy),rp) (vx,0-vy)
Nothing ->
Nothing