mirror of
https://github.com/Rockbox/rockbox.git
synced 2026-04-11 16:37:45 -04:00
Removes `#ifdef`s in many places to just have `HAVE_TOUCHSCREEN` be the input fall back, as is already tentatively the case in the code. Solitaire was the only app excluded since there aren’t enough buttons for it mapped in the SDL layer. Change-Id: I62450b7110b86c8037a121e96cd2e46754be79a3
772 lines
24 KiB
Lua
772 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
|
|
|
|
if rb.backlight_force_on then
|
|
rb.backlight_force_on()
|
|
end
|
|
|
|
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
|
|
if rb.backlight_use_settings then
|
|
rb.backlight_use_settings()
|
|
end
|
|
if rb.cpu_boost then rb.cpu_boost(false) end
|
|
|
|
os.exit()
|
|
--------------------------------------------------------------------------------
|