summaryrefslogtreecommitdiff
path: root/apps/plugins/boomshine.lua
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/boomshine.lua')
-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 @@
11 See http://www.yvoschaap.com/chainrxn/ and http://www.k2xl.com/games/boomshine/ 11 See http://www.yvoschaap.com/chainrxn/ and http://www.k2xl.com/games/boomshine/
12 12
13 Copyright (C) 2009 by Maurus Cuelenaere 13 Copyright (C) 2009 by Maurus Cuelenaere
14 Copyright (C) 2018 William Wilgus -- Added circles, blit cursor, hard levels 14 Copyright (C) 2018 William Wilgus -- Added circles, logic rewrite, hard levels
15 15
16 This program is free software; you can redistribute it and/or 16 This program is free software; you can redistribute it and/or
17 modify it under the terms of the GNU General Public License 17 modify it under the terms of the GNU General Public License
@@ -23,34 +23,44 @@
23 23
24]]-- 24]]--
25require "actions" 25require "actions"
26-- [[only save the actions we are using]] 26--[[only save the actions we are using]]
27actions_pla = {} 27pla = {}
28for key, val in pairs(rb.actions) do 28for key, val in pairs(rb.actions) do
29 for _, v in ipairs({"PLA_", "TOUCHSCREEN"}) do 29 for _, v in ipairs({"PLA_", "TOUCHSCREEN", "_NONE"}) do
30 if string.find (key, v) then 30 if string.find (key, v) then
31 actions_pla[key] = val 31 pla[key] = val; break
32 break
33 end 32 end
34 end 33 end
35end 34end
36rb.actions = nil 35rb.actions, rb.contexts = nil, nil
37rb.contexts = nil 36--------------------------------------
38-------------------------------------
39 37
40local _LCD = rb.lcd_framebuffer() 38local _LCD = rb.lcd_framebuffer()
41local BSAND = 0x8 39local rocklib_image = getmetatable(_LCD)
42local rocklib_image = getmetatable(rb.lcd_framebuffer())
43local _ellipse = rocklib_image.ellipse 40local _ellipse = rocklib_image.ellipse
41local BSAND = 0x8
42local irand = math.random
44 43
45local CYCLETIME = rb.HZ / 50
46local HAS_TOUCHSCREEN = rb.action_get_touchscreen_press ~= nil 44local HAS_TOUCHSCREEN = rb.action_get_touchscreen_press ~= nil
47local DEFAULT_BALL_SIZE = rb.LCD_HEIGHT > rb.LCD_WIDTH and rb.LCD_WIDTH / 30 45local LCD_H, LCD_W = rb.LCD_HEIGHT, rb.LCD_WIDTH
48 or rb.LCD_HEIGHT / 30 46local DEFAULT_BALL_SZ = LCD_H > LCD_W and LCD_W / 30 or LCD_H / 30
49local MAX_BALL_SPEED = DEFAULT_BALL_SIZE / 2 47 DEFAULT_BALL_SZ = DEFAULT_BALL_SZ - bit.band(DEFAULT_BALL_SZ, 1)
50local DEC_BALL_SPEED = DEFAULT_BALL_SIZE / 8 48local MAX_BALL_SPEED = DEFAULT_BALL_SZ / 2 + 1
51local DEFAULT_FOREGROUND_COLOR = rb.lcd_get_foreground ~= nil 49-- Ball states
52 and rb.lcd_get_foreground() 50local B_DEAD, B_MOVE, B_EXPLODE, B_DIE, B_WAIT, B_IMPLODE = 5, 4, 3, 2, 1, 0
53 or 0 51
52local DEFAULT_FG_CLR = 1
53local DEFAULT_BG_CLR = 0
54if rb.lcd_get_foreground ~= nil then
55 DEFAULT_FG_CLR = rb.lcd_get_foreground()
56 DEFAULT_BG_CLR = rb.lcd_get_background()
57elseif rb.LCD_DEFAULT_FG ~= nil then
58 DEFAULT_FG_CLR = rb.LCD_DEFAULT_FG
59 DEFAULT_BG_CLR = rb.LCD_DEFAULT_BG
60end
61
62local FMT_EXPEND, FMT_LEVEL = "%d balls expended", "Level %d"
63local FMT_TOTPTS, FMT_LVPTS = "%d total points", "%d level points"
54 64
55local levels = { 65local levels = {
56 -- {GOAL, TOTAL_BALLS}, 66 -- {GOAL, TOTAL_BALLS},
@@ -65,134 +75,103 @@ local levels = {
65 {30, 45}, 75 {30, 45},
66 {37, 50}, 76 {37, 50},
67 {48, 55}, 77 {48, 55},
68 {59, 60}, 78 {58, 60},
69 {29, 30}, 79 {28, 30},
70 {24, 25}, 80 {23, 25},
71 {19, 20}, 81 {18, 20},
72 {14, 15}, 82 {13, 15},
83 {8, 10},
73 {9, 10}, 84 {9, 10},
74 {10, 10},
75 {4, 5}, 85 {4, 5},
76 {5, 5} 86 {5, 5}
77 } 87 }
78
79local Ball = {
80 size = DEFAULT_BALL_SIZE,
81 exploded = false,
82 implosion = false
83 }
84
85local function create_cursor(size)
86 local cursor
87 if not HAS_TOUCHSCREEN then
88 cursor = rb.new_image(size, size)
89 cursor:clear(0)
90 local sz2 = size / 2
91 local sz4 = size / 4
92 88
93 cursor:line(1, 1, sz4, 1, 1) 89local function getstringsize(str)
94 cursor:line(1, 1, 1, sz4, 1) 90 local _, w, h = rb.font_getstringsize(str, rb.FONT_UI)
91 return w, h
92end
95 93
96 cursor:line(1, size, sz4, size, 1) 94local function set_foreground(color)
97 cursor:line(1, size, 1, size - sz4, 1) 95 if rb.lcd_set_foreground ~= nil then
96 rb.lcd_set_foreground(color)
97 end
98end
98 99
99 cursor:line(size, size, size - sz4, size, 1) 100local function random_color()
100 cursor:line(size, size, size, size - sz4, 1) 101 if rb.lcd_rgbpack ~= nil then -- color target
102 return rb.lcd_rgbpack(irand(1,255), irand(1,255), irand(1,255))
103 end
101 104
102 cursor:line(size, 1, size - sz4, 1, 1) 105 local color = irand(1, rb.LCD_DEPTH)
103 cursor:line(size, 1, size, sz4, 1) 106 color = (rb.LCD_DEPTH == 2) and (3 - color) or color -- invert for 2-bit screens
104 107
105 --crosshairs 108 return color
106 cursor:line(sz2 - sz4, sz2, sz2 + sz4, sz2, 1)
107 cursor:line(sz2, sz2 - sz4, sz2, sz2 + sz4, 1)
108 end
109 return cursor
110end 109end
111 110
111local Ball = {
112 -- Ball defaults
113 sz = DEFAULT_BALL_SZ,
114 next_tick = 0,
115 state = B_MOVE
116 }
117
112function Ball:new(o, level) 118function Ball:new(o, level)
113 level = level or 1
114 if o == nil then 119 if o == nil then
120 level = level or 1
121 local maxdelay = (level <= 5) and 10 or level
115 o = { 122 o = {
116 x = math.random(0, rb.LCD_WIDTH - self.size), 123 x = irand(1, LCD_W - self.sz),
117 y = math.random(0, rb.LCD_HEIGHT - self.size), 124 y = irand(1, LCD_H - self.sz),
118 color = random_color(), 125 color = random_color(),
119 up_speed = Ball:generateSpeed(level), 126 xi = Ball:genSpeed(),
120 right_speed = Ball:generateSpeed(level), 127 yi = Ball:genSpeed(),
121 explosion_size = math.random(2*self.size, 4*self.size), 128 step_delay = irand(3, maxdelay / 2),
122 life_duration = math.random(rb.HZ / level, rb.HZ*5), 129 explosion_sz = irand(2 * self.sz, 4 * self.sz),
130 life_ticks = irand(rb.HZ / level, rb.HZ * (maxdelay / 5))
123 } 131 }
124 end 132 end
125 133 o.life_ticks = o.life_ticks + DEFAULT_BALL_SZ -- extra time for larger screens
126 setmetatable(o, self) 134 setmetatable(o, self)
127 self.__index = self 135 self.__index = self
128 return o 136 return o
129end 137end
130 138
131function Ball:generateSpeed(level) 139function Ball:genSpeed()
132 local ballspeed = MAX_BALL_SPEED - (DEC_BALL_SPEED * (level - 1)) 140 local speed = irand(-MAX_BALL_SPEED, MAX_BALL_SPEED)
133 if ballspeed < 2 then ballspeed = 2 end 141 return speed ~= 0 and speed or MAX_BALL_SPEED -- Make sure all balls move
134 local speed = math.random(-ballspeed, ballspeed)
135 if speed == 0 then
136 speed = 1 -- Make sure all balls move
137 end
138
139 return speed
140end
141
142function Ball:draw_exploded()
143 --[[
144 --set_foreground(self.color)
145 --rb.lcd_fillrect(self.x, self.y, self.size, self.size)
146 ]]
147
148 _ellipse(_LCD, self.x, self.y,
149 self.x + self.size, self.y + self.size , self.color, nil, true)
150end 142end
151 143
152function Ball:draw() 144function Ball:draw()
153 --[[
154 --set_foreground(self.color)
155 --rb.lcd_fillrect(self.x, self.y, self.size, self.size)
156 ]]
157
158 _ellipse(_LCD, self.x, self.y, 145 _ellipse(_LCD, self.x, self.y,
159 self.x + self.size, self.y + self.size , self.color, self.color, true) 146 self.x + self.sz, self.y + self.sz , self.color, self.color, true)
160end 147end
161 148
162function Ball:step_exploded() 149function Ball:step(tick)
163 if self.implosion and self.size > 0 then 150 self.next_tick = tick + self.step_delay
164 self.size = self.size - 2 151 self.x = self.x + self.xi
165 self.x = self.x + 1 -- We do this because we want to stay centered 152 self.y = self.y + self.yi
166 self.y = self.y + 1
167 elseif self.size < self.explosion_size then
168 self.size = self.size + 2
169 self.x = self.x - 1 -- We do this for the same reasons as above
170 self.y = self.y - 1
171 end
172end
173 153
174function Ball:step() 154 if (self.x <= 0 or self.x >= (LCD_W - self.sz)) then
175 self.x = self.x + self.right_speed 155 self.xi = -self.xi
176 self.y = self.y + self.up_speed 156 self.x = self.x + self.xi
177 if (self.x <= 0 or self.x >= rb.LCD_WIDTH - self.size) then
178 self.right_speed = -self.right_speed
179 self.x = self.x + self.right_speed
180 end 157 end
181 if (self.y <= 0 or self.y >= rb.LCD_HEIGHT - self.size ) then 158
182 self.up_speed = -self.up_speed 159 if (self.y <= 0 or self.y >= (LCD_H - self.sz)) then
183 self.y = self.y + self.up_speed 160 self.yi = -self.yi
161 self.y = self.y + self.yi
184 end 162 end
185end 163end
186 164
187function Ball:checkHit(other) 165function Ball:checkHit(other)
188 if (other.x + other.size >= self.x) and (self.x + self.size >= other.x) and 166 if (other.xi >= self.x) and (other.yi >= self.y) and
189 (other.y + other.size >= self.y) and (self.y + self.size >= other.y) then 167 (self.x + self.sz >= other.x) and (self.y + self.sz >= other.y) then
190 assert(not self.exploded) 168 self.state = B_EXPLODE
191 self.exploded = true 169 -- x/y increment no longer needed it is now impact region
192 self.death_time = rb.current_tick() + self.life_duration 170 self.xi = self.x + self.sz
193 if not other.exploded then 171 self.yi = self.y + self.sz
194 other.exploded = true 172
195 other.death_time = rb.current_tick() + other.life_duration 173 if other.state < B_EXPLODE then -- add duration to the ball that got hit
174 other.next_tick = other.next_tick + self.life_ticks
196 end 175 end
197 return true 176 return true
198 end 177 end
@@ -200,236 +179,332 @@ function Ball:checkHit(other)
200 return false 179 return false
201end 180end
202 181
182function Ball:draw_exploded()
183 _ellipse(_LCD, self.x, self.y, self.xi, self.yi, self.color, nil, true)
184end
185
186function Ball:step_exploded(tick)
187 -- exploding ball state machine
188 -- B_EXPLODE >> B_DIE >> BWAIT >> B_IMPLODE >> B_DEAD
189 if self.state == B_EXPLODE and self.sz < self.explosion_sz then
190 self.sz = self.sz + 2
191 self.x = self.x - 1 -- We do this because we want to stay centered
192 self.y = self.y - 1
193 elseif self.state == B_DIE then
194 self.state = B_WAIT
195 self.next_tick = tick + self.life_ticks
196 return
197 elseif self.state == B_IMPLODE and self.sz > 0 then
198 self.sz = self.sz - 2
199 self.x = self.x + 1 -- We do this because we want to stay centered
200 self.y = self.y + 1
201 elseif self.state <= B_IMPLODE then
202 self.state = B_DEAD
203 return
204 elseif self.next_tick < tick then
205 self.state = self.state - 1
206 return
207 end
208 -- fell through, update next_tick and impact region
209 self.next_tick = tick + self.step_delay
210 self.xi = self.x + self.sz
211 self.yi = self.y + self.sz
212end
213
203local Cursor = { 214local Cursor = {
204 size = DEFAULT_BALL_SIZE*2, 215 sz = (DEFAULT_BALL_SZ * 2),
205 x = rb.LCD_WIDTH/2, 216 x = (LCD_W / 2),
206 y = rb.LCD_HEIGHT/2, 217 y = (LCD_H / 2),
207 image = create_cursor(DEFAULT_BALL_SIZE*2) 218 color = DEFAULT_FG_CLR
208 } 219 }
209 220
210function Cursor:new() 221function Cursor:new()
222 if rb.LCD_DEPTH == 2 then -- invert for 2 - bit screens
223 self.color = 3 - DEFAULT_FG_CLR
224 end
225 self:create_image(DEFAULT_BALL_SZ * 2)
211 return self 226 return self
212end 227end
213 228
229function Cursor:create_image(sz)
230 if not HAS_TOUCHSCREEN then
231 sz = sz + 1
232 local img = rb.new_image(sz, sz)
233 local sz2 = (sz / 2) + 1
234 local sz4 = (sz / 4)
235
236 img:clear(0)
237 img:line(1, 1, sz4 + 1, 1, 1)
238 img:line(1, 1, 1, sz4 + 1, 1)
239
240 img:line(1, sz, sz4 + 1, sz, 1)
241 img:line(1, sz, 1, sz - sz4, 1)
242
243 img:line(sz, sz, sz - sz4, sz, 1)
244 img:line(sz, sz, sz, sz - sz4, 1)
245
246 img:line(sz, 1, sz - sz4, 1, 1)
247 img:line(sz, 1, sz, sz4 + 1, 1)
248
249 -- crosshairs
250 img:line(sz2 - sz4, sz2, sz2 + sz4, sz2, 1)
251 img:line(sz2, sz2 - sz4, sz2, sz2 + sz4, 1)
252 self.image = img
253 end
254end
255
256local function clamp_roll(iVal, iMin, iMax)
257 if iVal < iMin then
258 iVal = iMax
259 elseif iVal > iMax then
260 iVal = iMin
261 end
262
263 return iVal
264end
265
214function Cursor:do_action(action) 266function Cursor:do_action(action)
215 if action == actions_pla.ACTION_TOUCHSCREEN and HAS_TOUCHSCREEN then 267 local xi, yi = 0, 0
268
269 if HAS_TOUCHSCREEN and action == pla.ACTION_TOUCHSCREEN then
216 _, self.x, self.y = rb.action_get_touchscreen_press() 270 _, self.x, self.y = rb.action_get_touchscreen_press()
217 return true 271 return true
218 elseif action == actions_pla.PLA_SELECT then 272 elseif action == pla.PLA_SELECT then
219 return true 273 return true
220 elseif (action == actions_pla.PLA_RIGHT or action == actions_pla.PLA_RIGHT_REPEAT) then 274 elseif (action == pla.PLA_RIGHT or action == pla.PLA_RIGHT_REPEAT) then
221 self.x = self.x + self.size 275 xi = self.sz
222 elseif (action == actions_pla.PLA_LEFT or action == actions_pla.PLA_LEFT_REPEAT) then 276 elseif (action == pla.PLA_LEFT or action == pla.PLA_LEFT_REPEAT) then
223 self.x = self.x - self.size 277 xi = -self.sz
224 elseif (action == actions_pla.PLA_UP or action == actions_pla.PLA_UP_REPEAT) then 278 elseif (action == pla.PLA_UP or action == pla.PLA_UP_REPEAT) then
225 self.y = self.y - self.size 279 yi = -self.sz
226 elseif (action == actions_pla.PLA_DOWN or action == actions_pla.PLA_DOWN_REPEAT) then 280 elseif (action == pla.PLA_DOWN or action == pla.PLA_DOWN_REPEAT) then
227 self.y = self.y + self.size 281 yi = self.sz
228 end 282 end
229 283
230 if self.x > rb.LCD_WIDTH then 284 self.x = clamp_roll(self.x + xi, 1, LCD_W - self.sz)
231 self.x = 0 285 self.y = clamp_roll(self.y + yi, 1, LCD_H - self.sz)
232 elseif self.x < 0 then
233 self.x = rb.LCD_WIDTH
234 end
235
236 if self.y > rb.LCD_HEIGHT then
237 self.y = 0
238 elseif self.y < 0 then
239 self.y = rb.LCD_HEIGHT
240 end
241 286
242 return false 287 return false
243end 288end
244 289
245function Cursor:draw() 290function Cursor:draw()
291 rocklib_image.copy(_LCD, self.image, self.x, self.y, _NIL, _NIL,
292 _NIL, _NIL, true, BSAND, self.color)
293end
246 294
247 rocklib_image.copy(_LCD, self.image, self.x - self.size/2, self.y - self.size/2, 295local function calc_score(total, level, goal, expended)
248 _NIL, _NIL, _NIL, _NIL, true, BSAND, DEFAULT_FOREGROUND_COLOR) 296 local score = (expended * level) * 100
297 if expended < goal then
298 score = -(score + (level * 100))
299 end
300 total = total + score
301 return total
249end 302end
250 303
251function draw_positioned_string(bottom, right, str) 304local function draw_pos_str(bottom, right, str)
252 local _, w, h = rb.font_getstringsize(str, rb.FONT_UI) 305 local w, h = getstringsize(str)
253 local x = not right or (rb.LCD_WIDTH-w)*right - 1 306 local x = (right > 0) and ((LCD_W - w) * right - 1) or 1
254 local y = not bottom or (rb.LCD_HEIGHT-h)*bottom - 1 307 local y = (bottom > 0) and ((LCD_H - h) * bottom - 1) or 1
255 rb.lcd_putsxy(x, y, str) 308 rb.lcd_putsxy(x, y, str)
256end 309end
257 310
258function set_foreground(color) 311local function wait_anykey(to_secs)
259 if rb.lcd_set_foreground ~= nil then 312 rb.sleep(rb.HZ / 2)
260 rb.lcd_set_foreground(color) 313 rb.button_clear_queue()
261 end 314 rb.button_get_w_tmo(rb.HZ * to_secs)
262end 315end
263 316
264function random_color() 317local function start_round(level, goal, nrBalls, total)
265 if rb.lcd_rgbpack ~= nil then --color target 318 local player_added, score = false, 0
266 return rb.lcd_rgbpack(math.random(1,255), math.random(1,255), math.random(1,255)) 319 local last_expend, nrBalls_expend = 0, 0
320 local balls_exploded = 1 -- keep looping when player_added == false
321 local action = 0
322 local Balls = {}
323 local str_level = string.format(FMT_LEVEL, level) -- static
324 local str_totpts = string.format(FMT_TOTPTS, total) -- static
325 local str_expend, str_lvlpts
326 local tick, cursor
327 local test_spd = false
328
329 local function update_stats()
330 -- we only create a new string when a hit is detected
331 str_expend = string.format(FMT_EXPEND, nrBalls_expend)
332 str_lvlpts = string.format(FMT_LVPTS, score)
267 end 333 end
268 334
269 return math.random(1, rb.LCD_DEPTH) 335 local function draw_stats()
270end 336 draw_pos_str(0, 0, str_expend)
337 draw_pos_str(0, 1, str_level)
338 draw_pos_str(1, 1, str_lvlpts)
339 draw_pos_str(1, 0, str_totpts)
340 end
271 341
272function start_round(level, goal, nrBalls, total) 342 local function add_player()
273 local player_added, score, exit, nrExpendedBalls = false, 0, false, 0 343 -- cursor becomes exploded ball
274 local Balls, explodedBalls = {}, {} 344 local player = Ball:new({
275 local ball_ct, ball_el 345 x = cursor.x,
276 local tick, endtick 346 y = cursor.y,
277 local action = 0 347 color = cursor.color,
278 local hit_detected = false 348 step_delay = 3,
279 local cursor = Cursor:new() 349 explosion_sz = (3 * DEFAULT_BALL_SZ),
350 life_ticks = (test_time == true) and (100) or
351 irand(rb.HZ * 2, rb.HZ * DEFAULT_BALL_SZ),
352 sz = 10,
353 state = B_EXPLODE
354 })
355 -- set x/y impact region
356 player.xi = player.x + player.sz
357 player.yi = player.y + player.sz
358
359 table.insert(Balls, player)
360 balls_exploded = 1
361 player_added = true
362 cursor = nil
363 end
280 364
281 -- Initialize the balls 365 if level < 1 then
282 for _=1,nrBalls do 366 -- check speed of target
283 table.insert(Balls, Ball:new(nil, level)) 367 set_foreground(DEFAULT_BG_CLR) --hide text during test
368 local bkcolor = (rb.LCD_DEPTH == 2) and (3) or 0
369 level = 1
370 nrBalls = 20
371 cursor = { x = LCD_W * 2, y = LCD_H * 2, color = bkcolor}
372 table.insert(Balls, Ball:new({
373 x = 1, y = 1, xi = 1, yi = 1,
374 color = bkcolor, step_delay = 1,
375 explosion_sz = 0, life_ticks = 0,
376 step = function() test_spd = test_spd + 1 end
377 })
378 )
379 add_player()
380 test_spd = 0
381 else
382 set_foreground(DEFAULT_FG_CLR) -- color for text
383 cursor = Cursor:new()
284 end 384 end
285 385
286 local function draw_stats() 386 -- Initialize the balls
287 draw_positioned_string(0, 0, string.format("%d balls expended", nrExpendedBalls)) 387 for i=1, nrBalls do
288 draw_positioned_string(0, 1, string.format("Level %d", level)) 388 table.insert(Balls, Ball:new(nil, level))
289 draw_positioned_string(1, 1, string.format("%d level points", score))
290 draw_positioned_string(1, 0, string.format("%d total points", total + score))
291 end 389 end
292 390
293 -- Make sure there are no unwanted touchscreen presses 391 -- Make sure there are no unwanted touchscreen presses
294 rb.button_clear_queue() 392 rb.button_clear_queue()
295 393
296 set_foreground(DEFAULT_FOREGROUND_COLOR) -- color for text 394 update_stats() -- load status strings
297
298 while true do
299 endtick = rb.current_tick() + CYCLETIME
300 395
301 -- Check if the round is over 396 -- Check if the round is over
302 if player_added and #explodedBalls == 0 then 397 while balls_exploded > 0 do
303 break 398 tick = rb.current_tick()
304 end
305 399
306 if action == actions_pla.PLA_EXIT or action == actions_pla.PLA_CANCEL then 400 if action ~= pla.ACTION_NONE and (action == pla.PLA_EXIT or
307 exit = true 401 action == pla.PLA_CANCEL) then
402 action = pla.PLA_EXIT
308 break 403 break
309 end 404 end
310 405
311 if not player_added and cursor:do_action(action) then 406 rb.lcd_clear_display()
312 local player = Ball:new({
313 x = cursor.x,
314 y = cursor.y,
315 color = DEFAULT_FOREGROUND_COLOR,
316 size = 10,
317 explosion_size = 3*DEFAULT_BALL_SIZE,
318 exploded = true,
319 death_time = rb.current_tick() + rb.HZ * 3
320 })
321 explodedBalls[1] = player
322 player_added = true
323 cursor = nil
324 end
325 407
326 -- Check for hits 408 if not player_added then
327 for i, Ball in ipairs(Balls) do 409 if action ~= pla.ACTION_NONE and cursor:do_action(action) then
328 for _, explodedBall in ipairs(explodedBalls) do 410 add_player()
329 if Ball:checkHit(explodedBall) then 411 elseif not HAS_TOUCHSCREEN then
330 explodedBalls[#explodedBalls + 1] = Ball 412 cursor:draw()
331 --table.remove(Balls, i)
332 Balls[i] = false
333 hit_detected = true
334 break
335 end
336 end 413 end
337 end 414 end
338 415
339 -- Check if we're dead yet 416 for _, Ball in ipairs(Balls) do
340 tick = rb.current_tick() 417 if Ball.state == B_MOVE then
341 for i, explodedBall in ipairs(explodedBalls) do 418 if tick > Ball.next_tick then
342 if explodedBall.death_time < tick then 419 Ball:step(tick)
343 if explodedBall.size > 0 then 420 for i = #Balls, 1, -1 do
344 explodedBall.implosion = true -- We should be dying 421 if Balls[i].state < B_MOVE and
345 else 422 Ball:checkHit(Balls[i]) then -- exploded?
346 table.remove(explodedBalls, i) -- We're imploded! 423 balls_exploded = balls_exploded + 1
424 nrBalls_expend = nrBalls_expend + 1
425 break
426 end
427 end
347 end 428 end
348 end 429 -- check if state changed draw ball if still moving
349 end 430 if Ball.state == B_MOVE then
350 431 Ball:draw()
351 -- Drawing phase 432 end
352 if hit_detected then 433 elseif Ball.state ~= B_DEAD then
353 hit_detected = false 434 if tick > Ball.next_tick then
354 -- same as table.remove(Balls, i) but more efficient 435 Ball:step_exploded(tick)
355 ball_el = 1 436 end
356 ball_ct = #Balls 437 if Ball.state ~= B_DEAD then
357 for i = 1, ball_ct do 438 Ball:draw_exploded()
358 if Balls[i] then 439 else
359 Balls[ball_el] = Balls[i] 440 balls_exploded = balls_exploded - 1
360 ball_el = ball_el + 1
361 end 441 end
362 end 442 end
363 -- remove any remaining exploded balls
364 for i = ball_el, ball_ct do
365 Balls[i] = nil
366 end
367 -- Calculate score
368 nrExpendedBalls = nrBalls - ball_el + 1
369 score = nrExpendedBalls * level * 100
370 end 443 end
371 444
372 rb.lcd_clear_display() 445 draw_stats() -- stats redrawn every frame
373 draw_stats() 446 rb.lcd_update() -- Push framebuffer to the LCD
374 447
375 if not (player_added or HAS_TOUCHSCREEN) then 448 if nrBalls_expend ~= last_expend then -- hit detected?
376 cursor:draw() 449 last_expend = nrBalls_expend
450 score = (nrBalls_expend * level) * 100
451 update_stats() -- only update stats when not current
452 if nrBalls_expend == nrBalls then break end -- round is over?
377 end 453 end
378 454
379 for _, Ball in ipairs(Balls) do 455 rb.yield() -- yield to other tasks
380 Ball:step()
381 Ball:draw()
382 end
383 456
384 for _, explodedBall in ipairs(explodedBalls) do 457 action = rb.get_plugin_action(0) -- Check for actions
385 explodedBall:step_exploded() 458 end
386 explodedBall:draw_exploded()
387 end
388 459
389 -- Push framebuffer to the LCD 460 if test_spd and test_spd > 0 then return test_spd end
390 rb.lcd_update()
391 461
392 -- Check for actions 462 -- splash the final stats for a moment at end
393 if rb.current_tick() < endtick then 463 rb.lcd_clear_display()
394 action = rb.get_plugin_action(endtick - rb.current_tick()) 464 for _, Ball in ipairs(Balls) do
395 else 465 -- move balls back to their initial exploded positions
396 rb.yield() 466 if Ball.state == B_DEAD then
397 action = rb.get_plugin_action(0) 467 Ball.sz = Ball.explosion_sz + 2
468 Ball.x = Ball.x - (Ball.explosion_sz / 2)
469 Ball.y = Ball.y - (Ball.explosion_sz / 2)
398 end 470 end
471 Ball:draw()
472 end
399 473
474 if DEFAULT_BALL_SZ > 3 then
475 _LCD:clear(nil, nil, nil, nil, nil, nil, 2, 2)
400 end 476 end
401 477
402 --splash the final stats for a moment at end 478 total = calc_score(total, level, goal, nrBalls_expend)
403 rb.lcd_clear_display() 479 str_totpts = string.format(FMT_TOTPTS, total)
404 for _, Ball in ipairs(Balls) do Ball:draw() end
405 _LCD:clear(nil, nil, nil, nil, nil, nil, 2, 2)
406 draw_stats() 480 draw_stats()
407 rb.lcd_update() 481 rb.lcd_update()
408 rb.sleep(rb.HZ * 2) 482 wait_anykey(2)
409 483
410 return exit, score, nrExpendedBalls 484 return action == pla.PLA_EXIT, score, nrBalls_expend
411end 485end
412 486
413-- Helper function to display a message 487-- Helper function to display a message
414function display_message(to, ...) 488local function disp_msg(to, ...)
415 local message = string.format(...) 489 local message = string.format(...)
416 local _, w, h = rb.font_getstringsize(message, rb.FONT_UI) 490 local w, h = getstringsize(message)
417 local x, y = (rb.LCD_WIDTH - w) / 2, (rb.LCD_HEIGHT - h) / 2 491 local x, y = (LCD_W - w) / 2, (LCD_H - h) / 2
418 492
419 rb.lcd_clear_display() 493 rb.lcd_clear_display()
420 set_foreground(DEFAULT_FOREGROUND_COLOR) 494 set_foreground(DEFAULT_FG_CLR)
421 495
422 if w > rb.LCD_WIDTH then 496 if w > LCD_W then
423 rb.lcd_puts_scroll(0, y/h, message) 497 rb.lcd_puts_scroll(0, (y / h), message)
424 else 498 else
425 rb.lcd_putsxy(x, y, message) 499 rb.lcd_putsxy(x, y, message)
426 end 500 end
501
427 if to == -1 then 502 if to == -1 then
428 local msg = "Press button to exit" 503 local msg = "Press button to exit"
429 w = rb.font_getstringsize(msg, rb.FONT_UI) 504 w, h = getstringsize(msg)
430 x = (rb.LCD_WIDTH - w) / 2 505 x = (LCD_W - w) / 2
431 if x < 0 then 506 if x < 0 then
432 rb.lcd_puts_scroll(0, y/h + 1, msg) 507 rb.lcd_puts_scroll(0, (y / h) + 1, msg)
433 else 508 else
434 rb.lcd_putsxy(x, y + h, msg) 509 rb.lcd_putsxy(x, y + h, msg)
435 end 510 end
@@ -437,9 +512,7 @@ function display_message(to, ...)
437 rb.lcd_update() 512 rb.lcd_update()
438 513
439 if to == -1 then 514 if to == -1 then
440 rb.sleep(rb.HZ/2) 515 wait_anykey(60)
441 rb.button_clear_queue()
442 rb.button_get(rb.HZ * 60)
443 else 516 else
444 rb.sleep(to) 517 rb.sleep(to)
445 end 518 end
@@ -447,47 +520,77 @@ function display_message(to, ...)
447 rb.lcd_scroll_stop() -- Stop our scrolling message 520 rb.lcd_scroll_stop() -- Stop our scrolling message
448end 521end
449 522
523--[[MAIN PROGRAM]]
524do -- attempt to get stats to fit on screen
525 local function getwidth(str)
526 local w, _ = getstringsize(str)
527 return w
528 end
529 local w0, w1 = getwidth(FMT_EXPEND), getwidth(FMT_LEVEL)
530 if (w0 + w1) > LCD_W then
531 FMT_EXPEND, FMT_LEVEL = "%d balls", "Lv %d"
532 end
533 w0, w1 = getwidth(FMT_TOTPTS), getwidth(FMT_LVPTS)
534 if (w0 + w1 + getwidth("0000000")) > LCD_W then
535 FMT_TOTPTS, FMT_LVPTS = "%d total", "%d lv"
536 end
537end
538
450if HAS_TOUCHSCREEN then 539if HAS_TOUCHSCREEN then
451 rb.touchscreen_set_mode(rb.TOUCHSCREEN_POINT) 540 rb.touchscreen_set_mode(rb.TOUCHSCREEN_POINT)
452end 541end
453 542
454--[[MAIN PROGRAM]]
455rb.backlight_force_on() 543rb.backlight_force_on()
456 544
457math.randomseed(os.time()) 545math.randomseed(os.time())
458 546
459local idx, highscore = 1, 0 547local idx, highscore = 1, 0
548
549local test_spd = start_round(0, 0, 0, 0) -- test speed of target
550
551if test_spd < 100 then
552 rb.splash(100, "Slow Target..")
553
554 if test_spd < 25 then
555 MAX_BALL_SPEED = MAX_BALL_SPEED + MAX_BALL_SPEED
556 elseif test_spd < 50 then
557 MAX_BALL_SPEED = MAX_BALL_SPEED + MAX_BALL_SPEED / 2
558 elseif test_spd < 100 then
559 MAX_BALL_SPEED = MAX_BALL_SPEED + MAX_BALL_SPEED / 4
560 end
561end
562
460while levels[idx] ~= nil do 563while levels[idx] ~= nil do
461 local goal, nrBalls = levels[idx][1], levels[idx][2] 564 local goal, nrBalls = levels[idx][1], levels[idx][2]
462 565
463 collectgarbage("collect") --run gc now to hopefully prevent interruption later 566 collectgarbage("collect") -- run gc now to hopefully prevent interruption later
464 567
465 display_message(rb.HZ*2, "Level %d: get %d out of %d balls", idx, goal, nrBalls) 568 disp_msg(rb.HZ * 2, "Level %d: get %d out of %d balls", idx, goal, nrBalls)
466 569
467 local exit, score, nrExpendedBalls = start_round(idx, goal, nrBalls, highscore) 570 local exit, score, nrBalls_expend = start_round(idx, goal, nrBalls, highscore)
468 571
469 if exit then 572 if exit then
470 break -- Exiting.. 573 break -- Exiting..
471 else 574 else
472 if nrExpendedBalls >= goal then 575 highscore = calc_score(highscore, idx, goal, nrBalls_expend)
473 display_message(rb.HZ*2, "You won!") 576 if nrBalls_expend >= goal then
577 disp_msg(rb.HZ * 2, "You won!")
578 levels[idx] = nil
474 idx = idx + 1 579 idx = idx + 1
475 highscore = highscore + score
476 else 580 else
477 display_message(rb.HZ*2, "You lost!") 581 disp_msg(rb.HZ * 2, "You lost %d points!", score + (idx * 100))
478 highscore = highscore - score - idx * 100
479 if highscore < 0 then break end 582 if highscore < 0 then break end
480 end 583 end
481 end 584 end
482end 585end
586
483if highscore <= 0 then 587if highscore <= 0 then
484 display_message(-1, "You lost at level %d", idx) 588 disp_msg(-1, "You lost at level %d", idx)
485elseif idx > #levels then 589elseif idx > #levels then
486 display_message(-1, "You finished the game with %d points!", highscore) 590 disp_msg(-1, "You finished the game with %d points!", highscore)
487else 591else
488 display_message(-1, "You made it till level %d with %d points!", idx, highscore) 592 disp_msg(-1, "You made it till level %d with %d points!", idx, highscore)
489end 593end
490 594
491-- Restore user backlight settings 595-- Restore user backlight settings
492rb.backlight_use_settings() 596rb.backlight_use_settings()
493