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(0, rb
.LCD_WIDTH
- self
.size
),
62 y
= math
.random(0, 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
.right_speed
> 0 and self
.x
+ self
.size
>= rb
.LCD_WIDTH
) or
112 (self
.right_speed
< 0 and self
.x
<= 0) then
113 self
.right_speed
= -self
.right_speed
115 if (self
.up_speed
> 0 and self
.y
+ self
.size
>= rb
.LCD_HEIGHT
) or
116 (self
.up_speed
< 0 and self
.y
<= 0) then
117 self
.up_speed
= -self
.up_speed
121 function Ball
:checkHit(other
)
122 if (other
.x
+ other
.size
>= self
.x
) and (self
.x
+ self
.size
>= other
.x
) and
123 (other
.y
+ other
.size
>= self
.y
) and (self
.y
+ self
.size
>= other
.y
) then
124 assert(not self
.exploded
)
126 self
.death_time
= rb
.current_tick() + self
.life_duration
127 if not other
.exploded
then
128 other
.exploded
= true
129 other
.death_time
= rb
.current_tick() + other
.life_duration
138 size
= DEFAULT_BALL_SIZE
*2,
143 function Cursor
:new()
147 function Cursor
:do_action(action
)
148 if action
== rb
.actions
.ACTION_TOUCHSCREEN
and HAS_TOUCHSCREEN
then
149 _
, self
.x
, self
.y
= rb
.action_get_touchscreen_press()
151 elseif action
== rb
.actions
.ACTION_KBD_SELECT
then
153 elseif (action
== rb
.actions
.ACTION_KBD_RIGHT
) then
154 self
.x
= self
.x
+ self
.size
155 elseif (action
== rb
.actions
.ACTION_KBD_LEFT
) then
156 self
.x
= self
.x
- self
.size
157 elseif (action
== rb
.actions
.ACTION_KBD_UP
) then
158 self
.y
= self
.y
- self
.size
159 elseif (action
== rb
.actions
.ACTION_KBD_DOWN
) then
160 self
.y
= self
.y
+ self
.size
163 if self
.x
> rb
.LCD_WIDTH
then
165 elseif self
.x
< 0 then
166 self
.x
= rb
.LCD_WIDTH
169 if self
.y
> rb
.LCD_HEIGHT
then
171 elseif self
.y
< 0 then
172 self
.y
= rb
.LCD_HEIGHT
178 function Cursor
:draw()
179 set_foreground(DEFAULT_FOREGROUND_COLOR
)
181 rb
.lcd_hline(self
.x
- self
.size
/2, self
.x
- self
.size
/4, self
.y
- self
.size
/2)
182 rb
.lcd_hline(self
.x
+ self
.size
/4, self
.x
+ self
.size
/2, self
.y
- self
.size
/2)
183 rb
.lcd_hline(self
.x
- self
.size
/2, self
.x
- self
.size
/4, self
.y
+ self
.size
/2)
184 rb
.lcd_hline(self
.x
+ self
.size
/4, self
.x
+ self
.size
/2, self
.y
+ self
.size
/2)
185 rb
.lcd_vline(self
.x
- self
.size
/2, self
.y
- self
.size
/2, self
.y
- self
.size
/4)
186 rb
.lcd_vline(self
.x
- self
.size
/2, self
.y
+ self
.size
/4, self
.y
+ self
.size
/2)
187 rb
.lcd_vline(self
.x
+ self
.size
/2, self
.y
- self
.size
/2, self
.y
- self
.size
/4)
188 rb
.lcd_vline(self
.x
+ self
.size
/2, self
.y
+ self
.size
/4, self
.y
+ self
.size
/2)
190 rb
.lcd_hline(self
.x
- self
.size
/4, self
.x
+ self
.size
/4, self
.y
)
191 rb
.lcd_vline(self
.x
, self
.y
- self
.size
/4, self
.y
+ self
.size
/4)
194 function draw_positioned_string(bottom
, right
, str
)
195 local _
, w
, h
= rb
.font_getstringsize(str
, rb
.FONT_UI
)
197 rb
.lcd_putsxy((rb
.LCD_WIDTH
-w
)*right
, (rb
.LCD_HEIGHT
-h
)*bottom
, str
)
200 function set_foreground(color
)
201 if rb
.lcd_set_foreground
~= nil then
202 rb
.lcd_set_foreground(color
)
206 function random_color()
207 if rb
.lcd_rgbpack
~= nil then --color target
208 return rb
.lcd_rgbpack(math
.random(1,255), math
.random(1,255), math
.random(1,255))
211 return math
.random(1, rb
.LCD_DEPTH
)
214 function start_round(level
, goal
, nrBalls
, total
)
215 local player_added
, score
, exit, nrExpandedBalls
= false, 0, false, 0
216 local balls
, explodedBalls
= {}, {}
217 local cursor
= Cursor
:new()
219 -- Initialize the balls
221 table.insert(balls
, Ball
:new())
224 -- Make sure there are no unwanted touchscreen presses
225 rb
.button_clear_queue()
228 local endtick
= rb
.current_tick() + CYCLETIME
230 -- Check if the round is over
231 if #explodedBalls
== 0 and player_added
then
236 local action
= rb
.get_action(rb
.contexts
.CONTEXT_KEYBOARD
, 0)
237 if(action
== rb
.actions
.ACTION_KBD_ABORT
) then
241 if not player_added
and cursor
:do_action(action
) then
242 local player
= Ball
:new({
245 color
= DEFAULT_FOREGROUND_COLOR
,
247 explosion_size
= 3*DEFAULT_BALL_SIZE
,
249 death_time
= rb
.current_tick() + rb
.HZ
* 3
251 table.insert(explodedBalls
, player
)
256 for i
, ball
in ipairs(balls
) do
257 for _
, explodedBall
in ipairs(explodedBalls
) do
258 if ball
:checkHit(explodedBall
) then
259 score
= score
+ 100*level
260 nrExpandedBalls
= nrExpandedBalls
+ 1
261 table.insert(explodedBalls
, ball
)
262 table.remove(balls
, i
)
268 -- Check if we're dead yet
269 for i
, explodedBall
in ipairs(explodedBalls
) do
270 if rb
.current_tick() >= explodedBall
.death_time
then
271 if explodedBall
.size
> 0 then
272 explodedBall
.implosion
= true -- We should be dying
274 table.remove(explodedBalls
, i
) -- We're imploded!
280 rb
.lcd_clear_display()
282 set_foreground(DEFAULT_FOREGROUND_COLOR
)
283 draw_positioned_string(0, 0, string.format("%d balls expanded", nrExpandedBalls
))
284 draw_positioned_string(0, 1, string.format("Level %d", level
))
285 draw_positioned_string(1, 1, string.format("%d level points", score
))
286 draw_positioned_string(1, 0, string.format("%d total points", total
+score
))
288 for _
, ball
in ipairs(balls
) do
293 for _
, explodedBall
in ipairs(explodedBalls
) do
298 if not HAS_TOUCHSCREEN
and not player_added
then
302 -- Push framebuffer to the LCD
305 if rb
.current_tick() < endtick
then
306 rb
.sleep(endtick
- rb
.current_tick())
312 return exit, score
, nrExpandedBalls
315 -- Helper function to display a message
316 function display_message(to
, ...)
317 local message
= string.format(...)
318 local _
, w
, h
= rb
.font_getstringsize(message
, rb
.FONT_UI
)
319 local x
, y
= (rb
.LCD_WIDTH
- w
) / 2, (rb
.LCD_HEIGHT
- h
) / 2
321 rb
.lcd_clear_display()
322 set_foreground(DEFAULT_FOREGROUND_COLOR
)
323 if w
> rb
.LCD_WIDTH
then
324 rb
.lcd_puts_scroll(0, y
/h
, message
)
326 rb
.lcd_putsxy(x
, y
, message
)
329 local msg
= "Press button to exit"
330 w
= rb
.font_getstringsize(msg
, rb
.FONT_UI
)
331 x
= (rb
.LCD_WIDTH
- w
) / 2
333 rb
.lcd_puts_scroll(0, y
/h
+ 1, msg
)
335 rb
.lcd_putsxy(x
, y
+ h
, msg
)
342 rb
.button_clear_queue()
348 rb
.lcd_stop_scroll() -- Stop our scrolling message
351 if HAS_TOUCHSCREEN
then
352 rb
.touchscreen_set_mode(rb
.TOUCHSCREEN_POINT
)
354 rb
.backlight_force_on()
356 local idx
, highscore
= 1, 0
357 while levels
[idx
] ~= nil do
358 local goal
, nrBalls
= levels
[idx
][1], levels
[idx
][2]
360 display_message(rb
.HZ
*2, "Level %d: get %d out of %d balls", idx
, goal
, nrBalls
)
362 local exit, score
, nrExpandedBalls
= start_round(idx
, goal
, nrBalls
, highscore
)
366 if nrExpandedBalls
>= goal
then
367 display_message(rb
.HZ
*2, "You won!")
369 highscore
= highscore
+ score
371 display_message(rb
.HZ
*2, "You lost!")
376 if idx
> #levels
then
377 display_message(-1, "You finished the game with %d points!", highscore
)
379 display_message(-1, "You made it till level %d with %d points!", idx
, highscore
)
382 -- Restore user backlight settings
383 rb
.backlight_use_settings()