-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHaskeroids.hs
More file actions
287 lines (210 loc) · 8.28 KB
/
Haskeroids.hs
File metadata and controls
287 lines (210 loc) · 8.28 KB
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
{-# LANGUAGE OverloadedStrings #-}
import Graphics.Blank
import Control.Concurrent
import Data.Char
import Data.Text
import System.Random
import Data.Map as Map
main = blankCanvas 3000 { events = ["keydown","keyup"] }$ run
type Coords = (Double,Double,Double) -- x and y , r to represent amount of rotation
type KeyDict = Map (Maybe Int) Bool
type ScreenDimension = (Double,Double)
type Size = Int
type Hits = Int
type Direction = Int -- 0 - 360 Degrees, 0 being straight up
type Speed = Double
type TravelDist = Int
type Projectile = (Coords, TravelDist)
type Asteroid = (Coords,Size,Hits,Direction,Speed)
data GameState = GameState { getCoords :: Coords, getMovDict :: KeyDict, getAsteroids :: [Asteroid], getProjectiles :: [Projectile] ,getTimeSinceFire :: Int , getScore :: Int}
run :: DeviceContext -> IO ()
run ctx =do
let canvasHeight = height ctx
let canvasWidth = width ctx
astroids <- createXLargeAsteroid (canvasWidth,canvasHeight)
loop (GameState (200,200,0) Map.empty [astroids] [] 0 0)ctx
loop :: GameState -> DeviceContext -> IO()
loop gState context = do
let preKeys = getMovDict gState
let coords = getCoords gState
let astroids = getAsteroids gState
let score = getScore gState
let screenDim = (width context,height context)
let projects = getProjectiles gState
let lastFire = getTimeSinceFire gState
let collisions = Prelude.map (detectAsteroidCollision coords) astroids
let astroidPostCol = Prelude.map (\ast -> Prelude.foldl updateHits ast projects) astroids
let remainingProjects = Prelude.filter (filterStopedProjectiles astroidPostCol) projects
let splittingAsts = Prelude.filter (\ast -> (((snd5 ast) <= (thr5 ast)) && ((snd5 ast) > 1))) astroidPostCol
let remainingAsteroids = Prelude.filter (\ast -> not $((snd5 ast) <= (thr5 ast))) astroidPostCol
smallAsteroids <- sequence $ Prelude.map (\ast -> createSplitAsteroids (snd5 ast) (fst5 ast)) splittingAsts
let allSmallAst = Prelude.concat smallAsteroids
let currentAst = remainingAsteroids ++ allSmallAst
let newScore = score + (Prelude.length splittingAsts)
if (or collisions)
then
do
print "Collision"
send context $ do
printDeath screenDim
else
do
send context $ do
clearCanvas
printScore score screenDim
sequence_ $ Prelude.map printSimpleAsteroid currentAst
sequence_ $ Prelude.map printProjectile remainingProjects
printShip coords
threadDelay (2 * 100)
newAsts <- repopulateAsteroids currentAst screenDim
let newAsteroids = moveAstroids newAsts screenDim
let mvdProjects = moveProjectiles remainingProjects screenDim
ch <- flush context
if ((Prelude.null ch) && (Map.null preKeys ))
then loop (GameState coords preKeys newAsteroids mvdProjects (lastFire +1) newScore) context
else
do
print ch
let dict = Prelude.foldl reduceKeys preKeys ch
let charList = keys dict
let newLoc = (Prelude.foldl (movement.(fixPosition screenDim)) coords charList)
if (lastFire > 15)
then
do
let newPros = fireProjectiles dict coords mvdProjects
loop (GameState newLoc dict newAsteroids newPros 0 newScore) context
else
loop (GameState newLoc dict newAsteroids mvdProjects (lastFire +1) newScore) context
filterStopedProjectiles :: [Asteroid] -> Projectile -> Bool
filterStopedProjectiles asts pro = not $ or $ Prelude.map (detectAsteroidCollision (fst pro)) asts
updateHits :: Asteroid -> Projectile -> Asteroid
updateHits ast proj = if (detectAsteroidCollision (fst proj) ast) then (fst5 ast , snd5 ast, ((thr5 ast)+1), for5 ast, fif5 ast) else ast
moveProjectiles :: [Projectile] -> ScreenDimension -> [Projectile]
moveProjectiles pros maxDim = Prelude.map (moveProjectile maxDim) (Prelude.filter (\pro -> snd pro < 100) pros)
moveProjectile :: ScreenDimension -> Projectile -> Projectile
moveProjectile maxWindow ((x,y,r), mvDst) = (crds,(mvDst+1)) where
x' = (x - (4 * sin(degreeToRad $ r)))
y' = (y - (4 * cos(degreeToRad $ r)))
crds = fixPosition maxWindow (x',y',r)
fireProjectiles :: KeyDict -> Coords -> [Projectile] -> [Projectile]
fireProjectiles dict (x,y,r) pros | (member (Just 32) dict) = pros ++ [(newCrds,0)] where
x' = x - (10 *sin(degreeToRad r))
y' = (y + 10 )- (10 * cos (degreeToRad r))
newCrds = (x',y',r)
fireProjectiles _ _ pros = pros
repopulateAsteroids :: [Asteroid] -> ScreenDimension -> IO([Asteroid])
repopulateAsteroids as dim= do
if (Prelude.length as) < 10
then
do
newAst <- createXLargeAsteroid dim
repopulateAsteroids (as ++ [newAst]) dim
else
return as
detectAsteroidCollision :: Coords -> Asteroid -> Bool
detectAsteroidCollision (x,y, r) (crds, sz, _, _, _) = (fromIntegral radAst) > distToShip where
radAst = 10 * sz
deltaX = abs ((fst3 crds) - x)
deltaY = abs ((snd3 crds) - (y+10))
distToShip = sqrt((deltaX ** 2) + (deltaY ** 2))
createSplitAsteroids :: Size -> Coords -> IO([Asteroid])
createSplitAsteroids sz crds = do
dir1 <- randomRIO(0, 360)
dir2 <- randomRIO(0, 360)
return [(crds, (sz-1),0,dir1, (1/(fromIntegral sz))),(crds, (sz-1),0,dir2, (1/(fromIntegral sz)))]
createXLargeAsteroid :: ScreenDimension -> IO(Asteroid)
createXLargeAsteroid (maxX,maxY) = do
x <- randomRIO(0,maxX)
y <- randomRIO(0,maxY)
r <- randomRIO(0 , 360)
dir <- randomRIO(0, 360)
return ((x,y,r),4,0,dir,0.25)
moveAstroids :: [Asteroid] -> ScreenDimension -> [Asteroid]
moveAstroids as maxWindow = Prelude.map (moveAstroid maxWindow) as
moveAstroid :: ScreenDimension -> Asteroid -> Asteroid
moveAstroid maxWindow ((x,y,r), sz, hts, dir, spd) = (crds ,sz,hts,dir,spd) where
x' = (x - (spd * sin(degreeToRad $ fromIntegral(dir))))
y' = (y - (spd * cos(degreeToRad $ fromIntegral(dir))))
crds = fixPosition maxWindow (x',y',r)
printProjectile :: Projectile -> Canvas ()
printProjectile ((x,y,r), _) = do
beginPath()
arc(x,y,4,0,pi*2,False)
fillStyle "red"
closePath()
fill()
printSimpleAsteroid :: Asteroid -> Canvas ()
printSimpleAsteroid ((x, y, _), size, hits, dir, spd)= do
beginPath()
--font "bold 16px Arial"
arc(x, y, (fromIntegral(size) * 10), 0, pi*2, False)
fillStyle "gray"
strokeStyle "black"
closePath()
fill()
--fillText((pack(show hits)),x,y)
stroke()
printScore :: Int -> ScreenDimension -> Canvas ()
printScore x (maxX, maxY) = do
fillStyle "black"
font "bold 36px Arial"
fillText((pack(show x)),(maxX -150), 40)
printDeath :: ScreenDimension -> Canvas ()
printDeath (maxX,maxY) = do
fillStyle "black"
font "bold 60px Arial"
fillText( "You Died",(maxX /2 ), (maxY /2))
printShip :: Coords -> Canvas ()
printShip (x,y,r) = do
save()
let rot = degreeToRad (360 - r)
translate(x,(y + 10))
rotate(rot)
translate(-x,-(y + 10))
beginPath()
moveTo(x,y)
lineTo( x +5, y +20)
lineTo(x -5,y +20)
closePath()
fillStyle "black"
fill()
restore()
reduceKeys :: KeyDict -> Event -> KeyDict
reduceKeys m ev = do
if (eType ev) == "keydown"
then (insert (eWhich ev) True m)
else (delete (eWhich ev) m)
movement :: Coords -> Maybe Int -> Coords
movement (x, y, r) (Just w) | 87 == w = ((x - (0.75 * sin(degreeToRad r))) ,(y - (0.75 * cos (degreeToRad r))) ,r) -- Foward 'w'
movement (x, y, r) (Just d) | 65 == d = (x , y, (fromIntegral(((ceiling r) + 1) `mod` 360::Int))) -- Right 'd'
movement (x, y, r) (Just s) | 83 == s = ((x + (0.75 * sin(degreeToRad r))) ,(y + (0.75 * cos (degreeToRad r))) ,r) -- Back 's'
movement (x, y, r) (Just a) | 68 == a = (x, y, (fixDegrees(r-1)) ) -- Left 'a'
movement (x, y, r) _ = (x,y,r)
fixDegrees :: Double -> Double
fixDegrees 360 = 0
fixDegrees (-1) = 359
fixDegrees x = x
fixPosition ::ScreenDimension -> Coords -> Coords
fixPosition (maxX,maxY) (x,y,r) = (maxValFix x maxX, maxValFix y maxY,r)
degreeToRad :: Double -> Double
degreeToRad x = (2 * pi / 360) * x
maxValFix :: Double -> Double -> Double
maxValFix val maxVal | val > (maxVal+20)= -20
maxValFix val maxVal | val < -20 = maxVal +20
maxValFix val maxVal = val
fst3 :: (a,b,c) -> a
fst3 (x, _, _) = x
snd3 :: (a,b,c) -> b
snd3 (_, y, _) = y
thr3 :: (a,b,c) -> c
thr3 (_, _, z) = z
fst5 :: (a,b,c,d,e) -> a
fst5 (a,_,_,_,_) = a
snd5 :: (a,b,c,d,e) -> b
snd5 (_,b,_,_,_) = b
thr5 :: (a,b,c,d,e) -> c
thr5 (_,_,c,_,_) = c
for5 :: (a,b,c,d,e) -> d
for5 (_,_,_,d,_) = d
fif5 :: (a,b,c,d,e) -> e
fif5 (_,_,_,_,e) = e