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
29 if rb
.action_get_touchscreen_press
== nil then
30 HAS_TOUCHSCREEN
= false
32 HAS_TOUCHSCREEN
= true
35 -- color used to write the text
36 if rb
.lcd_rgbpack
~= nil then
37 DEFAULT_FOREGROUND_COLOR
= rb
.lcd_rgbpack(255, 255, 255)
39 DEFAULT_FOREGROUND_COLOR
= 0
42 if rb
.LCD_HEIGHT
> rb
.LCD_WIDTH
then
43 DEFAULT_BALL_SIZE
= rb
.LCD_WIDTH
/40
45 DEFAULT_BALL_SIZE
= rb
.LCD_HEIGHT
/40
48 MAX_BALL_SPEED
= DEFAULT_BALL_SIZE
/2
51 -- {GOAL, TOTAL_BALLS},
67 size
= DEFAULT_BALL_SIZE
,
75 x
= math
.random(self
.size
, rb
.LCD_WIDTH
- self
.size
),
76 y
= math
.random(self
.size
, rb
.LCD_HEIGHT
- self
.size
),
77 color
= random_color(),
78 up_speed
= Ball
:generateSpeed(),
79 right_speed
= Ball
:generateSpeed(),
80 explosion_size
= math
.random((3*self
.size
)/2, (5*self
.size
)/2),
81 life_duration
= math
.random(rb
.HZ
, rb
.HZ
*5)
90 function Ball
:generateSpeed()
91 local speed
= math
.random(-MAX_BALL_SPEED
, MAX_BALL_SPEED
)
92 -- Make sure all balls move
93 if speed
== 0 then speed
= 1 end
99 I know these aren't circles, but as there's no current circle
100 implementation in Rockbox, rectangles will just do fine (drawing
101 circles from within Lua is far too slow).
103 set_foreground(self
.color
)
104 rb
.lcd_fillrect(self
.x
, self
.y
, self
.size
, self
.size
)
108 if self
.exploded
then
109 if self
.implosion
and self
.size
> 0 then
110 self
.size
= self
.size
- 2
111 self
.x
= self
.x
+ 1 -- We do this because we want to stay centered
113 elseif self
.size
< self
.explosion_size
then
114 self
.size
= self
.size
+ 2
115 self
.x
= self
.x
- 1 -- We do this for the same reasons as above
121 self
.x
= self
.x
+ self
.right_speed
122 self
.y
= self
.y
+ self
.up_speed
123 if (self
.x
+ self
.size
) >= rb
.LCD_WIDTH
or self
.x
<= self
.size
then
124 self
.right_speed
= self
.right_speed
* (-1)
125 elseif (self
.y
+ self
.size
) >= rb
.LCD_HEIGHT
or self
.y
<= self
.size
then
126 self
.up_speed
= self
.up_speed
* (-1)
130 function Ball
:checkHit(other
)
131 local x_dist
= math
.abs(other
.x
- self
.x
)
132 local y_dist
= math
.abs(other
.y
- self
.y
)
133 local x_size
= self
.x
> other
.x
and other
.size
or self
.size
134 local y_size
= self
.y
> other
.y
and other
.size
or self
.size
136 if (x_dist
<= x_size
) and (y_dist
<= y_size
) then
137 assert(not self
.exploded
)
139 self
.death_time
= rb
.current_tick() + self
.life_duration
140 if not other
.exploded
then
141 other
.exploded
= true
142 other
.death_time
= rb
.current_tick() + other
.life_duration
151 size
= DEFAULT_BALL_SIZE
*2,
156 function Cursor
:new()
160 function Cursor
:do_action(action
)
161 if action
== rb
.actions
.ACTION_TOUCHSCREEN
and HAS_TOUCHSCREEN
then
162 if HAS_TOUCHSCREEN
then
163 _
, self
.x
, self
.y
= rb
.action_get_touchscreen_press()
166 elseif action
== rb
.actions
.ACTION_KBD_SELECT
then
168 elseif (action
== rb
.actions
.ACTION_KBD_RIGHT
) then
169 self
.x
= self
.x
+ self
.size
170 elseif (action
== rb
.actions
.ACTION_KBD_LEFT
) then
171 self
.x
= self
.x
- self
.size
172 elseif (action
== rb
.actions
.ACTION_KBD_UP
) then
173 self
.y
= self
.y
- self
.size
174 elseif (action
== rb
.actions
.ACTION_KBD_DOWN
) then
175 self
.y
= self
.y
+ self
.size
177 if self
.x
> rb
.LCD_WIDTH
then
179 elseif self
.x
< 0 then
180 self
.x
= rb
.LCD_WIDTH
182 if self
.y
> rb
.LCD_HEIGHT
then
184 elseif self
.y
< 0 then
185 self
.y
= rb
.LCD_HEIGHT
190 function Cursor
:draw()
191 set_foreground(DEFAULT_FOREGROUND_COLOR
)
192 rb
.lcd_hline(self
.x
- self
.size
/2, self
.x
- self
.size
/4, self
.y
- self
.size
/2)
193 rb
.lcd_hline(self
.x
+ self
.size
/4, self
.x
+ self
.size
/2, self
.y
- self
.size
/2)
194 rb
.lcd_hline(self
.x
- self
.size
/2, self
.x
- self
.size
/4, self
.y
+ self
.size
/2)
195 rb
.lcd_hline(self
.x
+ self
.size
/4, self
.x
+ self
.size
/2, self
.y
+ self
.size
/2)
196 rb
.lcd_vline(self
.x
- self
.size
/2, self
.y
- self
.size
/2, self
.y
- self
.size
/4)
197 rb
.lcd_vline(self
.x
- self
.size
/2, self
.y
+ self
.size
/4, self
.y
+ self
.size
/2)
198 rb
.lcd_vline(self
.x
+ self
.size
/2, self
.y
- self
.size
/2, self
.y
- self
.size
/4)
199 rb
.lcd_vline(self
.x
+ self
.size
/2, self
.y
+ self
.size
/4, self
.y
+ self
.size
/2)
201 rb
.lcd_hline(self
.x
- self
.size
/4, self
.x
+ self
.size
/4, self
.y
)
202 rb
.lcd_vline(self
.x
, self
.y
- self
.size
/4, self
.y
+ self
.size
/4)
205 function draw_positioned_string(bottom
, right
, str
)
206 local _
, w
, h
= rb
.font_getstringsize(str
, rb
.FONT_UI
)
207 rb
.lcd_putsxy((rb
.LCD_WIDTH
-w
)*right
, (rb
.LCD_HEIGHT
-h
)*bottom
, str
)
210 function set_foreground(color
)
211 if rb
.lcd_set_foreground
~= nil then
212 rb
.lcd_set_foreground(color
)
216 function random_color()
217 if rb
.lcd_rgbpack
~= nil then --color target
218 return rb
.lcd_rgbpack(math
.random(1,255), math
.random(1,255), math
.random(1,255))
220 return math
.random(1, rb
.LCD_DEPTH
)
223 function start_round(level
, goal
, nrBalls
, total
)
224 local player_added
, score
, exit, nrExpandedBalls
= false, 0, false, 0
225 local balls
, explodedBalls
= {}, {}
226 local cursor
= Cursor
:new()
228 -- Initialize the balls
230 table.insert(balls
, Ball
:new())
233 -- Make sure there are no unwanted touchscreen presses
234 rb
.button_clear_queue()
237 local endtick
= rb
.current_tick() + CYCLETIME
239 -- Check if the round is over
240 if #explodedBalls
== 0 and player_added
then
245 local action
= rb
.get_action(rb
.contexts
.CONTEXT_KEYBOARD
, 0)
246 if(action
== rb
.actions
.ACTION_KBD_ABORT
) then
250 if not player_added
and cursor
:do_action(action
) then
251 local player
= Ball
:new({
254 color
= DEFAULT_FOREGROUND_COLOR
,
256 explosion_size
= 3*DEFAULT_BALL_SIZE
,
258 death_time
= rb
.current_tick() + rb
.HZ
* 3
260 table.insert(explodedBalls
, player
)
265 for i
, ball
in ipairs(balls
) do
266 for _
, explodedBall
in ipairs(explodedBalls
) do
267 if ball
:checkHit(explodedBall
) then
268 score
= score
+ 100*level
269 nrExpandedBalls
= nrExpandedBalls
+ 1
270 table.insert(explodedBalls
, ball
)
271 table.remove(balls
, i
)
277 -- Check if we're dead yet
278 for i
, explodedBall
in ipairs(explodedBalls
) do
279 if rb
.current_tick() >= explodedBall
.death_time
then
280 if explodedBall
.size
> 0 then
281 explodedBall
.implosion
= true -- We should be dying
283 table.remove(explodedBalls
, i
) -- We're imploded!
289 rb
.lcd_clear_display()
290 set_foreground(DEFAULT_FOREGROUND_COLOR
)
291 draw_positioned_string(0, 0, string.format("%d balls expanded", nrExpandedBalls
))
292 draw_positioned_string(0, 1, string.format("Level %d", level
))
293 draw_positioned_string(1, 1, string.format("%d level points", score
))
294 draw_positioned_string(1, 0, string.format("%d total points", total
+score
))
296 for _
, ball
in ipairs(balls
) do
301 for _
, explodedBall
in ipairs(explodedBalls
) do
305 if not HAS_TOUCHSCREEN
and not player_added
then
310 if rb
.current_tick() < endtick
then
311 rb
.sleep(endtick
- rb
.current_tick())
317 return exit, score
, nrExpandedBalls
320 -- Helper function to display a message
321 function display_message(message
)
322 local _
, w
, h
= rb
.font_getstringsize(message
, rb
.FONT_UI
)
323 local x
, y
= (rb
.LCD_WIDTH
- w
) / 2, (rb
.LCD_HEIGHT
- h
) / 2
325 rb
.lcd_clear_display()
326 set_foreground(DEFAULT_FOREGROUND_COLOR
)
327 if w
> rb
.LCD_WIDTH
then
328 rb
.lcd_puts_scroll(x
/w
, y
/h
, message
)
330 rb
.lcd_putsxy(x
, y
, message
)
336 rb
.lcd_stop_scroll() -- Stop our scrolling message
339 if HAS_TOUCHSCREEN
then
340 rb
.touchscreen_set_mode(rb
.TOUCHSCREEN_POINT
)
342 rb
.backlight_force_on()
344 local idx
, highscore
= 1, 0
346 local level
= levels
[idx
]
350 break -- No more levels to play
356 display_message(string.format("Level %d: get %d out of %d balls", idx
, goal
, nrBalls
))
358 local exit, score
, nrExpandedBalls
= start_round(idx
, goal
, nrBalls
, highscore
)
362 if nrExpandedBalls
>= goal
then
363 display_message("You won!")
365 highscore
= highscore
+ score
367 display_message("You lost!")
372 if idx
> #levels
then
373 display_message(string.format("You finished the game with %d points!", highscore
))
375 display_message(string.format("You made it till level %d with %d points!", idx
, highscore
))
378 -- Restore user backlight settings
379 rb
.backlight_use_settings()