summaryrefslogtreecommitdiff
path: root/apps/plugins/boomshine.lua
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/boomshine.lua')
-rw-r--r--apps/plugins/boomshine.lua278
1 files changed, 278 insertions, 0 deletions
diff --git a/apps/plugins/boomshine.lua b/apps/plugins/boomshine.lua
new file mode 100644
index 0000000000..0067d28e95
--- /dev/null
+++ b/apps/plugins/boomshine.lua
@@ -0,0 +1,278 @@
1--[[
2 __________ __ ___.
3 Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 \/ \/ \/ \/ \/
8 $Id$
9
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/
12
13 Copyright (C) 2009 by Maurus Cuelenaere
14
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.
19
20 This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
21 KIND, either express or implied.
22
23]]--
24
25require "actions"
26
27local CYCLETIME = rb.HZ / 50
28
29local levels = {
30 -- {GOAL, TOTAL_BALLS},
31 {1, 5},
32 {2, 10},
33 {4, 15},
34 {6, 20},
35 {10, 25},
36 {15, 30},
37 {18, 35},
38 {22, 40},
39 {30, 45},
40 {37, 50},
41 {48, 55},
42 {55, 60}
43 }
44
45local Ball = {
46 size = 10,
47 exploded = false,
48 implosion = false
49 }
50
51function Ball:new(o)
52 if o == nil then
53 o = {
54 x = math.random(self.size, rb.LCD_WIDTH - self.size),
55 y = math.random(self.size, rb.LCD_HEIGHT - self.size),
56 color = rb.lcd_rgbpack(math.random(0,255), math.random(0,255), math.random(0,255)),
57 up_speed = math.random(-3, 3),
58 right_speed = math.random(-3, 3),
59 explosion_size = math.random((3*self.size)/2, (5*self.size)/2),
60 life_duration = math.random(rb.HZ, rb.HZ*5)
61 }
62
63 -- Make sure all balls move
64 if o.up_speed == 0 then o.up_speed = 1 end
65 if o.right_speed == 0 then o.right_speed = 1 end
66 end
67
68 setmetatable(o, self)
69 self.__index = self
70 return o
71end
72
73function Ball:draw()
74 --[[
75 I know these aren't circles, but as there's no current circle
76 implementation in Rockbox, rectangles will just do fine (drawing
77 circles from within Lua is far too slow).
78 ]]--
79 rb.lcd_set_foreground(self.color)
80 rb.lcd_fillrect(self.x, self.y, self.size, self.size)
81end
82
83function Ball:step()
84 if self.exploded then
85 if self.implosion and self.size > 0 then
86 self.size = self.size - 2
87 self.x = self.x + 1 -- We do this because we want to stay centered
88 self.y = self.y + 1
89 elseif self.size < self.explosion_size then
90 self.size = self.size + 2
91 self.x = self.x - 1 -- We do this for the same reasons as above
92 self.y = self.y - 1
93 end
94 return
95 end
96
97 self.x = self.x + self.right_speed
98 self.y = self.y + self.up_speed
99 if (self.x + self.size) >= rb.LCD_WIDTH or self.x <= self.size then
100 self.right_speed = self.right_speed * (-1)
101 elseif (self.y + self.size) >= rb.LCD_HEIGHT or self.y <= self.size then
102 self.up_speed = self.up_speed * (-1)
103 end
104end
105
106function Ball:checkHit(other)
107 local x_dist = math.abs(other.x - self.x)
108 local y_dist = math.abs(other.y - self.y)
109 local x_size = self.x > other.x and other.size or self.size
110 local y_size = self.y > other.y and other.size or self.size
111
112 if (x_dist <= x_size) and (y_dist <= y_size) then
113 assert(not self.exploded)
114 self.exploded = true
115 self.death_time = rb.current_tick() + self.life_duration
116 if not other.exploded then
117 other.exploded = true
118 other.death_time = rb.current_tick() + other.life_duration
119 end
120 return true
121 end
122
123 return false
124end
125
126function draw_positioned_string(bottom, right, str)
127 local _, w, h = rb.font_getstringsize(str, rb.FONT_UI)
128 rb.lcd_putsxy((rb.LCD_WIDTH-w)*right, (rb.LCD_HEIGHT-h)*bottom, str)
129end
130
131function start_round(level, goal, nrBalls, total)
132 local player_added, score, exit, nrExpandedBalls = false, 0, false, 0
133 local balls, explodedBalls = {}, {}
134
135 -- Initialize the balls
136 for _=1,nrBalls do
137 table.insert(balls, Ball:new())
138 end
139
140 -- Make sure there are no unwanted touchscreen presses
141 rb.button_clear_queue()
142
143 while true do
144 local endtick = rb.current_tick() + CYCLETIME
145
146 -- Check if the round is over
147 if #explodedBalls == 0 and player_added then
148 break
149 end
150
151 -- Check for actions
152 local action = rb.get_action(rb.contexts.CONTEXT_STD, 0)
153 if (action == rb.actions.ACTION_TOUCHSCREEN) then
154 local _, x, y = rb.action_get_touchscreen_press()
155 if not player_added then
156 local player = Ball:new({
157 x = x,
158 y = y,
159 color = rb.lcd_rgbpack(255, 255, 255),
160 size = 10,
161 explosion_size = 30,
162 exploded = true,
163 death_time = rb.current_tick() + rb.HZ * 3
164 })
165 table.insert(explodedBalls, player)
166 player_added = true
167 end
168 elseif(action == rb.actions.ACTION_STD_CANCEL) then
169 exit = true
170 break
171 end
172
173 -- Check for hits
174 for i, ball in ipairs(balls) do
175 for _, explodedBall in ipairs(explodedBalls) do
176 if ball:checkHit(explodedBall) then
177 score = score + 100*level
178 nrExpandedBalls = nrExpandedBalls + 1
179 table.insert(explodedBalls, ball)
180 table.remove(balls, i)
181 break
182 end
183 end
184 end
185
186 -- Check if we're dead yet
187 for i, explodedBall in ipairs(explodedBalls) do
188 if rb.current_tick() >= explodedBall.death_time then
189 if explodedBall.size > 0 then
190 explodedBall.implosion = true -- We should be dying
191 else
192 table.remove(explodedBalls, i) -- We're imploded!
193 end
194 end
195 end
196
197 -- Drawing phase
198 rb.lcd_clear_display()
199 rb.lcd_set_foreground(rb.lcd_rgbpack(255, 255, 255))
200 draw_positioned_string(0, 0, string.format("%d balls expanded", nrExpandedBalls))
201 draw_positioned_string(0, 1, string.format("Level %d", level))
202 draw_positioned_string(1, 1, string.format("%d level points", score))
203 draw_positioned_string(1, 0, string.format("%d total points", total+score))
204
205 for _, ball in ipairs(balls) do
206 ball:step()
207 ball:draw()
208 end
209
210 for _, explodedBall in ipairs(explodedBalls) do
211 explodedBall:step()
212 explodedBall:draw()
213 end
214
215 rb.lcd_update()
216
217 if rb.current_tick() < endtick then
218 rb.sleep(endtick - rb.current_tick())
219 else
220 rb.yield()
221 end
222 end
223
224 return exit, score
225end
226
227-- Helper function to display a message
228function display_message(message)
229 local _, w, h = rb.font_getstringsize(message, rb.FONT_UI)
230 local x, y = (rb.LCD_WIDTH - w) / 2, (rb.LCD_HEIGHT - h) / 2
231
232 rb.lcd_clear_display()
233 rb.lcd_set_foreground(rb.lcd_rgbpack(255, 255, 255))
234 if w > rb.LCD_WIDTH then
235 rb.lcd_puts_scroll(x/w, y/h, message)
236 else
237 rb.lcd_putsxy(x, y, message)
238 end
239 rb.lcd_update()
240
241 rb.sleep(rb.HZ * 2)
242
243 rb.lcd_stop_scroll() -- Stop our scrolling message
244end
245
246rb.touchscreen_set_mode(rb.TOUCHSCREEN_POINT)
247rb.backlight_force_on()
248
249local idx, highscore = 1, 0
250while true do
251 local level = levels[idx]
252 local goal, nrBalls = level[1], level[2]
253
254 if level == nil then
255 break -- No more levels to play
256 end
257
258 display_message(string.format("Level %d: get %d out of %d balls", idx, goal, nrBalls))
259
260 local exit, score = start_round(idx, goal, nrBalls, highscore)
261 if exit then
262 break -- Exiting..
263 else
264 if score >= goal then
265 display_message("You won!")
266 idx = idx + 1
267 highscore = highscore + score
268 else
269 display_message("You lost!")
270 end
271 end
272end
273
274display_message(string.format("You made it till level %d with %d points!", idx, highscore))
275
276-- Restore user backlight settings
277rb.backlight_use_settings()
278