Lua Boomshine Update with circles + levels
added a few more levels and increased difficulty based on level Rliimage allows us to use circles in boomshine Optimized some of the code to exceed the speed of the original Change-Id: I68eb74f8b68fcdc8a9d022cd172374744425f15d
This commit is contained in:
parent
d9f9def505
commit
85079483c5
1 changed files with 194 additions and 86 deletions
|
@ -11,6 +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
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
|
@ -21,14 +22,32 @@
|
|||
KIND, either express or implied.
|
||||
|
||||
]]--
|
||||
|
||||
require "actions"
|
||||
-- [[only save the actions we are using]]
|
||||
actions_pla = {}
|
||||
for key, val in pairs(rb.actions) do
|
||||
for _, v in ipairs({"PLA_", "TOUCHSCREEN"}) do
|
||||
if string.find (key, v) then
|
||||
actions_pla[key] = val
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
rb.actions = nil
|
||||
rb.contexts = nil
|
||||
-------------------------------------
|
||||
|
||||
local _LCD = rb.lcd_framebuffer()
|
||||
local BSAND = 0x8
|
||||
local rocklib_image = getmetatable(rb.lcd_framebuffer())
|
||||
local _ellipse = rocklib_image.ellipse
|
||||
|
||||
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
|
||||
|
@ -46,7 +65,15 @@ local levels = {
|
|||
{30, 45},
|
||||
{37, 50},
|
||||
{48, 55},
|
||||
{55, 60}
|
||||
{59, 60},
|
||||
{29, 30},
|
||||
{24, 25},
|
||||
{19, 20},
|
||||
{14, 15},
|
||||
{9, 10},
|
||||
{10, 10},
|
||||
{4, 5},
|
||||
{5, 5}
|
||||
}
|
||||
|
||||
local Ball = {
|
||||
|
@ -55,16 +82,44 @@ local Ball = {
|
|||
implosion = false
|
||||
}
|
||||
|
||||
function Ball:new(o)
|
||||
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)
|
||||
|
||||
cursor:line(1, size, sz4, size, 1)
|
||||
cursor:line(1, size, 1, size - sz4, 1)
|
||||
|
||||
cursor:line(size, size, size - sz4, size, 1)
|
||||
cursor:line(size, size, size, size - sz4, 1)
|
||||
|
||||
cursor:line(size, 1, size - sz4, 1, 1)
|
||||
cursor:line(size, 1, size, sz4, 1)
|
||||
|
||||
--crosshairs
|
||||
cursor:line(sz2 - sz4, sz2, sz2 + sz4, sz2, 1)
|
||||
cursor:line(sz2, sz2 - sz4, sz2, sz2 + sz4, 1)
|
||||
end
|
||||
return cursor
|
||||
end
|
||||
|
||||
function Ball:new(o, level)
|
||||
level = level or 1
|
||||
if o == nil then
|
||||
o = {
|
||||
x = math.random(0, rb.LCD_WIDTH - self.size),
|
||||
y = math.random(0, rb.LCD_HEIGHT - self.size),
|
||||
color = random_color(),
|
||||
up_speed = Ball:generateSpeed(),
|
||||
right_speed = Ball:generateSpeed(),
|
||||
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, rb.HZ*5)
|
||||
life_duration = math.random(rb.HZ / level, rb.HZ*5),
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -73,8 +128,10 @@ function Ball:new(o)
|
|||
return o
|
||||
end
|
||||
|
||||
function Ball:generateSpeed()
|
||||
local speed = math.random(-MAX_BALL_SPEED, MAX_BALL_SPEED)
|
||||
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
|
||||
|
@ -82,39 +139,48 @@ function Ball:generateSpeed()
|
|||
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)
|
||||
end
|
||||
|
||||
function Ball:draw()
|
||||
--[[
|
||||
I know these aren't circles, but as there's no current circle
|
||||
implementation in Rockbox, rectangles will just do fine (drawing
|
||||
circles from within Lua is far too slow).
|
||||
]]--
|
||||
set_foreground(self.color)
|
||||
rb.lcd_fillrect(self.x, self.y, self.size, self.size)
|
||||
--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)
|
||||
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()
|
||||
if self.exploded then
|
||||
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
|
||||
return
|
||||
end
|
||||
|
||||
self.x = self.x + self.right_speed
|
||||
self.y = self.y + self.up_speed
|
||||
if (self.right_speed > 0 and self.x + self.size >= rb.LCD_WIDTH) or
|
||||
(self.right_speed < 0 and self.x <= 0) then
|
||||
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
|
||||
end
|
||||
if (self.up_speed > 0 and self.y + self.size >= rb.LCD_HEIGHT) or
|
||||
(self.up_speed < 0 and self.y <= 0) then
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -137,7 +203,8 @@ end
|
|||
local Cursor = {
|
||||
size = DEFAULT_BALL_SIZE*2,
|
||||
x = rb.LCD_WIDTH/2,
|
||||
y = rb.LCD_HEIGHT/2
|
||||
y = rb.LCD_HEIGHT/2,
|
||||
image = create_cursor(DEFAULT_BALL_SIZE*2)
|
||||
}
|
||||
|
||||
function Cursor:new()
|
||||
|
@ -145,18 +212,18 @@ function Cursor:new()
|
|||
end
|
||||
|
||||
function Cursor:do_action(action)
|
||||
if action == rb.actions.ACTION_TOUCHSCREEN and HAS_TOUCHSCREEN then
|
||||
if action == actions_pla.ACTION_TOUCHSCREEN and HAS_TOUCHSCREEN then
|
||||
_, self.x, self.y = rb.action_get_touchscreen_press()
|
||||
return true
|
||||
elseif action == rb.actions.PLA_SELECT then
|
||||
elseif action == actions_pla.PLA_SELECT then
|
||||
return true
|
||||
elseif (action == rb.actions.PLA_RIGHT) then
|
||||
elseif (action == actions_pla.PLA_RIGHT or action == actions_pla.PLA_RIGHT_REPEAT) then
|
||||
self.x = self.x + self.size
|
||||
elseif (action == rb.actions.PLA_LEFT) then
|
||||
elseif (action == actions_pla.PLA_LEFT or action == actions_pla.PLA_LEFT_REPEAT) then
|
||||
self.x = self.x - self.size
|
||||
elseif (action == rb.actions.PLA_UP) then
|
||||
elseif (action == actions_pla.PLA_UP or action == actions_pla.PLA_UP_REPEAT) then
|
||||
self.y = self.y - self.size
|
||||
elseif (action == rb.actions.PLA_DOWN) then
|
||||
elseif (action == actions_pla.PLA_DOWN or action == actions_pla.PLA_DOWN_REPEAT) then
|
||||
self.y = self.y + self.size
|
||||
end
|
||||
|
||||
|
@ -176,25 +243,16 @@ function Cursor:do_action(action)
|
|||
end
|
||||
|
||||
function Cursor:draw()
|
||||
set_foreground(DEFAULT_FOREGROUND_COLOR)
|
||||
|
||||
rb.lcd_hline(self.x - self.size/2, self.x - self.size/4, self.y - self.size/2)
|
||||
rb.lcd_hline(self.x + self.size/4, self.x + self.size/2, self.y - self.size/2)
|
||||
rb.lcd_hline(self.x - self.size/2, self.x - self.size/4, self.y + self.size/2)
|
||||
rb.lcd_hline(self.x + self.size/4, self.x + self.size/2, self.y + self.size/2)
|
||||
rb.lcd_vline(self.x - self.size/2, self.y - self.size/2, self.y - self.size/4)
|
||||
rb.lcd_vline(self.x - self.size/2, self.y + self.size/4, self.y + self.size/2)
|
||||
rb.lcd_vline(self.x + self.size/2, self.y - self.size/2, self.y - self.size/4)
|
||||
rb.lcd_vline(self.x + self.size/2, self.y + self.size/4, self.y + self.size/2)
|
||||
|
||||
rb.lcd_hline(self.x - self.size/4, self.x + self.size/4, self.y)
|
||||
rb.lcd_vline(self.x, self.y - self.size/4, self.y + self.size/4)
|
||||
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)
|
||||
end
|
||||
|
||||
function draw_positioned_string(bottom, right, str)
|
||||
local _, w, h = rb.font_getstringsize(str, rb.FONT_UI)
|
||||
|
||||
rb.lcd_putsxy((rb.LCD_WIDTH-w)*right, (rb.LCD_HEIGHT-h)*bottom, str)
|
||||
local x = not right or (rb.LCD_WIDTH-w)*right - 1
|
||||
local y = not bottom or (rb.LCD_HEIGHT-h)*bottom - 1
|
||||
rb.lcd_putsxy(x, y, str)
|
||||
end
|
||||
|
||||
function set_foreground(color)
|
||||
|
@ -213,31 +271,43 @@ end
|
|||
|
||||
function start_round(level, goal, nrBalls, total)
|
||||
local player_added, score, exit, nrExpendedBalls = false, 0, false, 0
|
||||
local balls, explodedBalls = {}, {}
|
||||
local Balls, explodedBalls = {}, {}
|
||||
local ball_ct, ball_el
|
||||
local tick, endtick
|
||||
local action = 0
|
||||
local hit_detected = false
|
||||
local cursor = Cursor:new()
|
||||
|
||||
-- Initialize the balls
|
||||
for _=1,nrBalls do
|
||||
table.insert(balls, Ball:new())
|
||||
table.insert(Balls, Ball:new(nil, level))
|
||||
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))
|
||||
end
|
||||
|
||||
-- Make sure there are no unwanted touchscreen presses
|
||||
rb.button_clear_queue()
|
||||
|
||||
set_foreground(DEFAULT_FOREGROUND_COLOR) -- color for text
|
||||
|
||||
while true do
|
||||
local endtick = rb.current_tick() + CYCLETIME
|
||||
endtick = rb.current_tick() + CYCLETIME
|
||||
|
||||
-- Check if the round is over
|
||||
if #explodedBalls == 0 and player_added then
|
||||
if player_added and #explodedBalls == 0 then
|
||||
break
|
||||
end
|
||||
|
||||
-- Check for actions
|
||||
local action = rb.get_plugin_action(0)
|
||||
if action == rb.actions.PLA_EXIT or action == rb.actions.PLA_CANCEL then
|
||||
if action == actions_pla.PLA_EXIT or action == actions_pla.PLA_CANCEL then
|
||||
exit = true
|
||||
break
|
||||
end
|
||||
|
||||
if not player_added and cursor:do_action(action) then
|
||||
local player = Ball:new({
|
||||
x = cursor.x,
|
||||
|
@ -248,26 +318,28 @@ function start_round(level, goal, nrBalls, total)
|
|||
exploded = true,
|
||||
death_time = rb.current_tick() + rb.HZ * 3
|
||||
})
|
||||
table.insert(explodedBalls, player)
|
||||
explodedBalls[1] = player
|
||||
player_added = true
|
||||
cursor = nil
|
||||
end
|
||||
|
||||
-- Check for hits
|
||||
for i, ball in ipairs(balls) do
|
||||
-- Check for hits
|
||||
for i, Ball in ipairs(Balls) do
|
||||
for _, explodedBall in ipairs(explodedBalls) do
|
||||
if ball:checkHit(explodedBall) then
|
||||
score = score + 100*level
|
||||
nrExpendedBalls = nrExpendedBalls + 1
|
||||
table.insert(explodedBalls, ball)
|
||||
table.remove(balls, i)
|
||||
if Ball:checkHit(explodedBall) then
|
||||
explodedBalls[#explodedBalls + 1] = Ball
|
||||
--table.remove(Balls, i)
|
||||
Balls[i] = false
|
||||
hit_detected = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Check if we're dead yet
|
||||
tick = rb.current_tick()
|
||||
for i, explodedBall in ipairs(explodedBalls) do
|
||||
if rb.current_tick() >= explodedBall.death_time then
|
||||
if explodedBall.death_time < tick then
|
||||
if explodedBall.size > 0 then
|
||||
explodedBall.implosion = true -- We should be dying
|
||||
else
|
||||
|
@ -277,38 +349,64 @@ function start_round(level, goal, nrBalls, total)
|
|||
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
|
||||
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()
|
||||
|
||||
set_foreground(DEFAULT_FOREGROUND_COLOR)
|
||||
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))
|
||||
if not (player_added or HAS_TOUCHSCREEN) then
|
||||
cursor:draw()
|
||||
end
|
||||
|
||||
for _, ball in ipairs(balls) do
|
||||
ball:step()
|
||||
ball:draw()
|
||||
for _, Ball in ipairs(Balls) do
|
||||
Ball:step()
|
||||
Ball:draw()
|
||||
end
|
||||
|
||||
for _, explodedBall in ipairs(explodedBalls) do
|
||||
explodedBall:step()
|
||||
explodedBall:draw()
|
||||
end
|
||||
|
||||
if not HAS_TOUCHSCREEN and not player_added then
|
||||
cursor:draw()
|
||||
explodedBall:step_exploded()
|
||||
explodedBall:draw_exploded()
|
||||
end
|
||||
|
||||
-- Push framebuffer to the LCD
|
||||
rb.lcd_update()
|
||||
|
||||
-- Check for actions
|
||||
if rb.current_tick() < endtick then
|
||||
rb.sleep(endtick - rb.current_tick())
|
||||
action = rb.get_plugin_action(endtick - rb.current_tick())
|
||||
else
|
||||
rb.yield()
|
||||
action = rb.get_plugin_action(0)
|
||||
end
|
||||
|
||||
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)
|
||||
draw_stats()
|
||||
rb.lcd_update()
|
||||
rb.sleep(rb.HZ * 2)
|
||||
|
||||
return exit, score, nrExpendedBalls
|
||||
end
|
||||
|
||||
|
@ -320,6 +418,7 @@ function display_message(to, ...)
|
|||
|
||||
rb.lcd_clear_display()
|
||||
set_foreground(DEFAULT_FOREGROUND_COLOR)
|
||||
|
||||
if w > rb.LCD_WIDTH then
|
||||
rb.lcd_puts_scroll(0, y/h, message)
|
||||
else
|
||||
|
@ -340,7 +439,7 @@ function display_message(to, ...)
|
|||
if to == -1 then
|
||||
rb.sleep(rb.HZ/2)
|
||||
rb.button_clear_queue()
|
||||
rb.button_get(1)
|
||||
rb.button_get(rb.HZ * 60)
|
||||
else
|
||||
rb.sleep(to)
|
||||
end
|
||||
|
@ -351,6 +450,8 @@ end
|
|||
if HAS_TOUCHSCREEN then
|
||||
rb.touchscreen_set_mode(rb.TOUCHSCREEN_POINT)
|
||||
end
|
||||
|
||||
--[[MAIN PROGRAM]]
|
||||
rb.backlight_force_on()
|
||||
|
||||
math.randomseed(os.time())
|
||||
|
@ -359,9 +460,12 @@ local idx, highscore = 1, 0
|
|||
while levels[idx] ~= nil do
|
||||
local goal, nrBalls = levels[idx][1], levels[idx][2]
|
||||
|
||||
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)
|
||||
|
||||
local exit, score, nrExpendedBalls = start_round(idx, goal, nrBalls, highscore)
|
||||
|
||||
if exit then
|
||||
break -- Exiting..
|
||||
else
|
||||
|
@ -371,11 +475,14 @@ while levels[idx] ~= nil do
|
|||
highscore = highscore + score
|
||||
else
|
||||
display_message(rb.HZ*2, "You lost!")
|
||||
highscore = highscore - score - idx * 100
|
||||
if highscore < 0 then break end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if idx > #levels then
|
||||
if highscore <= 0 then
|
||||
display_message(-1, "You lost at level %d", idx)
|
||||
elseif idx > #levels then
|
||||
display_message(-1, "You finished the game with %d points!", highscore)
|
||||
else
|
||||
display_message(-1, "You made it till level %d with %d points!", idx, highscore)
|
||||
|
@ -383,3 +490,4 @@ end
|
|||
|
||||
-- Restore user backlight settings
|
||||
rb.backlight_use_settings()
|
||||
|
||||
|
|
Loading…
Reference in a new issue