From 85079483c5595c2a04774ae9fbf529719eaec9fa Mon Sep 17 00:00:00 2001 From: William Wilgus Date: Wed, 17 Oct 2018 18:35:02 -0400 Subject: 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 --- apps/plugins/boomshine.lua | 278 +++++++++++++++++++++++++++++++-------------- 1 file changed, 193 insertions(+), 85 deletions(-) diff --git a/apps/plugins/boomshine.lua b/apps/plugins/boomshine.lua index 1729cbbf8c..63ac6b11fa 100644 --- a/apps/plugins/boomshine.lua +++ b/apps/plugins/boomshine.lua @@ -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() - 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 +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() 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 - rb.lcd_clear_display() + 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 - 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)) + rb.lcd_clear_display() + draw_stats() - for _, ball in ipairs(balls) do - ball:step() - ball:draw() + if not (player_added or HAS_TOUCHSCREEN) then + cursor:draw() end - for _, explodedBall in ipairs(explodedBalls) do - explodedBall:step() - explodedBall:draw() + for _, Ball in ipairs(Balls) do + Ball:step() + Ball:draw() end - if not HAS_TOUCHSCREEN and not player_added then - cursor:draw() + for _, explodedBall in ipairs(explodedBalls) do + 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() + -- cgit v1.2.3