1
+ ( function ( ) {
2
+ const BALL_RADIUS = 3 ;
3
+ const PADDLE_WIDTH = 26 ;
4
+ const PADDLE_HEIGHT = 6 ;
5
+ const BRICK_ROWS = 4 ;
6
+ const BRICK_HEIGHT = 8 ;
7
+ const BRICK_PADDING = 4 ;
8
+ const BRICK_OFFSET_TOP = 40 ;
9
+ const BRICK_OFFSET_LEFT = 2 ;
10
+ const SPEED_MULTIPLIER = 1.1 ;
11
+ const PADDLE_SPEED = 12 ;
12
+
13
+ let ball , paddle , interval ;
14
+ let bricks = [ ] ;
15
+ let BRICK_WIDTH , BRICK_COLS ;
16
+ let paddleIntervalLeft , paddleIntervalRight ;
17
+ let score = 0 ;
18
+ let level = 1 ;
19
+ let highScore = 0 ;
20
+ let paused = false ;
21
+ let gameOver = false ;
22
+ let lives = 3 ;
23
+
24
+ const storage = require ( "Storage" ) ;
25
+ //const BEEP = () => Bangle.buzz(100);
26
+
27
+ function loadHighScore ( ) {
28
+ const saved = storage . readJSON ( "breakout_highscore.json" , 1 ) ;
29
+ highScore = saved && saved . highScore ? saved . highScore : 0 ;
30
+ }
31
+
32
+ function saveHighScore ( ) {
33
+ if ( score > highScore ) {
34
+ highScore = score ;
35
+ storage . writeJSON ( "breakout_highscore.json" , { highScore } ) ;
36
+ }
37
+ }
38
+
39
+ function initBricks ( ) {
40
+ bricks = [ ] ; // Reset the array completely
41
+ for ( let r = 0 ; r < BRICK_ROWS ; r ++ ) {
42
+ for ( let c = 0 ; c < BRICK_COLS ; c ++ ) {
43
+ let brickX = BRICK_OFFSET_LEFT + c * ( BRICK_WIDTH + BRICK_PADDING ) ;
44
+ let brickY = BRICK_OFFSET_TOP + r * ( BRICK_HEIGHT + BRICK_PADDING ) ;
45
+ bricks . push ( { x : brickX , y : brickY , status : 1 } ) ;
46
+ }
47
+ }
48
+ }
49
+
50
+ function showGetReady ( callback ) {
51
+ g . clear ( ) ;
52
+ g . setFont ( "6x8" , 2 ) ;
53
+ g . setFontAlign ( 0 , 0 ) ;
54
+ g . setColor ( 1 , 1 , 0 ) ;
55
+ g . drawString ( "GET READY!" , g . getWidth ( ) / 2 , g . getHeight ( ) / 2 ) ;
56
+ g . flip ( ) ;
57
+ setTimeout ( callback , 1500 ) ; // wait 1.5 seconds then start
58
+ }
59
+
60
+ function initGame ( ) {
61
+ const screenWidth = g . getWidth ( ) ;
62
+ BRICK_COLS = Math . min ( 5 , Math . floor ( ( screenWidth - BRICK_OFFSET_LEFT + BRICK_PADDING ) / ( 15 + BRICK_PADDING ) ) ) ;
63
+ BRICK_WIDTH = Math . floor ( ( screenWidth - BRICK_OFFSET_LEFT - ( BRICK_COLS - 1 ) * BRICK_PADDING ) / BRICK_COLS ) ;
64
+
65
+ ball = {
66
+ x : screenWidth / 2 ,
67
+ y : g . getHeight ( ) - 20 ,
68
+ dx : 3 ,
69
+ dy : - 3 ,
70
+ radius : BALL_RADIUS
71
+ } ;
72
+
73
+ paddle = {
74
+ x : screenWidth / 2 - PADDLE_WIDTH / 2 ,
75
+ y : g . getHeight ( ) - 10 ,
76
+ width : PADDLE_WIDTH ,
77
+ height : PADDLE_HEIGHT
78
+ } ;
79
+ lives = 3 ;
80
+ score = 0 ;
81
+ level = 1 ;
82
+ gameOver = false ;
83
+ paused = false ;
84
+ loadHighScore ( ) ;
85
+ initBricks ( ) ;
86
+ }
87
+
88
+ function drawLives ( ) {
89
+ const heartSize = 6 ;
90
+ const spacing = 2 ;
91
+ const startX = g . getWidth ( ) - ( lives * ( heartSize + spacing ) ) - 2 ;
92
+ const y = 12 ;
93
+
94
+ g . setColor ( 1 , 0 , 0 ) ; // red
95
+
96
+ for ( let i = 0 ; i < lives ; i ++ ) {
97
+ const x = startX + i * ( heartSize + spacing ) ;
98
+ g . fillPoly ( [
99
+ x + 3 , y ,
100
+ x + 6 , y + 3 ,
101
+ x + 3 , y + 6 ,
102
+ x , y + 3
103
+ ] , true ) ;
104
+ }
105
+ }
106
+
107
+ function drawBricks ( ) {
108
+ g . setColor ( 0 , 1 , 0 ) ;
109
+ for ( let i = 0 ; i < bricks . length ; i ++ ) {
110
+ let b = bricks [ i ] ;
111
+ if ( b . status ) {
112
+ g . fillRect ( b . x , b . y , b . x + BRICK_WIDTH , b . y + BRICK_HEIGHT ) ;
113
+ }
114
+ }
115
+ }
116
+
117
+ function drawBall ( ) {
118
+ g . setColor ( 1 , 1 , 1 ) ;
119
+ g . fillCircle ( ball . x , ball . y , ball . radius ) ;
120
+ g . setColor ( 0.7 , 0.7 , 0.7 ) ;
121
+ g . fillCircle ( ball . x - 0.5 , ball . y - 0.5 , ball . radius - 1 ) ;
122
+ }
123
+
124
+ function drawPaddle ( ) {
125
+ g . setColor ( 0 , 1 , 1 ) ;
126
+ g . fillRect ( paddle . x , paddle . y , paddle . x + paddle . width , paddle . y + paddle . height ) ;
127
+ }
128
+
129
+ function drawHUD ( ) {
130
+ g . setColor ( 1 , 1 , 1 ) ;
131
+ g . setFont ( "6x8" , 1 ) ;
132
+ g . setFontAlign ( - 1 , - 1 ) ;
133
+ g . drawString ( "Score: " + score , 2 , 2 ) ;
134
+ g . setFontAlign ( 0 , - 1 ) ;
135
+ g . drawString ( "High: " + highScore , g . getWidth ( ) / 2 , 2 ) ;
136
+ g . setFontAlign ( 1 , - 1 ) ;
137
+ g . drawString ( "Lvl: " + level , g . getWidth ( ) - 2 , 2 ) ;
138
+ drawLives ( ) ;
139
+ if ( paused ) {
140
+ g . setFontAlign ( 0 , 0 ) ;
141
+ g . drawString ( "PAUSED" , g . getWidth ( ) / 2 , g . getHeight ( ) / 2 ) ;
142
+ }
143
+ }
144
+
145
+ function draw ( ) {
146
+ g . clear ( ) ;
147
+ drawBricks ( ) ;
148
+ drawBall ( ) ;
149
+ drawPaddle ( ) ;
150
+ drawHUD ( ) ;
151
+ g . flip ( ) ;
152
+ }
153
+
154
+ function showGameOver ( ) {
155
+ g . clear ( ) ;
156
+ g . setFont ( "6x8" , 2 ) ;
157
+ g . setFontAlign ( 0 , 0 ) ;
158
+ g . setColor ( 1 , 0 , 0 ) ;
159
+ g . drawString ( "GAME OVER" , g . getWidth ( ) / 2 , g . getHeight ( ) / 2 - 20 ) ;
160
+ g . setFont ( "6x8" , 1 ) ;
161
+ g . setColor ( 1 , 1 , 1 ) ;
162
+ g . drawString ( "Score: " + score , g . getWidth ( ) / 2 , g . getHeight ( ) / 2 ) ;
163
+ g . drawString ( "High: " + highScore , g . getWidth ( ) / 2 , g . getHeight ( ) / 2 + 12 ) ;
164
+ g . drawString ( "BTN2 = Restart" , g . getWidth ( ) / 2 , g . getHeight ( ) / 2 + 28 ) ;
165
+ g . flip ( ) ;
166
+ }
167
+
168
+ function collisionDetection ( ) {
169
+ for ( let i = 0 ; i < bricks . length ; i ++ ) {
170
+ let b = bricks [ i ] ;
171
+ if ( b . status ) {
172
+ if (
173
+ ball . x + ball . radius > b . x &&
174
+ ball . x - ball . radius < b . x + BRICK_WIDTH &&
175
+ ball . y + ball . radius > b . y &&
176
+ ball . y - ball . radius < b . y + BRICK_HEIGHT
177
+ ) {
178
+ ball . dy = - ball . dy ;
179
+ b . status = 0 ;
180
+ score += 10 ;
181
+ break ;
182
+ }
183
+ }
184
+ }
185
+ }
186
+
187
+
188
+ function allBricksCleared ( ) {
189
+ for ( let i = 0 ; i < bricks . length ; i ++ ) {
190
+ if ( bricks [ i ] . status !== 0 ) return false ;
191
+ }
192
+ return true ;
193
+ }
194
+
195
+ function update ( ) {
196
+ if ( paused || gameOver ) return ;
197
+
198
+ ball . x += ball . dx ;
199
+ ball . y += ball . dy ;
200
+
201
+ if ( ball . x + ball . radius > g . getWidth ( ) || ball . x - ball . radius < 0 ) {
202
+ ball . dx = - ball . dx ;
203
+ }
204
+ if ( ball . y - ball . radius < 0 ) {
205
+ ball . dy = - ball . dy ;
206
+ }
207
+
208
+ if (
209
+ ball . y + ball . radius >= paddle . y &&
210
+ ball . x >= paddle . x &&
211
+ ball . x <= paddle . x + paddle . width
212
+ ) {
213
+ ball . dy = - ball . dy ;
214
+ ball . y = paddle . y - ball . radius ;
215
+ }
216
+
217
+ if ( ball . y + ball . radius > g . getHeight ( ) ) {
218
+ lives -- ;
219
+ if ( lives > 0 ) {
220
+ // Reset ball and paddle only
221
+ ball . x = g . getWidth ( ) / 2 ;
222
+ ball . y = g . getHeight ( ) - 30 ;
223
+ ball . dx = 3 ;
224
+ ball . dy = - 3 ;
225
+ paddle . x = g . getWidth ( ) / 2 - PADDLE_WIDTH / 2 ;
226
+ paddle . y = g . getHeight ( ) - 20 ;
227
+ draw ( ) ;
228
+ return ;
229
+ } else {
230
+ clearInterval ( interval ) ;
231
+ interval = undefined ;
232
+ if ( paddleIntervalLeft ) {
233
+ clearInterval ( paddleIntervalLeft ) ;
234
+ paddleIntervalLeft = null ;
235
+ }
236
+ if ( paddleIntervalRight ) {
237
+ clearInterval ( paddleIntervalRight ) ;
238
+ paddleIntervalRight = null ;
239
+ }
240
+ saveHighScore ( ) ;
241
+ gameOver = true ;
242
+ draw ( ) ;
243
+ setTimeout ( showGameOver , 50 ) ;
244
+ return ;
245
+ }
246
+ }
247
+
248
+ collisionDetection ( ) ;
249
+
250
+ if ( allBricksCleared ( ) ) {
251
+ ball . dx *= SPEED_MULTIPLIER ;
252
+ ball . dy *= SPEED_MULTIPLIER ;
253
+ level ++ ;
254
+ initBricks ( ) ;
255
+ }
256
+
257
+ draw ( ) ;
258
+ }
259
+
260
+ function movePaddle ( x ) {
261
+ if ( gameOver || paused ) return ; // prevent paddle movement when not needed
262
+ paddle . x += x ;
263
+ if ( paddle . x < 0 ) paddle . x = 0 ;
264
+ if ( paddle . x + paddle . width > g . getWidth ( ) ) {
265
+ paddle . x = g . getWidth ( ) - paddle . width ;
266
+ }
267
+ }
268
+
269
+ function startGame ( ) {
270
+ initGame ( ) ;
271
+ draw ( ) ;
272
+ showGetReady ( ( ) => {
273
+ interval = setInterval ( update , 80 ) ;
274
+ } ) ;
275
+ }
276
+
277
+ setWatch ( ( ) => {
278
+ if ( gameOver ) {
279
+ startGame ( ) ;
280
+ } else {
281
+ paused = ! paused ;
282
+ draw ( ) ;
283
+ }
284
+ } , BTN2 , { repeat : true , edge : "rising" } ) ;
285
+
286
+ setWatch ( ( ) => {
287
+ if ( ! paddleIntervalLeft ) {
288
+ paddleIntervalLeft = setInterval ( ( ) => movePaddle ( - PADDLE_SPEED ) , 50 ) ;
289
+ }
290
+ } , BTN1 , { repeat : true , edge : "rising" } ) ;
291
+
292
+ setWatch ( ( ) => {
293
+ if ( paddleIntervalLeft ) {
294
+ clearInterval ( paddleIntervalLeft ) ;
295
+ paddleIntervalLeft = null ;
296
+ }
297
+ } , BTN1 , { repeat : true , edge : "falling" } ) ;
298
+
299
+ setWatch ( ( ) => {
300
+ if ( ! paddleIntervalRight ) {
301
+ paddleIntervalRight = setInterval ( ( ) => movePaddle ( PADDLE_SPEED ) , 50 ) ;
302
+ }
303
+ } , BTN3 , { repeat : true , edge : "rising" } ) ;
304
+
305
+ setWatch ( ( ) => {
306
+ if ( paddleIntervalRight ) {
307
+ clearInterval ( paddleIntervalRight ) ;
308
+ paddleIntervalRight = null ;
309
+ }
310
+ } , BTN3 , { repeat : true , edge : "falling" } ) ;
311
+
312
+ startGame ( ) ;
313
+ } ) ( ) ;
0 commit comments