summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWilliam Wilgus <me.theuser@yahoo.com>2018-11-25 15:24:19 -0500
committerWilliam Wilgus <me.theuser@yahoo.com>2019-01-24 05:39:38 +0100
commitcdd470832628b4d8b1db86dc597fec89a027fc37 (patch)
tree5fd4b49a5955e663bc926fd953dc715d9a16c2c0
parentf3b532a92a101d150f5214dfb6c31295fc7f54c5 (diff)
downloadrockbox-cdd470832628b4d8b1db86dc597fec89a027fc37.tar.gz
rockbox-cdd470832628b4d8b1db86dc597fec89a027fc37.zip
lua rewrite boomshine
rewrite the logic of boomshine to make it more difficult especially small screens rewrite of the game engine to make memory usage more consistent throught the level and decrease overall memory usage this also removes most of the stuttering in play Add test of device speed this should improve very slow devices but the threshold might still need tweaked Change-Id: I49f5269c69405f6b6060ab18f52c96e3f69ebb14
-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