Fix wpsbuild.pl so that rockbox_default wps is built
[kugel-rb.git] / apps / plugins / boomshine.lua
blobab00c52e2b2a9afb1f299bbd8f4e18ec2074654e
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
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
30 or rb.LCD_HEIGHT / 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()
34 or 0
36 local levels = {
37 -- {GOAL, TOTAL_BALLS},
38 {1, 5},
39 {2, 10},
40 {4, 15},
41 {6, 20},
42 {10, 25},
43 {15, 30},
44 {18, 35},
45 {22, 40},
46 {30, 45},
47 {37, 50},
48 {48, 55},
49 {55, 60}
52 local Ball = {
53 size = DEFAULT_BALL_SIZE,
54 exploded = false,
55 implosion = false
58 function Ball:new(o)
59 if o == nil then
60 o = {
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)
69 end
71 setmetatable(o, self)
72 self.__index = self
73 return o
74 end
76 function Ball:generateSpeed()
77 local speed = math.random(-MAX_BALL_SPEED, MAX_BALL_SPEED)
78 if speed == 0 then
79 speed = 1 -- Make sure all balls move
80 end
82 return speed
83 end
85 function Ball:draw()
86 --[[
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).
90 ]]--
91 set_foreground(self.color)
92 rb.lcd_fillrect(self.x, self.y, self.size, self.size)
93 end
95 function Ball:step()
96 if self.exploded then
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
100 self.y = self.y + 1
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
104 self.y = self.y - 1
106 return
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)
126 self.exploded = true
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
132 return true
135 return false
138 local Cursor = {
139 size = DEFAULT_BALL_SIZE*2,
140 x = rb.LCD_WIDTH/2,
141 y = rb.LCD_HEIGHT/2
144 function Cursor:new()
145 return self
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()
151 return true
152 elseif action == rb.actions.ACTION_KBD_SELECT then
153 return true
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
165 self.x = 0
166 elseif self.x < 0 then
167 self.x = rb.LCD_WIDTH
170 if self.y > rb.LCD_HEIGHT then
171 self.y = 0
172 elseif self.y < 0 then
173 self.y = rb.LCD_HEIGHT
176 return false
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
221 for _=1,nrBalls do
222 table.insert(balls, Ball:new())
225 -- Make sure there are no unwanted touchscreen presses
226 rb.button_clear_queue()
228 while true do
229 local endtick = rb.current_tick() + CYCLETIME
231 -- Check if the round is over
232 if #explodedBalls == 0 and player_added then
233 break
236 -- Check for actions
237 local action = rb.get_action(rb.contexts.CONTEXT_KEYBOARD, 0)
238 if(action == rb.actions.ACTION_KBD_ABORT) then
239 exit = true
240 break
242 if not player_added and cursor:do_action(action) then
243 local player = Ball:new({
244 x = cursor.x,
245 y = cursor.y,
246 color = DEFAULT_FOREGROUND_COLOR,
247 size = 10,
248 explosion_size = 3*DEFAULT_BALL_SIZE,
249 exploded = true,
250 death_time = rb.current_tick() + rb.HZ * 3
252 table.insert(explodedBalls, player)
253 player_added = true
256 -- Check for hits
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)
264 break
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
274 else
275 table.remove(explodedBalls, i) -- We're imploded!
280 -- Drawing phase
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
290 ball:step()
291 ball:draw()
294 for _, explodedBall in ipairs(explodedBalls) do
295 explodedBall:step()
296 explodedBall:draw()
299 if not HAS_TOUCHSCREEN and not player_added then
300 cursor:draw()
303 -- Push framebuffer to the LCD
304 rb.lcd_update()
306 if rb.current_tick() < endtick then
307 rb.sleep(endtick - rb.current_tick())
308 else
309 rb.yield()
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)
326 else
327 rb.lcd_putsxy(x, y, message)
329 rb.lcd_update()
331 rb.sleep(rb.HZ * 2)
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)
348 if exit then
349 break -- Exiting..
350 else
351 if nrExpandedBalls >= goal then
352 display_message("You won!")
353 idx = idx + 1
354 highscore = highscore + score
355 else
356 display_message("You lost!")
361 if idx > #levels then
362 display_message("You finished the game with %d points!", highscore)
363 else
364 display_message("You made it till level %d with %d points!", idx, highscore)
367 -- Restore user backlight settings
368 rb.backlight_use_settings()