Skip to content

Commit 2656f25

Browse files
authored
Merge pull request #3919 from IsReal8a/master
Adding a Breakout clone called BrickBreaker to the games
2 parents 36cf50f + cedf49c commit 2656f25

File tree

7 files changed

+358
-0
lines changed

7 files changed

+358
-0
lines changed

apps/bbreaker/ChangeLog

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
0.01: It works somehow, early version for testers and feedback :)
2+
0.02: Changed almost all code with Frederic version of Pong and adjusted to be a BrickBreaker!, still Alpha
3+
0.03: Rewrote the whole thing to have less code and better graphics, now it works.

apps/bbreaker/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# BrickBreaker
2+
3+
A simple BreakOut clone for the Banglejs
4+
5+
![Screenshot](bbreaker.png)
6+
7+
## Usage
8+
9+
![Screenshot](playing.png)
10+
11+
Buttons 1 and 3 to move the BrickBreaker!
12+
13+
Button 2 to pause and start the game again.
14+
15+
## Disclaimer
16+
17+
This game was created to learn JS and how to interact with Banglejs, meaning that it may not be perfect :).
18+
19+
Built with love with base on the tutorial: 2D breakout game using pure JavaScript
20+
https://developer.mozilla.org/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript
21+
22+
Started on 2020 but rewrote all in 2025 and this is the version I got without having issues with Memory Exhaustion.
23+
24+
## Creator
25+
26+
Israel Ochoa <isuraeru at gmail.com>

apps/bbreaker/app-icon.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/bbreaker/app.js

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
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+
})();

apps/bbreaker/bbreaker.png

2.03 KB
Loading

apps/bbreaker/metadata.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{ "id": "bbreaker",
2+
"name": "BrickBreaker",
3+
"shortName":"BrickBreaker",
4+
"icon": "bbreaker.png",
5+
"version":"0.03",
6+
"description": "A simple BreakOut clone for the Banglejs",
7+
"tags": "game",
8+
"readme": "README.md",
9+
"supports": ["BANGLEJS"],
10+
"allow_emulator": true,
11+
"storage": [
12+
{"name":"bbreaker.app.js","url":"app.js"},
13+
{"name":"bbreaker.img","url":"app-icon.js","evaluate":true}
14+
]
15+
}

apps/bbreaker/playing.png

2.62 KB
Loading

0 commit comments

Comments
 (0)