diff options
author | William Wilgus <me.theuser@yahoo.com> | 2018-11-25 15:24:19 -0500 |
---|---|---|
committer | William Wilgus <me.theuser@yahoo.com> | 2019-01-24 05:39:38 +0100 |
commit | cdd470832628b4d8b1db86dc597fec89a027fc37 (patch) | |
tree | 5fd4b49a5955e663bc926fd953dc715d9a16c2c0 | |
parent | f3b532a92a101d150f5214dfb6c31295fc7f54c5 (diff) | |
download | rockbox-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.lua | 669 |
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 | ]]-- |
25 | require "actions" | 25 | require "actions" |
26 | -- [[only save the actions we are using]] | 26 | --[[only save the actions we are using]] |
27 | actions_pla = {} | 27 | pla = {} |
28 | for key, val in pairs(rb.actions) do | 28 | for 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 |
35 | end | 34 | end |
36 | rb.actions = nil | 35 | rb.actions, rb.contexts = nil, nil |
37 | rb.contexts = nil | 36 | -------------------------------------- |
38 | ------------------------------------- | ||
39 | 37 | ||
40 | local _LCD = rb.lcd_framebuffer() | 38 | local _LCD = rb.lcd_framebuffer() |
41 | local BSAND = 0x8 | 39 | local rocklib_image = getmetatable(_LCD) |
42 | local rocklib_image = getmetatable(rb.lcd_framebuffer()) | ||
43 | local _ellipse = rocklib_image.ellipse | 40 | local _ellipse = rocklib_image.ellipse |
41 | local BSAND = 0x8 | ||
42 | local irand = math.random | ||
44 | 43 | ||
45 | local CYCLETIME = rb.HZ / 50 | ||
46 | local HAS_TOUCHSCREEN = rb.action_get_touchscreen_press ~= nil | 44 | local HAS_TOUCHSCREEN = rb.action_get_touchscreen_press ~= nil |
47 | local DEFAULT_BALL_SIZE = rb.LCD_HEIGHT > rb.LCD_WIDTH and rb.LCD_WIDTH / 30 | 45 | local LCD_H, LCD_W = rb.LCD_HEIGHT, rb.LCD_WIDTH |
48 | or rb.LCD_HEIGHT / 30 | 46 | local DEFAULT_BALL_SZ = LCD_H > LCD_W and LCD_W / 30 or LCD_H / 30 |
49 | local MAX_BALL_SPEED = DEFAULT_BALL_SIZE / 2 | 47 | DEFAULT_BALL_SZ = DEFAULT_BALL_SZ - bit.band(DEFAULT_BALL_SZ, 1) |
50 | local DEC_BALL_SPEED = DEFAULT_BALL_SIZE / 8 | 48 | local MAX_BALL_SPEED = DEFAULT_BALL_SZ / 2 + 1 |
51 | local DEFAULT_FOREGROUND_COLOR = rb.lcd_get_foreground ~= nil | 49 | -- Ball states |
52 | and rb.lcd_get_foreground() | 50 | local B_DEAD, B_MOVE, B_EXPLODE, B_DIE, B_WAIT, B_IMPLODE = 5, 4, 3, 2, 1, 0 |
53 | or 0 | 51 | |
52 | local DEFAULT_FG_CLR = 1 | ||
53 | local DEFAULT_BG_CLR = 0 | ||
54 | if rb.lcd_get_foreground ~= nil then | ||
55 | DEFAULT_FG_CLR = rb.lcd_get_foreground() | ||
56 | DEFAULT_BG_CLR = rb.lcd_get_background() | ||
57 | elseif rb.LCD_DEFAULT_FG ~= nil then | ||
58 | DEFAULT_FG_CLR = rb.LCD_DEFAULT_FG | ||
59 | DEFAULT_BG_CLR = rb.LCD_DEFAULT_BG | ||
60 | end | ||
61 | |||
62 | local FMT_EXPEND, FMT_LEVEL = "%d balls expended", "Level %d" | ||
63 | local FMT_TOTPTS, FMT_LVPTS = "%d total points", "%d level points" | ||
54 | 64 | ||
55 | local levels = { | 65 | local 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 | |||
79 | local Ball = { | ||
80 | size = DEFAULT_BALL_SIZE, | ||
81 | exploded = false, | ||
82 | implosion = false | ||
83 | } | ||
84 | |||
85 | local 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) | 89 | local 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 | ||
92 | end | ||
95 | 93 | ||
96 | cursor:line(1, size, sz4, size, 1) | 94 | local 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 | ||
98 | end | ||
98 | 99 | ||
99 | cursor:line(size, size, size - sz4, size, 1) | 100 | local 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 | ||
110 | end | 109 | end |
111 | 110 | ||
111 | local Ball = { | ||
112 | -- Ball defaults | ||
113 | sz = DEFAULT_BALL_SZ, | ||
114 | next_tick = 0, | ||
115 | state = B_MOVE | ||
116 | } | ||
117 | |||
112 | function Ball:new(o, level) | 118 | function 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 |
129 | end | 137 | end |
130 | 138 | ||
131 | function Ball:generateSpeed(level) | 139 | function 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 | ||
140 | end | ||
141 | |||
142 | function 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) | ||
150 | end | 142 | end |
151 | 143 | ||
152 | function Ball:draw() | 144 | function 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) |
160 | end | 147 | end |
161 | 148 | ||
162 | function Ball:step_exploded() | 149 | function 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 | ||
172 | end | ||
173 | 153 | ||
174 | function 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 |
185 | end | 163 | end |
186 | 164 | ||
187 | function Ball:checkHit(other) | 165 | function 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 |
201 | end | 180 | end |
202 | 181 | ||
182 | function Ball:draw_exploded() | ||
183 | _ellipse(_LCD, self.x, self.y, self.xi, self.yi, self.color, nil, true) | ||
184 | end | ||
185 | |||
186 | function 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 | ||
212 | end | ||
213 | |||
203 | local Cursor = { | 214 | local 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 | ||
210 | function Cursor:new() | 221 | function 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 |
212 | end | 227 | end |
213 | 228 | ||
229 | function 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 | ||
254 | end | ||
255 | |||
256 | local 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 | ||
264 | end | ||
265 | |||
214 | function Cursor:do_action(action) | 266 | function 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 |
243 | end | 288 | end |
244 | 289 | ||
245 | function Cursor:draw() | 290 | function Cursor:draw() |
291 | rocklib_image.copy(_LCD, self.image, self.x, self.y, _NIL, _NIL, | ||
292 | _NIL, _NIL, true, BSAND, self.color) | ||
293 | end | ||
246 | 294 | ||
247 | rocklib_image.copy(_LCD, self.image, self.x - self.size/2, self.y - self.size/2, | 295 | local 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 | ||
249 | end | 302 | end |
250 | 303 | ||
251 | function draw_positioned_string(bottom, right, str) | 304 | local 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) |
256 | end | 309 | end |
257 | 310 | ||
258 | function set_foreground(color) | 311 | local 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) |
262 | end | 315 | end |
263 | 316 | ||
264 | function random_color() | 317 | local 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() |
270 | end | 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 | ||
272 | function 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 |
411 | end | 485 | end |
412 | 486 | ||
413 | -- Helper function to display a message | 487 | -- Helper function to display a message |
414 | function display_message(to, ...) | 488 | local 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 |
448 | end | 521 | end |
449 | 522 | ||
523 | --[[MAIN PROGRAM]] | ||
524 | do -- 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 | ||
537 | end | ||
538 | |||
450 | if HAS_TOUCHSCREEN then | 539 | if HAS_TOUCHSCREEN then |
451 | rb.touchscreen_set_mode(rb.TOUCHSCREEN_POINT) | 540 | rb.touchscreen_set_mode(rb.TOUCHSCREEN_POINT) |
452 | end | 541 | end |
453 | 542 | ||
454 | --[[MAIN PROGRAM]] | ||
455 | rb.backlight_force_on() | 543 | rb.backlight_force_on() |
456 | 544 | ||
457 | math.randomseed(os.time()) | 545 | math.randomseed(os.time()) |
458 | 546 | ||
459 | local idx, highscore = 1, 0 | 547 | local idx, highscore = 1, 0 |
548 | |||
549 | local test_spd = start_round(0, 0, 0, 0) -- test speed of target | ||
550 | |||
551 | if 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 | ||
561 | end | ||
562 | |||
460 | while levels[idx] ~= nil do | 563 | while 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 |
482 | end | 585 | end |
586 | |||
483 | if highscore <= 0 then | 587 | if highscore <= 0 then |
484 | display_message(-1, "You lost at level %d", idx) | 588 | disp_msg(-1, "You lost at level %d", idx) |
485 | elseif idx > #levels then | 589 | elseif 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) |
487 | else | 591 | else |
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) |
489 | end | 593 | end |
490 | 594 | ||
491 | -- Restore user backlight settings | 595 | -- Restore user backlight settings |
492 | rb.backlight_use_settings() | 596 | rb.backlight_use_settings() |
493 | |||