A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 768 lines 24 kB view raw
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--------------------------------------------------------------------------------