ce61be4d59
using rockev for button presses misc code refactoring, comments drawing code is now split from game logic cpu boost for targets that support it removed quite a few if then statements by using dynamic functions for ball draw, step, hit_check shows two ways to do OO functions (closure and reference) Change-Id: I63e795bbe90b033eabadc1f519cf3b635cf5e1a7
768 lines
24 KiB
Lua
768 lines
24 KiB
Lua
--[[
|
|
__________ __ ___.
|
|
Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
\/ \/ \/ \/ \/
|
|
$Id$
|
|
|
|
Port of Chain Reaction (which is based on Boomshine) to Rockbox in Lua.
|
|
See http://www.yvoschaap.com/chainrxn/ and http://www.k2xl.com/games/boomshine/
|
|
|
|
Copyright (C) 2009 by Maurus Cuelenaere
|
|
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
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
KIND, either express or implied.
|
|
|
|
]]--
|
|
require "actions"
|
|
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
|
|
if string.find (key, v) then
|
|
pla[key] = val; break
|
|
end
|
|
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 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)
|
|
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"
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
--[[ Other ]]-------------------------------------------------------------------
|
|
local Player_Added
|
|
local Action_Evt = pla.ACTION_NONE
|
|
|
|
local levels = {
|
|
-- {GOAL, TOTAL_BALLS},
|
|
{1, 5},
|
|
{2, 10},
|
|
{4, 15},
|
|
{6, 20},
|
|
{10, 25},
|
|
{15, 30},
|
|
{18, 35},
|
|
{22, 40},
|
|
{30, 45},
|
|
{37, 50},
|
|
{48, 55},
|
|
{58, 60},
|
|
{28, 30},
|
|
{23, 25},
|
|
{18, 20},
|
|
{13, 15},
|
|
{8, 10},
|
|
{9, 10},
|
|
{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
|
|
end
|
|
|
|
local function set_foreground(color)
|
|
if rb.lcd_set_foreground ~= nil then
|
|
rb.lcd_set_foreground(color)
|
|
end
|
|
end
|
|
|
|
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
|
|
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,
|
|
}
|
|
|
|
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 15 or level * 2
|
|
o = {
|
|
x = irand(1, LCD_W - self.sz),
|
|
y = irand(1, LCD_H - self.sz),
|
|
color = random_color(),
|
|
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)),
|
|
draw_fn = nil,
|
|
step_fn = self.step -- reference to current stepping function
|
|
}
|
|
end
|
|
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: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 then
|
|
self.xi = -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 then
|
|
self.yi = -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: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 -- 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 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
|
|
-- 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
|
|
|
|
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
|
|
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: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
|
|
_, self.x, self.y = rb.action_get_touchscreen_press()
|
|
return true
|
|
elseif action == pla.PLA_SELECT then
|
|
return true
|
|
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
|
|
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
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
--[[ 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 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 = strfmt(FMT_EXPEND, nrBalls_expend)
|
|
str_lvlpts = strfmt(FMT_LVPTS, score)
|
|
end
|
|
|
|
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 - cursor.sz,
|
|
y = cursor.y - cursor.sz,
|
|
color = cursor.color,
|
|
step_delay = 3,
|
|
explosion_sz = (3 * DEFAULT_BALL_SZ),
|
|
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 -->[]
|
|
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
|
|
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
|
|
speedtest()
|
|
local bkcolor = (rb.LCD_DEPTH == 2) and (3) or 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()
|
|
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
|
|
|
|
if rb.cpu_boost then rb.cpu_boost(true) end
|
|
collectgarbage("collect") -- run gc now to hopefully prevent interruption later
|
|
|
|
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
|
|
|
|
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
|
|
rb.yield() -- yield to other tasks
|
|
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
|
|
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 -- dither
|
|
_LCD:clear(nil, nil, nil, nil, nil, nil, 2, 2)
|
|
end
|
|
|
|
total = calc_score(total, level, goal, nrBalls_expend)
|
|
str_level, str_totpts = level_stats(level, total)
|
|
draw_stats()
|
|
lcd_update()
|
|
wait_anykey(2)
|
|
|
|
return is_exit, score, nrBalls_expend
|
|
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_mode(rb.TOUCHSCREEN_POINT)
|
|
end
|
|
|
|
rb.backlight_force_on()
|
|
|
|
local eva = rockev.register("action", action_event, rb.HZ / 10)
|
|
|
|
math.randomseed(os.time() or 1)
|
|
|
|
local idx, highscore = 1, 0
|
|
|
|
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]
|
|
|
|
collectgarbage("collect") -- run gc now to hopefully prevent interruption later
|
|
|
|
disp_msg(rb.HZ * 2, "Level %d: get %d out of %d balls", idx, goal, nrBalls)
|
|
|
|
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 is_exit then
|
|
break -- Exiting..
|
|
else
|
|
highscore, score, bonus = calc_score(highscore, idx, goal, nrBalls_expend)
|
|
if nrBalls_expend >= goal then
|
|
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)
|
|
if highscore < 0 then break end
|
|
end
|
|
end
|
|
end
|
|
|
|
if highscore <= 0 then
|
|
disp_msg(-1, "You lost at level %d", idx)
|
|
elseif idx > #levels then
|
|
disp_msg(-1, "You finished the game with %d points!", highscore)
|
|
else
|
|
disp_msg(-1, "You made it till level %d with %d points!", idx, highscore)
|
|
end
|
|
|
|
-- Restore user backlight settings
|
|
rb.backlight_use_settings()
|
|
if rb.cpu_boost then rb.cpu_boost(false) end
|
|
|
|
os.exit()
|
|
--------------------------------------------------------------------------------
|