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