A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1--[[
2 __________ __ ___.
3 Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 \/ \/ \/ \/ \/
8 $Id$
9
10 Port of Chain Reaction (which is based on Boomshine) to Rockbox in Lua.
11 See http://www.yvoschaap.com/chainrxn/ and http://www.k2xl.com/games/boomshine/
12
13 Copyright (C) 2009 by Maurus Cuelenaere
14 Copyright (C) 2020 William Wilgus -- Added circles, logic rewrite, hard levels
15
16 This program is free software; you can redistribute it and/or
17 modify it under the terms of the GNU General Public License
18 as published by the Free Software Foundation; either version 2
19 of the License, or (at your option) any later version.
20
21 This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
22 KIND, either express or implied.
23
24]]--
25require "actions"
26local DEBUG = false
27
28--[[only save the actions we are using]]----------------------------------------
29pla = {}
30for key, val in pairs(rb.actions) do
31 for _, v in ipairs({"PLA_", "TOUCHSCREEN", "_NONE"}) do
32 if string.find (key, v) then
33 pla[key] = val; break
34 end
35 end
36end
37rb.actions, rb.contexts = nil, nil
38--------------------------------------------------------------------------------
39
40
41--[[ Profiling ]]---------------------------------------------------------------
42local screen_redraw_count = 0
43local screen_redraw_duration = 0
44--------------------------------------------------------------------------------
45
46
47--[[ References ]]--------------------------------------------------------------
48local _LCD = rb.lcd_framebuffer()
49local rocklib_image = getmetatable(_LCD)
50local _ellipse = rocklib_image.ellipse--
51local irand = math.random--
52local lcd_putsxy = rb.lcd_putsxy--
53local strfmt = string.format--
54local Cursor_Ref = nil
55local Ball_Ref = {}
56--------------------------------------------------------------------------------
57
58
59--[[ 'Constants' ]]-------------------------------------------------------------
60local SCORE_MULTIPLY = 10
61local BSAND = 0x8--
62local HAS_TOUCHSCREEN = rb.action_get_touchscreen_press ~= nil
63local Empty_fn = function() end
64local LCD_H, LCD_W = rb.LCD_HEIGHT, rb.LCD_WIDTH
65local DEFAULT_BALL_SZ = LCD_H > LCD_W and LCD_W / 30 or LCD_H / 30
66 DEFAULT_BALL_SZ = DEFAULT_BALL_SZ - bit.band(DEFAULT_BALL_SZ, 1)
67local MAX_BALL_SPEED = DEFAULT_BALL_SZ / 2 + 1
68-- Ball states
69local B_DEAD, B_MOVE, B_EXPLODE, B_DIE, B_WAIT, B_IMPLODE = 5, 4, 3, 2, 1, 0
70
71local DEFAULT_FG_CLR = 1
72local DEFAULT_BG_CLR = 0
73if rb.lcd_get_foreground ~= nil then
74 DEFAULT_FG_CLR = rb.lcd_get_foreground()
75 DEFAULT_BG_CLR = rb.lcd_get_background()
76elseif rb.LCD_DEFAULT_FG ~= nil then
77 DEFAULT_FG_CLR = rb.LCD_DEFAULT_FG
78 DEFAULT_BG_CLR = rb.LCD_DEFAULT_BG
79end
80
81local FMT_EXPEND, FMT_LEVEL = "%d balls expended", "Level %d"
82local FMT_TOTPTS, FMT_LVPTS = "%d total points", "%d level points"
83--------------------------------------------------------------------------------
84
85
86--[[ Other ]]-------------------------------------------------------------------
87local Player_Added
88local Action_Evt = pla.ACTION_NONE
89
90local levels = {
91 -- {GOAL, TOTAL_BALLS},
92 {1, 5},
93 {2, 10},
94 {4, 15},
95 {6, 20},
96 {10, 25},
97 {15, 30},
98 {18, 35},
99 {22, 40},
100 {30, 45},
101 {37, 50},
102 {48, 55},
103 {58, 60},
104 {28, 30},
105 {23, 25},
106 {18, 20},
107 {13, 15},
108 {8, 10},
109 {9, 10},
110 {4, 5},
111 {5, 5}
112 }
113--------------------------------------------------------------------------------
114
115
116--[[ Event Callback ]]----------------------------------------------------------
117function action_event(action)
118 if action == pla.PLA_CANCEL then
119 Action_Evt = pla.PLA_EXIT
120 else
121 Action_Evt = action
122 end
123end
124--------------------------------------------------------------------------------
125
126
127--[[ Helper functions ]]--------------------------------------------------------
128local function getstringsize(str)
129 local _, w, h = rb.font_getstringsize(str, rb.FONT_UI)
130 return w, h
131end
132
133local function set_foreground(color)
134 if rb.lcd_set_foreground ~= nil then
135 rb.lcd_set_foreground(color)
136 end
137end
138
139local function calc_score(total, level, goal, expended)
140 local bonus = 0
141 local score = (expended * level) * SCORE_MULTIPLY
142 if expended < goal then
143 score = -(score + (level * SCORE_MULTIPLY))
144 elseif expended > goal then
145 bonus = (expended - goal) * (level * SCORE_MULTIPLY)
146 end
147 total = total + score + bonus
148 return total, score, bonus
149end
150
151local function wait_anykey(to_secs)
152 rb.sleep(rb.HZ / 2)
153 rb.button_clear_queue()
154 rb.button_get_w_tmo(rb.HZ * to_secs)
155end
156
157local function disp_msg(to, ...)
158 local message = strfmt(...)
159 local w, h = getstringsize(message)
160 local x, y = (LCD_W - w) / 2, (LCD_H - h) / 2
161
162 rb.lcd_clear_display()
163 set_foreground(DEFAULT_FG_CLR)
164
165 if w > LCD_W then
166 rb.lcd_puts_scroll(0, (y / h), message)
167 else
168 lcd_putsxy(x, y, message)
169 end
170
171 if to == -1 then
172 local msg = "Press button to exit"
173 w, h = getstringsize(msg)
174 x = (LCD_W - w) / 2
175 if x < 0 then
176 rb.lcd_puts_scroll(0, (y / h) + 1, msg)
177 else
178 lcd_putsxy(x, y + h, msg)
179 end
180 end
181 rb.lcd_update()
182
183 if to == -1 then
184 wait_anykey(60)
185 else
186 rb.sleep(to)
187 end
188
189 rb.lcd_scroll_stop() -- Stop our scrolling message
190end
191
192local function test_speed()
193 local test_spd, redraw, dur = start_round(0, 0, 0, 0) -- test speed of target
194 --Logic speed, screen redraw, duration
195 if DEBUG == true then
196 disp_msg(rb.HZ * 5, "Spd: %d, Redraw: %d Dur: %d Ms", test_spd, redraw, dur)
197 end
198 if test_spd < 25 or redraw < 10 then
199 rb.splash(rb.HZ, "Slow Target..")
200
201 if test_spd < 10 then
202 MAX_BALL_SPEED = MAX_BALL_SPEED + MAX_BALL_SPEED
203 elseif test_spd < 15 then
204 MAX_BALL_SPEED = MAX_BALL_SPEED + MAX_BALL_SPEED / 2
205 elseif test_spd < 25 then
206 MAX_BALL_SPEED = MAX_BALL_SPEED + MAX_BALL_SPEED / 4
207 end
208 end
209end
210--------------------------------------------------------------------------------
211
212
213--[[ Ball Functions ]]----------------------------------------------------------
214local Ball = {
215 -- Ball defaults
216 sz = DEFAULT_BALL_SZ,
217 next_tick = 0,
218 state = B_MOVE,
219 }
220
221function Ball:new(o, level, color)
222 local function random_color()
223 if color == nil then
224 if rb.lcd_rgbpack ~= nil then -- color target
225 return rb.lcd_rgbpack(irand(1,255), irand(1,255), irand(1,255))
226 end
227 color = irand(1, rb.LCD_DEPTH)
228 color = (rb.LCD_DEPTH == 2) and (3 - color) or color -- invert for 2-bit screens
229 end
230 return color
231 end
232
233 if o == nil then
234 level = level or 1
235 local maxdelay = (level <= 5) and 15 or level * 2
236 o = {
237 x = irand(1, LCD_W - self.sz),
238 y = irand(1, LCD_H - self.sz),
239 color = random_color(),
240 xi = self.genSpeed(), -- x increment; x + sz after explode
241 yi = self.genSpeed(), -- y increment; y + sz after explode
242 step_delay = irand(3, maxdelay / 2),
243 explosion_sz = irand(2 * self.sz, 4 * self.sz),
244 life_ticks = irand(rb.HZ / level, rb.HZ * (maxdelay / 5)),
245 draw_fn = nil,
246 step_fn = self.step -- reference to current stepping function
247 }
248 end
249 local Ball = o or {} -- so we can use Ball. instead of self
250
251 -- these functions end up in a closure; faster to call, use more RAM
252 function Ball.draw()
253 _ellipse(_LCD, o.x, o.y, o.x + o.sz, o.y + o.sz, o.color, o.color, true)
254 end
255
256 function Ball.draw_exploded()
257 _ellipse(_LCD, o.x, o.y, o.xi, o.yi, o.color, nil, true)
258 end
259
260 if Ball.draw_fn == nil then
261 Ball.draw_fn = Ball.draw -- reference to current drawing function
262 end
263
264 setmetatable(o, self)
265 self.__index = self
266 return o
267end
268
269-- these functions are shared by reference, slower to call, use less RAM
270function Ball:genSpeed()
271 local speed = irand(-MAX_BALL_SPEED, MAX_BALL_SPEED)
272 return speed ~= 0 and speed or MAX_BALL_SPEED -- Make sure all balls move
273end
274
275function Ball:step(tick)
276 local function rndspeed(cur)
277 local speed = cur + irand(-2, 2)
278 if speed < -MAX_BALL_SPEED then
279 speed = -MAX_BALL_SPEED
280 elseif speed > MAX_BALL_SPEED then
281 speed = MAX_BALL_SPEED
282 elseif speed == 0 then
283 speed = cur
284 end
285 return speed
286 end
287
288 self.next_tick = tick + self.step_delay
289 self.x = self.x + self.xi
290 self.y = self.y + self.yi
291 local max_x = LCD_W - self.sz
292 local max_y = LCD_H - self.sz
293
294 if self.x <= 0 then
295 self.xi = -self.xi
296 self.x = 1
297 self.yi = rndspeed(self.yi)
298 elseif self.x >= max_x then
299 self.xi = -self.xi
300 self.x = max_x
301 self.yi = rndspeed(self.yi)
302 end
303
304 if self.y <= 0 then
305 self.yi = -self.yi
306 self.y = 1
307 self.xi = rndspeed(self.xi)
308 elseif self.y >= max_y then
309 self.yi = -self.yi
310 self.y = max_y
311 self.xi = rndspeed(self.xi)
312 end
313end
314
315function Ball:step_exploded(tick)
316 -- exploding ball state machine
317 -- B_EXPLODE >> B_DIE >> BWAIT >> B_IMPLODE >> B_DEAD
318 if self.state == B_EXPLODE and self.sz < self.explosion_sz then
319 self.sz = self.sz + 2
320 self.x = self.x - 1 -- stay centered
321 self.y = self.y - 1
322 elseif self.state == B_DIE then
323 self.state = B_WAIT
324 self.next_tick = tick + self.life_ticks
325 return
326 elseif self.state == B_IMPLODE then
327 if self.sz > 0 then
328 self.sz = self.sz - 2
329 self.x = self.x + 1 -- stay centered
330 self.y = self.y + 1
331 else
332 self.state = B_DEAD
333 self.draw_fn = Empty_fn
334 self.step_fn = Empty_fn
335 return B_DEAD
336 end
337 elseif self.next_tick < tick then -- decay to next lower state
338 self.state = self.state - 1
339 return
340 end
341 -- fell through, update next_tick and impact region
342 self.next_tick = tick + self.step_delay
343 self.xi = self.x + self.sz
344 self.yi = self.y + self.sz
345end
346
347function Ball:checkHit(other)
348 local x, y = self.x, self.y
349 if (other.xi >= x) and (other.yi >= y) then
350 local sz = self.sz
351 local xi, yi = x + sz, y + sz
352 if (xi >= other.x) and (yi >= other.y) then
353 -- update impact region
354 self.xi = xi
355 self.yi = yi
356 -- change to exploded state
357 self.state = B_EXPLODE
358 self.draw_fn = self.draw_exploded
359 self.step_fn = self.step_exploded
360
361 if other.state < B_EXPLODE then -- add duration to the ball that got hit
362 other.next_tick = other.next_tick + self.life_ticks
363 end
364 return true
365 end
366 end
367
368 return false
369end
370--------------------------------------------------------------------------------
371
372
373--[[ Cursor Functions ]]--------------------------------------------------------
374local Cursor = {
375 sz = (DEFAULT_BALL_SZ * 2),
376 x = (LCD_W / 2),
377 y = (LCD_H / 2),
378 color = DEFAULT_FG_CLR
379 --image = nil
380 }
381
382function Cursor:new()
383 if rb.LCD_DEPTH == 2 then -- invert for 2 - bit screens
384 self.color = 3 - DEFAULT_FG_CLR
385 end
386 if not HAS_TOUCHSCREEN and not self.image then
387 local function create_image(sz)
388 sz = sz + 1
389 local img = rb.new_image(sz, sz)
390 local sz2 = (sz / 2) + 1
391 local sz4 = (sz / 4)
392
393 img:clear(0)
394 img:line(1, 1, sz4 + 1, 1, 1)
395 img:line(1, 1, 1, sz4 + 1, 1)
396
397 img:line(1, sz, sz4 + 1, sz, 1)
398 img:line(1, sz, 1, sz - sz4, 1)
399
400 img:line(sz, sz, sz - sz4, sz, 1)
401 img:line(sz, sz, sz, sz - sz4, 1)
402
403 img:line(sz, 1, sz - sz4, 1, 1)
404 img:line(sz, 1, sz, sz4 + 1, 1)
405
406 -- crosshairs
407 img:line(sz2 - sz4, sz2, sz2 + sz4, sz2, 1)
408 img:line(sz2, sz2 - sz4, sz2, sz2 + sz4, 1)
409 return img
410 end
411 self.image = create_image(DEFAULT_BALL_SZ * 2)
412 end
413
414 function Cursor.draw()
415 rocklib_image.copy(_LCD, self.image, self.x, self.y, _NIL, _NIL,
416 _NIL, _NIL, true, BSAND, self.color)
417 end
418
419 return self
420end
421
422function Cursor:do_action(action)
423 local function clamp_roll(iVal, iMin, iMax)
424 if iVal < iMin then
425 iVal = iMax
426 elseif iVal > iMax then
427 iVal = iMin
428 end
429 return iVal
430 end
431 local xi, yi = 0, 0
432
433 if HAS_TOUCHSCREEN and action == pla.ACTION_TOUCHSCREEN then
434 _, self.x, self.y = rb.action_get_touchscreen_press()
435 return true
436 elseif action == pla.PLA_SELECT then
437 return true
438 elseif (action == pla.PLA_RIGHT or action == pla.PLA_RIGHT_REPEAT) then
439 xi = self.sz
440 elseif (action == pla.PLA_LEFT or action == pla.PLA_LEFT_REPEAT) then
441 xi = -self.sz
442 elseif (action == pla.PLA_UP or action == pla.PLA_UP_REPEAT) then
443 yi = -self.sz
444 elseif (action == pla.PLA_DOWN or action == pla.PLA_DOWN_REPEAT) then
445 yi = self.sz
446 else
447 Action_Evt = pla.ACTION_NONE
448 return false
449 end
450
451 self.x = clamp_roll(self.x + xi, 1, LCD_W - self.sz)
452 self.y = clamp_roll(self.y + yi, 1, LCD_H - self.sz)
453 Action_Evt = pla.ACTION_NONE
454 return false
455end
456--------------------------------------------------------------------------------
457
458
459--[[ Game function ]]-----------------------------------------------------------
460function start_round(level, goal, nrBalls, total)
461 Player_Added = false
462 --[[ References ]]--
463 local current_tick = rb.current_tick
464 local lcd_update = rb.lcd_update
465 local lcd_clear_display = rb.lcd_clear_display
466
467 local test_spd = false
468 local is_exit = false;
469 local score = 0
470 local last_expend, nrBalls_expend = 0, 0
471 local Balls = {}
472 local balls_exploded = 1 -- to keep looping when player_added == false
473
474 local tick_next = 0
475 local cursor = nil
476 local draw_cursor = Empty_fn
477 local refresh = rb.HZ/20
478
479 local function level_stats(level, total)
480 return strfmt(FMT_LEVEL, level), strfmt(FMT_TOTPTS, total)
481 end
482
483 local str_level, str_totpts = level_stats(level, total) -- static for lvl
484 local str_expend, str_lvlpts
485
486 local function update_stats()
487 -- we only create a new string when a hit is detected
488 str_expend = strfmt(FMT_EXPEND, nrBalls_expend)
489 str_lvlpts = strfmt(FMT_LVPTS, score)
490 end
491
492 function draw_stats()
493 local function draw_pos_str(bottom, right, str)
494 local w, h = getstringsize(str)
495 local x = (right > 0) and ((LCD_W - w) * right - 1) or 1
496 local y = (bottom > 0) and ((LCD_H - h) * bottom - 1) or 1
497 lcd_putsxy(x, y, str)
498 end
499 -- pos(B, R) [B/R]=0 => [y/x]=1, [B/R]=1 => [y/x]=lcd[H/W]-string[H/W]
500 draw_pos_str(0, 0, str_expend)
501 draw_pos_str(0, 1, str_level)
502 draw_pos_str(1, 1, str_lvlpts)
503 draw_pos_str(1, 0, str_totpts)
504 end
505
506 -- all Balls share same function, will by changed by player_add()
507 local checkhit_fn = Empty_fn
508
509 local function checkhit(Ball)
510 if Ball.state == B_MOVE then
511 for i = #Balls, 1, -1 do
512 if Balls[i].state < B_MOVE and
513 Ball:checkHit(Balls[i]) then -- exploded?
514 balls_exploded = balls_exploded + 1
515 nrBalls_expend = nrBalls_expend + 1
516 break
517 end
518 end
519 end
520 end
521
522 local function add_player()
523 -- cursor becomes exploded ball
524 local player = Ball:new({
525 x = cursor.x - cursor.sz,
526 y = cursor.y - cursor.sz,
527 color = cursor.color,
528 step_delay = 3,
529 explosion_sz = (3 * DEFAULT_BALL_SZ),
530 life_ticks = (test_spd) and (rb.HZ) or
531 irand(rb.HZ * 2, rb.HZ * DEFAULT_BALL_SZ),
532 sz = 10,
533 state = B_EXPLODE
534 })
535 -- set x/y impact region -->[]
536 player.xi = player.x + player.sz
537 player.yi = player.y + player.sz
538 player.draw_fn = player.draw_exploded
539 player.step_fn = player.step_exploded
540
541 table.insert(Balls, player)
542 balls_exploded = 1
543 Player_Added = true
544 cursor = nil
545 draw_cursor = Empty_fn
546 -- only need collision detection after player add
547 checkhit_fn = checkhit
548 end
549
550 local function speedtest()
551 -- check speed of target
552 set_foreground(DEFAULT_BG_CLR) --hide text during test
553 level = 1
554 nrBalls = 100
555 cursor = {x = LCD_W * 2, y = LCD_H * 2, color = 0, sz = 1}
556 table.insert(Balls, Ball:new({
557 x = 1, y = 1, xi = 1, yi = 1,
558 color = 0, step_delay = 0,
559 explosion_sz = 0, life_ticks = 0,
560 draw_fn = Empty_fn,
561 step_fn = function() test_spd = test_spd + 1 end
562 })
563 )
564 test_spd = 0
565 add_player()
566 end
567
568 local function screen_redraw()
569 -- (draw_fn changes dynamically at runtime)
570 for i = 1, #Balls do
571 Balls[i].draw_fn()
572 end
573
574 draw_stats()
575 draw_cursor()
576
577 lcd_update()
578 lcd_clear_display()
579 end
580
581 local function game_loop()
582 local tick = current_tick()
583 for _, Ball in ipairs(Balls) do
584 if tick > Ball.next_tick then
585 -- (step_fn changes dynamically at runtime)
586 if Ball:step_fn(tick) == B_DEAD then
587 balls_exploded = balls_exploded - 1
588 else
589 checkhit_fn(Ball)
590 end
591 end
592 end
593 return tick
594 end
595
596 if level < 1 then
597 speedtest()
598 local bkcolor = (rb.LCD_DEPTH == 2) and (3) or 0
599 -- Initialize the balls
600 if DEBUG then bkcolor = nil end
601 for i=1, nrBalls do
602 table.insert(Balls, Ball:new(nil, level, bkcolor))
603 end
604 else
605 speedtest = nil
606 set_foreground(DEFAULT_FG_CLR) -- color for text
607 cursor = Cursor:new()
608 if not HAS_TOUCHSCREEN then
609 draw_cursor = cursor.draw
610 end
611 -- Initialize the balls
612 for i=1, nrBalls do
613 table.insert(Balls, Ball:new(nil, level))
614 end
615 end
616
617 -- Make sure there are no unwanted touchscreen presses
618 rb.button_clear_queue()
619
620 Action_Evt = pla.ACTION_NONE
621
622 update_stats() -- load status strings
623
624 if rb.cpu_boost then rb.cpu_boost(true) end
625 collectgarbage("collect") -- run gc now to hopefully prevent interruption later
626
627 local duration = current_tick()
628 -- Game loop >> Check if the round is over
629 while balls_exploded > 0 do
630 if Action_Evt == pla.PLA_EXIT then
631 is_exit = true
632 break
633 end
634
635 if game_loop() > tick_next then
636 tick_next = current_tick() + refresh
637 if not Player_Added then
638 if Action_Evt ~= pla.ACTION_NONE and cursor:do_action(Action_Evt) then
639 add_player()
640 end
641 end
642
643 if nrBalls_expend ~= last_expend then -- hit detected?
644 last_expend = nrBalls_expend
645 score = (nrBalls_expend * level) * SCORE_MULTIPLY
646 update_stats() -- only update stats when not current
647 if nrBalls_expend == nrBalls then break end -- round is over?
648 end
649
650 screen_redraw_count = screen_redraw_count + 1
651 screen_redraw()
652 end
653 rb.yield() -- yield to other tasks
654 end
655
656 screen_redraw_duration = screen_redraw_duration + (rb.current_tick() - duration)
657 if rb.cpu_boost then rb.cpu_boost(false) end
658
659 if test_spd and test_spd > 0 then
660 return test_spd, screen_redraw_count, screen_redraw_duration *10 --ms
661 end
662
663 -- splash the final stats for a moment at end
664 lcd_clear_display()
665 for _, Ball in ipairs(Balls) do
666 -- move balls back to their initial exploded positions
667 if Ball.state == B_DEAD then
668 Ball.sz = Ball.explosion_sz + 2
669 Ball.x = Ball.x - (Ball.explosion_sz / 2)
670 Ball.y = Ball.y - (Ball.explosion_sz / 2)
671 end
672 Ball:draw()
673 end
674
675 if DEFAULT_BALL_SZ > 3 then -- dither
676 _LCD:clear(nil, nil, nil, nil, nil, nil, 2, 2)
677 end
678
679 total = calc_score(total, level, goal, nrBalls_expend)
680 str_level, str_totpts = level_stats(level, total)
681 draw_stats()
682 lcd_update()
683 wait_anykey(2)
684
685 return is_exit, score, nrBalls_expend
686end
687--------------------------------------------------------------------------------
688
689
690--[[MAIN PROGRAM]]--------------------------------------------------------------
691do -- attempt to get stats to fit on screen
692 local function getwidth(str)
693 local w, _ = getstringsize(str)
694 return w
695 end
696 local w0, w1 = getwidth(FMT_EXPEND), getwidth(FMT_LEVEL)
697 if (w0 + w1) > LCD_W then
698 FMT_EXPEND, FMT_LEVEL = "%d balls", "Lv %d"
699 end
700 w0, w1 = getwidth(FMT_TOTPTS), getwidth(FMT_LVPTS)
701 if (w0 + w1 + getwidth("0000000")) > LCD_W then
702 FMT_TOTPTS, FMT_LVPTS = "%d total", "%d lv"
703 end
704end
705
706if HAS_TOUCHSCREEN then
707 rb.touchscreen_mode(rb.TOUCHSCREEN_POINT)
708end
709
710rb.backlight_force_on()
711
712local eva = rockev.register("action", action_event, rb.HZ / 10)
713
714math.randomseed(os.time() or 1)
715
716local idx, highscore = 1, 0
717
718disp_msg(rb.HZ, "BoomShine")
719test_speed()
720rb.sleep(rb.HZ * 2)
721test_speed = nil
722
723while levels[idx] ~= nil do
724 local goal, nrBalls = levels[idx][1], levels[idx][2]
725
726 collectgarbage("collect") -- run gc now to hopefully prevent interruption later
727
728 disp_msg(rb.HZ * 2, "Level %d: get %d out of %d balls", idx, goal, nrBalls)
729
730 local is_exit, score, nrBalls_expend = start_round(idx, goal, nrBalls, highscore)
731 if DEBUG == true then
732 local fps = screen_redraw_count * 100 / screen_redraw_duration
733 disp_msg(rb.HZ * 5, "Redraw: %d fps", fps)
734 end
735
736 if is_exit then
737 break -- Exiting..
738 else
739 highscore, score, bonus = calc_score(highscore, idx, goal, nrBalls_expend)
740 if nrBalls_expend >= goal then
741 if bonus == 0 then
742 disp_msg(rb.HZ * 2, "You won!")
743 else
744 disp_msg(rb.HZ * 2, "You won BONUS!")
745 end
746 levels[idx] = nil
747 idx = idx + 1
748 else
749 disp_msg(rb.HZ * 2, "You lost %d points!", -score)
750 if highscore < 0 then break end
751 end
752 end
753end
754
755if highscore <= 0 then
756 disp_msg(-1, "You lost at level %d", idx)
757elseif idx > #levels then
758 disp_msg(-1, "You finished the game with %d points!", highscore)
759else
760 disp_msg(-1, "You made it till level %d with %d points!", idx, highscore)
761end
762
763-- Restore user backlight settings
764rb.backlight_use_settings()
765if rb.cpu_boost then rb.cpu_boost(false) end
766
767os.exit()
768--------------------------------------------------------------------------------