diff --git a/apps/plugins/boomshine.lua b/apps/plugins/boomshine.lua index c7ed6dfdf1..fcdf7c9a1c 100644 --- a/apps/plugins/boomshine.lua +++ b/apps/plugins/boomshine.lua @@ -11,7 +11,7 @@ See http://www.yvoschaap.com/chainrxn/ and http://www.k2xl.com/games/boomshine/ Copyright (C) 2009 by Maurus Cuelenaere - Copyright (C) 2018 William Wilgus -- Added circles, logic rewrite, hard levels + Copyright (C) 2020 William Wilgus -- Added circles, logic rewrite, hard levels This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -23,7 +23,9 @@ ]]-- require "actions" ---[[only save the actions we are using]] +local DEBUG = false + +--[[only save the actions we are using]]---------------------------------------- pla = {} for key, val in pairs(rb.actions) do for _, v in ipairs({"PLA_", "TOUCHSCREEN", "_NONE"}) do @@ -33,15 +35,32 @@ for key, val in pairs(rb.actions) do end end rb.actions, rb.contexts = nil, nil --------------------------------------- +-------------------------------------------------------------------------------- + +--[[ Profiling ]]--------------------------------------------------------------- +local screen_redraw_count = 0 +local screen_redraw_duration = 0 +-------------------------------------------------------------------------------- + + +--[[ References ]]-------------------------------------------------------------- local _LCD = rb.lcd_framebuffer() local rocklib_image = getmetatable(_LCD) -local _ellipse = rocklib_image.ellipse -local BSAND = 0x8 -local irand = math.random +local _ellipse = rocklib_image.ellipse-- +local irand = math.random-- +local lcd_putsxy = rb.lcd_putsxy-- +local strfmt = string.format-- +local Cursor_Ref = nil +local Ball_Ref = {} +-------------------------------------------------------------------------------- + +--[[ 'Constants' ]]------------------------------------------------------------- +local SCORE_MULTIPLY = 10 +local BSAND = 0x8-- local HAS_TOUCHSCREEN = rb.action_get_touchscreen_press ~= nil +local Empty_fn = function() end local LCD_H, LCD_W = rb.LCD_HEIGHT, rb.LCD_WIDTH local DEFAULT_BALL_SZ = LCD_H > LCD_W and LCD_W / 30 or LCD_H / 30 DEFAULT_BALL_SZ = DEFAULT_BALL_SZ - bit.band(DEFAULT_BALL_SZ, 1) @@ -61,10 +80,16 @@ end local FMT_EXPEND, FMT_LEVEL = "%d balls expended", "Level %d" local FMT_TOTPTS, FMT_LVPTS = "%d total points", "%d level points" +-------------------------------------------------------------------------------- + + +--[[ Other ]]------------------------------------------------------------------- +local Player_Added +local Action_Evt = pla.ACTION_NONE local levels = { -- {GOAL, TOTAL_BALLS}, - {1, 5}, + {1, 5}, {2, 10}, {4, 15}, {6, 20}, @@ -85,7 +110,21 @@ local levels = { {4, 5}, {5, 5} } +-------------------------------------------------------------------------------- + +--[[ Event Callback ]]---------------------------------------------------------- +function action_event(action) + if action == pla.PLA_CANCEL then + Action_Evt = pla.PLA_EXIT + else + Action_Evt = action + end +end +-------------------------------------------------------------------------------- + + +--[[ Helper functions ]]-------------------------------------------------------- local function getstringsize(str) local _, w, h = rb.font_getstringsize(str, rb.FONT_UI) return w, h @@ -97,111 +136,205 @@ local function set_foreground(color) end end -local function random_color() - if rb.lcd_rgbpack ~= nil then -- color target - return rb.lcd_rgbpack(irand(1,255), irand(1,255), irand(1,255)) +local function calc_score(total, level, goal, expended) + local bonus = 0 + local score = (expended * level) * SCORE_MULTIPLY + if expended < goal then + score = -(score + (level * SCORE_MULTIPLY)) + elseif expended > goal then + bonus = (expended - goal) * (level * SCORE_MULTIPLY) end - - local color = irand(1, rb.LCD_DEPTH) - color = (rb.LCD_DEPTH == 2) and (3 - color) or color -- invert for 2-bit screens - - return color + total = total + score + bonus + return total, score, bonus end +local function wait_anykey(to_secs) + rb.sleep(rb.HZ / 2) + rb.button_clear_queue() + rb.button_get_w_tmo(rb.HZ * to_secs) +end + +local function disp_msg(to, ...) + local message = strfmt(...) + local w, h = getstringsize(message) + local x, y = (LCD_W - w) / 2, (LCD_H - h) / 2 + + rb.lcd_clear_display() + set_foreground(DEFAULT_FG_CLR) + + if w > LCD_W then + rb.lcd_puts_scroll(0, (y / h), message) + else + lcd_putsxy(x, y, message) + end + + if to == -1 then + local msg = "Press button to exit" + w, h = getstringsize(msg) + x = (LCD_W - w) / 2 + if x < 0 then + rb.lcd_puts_scroll(0, (y / h) + 1, msg) + else + lcd_putsxy(x, y + h, msg) + end + end + rb.lcd_update() + + if to == -1 then + wait_anykey(60) + else + rb.sleep(to) + end + + rb.lcd_scroll_stop() -- Stop our scrolling message +end + +local function test_speed() + local test_spd, redraw, dur = start_round(0, 0, 0, 0) -- test speed of target + --Logic speed, screen redraw, duration + if DEBUG == true then + disp_msg(rb.HZ * 5, "Spd: %d, Redraw: %d Dur: %d Ms", test_spd, redraw, dur) + end + if test_spd < 25 or redraw < 10 then + rb.splash(rb.HZ, "Slow Target..") + + if test_spd < 10 then + MAX_BALL_SPEED = MAX_BALL_SPEED + MAX_BALL_SPEED + elseif test_spd < 15 then + MAX_BALL_SPEED = MAX_BALL_SPEED + MAX_BALL_SPEED / 2 + elseif test_spd < 25 then + MAX_BALL_SPEED = MAX_BALL_SPEED + MAX_BALL_SPEED / 4 + end + end +end +-------------------------------------------------------------------------------- + + +--[[ Ball Functions ]]---------------------------------------------------------- local Ball = { -- Ball defaults sz = DEFAULT_BALL_SZ, next_tick = 0, - state = B_MOVE + state = B_MOVE, } -function Ball:new(o, level) +function Ball:new(o, level, color) + local function random_color() + if color == nil then + if rb.lcd_rgbpack ~= nil then -- color target + return rb.lcd_rgbpack(irand(1,255), irand(1,255), irand(1,255)) + end + color = irand(1, rb.LCD_DEPTH) + color = (rb.LCD_DEPTH == 2) and (3 - color) or color -- invert for 2-bit screens + end + return color + end + if o == nil then level = level or 1 - local maxdelay = (level <= 5) and 10 or level + local maxdelay = (level <= 5) and 15 or level * 2 o = { x = irand(1, LCD_W - self.sz), y = irand(1, LCD_H - self.sz), color = random_color(), - xi = Ball:genSpeed(), - yi = Ball:genSpeed(), + xi = self.genSpeed(), -- x increment; x + sz after explode + yi = self.genSpeed(), -- y increment; y + sz after explode step_delay = irand(3, maxdelay / 2), explosion_sz = irand(2 * self.sz, 4 * self.sz), - life_ticks = irand(rb.HZ / level, rb.HZ * (maxdelay / 5)) + life_ticks = irand(rb.HZ / level, rb.HZ * (maxdelay / 5)), + draw_fn = nil, + step_fn = self.step -- reference to current stepping function } end - o.life_ticks = o.life_ticks + DEFAULT_BALL_SZ -- extra time for larger screens + local Ball = o or {} -- so we can use Ball. instead of self + + -- these functions end up in a closure; faster to call, use more RAM + function Ball.draw() + _ellipse(_LCD, o.x, o.y, o.x + o.sz, o.y + o.sz, o.color, o.color, true) + end + + function Ball.draw_exploded() + _ellipse(_LCD, o.x, o.y, o.xi, o.yi, o.color, nil, true) + end + + if Ball.draw_fn == nil then + Ball.draw_fn = Ball.draw -- reference to current drawing function + end + setmetatable(o, self) self.__index = self return o end +-- these functions are shared by reference, slower to call, use less RAM function Ball:genSpeed() local speed = irand(-MAX_BALL_SPEED, MAX_BALL_SPEED) return speed ~= 0 and speed or MAX_BALL_SPEED -- Make sure all balls move end -function Ball:draw() - _ellipse(_LCD, self.x, self.y, - self.x + self.sz, self.y + self.sz , self.color, self.color, true) -end - function Ball:step(tick) + local function rndspeed(cur) + local speed = cur + irand(-2, 2) + if speed < -MAX_BALL_SPEED then + speed = -MAX_BALL_SPEED + elseif speed > MAX_BALL_SPEED then + speed = MAX_BALL_SPEED + elseif speed == 0 then + speed = cur + end + return speed + end + self.next_tick = tick + self.step_delay self.x = self.x + self.xi self.y = self.y + self.yi + local max_x = LCD_W - self.sz + local max_y = LCD_H - self.sz - if (self.x <= 0 or self.x >= (LCD_W - self.sz)) then + if self.x <= 0 then self.xi = -self.xi - self.x = self.x + self.xi + self.x = 1 + self.yi = rndspeed(self.yi) + elseif self.x >= max_x then + self.xi = -self.xi + self.x = max_x + self.yi = rndspeed(self.yi) end - if (self.y <= 0 or self.y >= (LCD_H - self.sz)) then + if self.y <= 0 then self.yi = -self.yi - self.y = self.y + self.yi + self.y = 1 + self.xi = rndspeed(self.xi) + elseif self.y >= max_y then + self.yi = -self.yi + self.y = max_y + self.xi = rndspeed(self.xi) end end -function Ball:checkHit(other) - if (other.xi >= self.x) and (other.yi >= self.y) and - (self.x + self.sz >= other.x) and (self.y + self.sz >= other.y) then - self.state = B_EXPLODE - -- x/y increment no longer needed it is now impact region - self.xi = self.x + self.sz - self.yi = self.y + self.sz - - if other.state < B_EXPLODE then -- add duration to the ball that got hit - other.next_tick = other.next_tick + self.life_ticks - end - return true - end - - return false -end - -function Ball:draw_exploded() - _ellipse(_LCD, self.x, self.y, self.xi, self.yi, self.color, nil, true) -end - function Ball:step_exploded(tick) -- exploding ball state machine -- B_EXPLODE >> B_DIE >> BWAIT >> B_IMPLODE >> B_DEAD if self.state == B_EXPLODE and self.sz < self.explosion_sz then self.sz = self.sz + 2 - self.x = self.x - 1 -- We do this because we want to stay centered + self.x = self.x - 1 -- stay centered self.y = self.y - 1 elseif self.state == B_DIE then self.state = B_WAIT self.next_tick = tick + self.life_ticks return - elseif self.state == B_IMPLODE and self.sz > 0 then - self.sz = self.sz - 2 - self.x = self.x + 1 -- We do this because we want to stay centered - self.y = self.y + 1 - elseif self.state <= B_IMPLODE then - self.state = B_DEAD - return - elseif self.next_tick < tick then + elseif self.state == B_IMPLODE then + if self.sz > 0 then + self.sz = self.sz - 2 + self.x = self.x + 1 -- stay centered + self.y = self.y + 1 + else + self.state = B_DEAD + self.draw_fn = Empty_fn + self.step_fn = Empty_fn + return B_DEAD + end + elseif self.next_tick < tick then -- decay to next lower state self.state = self.state - 1 return end @@ -211,59 +344,90 @@ function Ball:step_exploded(tick) self.yi = self.y + self.sz end +function Ball:checkHit(other) + local x, y = self.x, self.y + if (other.xi >= x) and (other.yi >= y) then + local sz = self.sz + local xi, yi = x + sz, y + sz + if (xi >= other.x) and (yi >= other.y) then + -- update impact region + self.xi = xi + self.yi = yi + -- change to exploded state + self.state = B_EXPLODE + self.draw_fn = self.draw_exploded + self.step_fn = self.step_exploded + + if other.state < B_EXPLODE then -- add duration to the ball that got hit + other.next_tick = other.next_tick + self.life_ticks + end + return true + end + end + + return false +end +-------------------------------------------------------------------------------- + + +--[[ Cursor Functions ]]-------------------------------------------------------- local Cursor = { sz = (DEFAULT_BALL_SZ * 2), x = (LCD_W / 2), y = (LCD_H / 2), color = DEFAULT_FG_CLR + --image = nil } function Cursor:new() if rb.LCD_DEPTH == 2 then -- invert for 2 - bit screens self.color = 3 - DEFAULT_FG_CLR end - self:create_image(DEFAULT_BALL_SZ * 2) + if not HAS_TOUCHSCREEN and not self.image then + local function create_image(sz) + sz = sz + 1 + local img = rb.new_image(sz, sz) + local sz2 = (sz / 2) + 1 + local sz4 = (sz / 4) + + img:clear(0) + img:line(1, 1, sz4 + 1, 1, 1) + img:line(1, 1, 1, sz4 + 1, 1) + + img:line(1, sz, sz4 + 1, sz, 1) + img:line(1, sz, 1, sz - sz4, 1) + + img:line(sz, sz, sz - sz4, sz, 1) + img:line(sz, sz, sz, sz - sz4, 1) + + img:line(sz, 1, sz - sz4, 1, 1) + img:line(sz, 1, sz, sz4 + 1, 1) + + -- crosshairs + img:line(sz2 - sz4, sz2, sz2 + sz4, sz2, 1) + img:line(sz2, sz2 - sz4, sz2, sz2 + sz4, 1) + return img + end + self.image = create_image(DEFAULT_BALL_SZ * 2) + end + + function Cursor.draw() + rocklib_image.copy(_LCD, self.image, self.x, self.y, _NIL, _NIL, + _NIL, _NIL, true, BSAND, self.color) + end + return self end -function Cursor:create_image(sz) - if not HAS_TOUCHSCREEN then - sz = sz + 1 - local img = rb.new_image(sz, sz) - local sz2 = (sz / 2) + 1 - local sz4 = (sz / 4) - - img:clear(0) - img:line(1, 1, sz4 + 1, 1, 1) - img:line(1, 1, 1, sz4 + 1, 1) - - img:line(1, sz, sz4 + 1, sz, 1) - img:line(1, sz, 1, sz - sz4, 1) - - img:line(sz, sz, sz - sz4, sz, 1) - img:line(sz, sz, sz, sz - sz4, 1) - - img:line(sz, 1, sz - sz4, 1, 1) - img:line(sz, 1, sz, sz4 + 1, 1) - - -- crosshairs - img:line(sz2 - sz4, sz2, sz2 + sz4, sz2, 1) - img:line(sz2, sz2 - sz4, sz2, sz2 + sz4, 1) - self.image = img - end -end - -local function clamp_roll(iVal, iMin, iMax) - if iVal < iMin then - iVal = iMax - elseif iVal > iMax then - iVal = iMin - end - - return iVal -end - function Cursor:do_action(action) + local function clamp_roll(iVal, iMin, iMax) + if iVal < iMin then + iVal = iMax + elseif iVal > iMax then + iVal = iMin + end + return iVal + end local xi, yi = 0, 0 if HAS_TOUCHSCREEN and action == pla.ACTION_TOUCHSCREEN then @@ -279,188 +443,225 @@ function Cursor:do_action(action) yi = -self.sz elseif (action == pla.PLA_DOWN or action == pla.PLA_DOWN_REPEAT) then yi = self.sz + else + Action_Evt = pla.ACTION_NONE + return false end self.x = clamp_roll(self.x + xi, 1, LCD_W - self.sz) self.y = clamp_roll(self.y + yi, 1, LCD_H - self.sz) - + Action_Evt = pla.ACTION_NONE return false end +-------------------------------------------------------------------------------- -function Cursor:draw() - rocklib_image.copy(_LCD, self.image, self.x, self.y, _NIL, _NIL, - _NIL, _NIL, true, BSAND, self.color) -end -local function calc_score(total, level, goal, expended) - local score = (expended * level) * 100 - if expended < goal then - score = -(score + (level * 100)) - end - total = total + score - return total -end +--[[ Game function ]]----------------------------------------------------------- +function start_round(level, goal, nrBalls, total) + Player_Added = false + --[[ References ]]-- + local current_tick = rb.current_tick + local lcd_update = rb.lcd_update + local lcd_clear_display = rb.lcd_clear_display -local function draw_pos_str(bottom, right, str) - local w, h = getstringsize(str) - local x = (right > 0) and ((LCD_W - w) * right - 1) or 1 - local y = (bottom > 0) and ((LCD_H - h) * bottom - 1) or 1 - rb.lcd_putsxy(x, y, str) -end - -local function wait_anykey(to_secs) - rb.sleep(rb.HZ / 2) - rb.button_clear_queue() - rb.button_get_w_tmo(rb.HZ * to_secs) -end - -local function start_round(level, goal, nrBalls, total) - local player_added, score = false, 0 - local last_expend, nrBalls_expend = 0, 0 - local balls_exploded = 1 -- keep looping when player_added == false - local action = 0 - local Balls = {} - local str_level = string.format(FMT_LEVEL, level) -- static - local str_totpts = string.format(FMT_TOTPTS, total) -- static - local str_expend, str_lvlpts - local tick, cursor local test_spd = false + local is_exit = false; + local score = 0 + local last_expend, nrBalls_expend = 0, 0 + local Balls = {} + local balls_exploded = 1 -- to keep looping when player_added == false + + local tick_next = 0 + local cursor = nil + local draw_cursor = Empty_fn + local refresh = rb.HZ/20 + + local function level_stats(level, total) + return strfmt(FMT_LEVEL, level), strfmt(FMT_TOTPTS, total) + end + + local str_level, str_totpts = level_stats(level, total) -- static for lvl + local str_expend, str_lvlpts local function update_stats() -- we only create a new string when a hit is detected - str_expend = string.format(FMT_EXPEND, nrBalls_expend) - str_lvlpts = string.format(FMT_LVPTS, score) + str_expend = strfmt(FMT_EXPEND, nrBalls_expend) + str_lvlpts = strfmt(FMT_LVPTS, score) end - local function draw_stats() + function draw_stats() + local function draw_pos_str(bottom, right, str) + local w, h = getstringsize(str) + local x = (right > 0) and ((LCD_W - w) * right - 1) or 1 + local y = (bottom > 0) and ((LCD_H - h) * bottom - 1) or 1 + lcd_putsxy(x, y, str) + end + -- pos(B, R) [B/R]=0 => [y/x]=1, [B/R]=1 => [y/x]=lcd[H/W]-string[H/W] draw_pos_str(0, 0, str_expend) draw_pos_str(0, 1, str_level) draw_pos_str(1, 1, str_lvlpts) draw_pos_str(1, 0, str_totpts) end + -- all Balls share same function, will by changed by player_add() + local checkhit_fn = Empty_fn + + local function checkhit(Ball) + if Ball.state == B_MOVE then + for i = #Balls, 1, -1 do + if Balls[i].state < B_MOVE and + Ball:checkHit(Balls[i]) then -- exploded? + balls_exploded = balls_exploded + 1 + nrBalls_expend = nrBalls_expend + 1 + break + end + end + end + end + local function add_player() -- cursor becomes exploded ball local player = Ball:new({ - x = cursor.x, - y = cursor.y, + x = cursor.x - cursor.sz, + y = cursor.y - cursor.sz, color = cursor.color, step_delay = 3, explosion_sz = (3 * DEFAULT_BALL_SZ), - life_ticks = (test_time == true) and (100) or + life_ticks = (test_spd) and (rb.HZ) or irand(rb.HZ * 2, rb.HZ * DEFAULT_BALL_SZ), sz = 10, state = B_EXPLODE }) - -- set x/y impact region + -- set x/y impact region -->[] player.xi = player.x + player.sz player.yi = player.y + player.sz + player.draw_fn = player.draw_exploded + player.step_fn = player.step_exploded table.insert(Balls, player) balls_exploded = 1 - player_added = true + Player_Added = true cursor = nil + draw_cursor = Empty_fn + -- only need collision detection after player add + checkhit_fn = checkhit + end + + local function speedtest() + -- check speed of target + set_foreground(DEFAULT_BG_CLR) --hide text during test + level = 1 + nrBalls = 100 + cursor = {x = LCD_W * 2, y = LCD_H * 2, color = 0, sz = 1} + table.insert(Balls, Ball:new({ + x = 1, y = 1, xi = 1, yi = 1, + color = 0, step_delay = 0, + explosion_sz = 0, life_ticks = 0, + draw_fn = Empty_fn, + step_fn = function() test_spd = test_spd + 1 end + }) + ) + test_spd = 0 + add_player() + end + + local function screen_redraw() + -- (draw_fn changes dynamically at runtime) + for i = 1, #Balls do + Balls[i].draw_fn() + end + + draw_stats() + draw_cursor() + + lcd_update() + lcd_clear_display() + end + + local function game_loop() + local tick = current_tick() + for _, Ball in ipairs(Balls) do + if tick > Ball.next_tick then + -- (step_fn changes dynamically at runtime) + if Ball:step_fn(tick) == B_DEAD then + balls_exploded = balls_exploded - 1 + else + checkhit_fn(Ball) + end + end + end + return tick end if level < 1 then - -- check speed of target - set_foreground(DEFAULT_BG_CLR) --hide text during test + speedtest() local bkcolor = (rb.LCD_DEPTH == 2) and (3) or 0 - level = 1 - nrBalls = 20 - cursor = { x = LCD_W * 2, y = LCD_H * 2, color = bkcolor} - table.insert(Balls, Ball:new({ - x = 1, y = 1, xi = 1, yi = 1, - color = bkcolor, step_delay = 1, - explosion_sz = 0, life_ticks = 0, - step = function() test_spd = test_spd + 1 end - }) - ) - add_player() - test_spd = 0 + -- Initialize the balls + if DEBUG then bkcolor = nil end + for i=1, nrBalls do + table.insert(Balls, Ball:new(nil, level, bkcolor)) + end else + speedtest = nil set_foreground(DEFAULT_FG_CLR) -- color for text cursor = Cursor:new() - end - - -- Initialize the balls - for i=1, nrBalls do - table.insert(Balls, Ball:new(nil, level)) + if not HAS_TOUCHSCREEN then + draw_cursor = cursor.draw + end + -- Initialize the balls + for i=1, nrBalls do + table.insert(Balls, Ball:new(nil, level)) + end end -- Make sure there are no unwanted touchscreen presses rb.button_clear_queue() + Action_Evt = pla.ACTION_NONE + update_stats() -- load status strings - -- Check if the round is over - while balls_exploded > 0 do - tick = rb.current_tick() + if rb.cpu_boost then rb.cpu_boost(true) end + collectgarbage("collect") -- run gc now to hopefully prevent interruption later - if action ~= pla.ACTION_NONE and (action == pla.PLA_EXIT or - action == pla.PLA_CANCEL) then - action = pla.PLA_EXIT + local duration = current_tick() + -- Game loop >> Check if the round is over + while balls_exploded > 0 do + if Action_Evt == pla.PLA_EXIT then + is_exit = true break end - rb.lcd_clear_display() - - if not player_added then - if action ~= pla.ACTION_NONE and cursor:do_action(action) then - add_player() - elseif not HAS_TOUCHSCREEN then - cursor:draw() - end - end - - for _, Ball in ipairs(Balls) do - if Ball.state == B_MOVE then - if tick > Ball.next_tick then - Ball:step(tick) - for i = #Balls, 1, -1 do - if Balls[i].state < B_MOVE and - Ball:checkHit(Balls[i]) then -- exploded? - balls_exploded = balls_exploded + 1 - nrBalls_expend = nrBalls_expend + 1 - break - end - end - end - -- check if state changed draw ball if still moving - if Ball.state == B_MOVE then - Ball:draw() - end - elseif Ball.state ~= B_DEAD then - if tick > Ball.next_tick then - Ball:step_exploded(tick) - end - if Ball.state ~= B_DEAD then - Ball:draw_exploded() - else - balls_exploded = balls_exploded - 1 + if game_loop() > tick_next then + tick_next = current_tick() + refresh + if not Player_Added then + if Action_Evt ~= pla.ACTION_NONE and cursor:do_action(Action_Evt) then + add_player() end end + + if nrBalls_expend ~= last_expend then -- hit detected? + last_expend = nrBalls_expend + score = (nrBalls_expend * level) * SCORE_MULTIPLY + update_stats() -- only update stats when not current + if nrBalls_expend == nrBalls then break end -- round is over? + end + + screen_redraw_count = screen_redraw_count + 1 + screen_redraw() end - - draw_stats() -- stats redrawn every frame - rb.lcd_update() -- Push framebuffer to the LCD - - if nrBalls_expend ~= last_expend then -- hit detected? - last_expend = nrBalls_expend - score = (nrBalls_expend * level) * 100 - update_stats() -- only update stats when not current - if nrBalls_expend == nrBalls then break end -- round is over? - end - rb.yield() -- yield to other tasks - - action = rb.get_plugin_action(0) -- Check for actions end - if test_spd and test_spd > 0 then return test_spd end + screen_redraw_duration = screen_redraw_duration + (rb.current_tick() - duration) + if rb.cpu_boost then rb.cpu_boost(false) end + + if test_spd and test_spd > 0 then + return test_spd, screen_redraw_count, screen_redraw_duration *10 --ms + end -- splash the final stats for a moment at end - rb.lcd_clear_display() + lcd_clear_display() for _, Ball in ipairs(Balls) do -- move balls back to their initial exploded positions if Ball.state == B_DEAD then @@ -471,56 +672,22 @@ local function start_round(level, goal, nrBalls, total) Ball:draw() end - if DEFAULT_BALL_SZ > 3 then + if DEFAULT_BALL_SZ > 3 then -- dither _LCD:clear(nil, nil, nil, nil, nil, nil, 2, 2) end total = calc_score(total, level, goal, nrBalls_expend) - str_totpts = string.format(FMT_TOTPTS, total) + str_level, str_totpts = level_stats(level, total) draw_stats() - rb.lcd_update() + lcd_update() wait_anykey(2) - return action == pla.PLA_EXIT, score, nrBalls_expend + return is_exit, score, nrBalls_expend end +-------------------------------------------------------------------------------- --- Helper function to display a message -local function disp_msg(to, ...) - local message = string.format(...) - local w, h = getstringsize(message) - local x, y = (LCD_W - w) / 2, (LCD_H - h) / 2 - rb.lcd_clear_display() - set_foreground(DEFAULT_FG_CLR) - - if w > LCD_W then - rb.lcd_puts_scroll(0, (y / h), message) - else - rb.lcd_putsxy(x, y, message) - end - - if to == -1 then - local msg = "Press button to exit" - w, h = getstringsize(msg) - x = (LCD_W - w) / 2 - if x < 0 then - rb.lcd_puts_scroll(0, (y / h) + 1, msg) - else - rb.lcd_putsxy(x, y + h, msg) - end - end - rb.lcd_update() - - if to == -1 then - wait_anykey(60) - else - rb.sleep(to) - end - - rb.lcd_scroll_stop() -- Stop our scrolling message -end - ---[[MAIN PROGRAM]] +--[[MAIN PROGRAM]]-------------------------------------------------------------- do -- attempt to get stats to fit on screen local function getwidth(str) local w, _ = getstringsize(str) @@ -530,7 +697,7 @@ do -- attempt to get stats to fit on screen if (w0 + w1) > LCD_W then FMT_EXPEND, FMT_LEVEL = "%d balls", "Lv %d" end - w0, w1 = getwidth(FMT_TOTPTS), getwidth(FMT_LVPTS) + w0, w1 = getwidth(FMT_TOTPTS), getwidth(FMT_LVPTS) if (w0 + w1 + getwidth("0000000")) > LCD_W then FMT_TOTPTS, FMT_LVPTS = "%d total", "%d lv" end @@ -542,23 +709,16 @@ end rb.backlight_force_on() -math.randomseed(os.time()) +local eva = rockev.register("action", action_event, rb.HZ / 10) + +math.randomseed(os.time() or 1) local idx, highscore = 1, 0 -local test_spd = start_round(0, 0, 0, 0) -- test speed of target - -if test_spd < 100 then - rb.splash(100, "Slow Target..") - - if test_spd < 25 then - MAX_BALL_SPEED = MAX_BALL_SPEED + MAX_BALL_SPEED - elseif test_spd < 50 then - MAX_BALL_SPEED = MAX_BALL_SPEED + MAX_BALL_SPEED / 2 - elseif test_spd < 100 then - MAX_BALL_SPEED = MAX_BALL_SPEED + MAX_BALL_SPEED / 4 - end -end +disp_msg(rb.HZ, "BoomShine") +test_speed() +rb.sleep(rb.HZ * 2) +test_speed = nil while levels[idx] ~= nil do local goal, nrBalls = levels[idx][1], levels[idx][2] @@ -567,18 +727,26 @@ while levels[idx] ~= nil do disp_msg(rb.HZ * 2, "Level %d: get %d out of %d balls", idx, goal, nrBalls) - local exit, score, nrBalls_expend = start_round(idx, goal, nrBalls, highscore) + local is_exit, score, nrBalls_expend = start_round(idx, goal, nrBalls, highscore) + if DEBUG == true then + local fps = screen_redraw_count * 100 / screen_redraw_duration + disp_msg(rb.HZ * 5, "Redraw: %d fps", fps) + end - if exit then + if is_exit then break -- Exiting.. else - highscore = calc_score(highscore, idx, goal, nrBalls_expend) + highscore, score, bonus = calc_score(highscore, idx, goal, nrBalls_expend) if nrBalls_expend >= goal then - disp_msg(rb.HZ * 2, "You won!") + if bonus == 0 then + disp_msg(rb.HZ * 2, "You won!") + else + disp_msg(rb.HZ * 2, "You won BONUS!") + end levels[idx] = nil idx = idx + 1 else - disp_msg(rb.HZ * 2, "You lost %d points!", score + (idx * 100)) + disp_msg(rb.HZ * 2, "You lost %d points!", -score) if highscore < 0 then break end end end @@ -594,3 +762,7 @@ end -- Restore user backlight settings rb.backlight_use_settings() +if rb.cpu_boost then rb.cpu_boost(false) end + +os.exit() +--------------------------------------------------------------------------------