@@ -9,9 +9,61 @@ const color = getComputedStyle(document.documentElement).getPropertyValue(
9
9
const secondaryColor = getComputedStyle (
10
10
document . documentElement
11
11
) . getPropertyValue ( "--sidebar-color" ) ;
12
+
13
+ // Refactor Magic Numbers
14
+ const config = {
15
+ ballSpeed : 4 ,
16
+ ballSize : 10 ,
17
+ paddleSpeed : 8 ,
18
+ paddleWidth : 80 ,
19
+ paddleHeight : 10 ,
20
+ brickWidth : 70 ,
21
+ brickHeight : 20 ,
22
+ brickPadding : 10 ,
23
+ brickOffsetX : 45 ,
24
+ brickOffsetY : 60 ,
25
+ brickRowCount : 9 ,
26
+ brickColumnCount : 5 ,
27
+ powerUpChance : 0.3 , // 30% chance
28
+ powerUpTypes : [ "widen" , "slow" , "score" ] ,
29
+ powerUpSize : 12 ,
30
+ powerUpFallSpeed : 3 ,
31
+ powerUpDuration : 5000 ,
32
+ } ;
33
+
12
34
let score = 0 ;
13
- const brickRowCount = 9 ;
14
- const brickColumnCount = 5 ;
35
+ // const brickRowCount = config.brickRowCount;
36
+ // const brickColumnCount = config.brickColumnCount;
37
+ const powerUps = [ ] ;
38
+ const levels = [
39
+ // Level 1: Super Easy
40
+ {
41
+ brickRowCount : 2 ,
42
+ brickColumnCount : 4 ,
43
+ ballSpeed : 2 ,
44
+ } ,
45
+ // Level 2: Easy
46
+ {
47
+ brickRowCount : 4 ,
48
+ brickColumnCount : 5 ,
49
+ ballSpeed : 3 ,
50
+ } ,
51
+ // Level 3: Medium
52
+ {
53
+ brickRowCount : 6 ,
54
+ brickColumnCount : 7 ,
55
+ ballSpeed : 4 ,
56
+ } ,
57
+ // Level 4: Hard
58
+ {
59
+ brickRowCount : 8 ,
60
+ brickColumnCount : 9 ,
61
+ ballSpeed : 5 ,
62
+ } ,
63
+ ] ;
64
+ let currentLevel = 0 ;
65
+ let paused = false ;
66
+ let levelMessage = "" ;
15
67
16
68
// Reference: https://stackoverflow.com/questions/34772957/how-to-make-canvas-responsive
17
69
// https://stackoverflow.com/questions/39771732/drawing-to-responsive-canvas-that-is-100-width-and-height
@@ -24,39 +76,39 @@ ctx.canvas.height = ctx.canvas.width * heightRatio;
24
76
const ball = {
25
77
x : canvas . width / 2 ,
26
78
y : canvas . height / 2 ,
27
- size : 10 ,
28
- speed : 4 ,
29
- dx : 4 ,
30
- dy : - 4 ,
79
+ size : config . ballSize ,
80
+ speed : config . ballSpeed ,
81
+ dx : config . ballSpeed ,
82
+ dy : - config . ballSpeed ,
31
83
} ;
32
84
33
85
const paddle = {
34
- x : canvas . width / 2 - 40 ,
35
- y : canvas . height - 20 ,
36
- w : 80 ,
37
- h : 10 ,
38
- speed : 8 ,
86
+ x : canvas . width / 2 - config . paddleWidth / 2 ,
87
+ y : canvas . height - config . paddleHeight - 10 ,
88
+ w : config . paddleWidth ,
89
+ h : config . paddleHeight ,
90
+ speed : config . paddleSpeed ,
39
91
dx : 0 ,
40
92
} ;
41
93
42
94
const brickInfo = {
43
- w : 70 ,
44
- h : 20 ,
45
- padding : 10 ,
46
- offsetX : 45 ,
47
- offsetY : 60 ,
95
+ w : config . brickWidth ,
96
+ h : config . brickHeight ,
97
+ padding : config . brickPadding ,
98
+ offsetX : config . brickOffsetX ,
99
+ offsetY : config . brickOffsetY ,
48
100
visible : true ,
49
101
} ;
50
102
51
103
const bricks = [ ] ;
52
- for ( let i = 0 ; i < brickRowCount ; i ++ ) {
53
- bricks [ i ] = [ ] ;
54
- for ( let j = 0 ; j < brickColumnCount ; j ++ ) {
55
- const x = i * ( brickInfo . w + brickInfo . padding ) + brickInfo . offsetX ;
56
- const y = j * ( brickInfo . h + brickInfo . padding ) + brickInfo . offsetY ;
57
- bricks [ i ] [ j ] = { x, y, ...brickInfo } ;
58
- }
59
- }
104
+ // for (let i = 0; i < brickRowCount; i++) {
105
+ // bricks[i] = [];
106
+ // for (let j = 0; j < brickColumnCount; j++) {
107
+ // const x = i * (brickInfo.w + brickInfo.padding) + brickInfo.offsetX;
108
+ // const y = j * (brickInfo.h + brickInfo.padding) + brickInfo.offsetY;
109
+ // bricks[i][j] = { x, y, ...brickInfo };
110
+ // }
111
+ // }
60
112
61
113
// Create Elements
62
114
function drawBall ( ) {
@@ -80,6 +132,21 @@ function drawScore() {
80
132
ctx . fillText ( `Score: ${ score } ` , canvas . width - 100 , 30 ) ;
81
133
}
82
134
135
+ function drawLevel ( ) {
136
+ ctx . font = '20px "Balsamiq Sans"' ;
137
+ ctx . fillStyle = color ;
138
+ ctx . fillText ( `Level: ${ currentLevel + 1 } ` , 20 , 30 ) ;
139
+ }
140
+
141
+ function drawLevelMessage ( ) {
142
+ ctx . save ( ) ;
143
+ ctx . font = '40px "Balsamiq Sans"' ;
144
+ ctx . fillStyle = color ;
145
+ ctx . textAlign = "center" ;
146
+ ctx . fillText ( levelMessage , canvas . width / 2 , canvas . height / 2 ) ;
147
+ ctx . restore ( ) ;
148
+ }
149
+
83
150
function drawBricks ( ) {
84
151
bricks . forEach ( ( column ) => {
85
152
column . forEach ( ( brick ) => {
@@ -99,7 +166,12 @@ function draw() {
99
166
drawBall ( ) ;
100
167
drawPaddle ( ) ;
101
168
drawScore ( ) ;
169
+ drawLevel ( ) ;
102
170
drawBricks ( ) ;
171
+ generatePowerUps ( ) ;
172
+ if ( paused && levelMessage ) {
173
+ drawLevelMessage ( ) ;
174
+ }
103
175
}
104
176
105
177
// Animate Elements
@@ -127,6 +199,9 @@ function moveBall() {
127
199
ball . x + ball . size < paddle . x + paddle . w &&
128
200
ball . y + ball . size > paddle . y
129
201
) {
202
+ // Fix Ball-Paddle Collision Logic
203
+ const dist = ball . x - ( paddle . x + paddle . w / 2 ) ;
204
+ ball . dx = dist * 0.1 ;
130
205
ball . dy = - ball . speed ;
131
206
}
132
207
// bricks
@@ -141,6 +216,18 @@ function moveBall() {
141
216
) {
142
217
ball . dy *= - 1 ;
143
218
brick . visible = false ;
219
+ if ( Math . random ( ) < config . powerUpChance ) {
220
+ const type =
221
+ config . powerUpTypes [
222
+ Math . floor ( Math . random ( ) * config . powerUpTypes . length )
223
+ ] ;
224
+ powerUps . push ( {
225
+ x : brick . x + brick . w / 2 ,
226
+ y : brick . y ,
227
+ type,
228
+ active : true ,
229
+ } ) ;
230
+ }
144
231
increaseScore ( ) ;
145
232
}
146
233
}
@@ -155,9 +242,20 @@ function moveBall() {
155
242
156
243
function increaseScore ( ) {
157
244
score ++ ;
158
- if ( score % ( brickRowCount * brickRowCount ) === 0 ) {
159
- // no remainder
160
- showAllBricks ( ) ;
245
+ const allBricksGone = bricks . flat ( ) . every ( ( brick ) => ! brick . visible ) ;
246
+ if ( allBricksGone ) {
247
+ currentLevel ++ ;
248
+ if ( currentLevel >= levels . length ) {
249
+ // loop back to last level
250
+ currentLevel = levels . length - 1 ;
251
+ }
252
+ paused = true ;
253
+ levelMessage = `Level ${ currentLevel + 1 } ` ;
254
+ setTimeout ( ( ) => {
255
+ setupLevel ( currentLevel ) ;
256
+ paused = false ;
257
+ levelMessage = "" ;
258
+ } , 5000 ) ;
161
259
}
162
260
}
163
261
@@ -167,6 +265,86 @@ function showAllBricks() {
167
265
} ) ;
168
266
}
169
267
268
+ // Add Power-ups
269
+ function applyPowerUp ( type ) {
270
+ if ( type === "widen" ) {
271
+ paddle . w = 140 ;
272
+ setTimeout ( ( ) => ( paddle . w = config . paddleWidth ) , config . powerUpDuration ) ;
273
+ } else if ( type === "slow" ) {
274
+ ball . speed = 2 ;
275
+ ball . dx = Math . sign ( ball . dx ) * ball . speed ;
276
+ ball . dy = Math . sign ( ball . dy ) * ball . speed ;
277
+ setTimeout ( ( ) => {
278
+ ball . speed = config . ballSpeed ;
279
+ ball . dx = Math . sign ( ball . dx ) * ball . speed ;
280
+ ball . dy = Math . sign ( ball . dy ) * ball . speed ;
281
+ } , config . powerUpDuration ) ;
282
+ } else if ( type === "score" ) {
283
+ score += 5 ;
284
+ }
285
+ }
286
+
287
+ function generatePowerUps ( ) {
288
+ powerUps . forEach ( ( powerUp ) => {
289
+ if ( ! powerUp . active ) return ;
290
+ powerUp . y += config . powerUpFallSpeed ;
291
+ // Draw power-up
292
+ ctx . beginPath ( ) ;
293
+ ctx . arc ( powerUp . x , powerUp . y , config . powerUpSize , 0 , Math . PI * 2 ) ;
294
+ ctx . fillStyle =
295
+ powerUp . type === "widen"
296
+ ? "#91eae4"
297
+ : powerUp . type === "slow"
298
+ ? "#86a8e7"
299
+ : "#DBB957" ;
300
+ ctx . fill ( ) ;
301
+ ctx . closePath ( ) ;
302
+ // Paddle collision
303
+ if (
304
+ powerUp . y + config . powerUpSize > paddle . y &&
305
+ powerUp . x > paddle . x &&
306
+ powerUp . x < paddle . x + paddle . w
307
+ ) {
308
+ applyPowerUp ( powerUp . type ) ;
309
+ powerUp . active = false ;
310
+ }
311
+ // Remove if off screen
312
+ if ( powerUp . y > canvas . height ) powerUp . active = false ;
313
+ } ) ;
314
+ }
315
+
316
+ // Implement Game Levels
317
+ function setupLevel ( levelIndex ) {
318
+ const level = levels [ levelIndex ] ;
319
+ config . brickRowCount = level . brickRowCount ;
320
+ config . brickColumnCount = level . brickColumnCount ;
321
+ ball . speed = level . ballSpeed ;
322
+ ball . dx = level . ballSpeed ;
323
+ ball . dy = - level . ballSpeed ;
324
+ // Reset paddle
325
+ paddle . x = canvas . width / 2 - config . paddleWidth / 2 ;
326
+ paddle . y = canvas . height - config . paddleHeight - 10 ;
327
+ paddle . w = config . paddleWidth ;
328
+ // Reset ball
329
+ ball . x = canvas . width / 2 ;
330
+ ball . y = canvas . height / 2 ;
331
+ // Center bricks horizontally
332
+ const totalBricksWidth =
333
+ config . brickColumnCount * config . brickWidth +
334
+ ( config . brickColumnCount - 1 ) * config . brickPadding ;
335
+ const centeredOffsetX = ( canvas . width - totalBricksWidth ) / 2 ;
336
+ // Rebuild bricks
337
+ bricks . length = 0 ;
338
+ for ( let i = 0 ; i < config . brickRowCount ; i ++ ) {
339
+ bricks [ i ] = [ ] ;
340
+ for ( let j = 0 ; j < config . brickColumnCount ; j ++ ) {
341
+ const x = j * ( brickInfo . w + brickInfo . padding ) + centeredOffsetX ;
342
+ const y = i * ( brickInfo . h + brickInfo . padding ) + brickInfo . offsetY ;
343
+ bricks [ i ] [ j ] = { x, y, w : brickInfo . w , h : brickInfo . h , visible : true } ;
344
+ }
345
+ }
346
+ }
347
+
170
348
// Handle Key Events
171
349
function keyDown ( e ) {
172
350
if ( e . key === "Right" || e . key === "ArrowRight" ) paddle . dx = paddle . speed ;
@@ -187,8 +365,10 @@ function keyUp(e) {
187
365
// Update Canvas
188
366
function update ( ) {
189
367
// update
190
- movePaddle ( ) ;
191
- moveBall ( ) ;
368
+ if ( ! paused ) {
369
+ movePaddle ( ) ;
370
+ moveBall ( ) ;
371
+ }
192
372
// draw
193
373
draw ( ) ;
194
374
requestAnimationFrame ( update ) ;
@@ -201,4 +381,5 @@ rulesButton.addEventListener("click", () => rules.classList.add("show"));
201
381
closeButton . addEventListener ( "click" , ( ) => rules . classList . remove ( "show" ) ) ;
202
382
203
383
// Init
384
+ setupLevel ( 0 ) ;
204
385
update ( ) ;
0 commit comments