summaryrefslogtreecommitdiffstats
path: root/apps/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins')
-rw-r--r--apps/plugins/boomshine.lua669
1 files changed, 386 insertions, 283 deletions
diff --git a/apps/plugins/boomshine.lua b/apps/plugins/boomshine.lua
index 63ac6b11fa..224708d065 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, blit cursor, hard levels
+ Copyright (C) 2018 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,34 +23,44 @@
]]--
require "actions"
--- [[only save the actions we are using]]
-actions_pla = {}
+--[[only save the actions we are using]]
+pla = {}
for key, val in pairs(rb.actions) do
- for _, v in ipairs({"PLA_", "TOUCHSCREEN"}) do
+ for _, v in ipairs({"PLA_", "TOUCHSCREEN", "_NONE"}) do
if string.find (key, v) then
- actions_pla[key] = val
- break
+ pla[key] = val; break
end
end
end
-rb.actions = nil
-rb.contexts = nil
--------------------------------------
+rb.actions, rb.contexts = nil, nil
+--------------------------------------
local _LCD = rb.lcd_framebuffer()
-local BSAND = 0x8
-local rocklib_image = getmetatable(rb.lcd_framebuffer())
+local rocklib_image = getmetatable(_LCD)
local _ellipse = rocklib_image.ellipse
+local BSAND = 0x8
+local irand = math.random
-local CYCLETIME = rb.HZ / 50
local HAS_TOUCHSCREEN = rb.action_get_touchscreen_press ~= nil
-local DEFAULT_BALL_SIZE = rb.LCD_HEIGHT > rb.LCD_WIDTH and rb.LCD_WIDTH / 30
- or rb.LCD_HEIGHT / 30
-local MAX_BALL_SPEED = DEFAULT_BALL_SIZE / 2
-local DEC_BALL_SPEED = DEFAULT_BALL_SIZE / 8
-local DEFAULT_FOREGROUND_COLOR = rb.lcd_get_foreground ~= nil
- and rb.lcd_get_foreground()
- or 0
+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)
+local MAX_BALL_SPEED = DEFAULT_BALL_SZ / 2 + 1
+-- Ball states
+local B_DEAD, B_MOVE, B_EXPLODE, B_DIE, B_WAIT, B_IMPLODE = 5, 4, 3, 2, 1, 0
+
+local DEFAULT_FG_CLR = 1
+local DEFAULT_BG_CLR = 0
+if rb.lcd_get_foreground ~= nil then
+ DEFAULT_FG_CLR = rb.lcd_get_foreground()
+ DEFAULT_BG_CLR = rb.lcd_get_background()
+elseif rb.LCD_DEFAULT_FG ~= nil then
+ DEFAULT_FG_CLR = rb.LCD_DEFAULT_FG
+ DEFAULT_BG_CLR = rb.LCD_DEFAULT_BG
+end
+
+local FMT_EXPEND, FMT_LEVEL = "%d balls expended", "Level %d"
+local FMT_TOTPTS, FMT_LVPTS = "%d total points", "%d level points"
local levels = {
-- {GOAL, TOTAL_BALLS},
@@ -65,134 +75,103 @@ local levels = {
{30, 45},
{37, 50},
{48, 55},
- {59, 60},
- {29, 30},
- {24, 25},
- {19, 20},
- {14, 15},
+ {58, 60},
+ {28, 30},
+ {23, 25},
+ {18, 20},
+ {13, 15},
+ {8, 10},
{9, 10},
- {10, 10},
{4, 5},
{5, 5}
- }
-
-local Ball = {
- size = DEFAULT_BALL_SIZE,
- exploded = false,
- implosion = false
- }
-
-local function create_cursor(size)
- local cursor
- if not HAS_TOUCHSCREEN then
- cursor = rb.new_image(size, size)
- cursor:clear(0)
- local sz2 = size / 2
- local sz4 = size / 4
+ }
- cursor:line(1, 1, sz4, 1, 1)
- cursor:line(1, 1, 1, sz4, 1)
+local function getstringsize(str)
+ local _, w, h = rb.font_getstringsize(str, rb.FONT_UI)
+ return w, h
+end
- cursor:line(1, size, sz4, size, 1)
- cursor:line(1, size, 1, size - sz4, 1)
+local function set_foreground(color)
+ if rb.lcd_set_foreground ~= nil then
+ rb.lcd_set_foreground(color)
+ end
+end
- cursor:line(size, size, size - sz4, size, 1)
- cursor:line(size, size, size, size - sz4, 1)
+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))
+ end
- cursor:line(size, 1, size - sz4, 1, 1)
- cursor:line(size, 1, size, sz4, 1)
+ local color = irand(1, rb.LCD_DEPTH)
+ color = (rb.LCD_DEPTH == 2) and (3 - color) or color -- invert for 2-bit screens
- --crosshairs
- cursor:line(sz2 - sz4, sz2, sz2 + sz4, sz2, 1)
- cursor:line(sz2, sz2 - sz4, sz2, sz2 + sz4, 1)
- end
- return cursor
+ return color
end
+local Ball = {
+ -- Ball defaults
+ sz = DEFAULT_BALL_SZ,
+ next_tick = 0,
+ state = B_MOVE
+ }
+
function Ball:new(o, level)
- level = level or 1
if o == nil then
+ level = level or 1
+ local maxdelay = (level <= 5) and 10 or level
o = {
- x = math.random(0, rb.LCD_WIDTH - self.size),
- y = math.random(0, rb.LCD_HEIGHT - self.size),
+ x = irand(1, LCD_W - self.sz),
+ y = irand(1, LCD_H - self.sz),
color = random_color(),
- up_speed = Ball:generateSpeed(level),
- right_speed = Ball:generateSpeed(level),
- explosion_size = math.random(2*self.size, 4*self.size),
- life_duration = math.random(rb.HZ / level, rb.HZ*5),
+ xi = Ball:genSpeed(),
+ yi = Ball:genSpeed(),
+ 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))
}
end
-
+ o.life_ticks = o.life_ticks + DEFAULT_BALL_SZ -- extra time for larger screens
setmetatable(o, self)
self.__index = self
return o
end
-function Ball:generateSpeed(level)
- local ballspeed = MAX_BALL_SPEED - (DEC_BALL_SPEED * (level - 1))
- if ballspeed < 2 then ballspeed = 2 end
- local speed = math.random(-ballspeed, ballspeed)
- if speed == 0 then
- speed = 1 -- Make sure all balls move
- end
-
- return speed
-end
-
-function Ball:draw_exploded()
- --[[
- --set_foreground(self.color)
- --rb.lcd_fillrect(self.x, self.y, self.size, self.size)
- ]]
-
- _ellipse(_LCD, self.x, self.y,
- self.x + self.size, self.y + self.size , self.color, nil, true)
+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()
- --[[
- --set_foreground(self.color)
- --rb.lcd_fillrect(self.x, self.y, self.size, self.size)
- ]]
-
_ellipse(_LCD, self.x, self.y,
- self.x + self.size, self.y + self.size , self.color, self.color, true)
+ self.x + self.sz, self.y + self.sz , self.color, self.color, true)
end
-function Ball:step_exploded()
- if self.implosion and self.size > 0 then
- self.size = self.size - 2
- self.x = self.x + 1 -- We do this because we want to stay centered
- self.y = self.y + 1
- elseif self.size < self.explosion_size then
- self.size = self.size + 2
- self.x = self.x - 1 -- We do this for the same reasons as above
- self.y = self.y - 1
- end
-end
+function Ball:step(tick)
+ self.next_tick = tick + self.step_delay
+ self.x = self.x + self.xi
+ self.y = self.y + self.yi
-function Ball:step()
- self.x = self.x + self.right_speed
- self.y = self.y + self.up_speed
- if (self.x <= 0 or self.x >= rb.LCD_WIDTH - self.size) then
- self.right_speed = -self.right_speed
- self.x = self.x + self.right_speed
+ if (self.x <= 0 or self.x >= (LCD_W - self.sz)) then
+ self.xi = -self.xi
+ self.x = self.x + self.xi
end
- if (self.y <= 0 or self.y >= rb.LCD_HEIGHT - self.size ) then
- self.up_speed = -self.up_speed
- self.y = self.y + self.up_speed
+
+ if (self.y <= 0 or self.y >= (LCD_H - self.sz)) then
+ self.yi = -self.yi
+ self.y = self.y + self.yi
end
end
function Ball:checkHit(other)
- if (other.x + other.size >= self.x) and (self.x + self.size >= other.x) and
- (other.y + other.size >= self.y) and (self.y + self.size >= other.y) then
- assert(not self.exploded)
- self.exploded = true
- self.death_time = rb.current_tick() + self.life_duration
- if not other.exploded then
- other.exploded = true
- other.death_time = rb.current_tick() + other.life_duration
+ 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
@@ -200,236 +179,332 @@ function Ball:checkHit(other)
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.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
+ self.state = self.state - 1
+ return
+ end
+ -- fell through, update next_tick and impact region
+ self.next_tick = tick + self.step_delay
+ self.xi = self.x + self.sz
+ self.yi = self.y + self.sz
+end
+
local Cursor = {
- size = DEFAULT_BALL_SIZE*2,
- x = rb.LCD_WIDTH/2,
- y = rb.LCD_HEIGHT/2,
- image = create_cursor(DEFAULT_BALL_SIZE*2)
+ sz = (DEFAULT_BALL_SZ * 2),
+ x = (LCD_W / 2),
+ y = (LCD_H / 2),
+ color = DEFAULT_FG_CLR
}
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)
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)
- if action == actions_pla.ACTION_TOUCHSCREEN and HAS_TOUCHSCREEN then
+ local xi, yi = 0, 0
+
+ if HAS_TOUCHSCREEN and action == pla.ACTION_TOUCHSCREEN then
_, self.x, self.y = rb.action_get_touchscreen_press()
return true
- elseif action == actions_pla.PLA_SELECT then
+ elseif action == pla.PLA_SELECT then
return true
- elseif (action == actions_pla.PLA_RIGHT or action == actions_pla.PLA_RIGHT_REPEAT) then
- self.x = self.x + self.size
- elseif (action == actions_pla.PLA_LEFT or action == actions_pla.PLA_LEFT_REPEAT) then
- self.x = self.x - self.size
- elseif (action == actions_pla.PLA_UP or action == actions_pla.PLA_UP_REPEAT) then
- self.y = self.y - self.size
- elseif (action == actions_pla.PLA_DOWN or action == actions_pla.PLA_DOWN_REPEAT) then
- self.y = self.y + self.size
+ elseif (action == pla.PLA_RIGHT or action == pla.PLA_RIGHT_REPEAT) then
+ xi = self.sz
+ elseif (action == pla.PLA_LEFT or action == pla.PLA_LEFT_REPEAT) then
+ xi = -self.sz
+ elseif (action == pla.PLA_UP or action == pla.PLA_UP_REPEAT) then
+ yi = -self.sz
+ elseif (action == pla.PLA_DOWN or action == pla.PLA_DOWN_REPEAT) then
+ yi = self.sz
end
- if self.x > rb.LCD_WIDTH then
- self.x = 0
- elseif self.x < 0 then
- self.x = rb.LCD_WIDTH
- end
-
- if self.y > rb.LCD_HEIGHT then
- self.y = 0
- elseif self.y < 0 then
- self.y = rb.LCD_HEIGHT
- 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)
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
- rocklib_image.copy(_LCD, self.image, self.x - self.size/2, self.y - self.size/2,
- _NIL, _NIL, _NIL, _NIL, true, BSAND, DEFAULT_FOREGROUND_COLOR)
+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
-function draw_positioned_string(bottom, right, str)
- local _, w, h = rb.font_getstringsize(str, rb.FONT_UI)
- local x = not right or (rb.LCD_WIDTH-w)*right - 1
- local y = not bottom or (rb.LCD_HEIGHT-h)*bottom - 1
+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
-function set_foreground(color)
- if rb.lcd_set_foreground ~= nil then
- rb.lcd_set_foreground(color)
- 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
-function random_color()
- if rb.lcd_rgbpack ~= nil then --color target
- return rb.lcd_rgbpack(math.random(1,255), math.random(1,255), math.random(1,255))
+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 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)
end
- return math.random(1, rb.LCD_DEPTH)
-end
+ local function draw_stats()
+ 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
-function start_round(level, goal, nrBalls, total)
- local player_added, score, exit, nrExpendedBalls = false, 0, false, 0
- local Balls, explodedBalls = {}, {}
- local ball_ct, ball_el
- local tick, endtick
- local action = 0
- local hit_detected = false
- local cursor = Cursor:new()
+ local function add_player()
+ -- cursor becomes exploded ball
+ local player = Ball:new({
+ x = cursor.x,
+ y = cursor.y,
+ color = cursor.color,
+ step_delay = 3,
+ explosion_sz = (3 * DEFAULT_BALL_SZ),
+ life_ticks = (test_time == true) and (100) or
+ irand(rb.HZ * 2, rb.HZ * DEFAULT_BALL_SZ),
+ sz = 10,
+ state = B_EXPLODE
+ })
+ -- set x/y impact region
+ player.xi = player.x + player.sz
+ player.yi = player.y + player.sz
+
+ table.insert(Balls, player)
+ balls_exploded = 1
+ player_added = true
+ cursor = nil
+ end
- -- Initialize the balls
- for _=1,nrBalls do
- table.insert(Balls, Ball:new(nil, level))
+ if level < 1 then
+ -- check speed of target
+ set_foreground(DEFAULT_BG_CLR) --hide text during test
+ 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
+ else
+ set_foreground(DEFAULT_FG_CLR) -- color for text
+ cursor = Cursor:new()
end
- local function draw_stats()
- draw_positioned_string(0, 0, string.format("%d balls expended", nrExpendedBalls))
- draw_positioned_string(0, 1, string.format("Level %d", level))
- draw_positioned_string(1, 1, string.format("%d level points", score))
- draw_positioned_string(1, 0, string.format("%d total points", total + score))
+ -- Initialize the balls
+ for i=1, nrBalls do
+ table.insert(Balls, Ball:new(nil, level))
end
-- Make sure there are no unwanted touchscreen presses
rb.button_clear_queue()
- set_foreground(DEFAULT_FOREGROUND_COLOR) -- color for text
-
- while true do
- endtick = rb.current_tick() + CYCLETIME
+ update_stats() -- load status strings
- -- Check if the round is over
- if player_added and #explodedBalls == 0 then
- break
- end
+ -- Check if the round is over
+ while balls_exploded > 0 do
+ tick = rb.current_tick()
- if action == actions_pla.PLA_EXIT or action == actions_pla.PLA_CANCEL then
- exit = true
+ if action ~= pla.ACTION_NONE and (action == pla.PLA_EXIT or
+ action == pla.PLA_CANCEL) then
+ action = pla.PLA_EXIT
break
end
- if not player_added and cursor:do_action(action) then
- local player = Ball:new({
- x = cursor.x,
- y = cursor.y,
- color = DEFAULT_FOREGROUND_COLOR,
- size = 10,
- explosion_size = 3*DEFAULT_BALL_SIZE,
- exploded = true,
- death_time = rb.current_tick() + rb.HZ * 3
- })
- explodedBalls[1] = player
- player_added = true
- cursor = nil
- end
+ rb.lcd_clear_display()
- -- Check for hits
- for i, Ball in ipairs(Balls) do
- for _, explodedBall in ipairs(explodedBalls) do
- if Ball:checkHit(explodedBall) then
- explodedBalls[#explodedBalls + 1] = Ball
- --table.remove(Balls, i)
- Balls[i] = false
- hit_detected = true
- break
- end
+ 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
- -- Check if we're dead yet
- tick = rb.current_tick()
- for i, explodedBall in ipairs(explodedBalls) do
- if explodedBall.death_time < tick then
- if explodedBall.size > 0 then
- explodedBall.implosion = true -- We should be dying
- else
- table.remove(explodedBalls, i) -- We're imploded!
+ 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
- end
- end
-
- -- Drawing phase
- if hit_detected then
- hit_detected = false
- -- same as table.remove(Balls, i) but more efficient
- ball_el = 1
- ball_ct = #Balls
- for i = 1, ball_ct do
- if Balls[i] then
- Balls[ball_el] = Balls[i]
- ball_el = ball_el + 1
+ -- 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
end
end
- -- remove any remaining exploded balls
- for i = ball_el, ball_ct do
- Balls[i] = nil
- end
- -- Calculate score
- nrExpendedBalls = nrBalls - ball_el + 1
- score = nrExpendedBalls * level * 100
end
- rb.lcd_clear_display()
- draw_stats()
+ draw_stats() -- stats redrawn every frame
+ rb.lcd_update() -- Push framebuffer to the LCD
- if not (player_added or HAS_TOUCHSCREEN) then
- cursor:draw()
+ 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
- for _, Ball in ipairs(Balls) do
- Ball:step()
- Ball:draw()
- end
+ rb.yield() -- yield to other tasks
- for _, explodedBall in ipairs(explodedBalls) do
- explodedBall:step_exploded()
- explodedBall:draw_exploded()
- end
+ action = rb.get_plugin_action(0) -- Check for actions
+ end
- -- Push framebuffer to the LCD
- rb.lcd_update()
+ if test_spd and test_spd > 0 then return test_spd end
- -- Check for actions
- if rb.current_tick() < endtick then
- action = rb.get_plugin_action(endtick - rb.current_tick())
- else
- rb.yield()
- action = rb.get_plugin_action(0)
+ -- splash the final stats for a moment at end
+ rb.lcd_clear_display()
+ for _, Ball in ipairs(Balls) do
+ -- move balls back to their initial exploded positions
+ if Ball.state == B_DEAD then
+ Ball.sz = Ball.explosion_sz + 2
+ Ball.x = Ball.x - (Ball.explosion_sz / 2)
+ Ball.y = Ball.y - (Ball.explosion_sz / 2)
end
+ Ball:draw()
+ end
+ if DEFAULT_BALL_SZ > 3 then
+ _LCD:clear(nil, nil, nil, nil, nil, nil, 2, 2)
end
- --splash the final stats for a moment at end
- rb.lcd_clear_display()
- for _, Ball in ipairs(Balls) do Ball:draw() end
- _LCD:clear(nil, nil, nil, nil, nil, nil, 2, 2)
+ total = calc_score(total, level, goal, nrBalls_expend)
+ str_totpts = string.format(FMT_TOTPTS, total)
draw_stats()
rb.lcd_update()
- rb.sleep(rb.HZ * 2)
+ wait_anykey(2)
- return exit, score, nrExpendedBalls
+ return action == pla.PLA_EXIT, score, nrBalls_expend
end
-- Helper function to display a message
-function display_message(to, ...)
+local function disp_msg(to, ...)
local message = string.format(...)
- local _, w, h = rb.font_getstringsize(message, rb.FONT_UI)
- local x, y = (rb.LCD_WIDTH - w) / 2, (rb.LCD_HEIGHT - h) / 2
+ local w, h = getstringsize(message)
+ local x, y = (LCD_W - w) / 2, (LCD_H - h) / 2
rb.lcd_clear_display()
- set_foreground(DEFAULT_FOREGROUND_COLOR)
+ set_foreground(DEFAULT_FG_CLR)
- if w > rb.LCD_WIDTH then
- rb.lcd_puts_scroll(0, y/h, message)
+ 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 = rb.font_getstringsize(msg, rb.FONT_UI)
- x = (rb.LCD_WIDTH - w) / 2
+ w, h = getstringsize(msg)
+ x = (LCD_W - w) / 2
if x < 0 then
- rb.lcd_puts_scroll(0, y/h + 1, msg)
+ rb.lcd_puts_scroll(0, (y / h) + 1, msg)
else
rb.lcd_putsxy(x, y + h, msg)
end
@@ -437,9 +512,7 @@ function display_message(to, ...)
rb.lcd_update()
if to == -1 then
- rb.sleep(rb.HZ/2)
- rb.button_clear_queue()
- rb.button_get(rb.HZ * 60)
+ wait_anykey(60)
else
rb.sleep(to)
end
@@ -447,47 +520,77 @@ function display_message(to, ...)
rb.lcd_scroll_stop() -- Stop our scrolling message
end
+--[[MAIN PROGRAM]]
+do -- attempt to get stats to fit on screen
+ local function getwidth(str)
+ local w, _ = getstringsize(str)
+ return w
+ end
+ local w0, w1 = getwidth(FMT_EXPEND), getwidth(FMT_LEVEL)
+ if (w0 + w1) > LCD_W then
+ FMT_EXPEND, FMT_LEVEL = "%d balls", "Lv %d"
+ end
+ 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
+end
+
if HAS_TOUCHSCREEN then
rb.touchscreen_set_mode(rb.TOUCHSCREEN_POINT)
end
---[[MAIN PROGRAM]]
rb.backlight_force_on()
math.randomseed(os.time())
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
+
while levels[idx] ~= nil do
local goal, nrBalls = levels[idx][1], levels[idx][2]
- collectgarbage("collect") --run gc now to hopefully prevent interruption later
+ collectgarbage("collect") -- run gc now to hopefully prevent interruption later
- display_message(rb.HZ*2, "Level %d: get %d out of %d balls", idx, goal, nrBalls)
+ disp_msg(rb.HZ * 2, "Level %d: get %d out of %d balls", idx, goal, nrBalls)
- local exit, score, nrExpendedBalls = start_round(idx, goal, nrBalls, highscore)
+ local exit, score, nrBalls_expend = start_round(idx, goal, nrBalls, highscore)
if exit then
break -- Exiting..
else
- if nrExpendedBalls >= goal then
- display_message(rb.HZ*2, "You won!")
+ highscore = calc_score(highscore, idx, goal, nrBalls_expend)
+ if nrBalls_expend >= goal then
+ disp_msg(rb.HZ * 2, "You won!")
+ levels[idx] = nil
idx = idx + 1
- highscore = highscore + score
else
- display_message(rb.HZ*2, "You lost!")
- highscore = highscore - score - idx * 100
+ disp_msg(rb.HZ * 2, "You lost %d points!", score + (idx * 100))
if highscore < 0 then break end
end
end
end
+
if highscore <= 0 then
- display_message(-1, "You lost at level %d", idx)
+ disp_msg(-1, "You lost at level %d", idx)
elseif idx > #levels then
- display_message(-1, "You finished the game with %d points!", highscore)
+ disp_msg(-1, "You finished the game with %d points!", highscore)
else
- display_message(-1, "You made it till level %d with %d points!", idx, highscore)
+ disp_msg(-1, "You made it till level %d with %d points!", idx, highscore)
end
-- Restore user backlight settings
rb.backlight_use_settings()
-