summaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
Diffstat (limited to 'apps')
-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--------------------------------------------------------------------------------