boomshine: use theme colour to draw text so that text would be more readable with...
[kugel-rb.git] / apps / plugins / boomshine.lua
blobc9bd4a3edee6ace3f7c6eef791c1b05537b77e67
1 --[[
2 __________ __ ___.
3 Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 \/ \/ \/ \/ \/
8 $Id$
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.
23 ]]--
25 require "actions"
27 local CYCLETIME = rb.HZ / 50
29 if rb.action_get_touchscreen_press == nil then
30 HAS_TOUCHSCREEN = false
31 else
32 HAS_TOUCHSCREEN = true
33 end
35 -- color used to write the text
36 if rb.lcd_get_foreground ~= nil then
37 DEFAULT_FOREGROUND_COLOR = rb.lcd_get_foreground()
38 else
39 DEFAULT_FOREGROUND_COLOR = 0
40 end
42 if rb.LCD_HEIGHT > rb.LCD_WIDTH then
43 DEFAULT_BALL_SIZE = rb.LCD_WIDTH/40
44 else
45 DEFAULT_BALL_SIZE = rb.LCD_HEIGHT/40
46 end
48 MAX_BALL_SPEED = DEFAULT_BALL_SIZE/2
50 local levels = {
51 -- {GOAL, TOTAL_BALLS},
52 {1, 5},
53 {2, 10},
54 {4, 15},
55 {6, 20},
56 {10, 25},
57 {15, 30},
58 {18, 35},
59 {22, 40},
60 {30, 45},
61 {37, 50},
62 {48, 55},
63 {55, 60}
66 local Ball = {
67 size = DEFAULT_BALL_SIZE,
68 exploded = false,
69 implosion = false
72 function Ball:new(o)
73 if o == nil then
74 o = {
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)
83 end
85 setmetatable(o, self)
86 self.__index = self
87 return o
88 end
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
94 return speed
95 end
97 function Ball:draw()
98 --[[
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).
102 ]]--
103 set_foreground(self.color)
104 rb.lcd_fillrect(self.x, self.y, self.size, self.size)
107 function Ball:step()
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
112 self.y = self.y + 1
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
116 self.y = self.y - 1
118 return
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)
138 self.exploded = true
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
144 return true
147 return false
150 local Cursor = {
151 size = DEFAULT_BALL_SIZE*2,
152 x = rb.LCD_WIDTH/2,
153 y = rb.LCD_HEIGHT/2
156 function Cursor:new()
157 return self
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()
165 return true
166 elseif action == rb.actions.ACTION_KBD_SELECT then
167 return true
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
178 self.x = 0
179 elseif self.x < 0 then
180 self.x = rb.LCD_WIDTH
182 if self.y > rb.LCD_HEIGHT then
183 self.y = 0
184 elseif self.y < 0 then
185 self.y = rb.LCD_HEIGHT
187 return false
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
229 for _=1,nrBalls do
230 table.insert(balls, Ball:new())
233 -- Make sure there are no unwanted touchscreen presses
234 rb.button_clear_queue()
236 while true do
237 local endtick = rb.current_tick() + CYCLETIME
239 -- Check if the round is over
240 if #explodedBalls == 0 and player_added then
241 break
244 -- Check for actions
245 local action = rb.get_action(rb.contexts.CONTEXT_KEYBOARD, 0)
246 if(action == rb.actions.ACTION_KBD_ABORT) then
247 exit = true
248 break
250 if not player_added and cursor:do_action(action) then
251 local player = Ball:new({
252 x = cursor.x,
253 y = cursor.y,
254 color = DEFAULT_FOREGROUND_COLOR,
255 size = 10,
256 explosion_size = 3*DEFAULT_BALL_SIZE,
257 exploded = true,
258 death_time = rb.current_tick() + rb.HZ * 3
260 table.insert(explodedBalls, player)
261 player_added = true
264 -- Check for hits
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)
272 break
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
282 else
283 table.remove(explodedBalls, i) -- We're imploded!
288 -- Drawing phase
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
297 ball:step()
298 ball:draw()
301 for _, explodedBall in ipairs(explodedBalls) do
302 explodedBall:step()
303 explodedBall:draw()
305 if not HAS_TOUCHSCREEN and not player_added then
306 cursor:draw()
308 rb.lcd_update()
310 if rb.current_tick() < endtick then
311 rb.sleep(endtick - rb.current_tick())
312 else
313 rb.yield()
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)
329 else
330 rb.lcd_putsxy(x, y, message)
332 rb.lcd_update()
334 rb.sleep(rb.HZ * 2)
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
345 while true do
346 local level = levels[idx]
347 local goal, nrBalls
349 if level == nil then
350 break -- No more levels to play
353 goal = level[1]
354 nrBalls = level[2]
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)
359 if exit then
360 break -- Exiting..
361 else
362 if nrExpandedBalls >= goal then
363 display_message("You won!")
364 idx = idx + 1
365 highscore = highscore + score
366 else
367 display_message("You lost!")
372 if idx > #levels then
373 display_message(string.format("You finished the game with %d points!", highscore))
374 else
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()