3 Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
10 Port of Chain Reaction (which is based on Boomshine) to Rockbox in Lua.
11 See http://www.yvoschaap.com/chainrxn/ and http://www.k2xl.com/games/boomshine/
13 Copyright (C) 2009 by Maurus Cuelenaere
15 This program is free software; you can redistribute it and/or
16 modify it under the terms of the GNU General Public License
17 as published by the Free Software Foundation; either version 2
18 of the License, or (at your option) any later version.
20 This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
21 KIND, either express or implied.
27 local CYCLETIME
= rb
.HZ
/ 50
28 local HAS_TOUCHSCREEN
= rb
.action_get_touchscreen_press
~= nil
29 local DEFAULT_BALL_SIZE
= rb
.LCD_HEIGHT
> rb
.LCD_WIDTH
and rb
.LCD_WIDTH
/ 30
31 local MAX_BALL_SPEED
= DEFAULT_BALL_SIZE
/ 2
32 local DEFAULT_FOREGROUND_COLOR
= rb
.lcd_get_foreground
~= nil
33 and rb
.lcd_get_foreground()
37 -- {GOAL, TOTAL_BALLS},
53 size
= DEFAULT_BALL_SIZE
,
61 x
= math
.random(self
.size
, rb
.LCD_WIDTH
- self
.size
),
62 y
= math
.random(self
.size
, rb
.LCD_HEIGHT
- self
.size
),
63 color
= random_color(),
64 up_speed
= Ball
:generateSpeed(),
65 right_speed
= Ball
:generateSpeed(),
66 explosion_size
= math
.random(2*self
.size
, 4*self
.size
),
67 life_duration
= math
.random(rb
.HZ
, rb
.HZ
*5)
76 function Ball
:generateSpeed()
77 local speed
= math
.random(-MAX_BALL_SPEED
, MAX_BALL_SPEED
)
79 speed
= 1 -- Make sure all balls move
87 I know these aren't circles, but as there's no current circle
88 implementation in Rockbox, rectangles will just do fine (drawing
89 circles from within Lua is far too slow).
91 set_foreground(self
.color
)
92 rb
.lcd_fillrect(self
.x
, self
.y
, self
.size
, self
.size
)
97 if self
.implosion
and self
.size
> 0 then
98 self
.size
= self
.size
- 2
99 self
.x
= self
.x
+ 1 -- We do this because we want to stay centered
101 elseif self
.size
< self
.explosion_size
then
102 self
.size
= self
.size
+ 2
103 self
.x
= self
.x
- 1 -- We do this for the same reasons as above
109 self
.x
= self
.x
+ self
.right_speed
110 self
.y
= self
.y
+ self
.up_speed
111 if (self
.x
+ self
.size
) >= rb
.LCD_WIDTH
or self
.x
<= self
.size
then
112 self
.right_speed
= self
.right_speed
* (-1)
113 elseif (self
.y
+ self
.size
) >= rb
.LCD_HEIGHT
or self
.y
<= self
.size
then
114 self
.up_speed
= self
.up_speed
* (-1)
118 function Ball
:checkHit(other
)
119 local x_dist
= math
.abs(other
.x
- self
.x
)
120 local y_dist
= math
.abs(other
.y
- self
.y
)
121 local x_size
= self
.x
> other
.x
and other
.size
or self
.size
122 local y_size
= self
.y
> other
.y
and other
.size
or self
.size
124 if (x_dist
<= x_size
) and (y_dist
<= y_size
) then
125 assert(not self
.exploded
)
127 self
.death_time
= rb
.current_tick() + self
.life_duration
128 if not other
.exploded
then
129 other
.exploded
= true
130 other
.death_time
= rb
.current_tick() + other
.life_duration
139 size
= DEFAULT_BALL_SIZE
*2,
144 function Cursor
:new()
148 function Cursor
:do_action(action
)
149 if action
== rb
.actions
.ACTION_TOUCHSCREEN
and HAS_TOUCHSCREEN
then
150 _
, self
.x
, self
.y
= rb
.action_get_touchscreen_press()
152 elseif action
== rb
.actions
.ACTION_KBD_SELECT
then
154 elseif (action
== rb
.actions
.ACTION_KBD_RIGHT
) then
155 self
.x
= self
.x
+ self
.size
156 elseif (action
== rb
.actions
.ACTION_KBD_LEFT
) then
157 self
.x
= self
.x
- self
.size
158 elseif (action
== rb
.actions
.ACTION_KBD_UP
) then
159 self
.y
= self
.y
- self
.size
160 elseif (action
== rb
.actions
.ACTION_KBD_DOWN
) then
161 self
.y
= self
.y
+ self
.size
164 if self
.x
> rb
.LCD_WIDTH
then
166 elseif self
.x
< 0 then
167 self
.x
= rb
.LCD_WIDTH
170 if self
.y
> rb
.LCD_HEIGHT
then
172 elseif self
.y
< 0 then
173 self
.y
= rb
.LCD_HEIGHT
179 function Cursor
:draw()
180 set_foreground(DEFAULT_FOREGROUND_COLOR
)
182 rb
.lcd_hline(self
.x
- self
.size
/2, self
.x
- self
.size
/4, self
.y
- self
.size
/2)
183 rb
.lcd_hline(self
.x
+ self
.size
/4, self
.x
+ self
.size
/2, self
.y
- self
.size
/2)
184 rb
.lcd_hline(self
.x
- self
.size
/2, self
.x
- self
.size
/4, self
.y
+ self
.size
/2)
185 rb
.lcd_hline(self
.x
+ self
.size
/4, self
.x
+ self
.size
/2, self
.y
+ self
.size
/2)
186 rb
.lcd_vline(self
.x
- self
.size
/2, self
.y
- self
.size
/2, self
.y
- self
.size
/4)
187 rb
.lcd_vline(self
.x
- self
.size
/2, self
.y
+ self
.size
/4, self
.y
+ self
.size
/2)
188 rb
.lcd_vline(self
.x
+ self
.size
/2, self
.y
- self
.size
/2, self
.y
- self
.size
/4)
189 rb
.lcd_vline(self
.x
+ self
.size
/2, self
.y
+ self
.size
/4, self
.y
+ self
.size
/2)
191 rb
.lcd_hline(self
.x
- self
.size
/4, self
.x
+ self
.size
/4, self
.y
)
192 rb
.lcd_vline(self
.x
, self
.y
- self
.size
/4, self
.y
+ self
.size
/4)
195 function draw_positioned_string(bottom
, right
, str
)
196 local _
, w
, h
= rb
.font_getstringsize(str
, rb
.FONT_UI
)
198 rb
.lcd_putsxy((rb
.LCD_WIDTH
-w
)*right
, (rb
.LCD_HEIGHT
-h
)*bottom
, str
)
201 function set_foreground(color
)
202 if rb
.lcd_set_foreground
~= nil then
203 rb
.lcd_set_foreground(color
)
207 function random_color()
208 if rb
.lcd_rgbpack
~= nil then --color target
209 return rb
.lcd_rgbpack(math
.random(1,255), math
.random(1,255), math
.random(1,255))
212 return math
.random(1, rb
.LCD_DEPTH
)
215 function start_round(level
, goal
, nrBalls
, total
)
216 local player_added
, score
, exit, nrExpandedBalls
= false, 0, false, 0
217 local balls
, explodedBalls
= {}, {}
218 local cursor
= Cursor
:new()
220 -- Initialize the balls
222 table.insert(balls
, Ball
:new())
225 -- Make sure there are no unwanted touchscreen presses
226 rb
.button_clear_queue()
229 local endtick
= rb
.current_tick() + CYCLETIME
231 -- Check if the round is over
232 if #explodedBalls
== 0 and player_added
then
237 local action
= rb
.get_action(rb
.contexts
.CONTEXT_KEYBOARD
, 0)
238 if(action
== rb
.actions
.ACTION_KBD_ABORT
) then
242 if not player_added
and cursor
:do_action(action
) then
243 local player
= Ball
:new({
246 color
= DEFAULT_FOREGROUND_COLOR
,
248 explosion_size
= 3*DEFAULT_BALL_SIZE
,
250 death_time
= rb
.current_tick() + rb
.HZ
* 3
252 table.insert(explodedBalls
, player
)
257 for i
, ball
in ipairs(balls
) do
258 for _
, explodedBall
in ipairs(explodedBalls
) do
259 if ball
:checkHit(explodedBall
) then
260 score
= score
+ 100*level
261 nrExpandedBalls
= nrExpandedBalls
+ 1
262 table.insert(explodedBalls
, ball
)
263 table.remove(balls
, i
)
269 -- Check if we're dead yet
270 for i
, explodedBall
in ipairs(explodedBalls
) do
271 if rb
.current_tick() >= explodedBall
.death_time
then
272 if explodedBall
.size
> 0 then
273 explodedBall
.implosion
= true -- We should be dying
275 table.remove(explodedBalls
, i
) -- We're imploded!
281 rb
.lcd_clear_display()
283 set_foreground(DEFAULT_FOREGROUND_COLOR
)
284 draw_positioned_string(0, 0, string.format("%d balls expanded", nrExpandedBalls
))
285 draw_positioned_string(0, 1, string.format("Level %d", level
))
286 draw_positioned_string(1, 1, string.format("%d level points", score
))
287 draw_positioned_string(1, 0, string.format("%d total points", total
+score
))
289 for _
, ball
in ipairs(balls
) do
294 for _
, explodedBall
in ipairs(explodedBalls
) do
299 if not HAS_TOUCHSCREEN
and not player_added
then
303 -- Push framebuffer to the LCD
306 if rb
.current_tick() < endtick
then
307 rb
.sleep(endtick
- rb
.current_tick())
313 return exit, score
, nrExpandedBalls
316 -- Helper function to display a message
317 function display_message(...)
318 local message
= string.format(...)
319 local _
, w
, h
= rb
.font_getstringsize(message
, rb
.FONT_UI
)
320 local x
, y
= (rb
.LCD_WIDTH
- w
) / 2, (rb
.LCD_HEIGHT
- h
) / 2
322 rb
.lcd_clear_display()
323 set_foreground(DEFAULT_FOREGROUND_COLOR
)
324 if w
> rb
.LCD_WIDTH
then
325 rb
.lcd_puts_scroll(x
/w
, y
/h
, message
)
327 rb
.lcd_putsxy(x
, y
, message
)
333 rb
.lcd_stop_scroll() -- Stop our scrolling message
336 if HAS_TOUCHSCREEN
then
337 rb
.touchscreen_set_mode(rb
.TOUCHSCREEN_POINT
)
339 rb
.backlight_force_on()
341 local idx
, highscore
= 1, 0
342 while levels
[idx
] ~= nil do
343 local goal
, nrBalls
= levels
[idx
][1], levels
[idx
][2]
345 display_message("Level %d: get %d out of %d balls", idx
, goal
, nrBalls
)
347 local exit, score
, nrExpandedBalls
= start_round(idx
, goal
, nrBalls
, highscore
)
351 if nrExpandedBalls
>= goal
then
352 display_message("You won!")
354 highscore
= highscore
+ score
356 display_message("You lost!")
361 if idx
> #levels
then
362 display_message("You finished the game with %d points!", highscore
)
364 display_message("You made it till level %d with %d points!", idx
, highscore
)
367 -- Restore user backlight settings
368 rb
.backlight_use_settings()