summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWilliam Wilgus <me.theuser@yahoo.com>2020-06-22 11:38:30 -0400
committerWilliam Wilgus <me.theuser@yahoo.com>2020-06-25 13:50:57 +0000
commitce61be4d59607c579594a66e59db1569c775f3da (patch)
tree7f629703cf9960e7dde0faef6b31b991841c09ff
parent4bb467242a18355295a19e61217576712edcf422 (diff)
downloadrockbox-ce61be4d59607c579594a66e59db1569c775f3da.tar.gz
rockbox-ce61be4d59607c579594a66e59db1569c775f3da.zip
lua boomshine update to use rockevents library
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
-rw-r--r--apps/plugins/boomshine.lua698
1 files changed, 435 insertions, 263 deletions
diff --git a/apps/plugins/boomshine.lua b/apps/plugins/boomshine.lua
index c7ed6dfdf1..fcdf7c9a1c 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, logic rewrite, hard levels 14 Copyright (C) 2020 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,7 +23,9 @@
23 23
24]]-- 24]]--
25require "actions" 25require "actions"
26--[[only save the actions we are using]] 26local DEBUG = false
27
28--[[only save the actions we are using]]----------------------------------------
27pla = {} 29pla = {}
28for key, val in pairs(rb.actions) do 30for key, val in pairs(rb.actions) do
29 for _, v in ipairs({"PLA_", "TOUCHSCREEN", "_NONE"}) do 31 for _, v in ipairs({"PLA_", "TOUCHSCREEN", "_NONE"}) do
@@ -33,15 +35,32 @@ for key, val in pairs(rb.actions) do
33 end 35 end
34end 36end
35rb.actions, rb.contexts = nil, nil 37rb.actions, rb.contexts = nil, nil
36-------------------------------------- 38--------------------------------------------------------------------------------
39
40
41--[[ Profiling ]]---------------------------------------------------------------
42local screen_redraw_count = 0
43local screen_redraw_duration = 0
44--------------------------------------------------------------------------------
45
37 46
47--[[ References ]]--------------------------------------------------------------
38local _LCD = rb.lcd_framebuffer() 48local _LCD = rb.lcd_framebuffer()
39local rocklib_image = getmetatable(_LCD) 49local rocklib_image = getmetatable(_LCD)
40local _ellipse = rocklib_image.ellipse 50local _ellipse = rocklib_image.ellipse--
41local BSAND = 0x8 51local irand = math.random--
42local irand = math.random 52local lcd_putsxy = rb.lcd_putsxy--
43 53local strfmt = string.format--
54local Cursor_Ref = nil
55local Ball_Ref = {}
56--------------------------------------------------------------------------------
57
58
59--[[ 'Constants' ]]-------------------------------------------------------------
60local SCORE_MULTIPLY = 10
61local BSAND = 0x8--
44local HAS_TOUCHSCREEN = rb.action_get_touchscreen_press ~= nil 62local HAS_TOUCHSCREEN = rb.action_get_touchscreen_press ~= nil
63local Empty_fn = function() end
45local LCD_H, LCD_W = rb.LCD_HEIGHT, rb.LCD_WIDTH 64local LCD_H, LCD_W = rb.LCD_HEIGHT, rb.LCD_WIDTH
46local DEFAULT_BALL_SZ = LCD_H > LCD_W and LCD_W / 30 or LCD_H / 30 65local DEFAULT_BALL_SZ = LCD_H > LCD_W and LCD_W / 30 or LCD_H / 30
47 DEFAULT_BALL_SZ = DEFAULT_BALL_SZ - bit.band(DEFAULT_BALL_SZ, 1) 66 DEFAULT_BALL_SZ = DEFAULT_BALL_SZ - bit.band(DEFAULT_BALL_SZ, 1)
@@ -61,10 +80,16 @@ end
61 80
62local FMT_EXPEND, FMT_LEVEL = "%d balls expended", "Level %d" 81local FMT_EXPEND, FMT_LEVEL = "%d balls expended", "Level %d"
63local FMT_TOTPTS, FMT_LVPTS = "%d total points", "%d level points" 82local FMT_TOTPTS, FMT_LVPTS = "%d total points", "%d level points"
83--------------------------------------------------------------------------------
84
85
86--[[ Other ]]-------------------------------------------------------------------
87local Player_Added
88local Action_Evt = pla.ACTION_NONE
64 89
65local levels = { 90local levels = {
66 -- {GOAL, TOTAL_BALLS}, 91 -- {GOAL, TOTAL_BALLS},
67 {1, 5}, 92 {1, 5},
68 {2, 10}, 93 {2, 10},
69 {4, 15}, 94 {4, 15},
70 {6, 20}, 95 {6, 20},
@@ -85,7 +110,21 @@ local levels = {
85 {4, 5}, 110 {4, 5},
86 {5, 5} 111 {5, 5}
87 } 112 }
113--------------------------------------------------------------------------------
88 114
115
116--[[ Event Callback ]]----------------------------------------------------------
117function action_event(action)
118 if action == pla.PLA_CANCEL then
119 Action_Evt = pla.PLA_EXIT
120 else
121 Action_Evt = action
122 end
123end
124--------------------------------------------------------------------------------
125
126
127--[[ Helper functions ]]--------------------------------------------------------
89local function getstringsize(str) 128local function getstringsize(str)
90 local _, w, h = rb.font_getstringsize(str, rb.FONT_UI) 129 local _, w, h = rb.font_getstringsize(str, rb.FONT_UI)
91 return w, h 130 return w, h
@@ -97,90 +136,180 @@ local function set_foreground(color)
97 end 136 end
98end 137end
99 138
100local function random_color() 139local function calc_score(total, level, goal, expended)
101 if rb.lcd_rgbpack ~= nil then -- color target 140 local bonus = 0
102 return rb.lcd_rgbpack(irand(1,255), irand(1,255), irand(1,255)) 141 local score = (expended * level) * SCORE_MULTIPLY
142 if expended < goal then
143 score = -(score + (level * SCORE_MULTIPLY))
144 elseif expended > goal then
145 bonus = (expended - goal) * (level * SCORE_MULTIPLY)
146 end
147 total = total + score + bonus
148 return total, score, bonus
149end
150
151local function wait_anykey(to_secs)
152 rb.sleep(rb.HZ / 2)
153 rb.button_clear_queue()
154 rb.button_get_w_tmo(rb.HZ * to_secs)
155end
156
157local function disp_msg(to, ...)
158 local message = strfmt(...)
159 local w, h = getstringsize(message)
160 local x, y = (LCD_W - w) / 2, (LCD_H - h) / 2
161
162 rb.lcd_clear_display()
163 set_foreground(DEFAULT_FG_CLR)
164
165 if w > LCD_W then
166 rb.lcd_puts_scroll(0, (y / h), message)
167 else
168 lcd_putsxy(x, y, message)
169 end
170
171 if to == -1 then
172 local msg = "Press button to exit"
173 w, h = getstringsize(msg)
174 x = (LCD_W - w) / 2
175 if x < 0 then
176 rb.lcd_puts_scroll(0, (y / h) + 1, msg)
177 else
178 lcd_putsxy(x, y + h, msg)
179 end
180 end
181 rb.lcd_update()
182
183 if to == -1 then
184 wait_anykey(60)
185 else
186 rb.sleep(to)
103 end 187 end
104 188
105 local color = irand(1, rb.LCD_DEPTH) 189 rb.lcd_scroll_stop() -- Stop our scrolling message
106 color = (rb.LCD_DEPTH == 2) and (3 - color) or color -- invert for 2-bit screens 190end
107 191
108 return color 192local function test_speed()
193 local test_spd, redraw, dur = start_round(0, 0, 0, 0) -- test speed of target
194 --Logic speed, screen redraw, duration
195 if DEBUG == true then
196 disp_msg(rb.HZ * 5, "Spd: %d, Redraw: %d Dur: %d Ms", test_spd, redraw, dur)
197 end
198 if test_spd < 25 or redraw < 10 then
199 rb.splash(rb.HZ, "Slow Target..")
200
201 if test_spd < 10 then
202 MAX_BALL_SPEED = MAX_BALL_SPEED + MAX_BALL_SPEED
203 elseif test_spd < 15 then
204 MAX_BALL_SPEED = MAX_BALL_SPEED + MAX_BALL_SPEED / 2
205 elseif test_spd < 25 then
206 MAX_BALL_SPEED = MAX_BALL_SPEED + MAX_BALL_SPEED / 4
207 end
208 end
109end 209end
210--------------------------------------------------------------------------------
110 211
212
213--[[ Ball Functions ]]----------------------------------------------------------
111local Ball = { 214local Ball = {
112 -- Ball defaults 215 -- Ball defaults
113 sz = DEFAULT_BALL_SZ, 216 sz = DEFAULT_BALL_SZ,
114 next_tick = 0, 217 next_tick = 0,
115 state = B_MOVE 218 state = B_MOVE,
116 } 219 }
117 220
118function Ball:new(o, level) 221function Ball:new(o, level, color)
222 local function random_color()
223 if color == nil then
224 if rb.lcd_rgbpack ~= nil then -- color target
225 return rb.lcd_rgbpack(irand(1,255), irand(1,255), irand(1,255))
226 end
227 color = irand(1, rb.LCD_DEPTH)
228 color = (rb.LCD_DEPTH == 2) and (3 - color) or color -- invert for 2-bit screens
229 end
230 return color
231 end
232
119 if o == nil then 233 if o == nil then
120 level = level or 1 234 level = level or 1
121 local maxdelay = (level <= 5) and 10 or level 235 local maxdelay = (level <= 5) and 15 or level * 2
122 o = { 236 o = {
123 x = irand(1, LCD_W - self.sz), 237 x = irand(1, LCD_W - self.sz),
124 y = irand(1, LCD_H - self.sz), 238 y = irand(1, LCD_H - self.sz),
125 color = random_color(), 239 color = random_color(),
126 xi = Ball:genSpeed(), 240 xi = self.genSpeed(), -- x increment; x + sz after explode
127 yi = Ball:genSpeed(), 241 yi = self.genSpeed(), -- y increment; y + sz after explode
128 step_delay = irand(3, maxdelay / 2), 242 step_delay = irand(3, maxdelay / 2),
129 explosion_sz = irand(2 * self.sz, 4 * self.sz), 243 explosion_sz = irand(2 * self.sz, 4 * self.sz),
130 life_ticks = irand(rb.HZ / level, rb.HZ * (maxdelay / 5)) 244 life_ticks = irand(rb.HZ / level, rb.HZ * (maxdelay / 5)),
245 draw_fn = nil,
246 step_fn = self.step -- reference to current stepping function
131 } 247 }
132 end 248 end
133 o.life_ticks = o.life_ticks + DEFAULT_BALL_SZ -- extra time for larger screens 249 local Ball = o or {} -- so we can use Ball. instead of self
250
251 -- these functions end up in a closure; faster to call, use more RAM
252 function Ball.draw()
253 _ellipse(_LCD, o.x, o.y, o.x + o.sz, o.y + o.sz, o.color, o.color, true)
254 end
255
256 function Ball.draw_exploded()
257 _ellipse(_LCD, o.x, o.y, o.xi, o.yi, o.color, nil, true)
258 end
259
260 if Ball.draw_fn == nil then
261 Ball.draw_fn = Ball.draw -- reference to current drawing function
262 end
263
134 setmetatable(o, self) 264 setmetatable(o, self)
135 self.__index = self 265 self.__index = self
136 return o 266 return o
137end 267end
138 268
269-- these functions are shared by reference, slower to call, use less RAM
139function Ball:genSpeed() 270function Ball:genSpeed()
140 local speed = irand(-MAX_BALL_SPEED, MAX_BALL_SPEED) 271 local speed = irand(-MAX_BALL_SPEED, MAX_BALL_SPEED)
141 return speed ~= 0 and speed or MAX_BALL_SPEED -- Make sure all balls move 272 return speed ~= 0 and speed or MAX_BALL_SPEED -- Make sure all balls move
142end 273end
143 274
144function Ball:draw()
145 _ellipse(_LCD, self.x, self.y,
146 self.x + self.sz, self.y + self.sz , self.color, self.color, true)
147end
148
149function Ball:step(tick) 275function Ball:step(tick)
276 local function rndspeed(cur)
277 local speed = cur + irand(-2, 2)
278 if speed < -MAX_BALL_SPEED then
279 speed = -MAX_BALL_SPEED
280 elseif speed > MAX_BALL_SPEED then
281 speed = MAX_BALL_SPEED
282 elseif speed == 0 then
283 speed = cur
284 end
285 return speed
286 end
287
150 self.next_tick = tick + self.step_delay 288 self.next_tick = tick + self.step_delay
151 self.x = self.x + self.xi 289 self.x = self.x + self.xi
152 self.y = self.y + self.yi 290 self.y = self.y + self.yi
291 local max_x = LCD_W - self.sz
292 local max_y = LCD_H - self.sz
153 293
154 if (self.x <= 0 or self.x >= (LCD_W - self.sz)) then 294 if self.x <= 0 then
155 self.xi = -self.xi 295 self.xi = -self.xi
156 self.x = self.x + self.xi 296 self.x = 1
297 self.yi = rndspeed(self.yi)
298 elseif self.x >= max_x then
299 self.xi = -self.xi
300 self.x = max_x
301 self.yi = rndspeed(self.yi)
157 end 302 end
158 303
159 if (self.y <= 0 or self.y >= (LCD_H - self.sz)) then 304 if self.y <= 0 then
160 self.yi = -self.yi 305 self.yi = -self.yi
161 self.y = self.y + self.yi 306 self.y = 1
162 end 307 self.xi = rndspeed(self.xi)
163end 308 elseif self.y >= max_y then
164 309 self.yi = -self.yi
165function Ball:checkHit(other) 310 self.y = max_y
166 if (other.xi >= self.x) and (other.yi >= self.y) and 311 self.xi = rndspeed(self.xi)
167 (self.x + self.sz >= other.x) and (self.y + self.sz >= other.y) then
168 self.state = B_EXPLODE
169 -- x/y increment no longer needed it is now impact region
170 self.xi = self.x + self.sz
171 self.yi = self.y + self.sz
172
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
175 end
176 return true
177 end 312 end
178
179 return false
180end
181
182function Ball:draw_exploded()
183 _ellipse(_LCD, self.x, self.y, self.xi, self.yi, self.color, nil, true)
184end 313end
185 314
186function Ball:step_exploded(tick) 315function Ball:step_exploded(tick)
@@ -188,20 +317,24 @@ function Ball:step_exploded(tick)
188 -- B_EXPLODE >> B_DIE >> BWAIT >> B_IMPLODE >> B_DEAD 317 -- B_EXPLODE >> B_DIE >> BWAIT >> B_IMPLODE >> B_DEAD
189 if self.state == B_EXPLODE and self.sz < self.explosion_sz then 318 if self.state == B_EXPLODE and self.sz < self.explosion_sz then
190 self.sz = self.sz + 2 319 self.sz = self.sz + 2
191 self.x = self.x - 1 -- We do this because we want to stay centered 320 self.x = self.x - 1 -- stay centered
192 self.y = self.y - 1 321 self.y = self.y - 1
193 elseif self.state == B_DIE then 322 elseif self.state == B_DIE then
194 self.state = B_WAIT 323 self.state = B_WAIT
195 self.next_tick = tick + self.life_ticks 324 self.next_tick = tick + self.life_ticks
196 return 325 return
197 elseif self.state == B_IMPLODE and self.sz > 0 then 326 elseif self.state == B_IMPLODE then
198 self.sz = self.sz - 2 327 if self.sz > 0 then
199 self.x = self.x + 1 -- We do this because we want to stay centered 328 self.sz = self.sz - 2
200 self.y = self.y + 1 329 self.x = self.x + 1 -- stay centered
201 elseif self.state <= B_IMPLODE then 330 self.y = self.y + 1
202 self.state = B_DEAD 331 else
203 return 332 self.state = B_DEAD
204 elseif self.next_tick < tick then 333 self.draw_fn = Empty_fn
334 self.step_fn = Empty_fn
335 return B_DEAD
336 end
337 elseif self.next_tick < tick then -- decay to next lower state
205 self.state = self.state - 1 338 self.state = self.state - 1
206 return 339 return
207 end 340 end
@@ -211,59 +344,90 @@ function Ball:step_exploded(tick)
211 self.yi = self.y + self.sz 344 self.yi = self.y + self.sz
212end 345end
213 346
347function Ball:checkHit(other)
348 local x, y = self.x, self.y
349 if (other.xi >= x) and (other.yi >= y) then
350 local sz = self.sz
351 local xi, yi = x + sz, y + sz
352 if (xi >= other.x) and (yi >= other.y) then
353 -- update impact region
354 self.xi = xi
355 self.yi = yi
356 -- change to exploded state
357 self.state = B_EXPLODE
358 self.draw_fn = self.draw_exploded
359 self.step_fn = self.step_exploded
360
361 if other.state < B_EXPLODE then -- add duration to the ball that got hit
362 other.next_tick = other.next_tick + self.life_ticks
363 end
364 return true
365 end
366 end
367
368 return false
369end
370--------------------------------------------------------------------------------
371
372
373--[[ Cursor Functions ]]--------------------------------------------------------
214local Cursor = { 374local Cursor = {
215 sz = (DEFAULT_BALL_SZ * 2), 375 sz = (DEFAULT_BALL_SZ * 2),
216 x = (LCD_W / 2), 376 x = (LCD_W / 2),
217 y = (LCD_H / 2), 377 y = (LCD_H / 2),
218 color = DEFAULT_FG_CLR 378 color = DEFAULT_FG_CLR
379 --image = nil
219 } 380 }
220 381
221function Cursor:new() 382function Cursor:new()
222 if rb.LCD_DEPTH == 2 then -- invert for 2 - bit screens 383 if rb.LCD_DEPTH == 2 then -- invert for 2 - bit screens
223 self.color = 3 - DEFAULT_FG_CLR 384 self.color = 3 - DEFAULT_FG_CLR
224 end 385 end
225 self:create_image(DEFAULT_BALL_SZ * 2) 386 if not HAS_TOUCHSCREEN and not self.image then
226 return self 387 local function create_image(sz)
227end 388 sz = sz + 1
228 389 local img = rb.new_image(sz, sz)
229function Cursor:create_image(sz) 390 local sz2 = (sz / 2) + 1
230 if not HAS_TOUCHSCREEN then 391 local sz4 = (sz / 4)
231 sz = sz + 1 392
232 local img = rb.new_image(sz, sz) 393 img:clear(0)
233 local sz2 = (sz / 2) + 1 394 img:line(1, 1, sz4 + 1, 1, 1)
234 local sz4 = (sz / 4) 395 img:line(1, 1, 1, sz4 + 1, 1)
235 396
236 img:clear(0) 397 img:line(1, sz, sz4 + 1, sz, 1)
237 img:line(1, 1, sz4 + 1, 1, 1) 398 img:line(1, sz, 1, sz - sz4, 1)
238 img:line(1, 1, 1, sz4 + 1, 1) 399
239 400 img:line(sz, sz, sz - sz4, sz, 1)
240 img:line(1, sz, sz4 + 1, sz, 1) 401 img:line(sz, sz, sz, sz - sz4, 1)
241 img:line(1, sz, 1, sz - sz4, 1) 402
242 403 img:line(sz, 1, sz - sz4, 1, 1)
243 img:line(sz, sz, sz - sz4, sz, 1) 404 img:line(sz, 1, sz, sz4 + 1, 1)
244 img:line(sz, sz, sz, sz - sz4, 1) 405
245 406 -- crosshairs
246 img:line(sz, 1, sz - sz4, 1, 1) 407 img:line(sz2 - sz4, sz2, sz2 + sz4, sz2, 1)
247 img:line(sz, 1, sz, sz4 + 1, 1) 408 img:line(sz2, sz2 - sz4, sz2, sz2 + sz4, 1)
248 409 return img
249 -- crosshairs 410 end
250 img:line(sz2 - sz4, sz2, sz2 + sz4, sz2, 1) 411 self.image = create_image(DEFAULT_BALL_SZ * 2)
251 img:line(sz2, sz2 - sz4, sz2, sz2 + sz4, 1)
252 self.image = img
253 end 412 end
254end
255 413
256local function clamp_roll(iVal, iMin, iMax) 414 function Cursor.draw()
257 if iVal < iMin then 415 rocklib_image.copy(_LCD, self.image, self.x, self.y, _NIL, _NIL,
258 iVal = iMax 416 _NIL, _NIL, true, BSAND, self.color)
259 elseif iVal > iMax then
260 iVal = iMin
261 end 417 end
262 418
263 return iVal 419 return self
264end 420end
265 421
266function Cursor:do_action(action) 422function Cursor:do_action(action)
423 local function clamp_roll(iVal, iMin, iMax)
424 if iVal < iMin then
425 iVal = iMax
426 elseif iVal > iMax then
427 iVal = iMin
428 end
429 return iVal
430 end
267 local xi, yi = 0, 0 431 local xi, yi = 0, 0
268 432
269 if HAS_TOUCHSCREEN and action == pla.ACTION_TOUCHSCREEN then 433 if HAS_TOUCHSCREEN and action == pla.ACTION_TOUCHSCREEN then
@@ -279,188 +443,225 @@ function Cursor:do_action(action)
279 yi = -self.sz 443 yi = -self.sz
280 elseif (action == pla.PLA_DOWN or action == pla.PLA_DOWN_REPEAT) then 444 elseif (action == pla.PLA_DOWN or action == pla.PLA_DOWN_REPEAT) then
281 yi = self.sz 445 yi = self.sz
446 else
447 Action_Evt = pla.ACTION_NONE
448 return false
282 end 449 end
283 450
284 self.x = clamp_roll(self.x + xi, 1, LCD_W - self.sz) 451 self.x = clamp_roll(self.x + xi, 1, LCD_W - self.sz)
285 self.y = clamp_roll(self.y + yi, 1, LCD_H - self.sz) 452 self.y = clamp_roll(self.y + yi, 1, LCD_H - self.sz)
286 453 Action_Evt = pla.ACTION_NONE
287 return false 454 return false
288end 455end
456--------------------------------------------------------------------------------
289 457
290function Cursor:draw()
291 rocklib_image.copy(_LCD, self.image, self.x, self.y, _NIL, _NIL,
292 _NIL, _NIL, true, BSAND, self.color)
293end
294 458
295local function calc_score(total, level, goal, expended) 459--[[ Game function ]]-----------------------------------------------------------
296 local score = (expended * level) * 100 460function start_round(level, goal, nrBalls, total)
297 if expended < goal then 461 Player_Added = false
298 score = -(score + (level * 100)) 462 --[[ References ]]--
299 end 463 local current_tick = rb.current_tick
300 total = total + score 464 local lcd_update = rb.lcd_update
301 return total 465 local lcd_clear_display = rb.lcd_clear_display
302end
303
304local function draw_pos_str(bottom, right, str)
305 local w, h = getstringsize(str)
306 local x = (right > 0) and ((LCD_W - w) * right - 1) or 1
307 local y = (bottom > 0) and ((LCD_H - h) * bottom - 1) or 1
308 rb.lcd_putsxy(x, y, str)
309end
310
311local function wait_anykey(to_secs)
312 rb.sleep(rb.HZ / 2)
313 rb.button_clear_queue()
314 rb.button_get_w_tmo(rb.HZ * to_secs)
315end
316 466
317local function start_round(level, goal, nrBalls, total) 467 local test_spd = false
318 local player_added, score = false, 0 468 local is_exit = false;
469 local score = 0
319 local last_expend, nrBalls_expend = 0, 0 470 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 = {} 471 local Balls = {}
323 local str_level = string.format(FMT_LEVEL, level) -- static 472 local balls_exploded = 1 -- to keep looping when player_added == false
324 local str_totpts = string.format(FMT_TOTPTS, total) -- static 473
474 local tick_next = 0
475 local cursor = nil
476 local draw_cursor = Empty_fn
477 local refresh = rb.HZ/20
478
479 local function level_stats(level, total)
480 return strfmt(FMT_LEVEL, level), strfmt(FMT_TOTPTS, total)
481 end
482
483 local str_level, str_totpts = level_stats(level, total) -- static for lvl
325 local str_expend, str_lvlpts 484 local str_expend, str_lvlpts
326 local tick, cursor
327 local test_spd = false
328 485
329 local function update_stats() 486 local function update_stats()
330 -- we only create a new string when a hit is detected 487 -- we only create a new string when a hit is detected
331 str_expend = string.format(FMT_EXPEND, nrBalls_expend) 488 str_expend = strfmt(FMT_EXPEND, nrBalls_expend)
332 str_lvlpts = string.format(FMT_LVPTS, score) 489 str_lvlpts = strfmt(FMT_LVPTS, score)
333 end 490 end
334 491
335 local function draw_stats() 492 function draw_stats()
493 local function draw_pos_str(bottom, right, str)
494 local w, h = getstringsize(str)
495 local x = (right > 0) and ((LCD_W - w) * right - 1) or 1
496 local y = (bottom > 0) and ((LCD_H - h) * bottom - 1) or 1
497 lcd_putsxy(x, y, str)
498 end
499 -- pos(B, R) [B/R]=0 => [y/x]=1, [B/R]=1 => [y/x]=lcd[H/W]-string[H/W]
336 draw_pos_str(0, 0, str_expend) 500 draw_pos_str(0, 0, str_expend)
337 draw_pos_str(0, 1, str_level) 501 draw_pos_str(0, 1, str_level)
338 draw_pos_str(1, 1, str_lvlpts) 502 draw_pos_str(1, 1, str_lvlpts)
339 draw_pos_str(1, 0, str_totpts) 503 draw_pos_str(1, 0, str_totpts)
340 end 504 end
341 505
506 -- all Balls share same function, will by changed by player_add()
507 local checkhit_fn = Empty_fn
508
509 local function checkhit(Ball)
510 if Ball.state == B_MOVE then
511 for i = #Balls, 1, -1 do
512 if Balls[i].state < B_MOVE and
513 Ball:checkHit(Balls[i]) then -- exploded?
514 balls_exploded = balls_exploded + 1
515 nrBalls_expend = nrBalls_expend + 1
516 break
517 end
518 end
519 end
520 end
521
342 local function add_player() 522 local function add_player()
343 -- cursor becomes exploded ball 523 -- cursor becomes exploded ball
344 local player = Ball:new({ 524 local player = Ball:new({
345 x = cursor.x, 525 x = cursor.x - cursor.sz,
346 y = cursor.y, 526 y = cursor.y - cursor.sz,
347 color = cursor.color, 527 color = cursor.color,
348 step_delay = 3, 528 step_delay = 3,
349 explosion_sz = (3 * DEFAULT_BALL_SZ), 529 explosion_sz = (3 * DEFAULT_BALL_SZ),
350 life_ticks = (test_time == true) and (100) or 530 life_ticks = (test_spd) and (rb.HZ) or
351 irand(rb.HZ * 2, rb.HZ * DEFAULT_BALL_SZ), 531 irand(rb.HZ * 2, rb.HZ * DEFAULT_BALL_SZ),
352 sz = 10, 532 sz = 10,
353 state = B_EXPLODE 533 state = B_EXPLODE
354 }) 534 })
355 -- set x/y impact region 535 -- set x/y impact region -->[]
356 player.xi = player.x + player.sz 536 player.xi = player.x + player.sz
357 player.yi = player.y + player.sz 537 player.yi = player.y + player.sz
538 player.draw_fn = player.draw_exploded
539 player.step_fn = player.step_exploded
358 540
359 table.insert(Balls, player) 541 table.insert(Balls, player)
360 balls_exploded = 1 542 balls_exploded = 1
361 player_added = true 543 Player_Added = true
362 cursor = nil 544 cursor = nil
545 draw_cursor = Empty_fn
546 -- only need collision detection after player add
547 checkhit_fn = checkhit
363 end 548 end
364 549
365 if level < 1 then 550 local function speedtest()
366 -- check speed of target 551 -- check speed of target
367 set_foreground(DEFAULT_BG_CLR) --hide text during test 552 set_foreground(DEFAULT_BG_CLR) --hide text during test
368 local bkcolor = (rb.LCD_DEPTH == 2) and (3) or 0
369 level = 1 553 level = 1
370 nrBalls = 20 554 nrBalls = 100
371 cursor = { x = LCD_W * 2, y = LCD_H * 2, color = bkcolor} 555 cursor = {x = LCD_W * 2, y = LCD_H * 2, color = 0, sz = 1}
372 table.insert(Balls, Ball:new({ 556 table.insert(Balls, Ball:new({
373 x = 1, y = 1, xi = 1, yi = 1, 557 x = 1, y = 1, xi = 1, yi = 1,
374 color = bkcolor, step_delay = 1, 558 color = 0, step_delay = 0,
375 explosion_sz = 0, life_ticks = 0, 559 explosion_sz = 0, life_ticks = 0,
376 step = function() test_spd = test_spd + 1 end 560 draw_fn = Empty_fn,
561 step_fn = function() test_spd = test_spd + 1 end
377 }) 562 })
378 ) 563 )
379 add_player()
380 test_spd = 0 564 test_spd = 0
565 add_player()
566 end
567
568 local function screen_redraw()
569 -- (draw_fn changes dynamically at runtime)
570 for i = 1, #Balls do
571 Balls[i].draw_fn()
572 end
573
574 draw_stats()
575 draw_cursor()
576
577 lcd_update()
578 lcd_clear_display()
579 end
580
581 local function game_loop()
582 local tick = current_tick()
583 for _, Ball in ipairs(Balls) do
584 if tick > Ball.next_tick then
585 -- (step_fn changes dynamically at runtime)
586 if Ball:step_fn(tick) == B_DEAD then
587 balls_exploded = balls_exploded - 1
588 else
589 checkhit_fn(Ball)
590 end
591 end
592 end
593 return tick
594 end
595
596 if level < 1 then
597 speedtest()
598 local bkcolor = (rb.LCD_DEPTH == 2) and (3) or 0
599 -- Initialize the balls
600 if DEBUG then bkcolor = nil end
601 for i=1, nrBalls do
602 table.insert(Balls, Ball:new(nil, level, bkcolor))
603 end
381 else 604 else
605 speedtest = nil
382 set_foreground(DEFAULT_FG_CLR) -- color for text 606 set_foreground(DEFAULT_FG_CLR) -- color for text
383 cursor = Cursor:new() 607 cursor = Cursor:new()
384 end 608 if not HAS_TOUCHSCREEN then
385 609 draw_cursor = cursor.draw
386 -- Initialize the balls 610 end
387 for i=1, nrBalls do 611 -- Initialize the balls
388 table.insert(Balls, Ball:new(nil, level)) 612 for i=1, nrBalls do
613 table.insert(Balls, Ball:new(nil, level))
614 end
389 end 615 end
390 616
391 -- Make sure there are no unwanted touchscreen presses 617 -- Make sure there are no unwanted touchscreen presses
392 rb.button_clear_queue() 618 rb.button_clear_queue()
393 619
620 Action_Evt = pla.ACTION_NONE
621
394 update_stats() -- load status strings 622 update_stats() -- load status strings
395 623
396 -- Check if the round is over 624 if rb.cpu_boost then rb.cpu_boost(true) end
397 while balls_exploded > 0 do 625 collectgarbage("collect") -- run gc now to hopefully prevent interruption later
398 tick = rb.current_tick()
399 626
400 if action ~= pla.ACTION_NONE and (action == pla.PLA_EXIT or 627 local duration = current_tick()
401 action == pla.PLA_CANCEL) then 628 -- Game loop >> Check if the round is over
402 action = pla.PLA_EXIT 629 while balls_exploded > 0 do
630 if Action_Evt == pla.PLA_EXIT then
631 is_exit = true
403 break 632 break
404 end 633 end
405 634
406 rb.lcd_clear_display() 635 if game_loop() > tick_next then
407 636 tick_next = current_tick() + refresh
408 if not player_added then 637 if not Player_Added then
409 if action ~= pla.ACTION_NONE and cursor:do_action(action) then 638 if Action_Evt ~= pla.ACTION_NONE and cursor:do_action(Action_Evt) then
410 add_player() 639 add_player()
411 elseif not HAS_TOUCHSCREEN then
412 cursor:draw()
413 end
414 end
415
416 for _, Ball in ipairs(Balls) do
417 if Ball.state == B_MOVE then
418 if tick > Ball.next_tick then
419 Ball:step(tick)
420 for i = #Balls, 1, -1 do
421 if Balls[i].state < B_MOVE and
422 Ball:checkHit(Balls[i]) then -- exploded?
423 balls_exploded = balls_exploded + 1
424 nrBalls_expend = nrBalls_expend + 1
425 break
426 end
427 end
428 end
429 -- check if state changed draw ball if still moving
430 if Ball.state == B_MOVE then
431 Ball:draw()
432 end
433 elseif Ball.state ~= B_DEAD then
434 if tick > Ball.next_tick then
435 Ball:step_exploded(tick)
436 end
437 if Ball.state ~= B_DEAD then
438 Ball:draw_exploded()
439 else
440 balls_exploded = balls_exploded - 1
441 end 640 end
442 end 641 end
443 end
444 642
445 draw_stats() -- stats redrawn every frame 643 if nrBalls_expend ~= last_expend then -- hit detected?
446 rb.lcd_update() -- Push framebuffer to the LCD 644 last_expend = nrBalls_expend
645 score = (nrBalls_expend * level) * SCORE_MULTIPLY
646 update_stats() -- only update stats when not current
647 if nrBalls_expend == nrBalls then break end -- round is over?
648 end
447 649
448 if nrBalls_expend ~= last_expend then -- hit detected? 650 screen_redraw_count = screen_redraw_count + 1
449 last_expend = nrBalls_expend 651 screen_redraw()
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?
453 end 652 end
454
455 rb.yield() -- yield to other tasks 653 rb.yield() -- yield to other tasks
456
457 action = rb.get_plugin_action(0) -- Check for actions
458 end 654 end
459 655
460 if test_spd and test_spd > 0 then return test_spd end 656 screen_redraw_duration = screen_redraw_duration + (rb.current_tick() - duration)
657 if rb.cpu_boost then rb.cpu_boost(false) end
658
659 if test_spd and test_spd > 0 then
660 return test_spd, screen_redraw_count, screen_redraw_duration *10 --ms
661 end
461 662
462 -- splash the final stats for a moment at end 663 -- splash the final stats for a moment at end
463 rb.lcd_clear_display() 664 lcd_clear_display()
464 for _, Ball in ipairs(Balls) do 665 for _, Ball in ipairs(Balls) do
465 -- move balls back to their initial exploded positions 666 -- move balls back to their initial exploded positions
466 if Ball.state == B_DEAD then 667 if Ball.state == B_DEAD then
@@ -471,56 +672,22 @@ local function start_round(level, goal, nrBalls, total)
471 Ball:draw() 672 Ball:draw()
472 end 673 end
473 674
474 if DEFAULT_BALL_SZ > 3 then 675 if DEFAULT_BALL_SZ > 3 then -- dither
475 _LCD:clear(nil, nil, nil, nil, nil, nil, 2, 2) 676 _LCD:clear(nil, nil, nil, nil, nil, nil, 2, 2)
476 end 677 end
477 678
478 total = calc_score(total, level, goal, nrBalls_expend) 679 total = calc_score(total, level, goal, nrBalls_expend)
479 str_totpts = string.format(FMT_TOTPTS, total) 680 str_level, str_totpts = level_stats(level, total)
480 draw_stats() 681 draw_stats()
481 rb.lcd_update() 682 lcd_update()
482 wait_anykey(2) 683 wait_anykey(2)
483 684
484 return action == pla.PLA_EXIT, score, nrBalls_expend 685 return is_exit, score, nrBalls_expend
485end 686end
687--------------------------------------------------------------------------------
486 688
487-- Helper function to display a message
488local function disp_msg(to, ...)
489 local message = string.format(...)
490 local w, h = getstringsize(message)
491 local x, y = (LCD_W - w) / 2, (LCD_H - h) / 2
492
493 rb.lcd_clear_display()
494 set_foreground(DEFAULT_FG_CLR)
495
496 if w > LCD_W then
497 rb.lcd_puts_scroll(0, (y / h), message)
498 else
499 rb.lcd_putsxy(x, y, message)
500 end
501 689
502 if to == -1 then 690--[[MAIN PROGRAM]]--------------------------------------------------------------
503 local msg = "Press button to exit"
504 w, h = getstringsize(msg)
505 x = (LCD_W - w) / 2
506 if x < 0 then
507 rb.lcd_puts_scroll(0, (y / h) + 1, msg)
508 else
509 rb.lcd_putsxy(x, y + h, msg)
510 end
511 end
512 rb.lcd_update()
513
514 if to == -1 then
515 wait_anykey(60)
516 else
517 rb.sleep(to)
518 end
519
520 rb.lcd_scroll_stop() -- Stop our scrolling message
521end
522
523--[[MAIN PROGRAM]]
524do -- attempt to get stats to fit on screen 691do -- attempt to get stats to fit on screen
525 local function getwidth(str) 692 local function getwidth(str)
526 local w, _ = getstringsize(str) 693 local w, _ = getstringsize(str)
@@ -530,7 +697,7 @@ do -- attempt to get stats to fit on screen
530 if (w0 + w1) > LCD_W then 697 if (w0 + w1) > LCD_W then
531 FMT_EXPEND, FMT_LEVEL = "%d balls", "Lv %d" 698 FMT_EXPEND, FMT_LEVEL = "%d balls", "Lv %d"
532 end 699 end
533 w0, w1 = getwidth(FMT_TOTPTS), getwidth(FMT_LVPTS) 700 w0, w1 = getwidth(FMT_TOTPTS), getwidth(FMT_LVPTS)
534 if (w0 + w1 + getwidth("0000000")) > LCD_W then 701 if (w0 + w1 + getwidth("0000000")) > LCD_W then
535 FMT_TOTPTS, FMT_LVPTS = "%d total", "%d lv" 702 FMT_TOTPTS, FMT_LVPTS = "%d total", "%d lv"
536 end 703 end
@@ -542,23 +709,16 @@ end
542 709
543rb.backlight_force_on() 710rb.backlight_force_on()
544 711
545math.randomseed(os.time()) 712local eva = rockev.register("action", action_event, rb.HZ / 10)
546
547local idx, highscore = 1, 0
548 713
549local test_spd = start_round(0, 0, 0, 0) -- test speed of target 714math.randomseed(os.time() or 1)
550 715
551if test_spd < 100 then 716local idx, highscore = 1, 0
552 rb.splash(100, "Slow Target..")
553 717
554 if test_spd < 25 then 718disp_msg(rb.HZ, "BoomShine")
555 MAX_BALL_SPEED = MAX_BALL_SPEED + MAX_BALL_SPEED 719test_speed()
556 elseif test_spd < 50 then 720rb.sleep(rb.HZ * 2)
557 MAX_BALL_SPEED = MAX_BALL_SPEED + MAX_BALL_SPEED / 2 721test_speed = nil
558 elseif test_spd < 100 then
559 MAX_BALL_SPEED = MAX_BALL_SPEED + MAX_BALL_SPEED / 4
560 end
561end
562 722
563while levels[idx] ~= nil do 723while levels[idx] ~= nil do
564 local goal, nrBalls = levels[idx][1], levels[idx][2] 724 local goal, nrBalls = levels[idx][1], levels[idx][2]
@@ -567,18 +727,26 @@ while levels[idx] ~= nil do
567 727
568 disp_msg(rb.HZ * 2, "Level %d: get %d out of %d balls", idx, goal, nrBalls) 728 disp_msg(rb.HZ * 2, "Level %d: get %d out of %d balls", idx, goal, nrBalls)
569 729
570 local exit, score, nrBalls_expend = start_round(idx, goal, nrBalls, highscore) 730 local is_exit, score, nrBalls_expend = start_round(idx, goal, nrBalls, highscore)
731 if DEBUG == true then
732 local fps = screen_redraw_count * 100 / screen_redraw_duration
733 disp_msg(rb.HZ * 5, "Redraw: %d fps", fps)
734 end
571 735
572 if exit then 736 if is_exit then
573 break -- Exiting.. 737 break -- Exiting..
574 else 738 else
575 highscore = calc_score(highscore, idx, goal, nrBalls_expend) 739 highscore, score, bonus = calc_score(highscore, idx, goal, nrBalls_expend)
576 if nrBalls_expend >= goal then 740 if nrBalls_expend >= goal then
577 disp_msg(rb.HZ * 2, "You won!") 741 if bonus == 0 then
742 disp_msg(rb.HZ * 2, "You won!")
743 else
744 disp_msg(rb.HZ * 2, "You won BONUS!")
745 end
578 levels[idx] = nil 746 levels[idx] = nil
579 idx = idx + 1 747 idx = idx + 1
580 else 748 else
581 disp_msg(rb.HZ * 2, "You lost %d points!", score + (idx * 100)) 749 disp_msg(rb.HZ * 2, "You lost %d points!", -score)
582 if highscore < 0 then break end 750 if highscore < 0 then break end
583 end 751 end
584 end 752 end
@@ -594,3 +762,7 @@ end
594 762
595-- Restore user backlight settings 763-- Restore user backlight settings
596rb.backlight_use_settings() 764rb.backlight_use_settings()
765if rb.cpu_boost then rb.cpu_boost(false) end
766
767os.exit()
768--------------------------------------------------------------------------------