Skip to content

Commit 0e5df54

Browse files
committed
feat: day 75
1 parent 38eabc9 commit 0e5df54

File tree

1 file changed

+210
-29
lines changed

1 file changed

+210
-29
lines changed

075-breakout game/script.js

Lines changed: 210 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,61 @@ const color = getComputedStyle(document.documentElement).getPropertyValue(
99
const secondaryColor = getComputedStyle(
1010
document.documentElement
1111
).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+
1234
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 = "";
1567

1668
// Reference: https://stackoverflow.com/questions/34772957/how-to-make-canvas-responsive
1769
// 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;
2476
const ball = {
2577
x: canvas.width / 2,
2678
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,
3183
};
3284

3385
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,
3991
dx: 0,
4092
};
4193

4294
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,
48100
visible: true,
49101
};
50102

51103
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+
// }
60112

61113
// Create Elements
62114
function drawBall() {
@@ -80,6 +132,21 @@ function drawScore() {
80132
ctx.fillText(`Score: ${score}`, canvas.width - 100, 30);
81133
}
82134

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+
83150
function drawBricks() {
84151
bricks.forEach((column) => {
85152
column.forEach((brick) => {
@@ -99,7 +166,12 @@ function draw() {
99166
drawBall();
100167
drawPaddle();
101168
drawScore();
169+
drawLevel();
102170
drawBricks();
171+
generatePowerUps();
172+
if (paused && levelMessage) {
173+
drawLevelMessage();
174+
}
103175
}
104176

105177
// Animate Elements
@@ -127,6 +199,9 @@ function moveBall() {
127199
ball.x + ball.size < paddle.x + paddle.w &&
128200
ball.y + ball.size > paddle.y
129201
) {
202+
// Fix Ball-Paddle Collision Logic
203+
const dist = ball.x - (paddle.x + paddle.w / 2);
204+
ball.dx = dist * 0.1;
130205
ball.dy = -ball.speed;
131206
}
132207
// bricks
@@ -141,6 +216,18 @@ function moveBall() {
141216
) {
142217
ball.dy *= -1;
143218
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+
}
144231
increaseScore();
145232
}
146233
}
@@ -155,9 +242,20 @@ function moveBall() {
155242

156243
function increaseScore() {
157244
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);
161259
}
162260
}
163261

@@ -167,6 +265,86 @@ function showAllBricks() {
167265
});
168266
}
169267

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+
170348
// Handle Key Events
171349
function keyDown(e) {
172350
if (e.key === "Right" || e.key === "ArrowRight") paddle.dx = paddle.speed;
@@ -187,8 +365,10 @@ function keyUp(e) {
187365
// Update Canvas
188366
function update() {
189367
// update
190-
movePaddle();
191-
moveBall();
368+
if (!paused) {
369+
movePaddle();
370+
moveBall();
371+
}
192372
// draw
193373
draw();
194374
requestAnimationFrame(update);
@@ -201,4 +381,5 @@ rulesButton.addEventListener("click", () => rules.classList.add("show"));
201381
closeButton.addEventListener("click", () => rules.classList.remove("show"));
202382

203383
// Init
384+
setupLevel(0);
204385
update();

0 commit comments

Comments
 (0)