diff options
Diffstat (limited to 'apps')
-rw-r--r-- | apps/plugins/SOURCES | 3 | ||||
-rw-r--r-- | apps/plugins/boomshine.lua | 278 |
2 files changed, 281 insertions, 0 deletions
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES index 82dd4b78e4..5dbdffd4ad 100644 --- a/apps/plugins/SOURCES +++ b/apps/plugins/SOURCES | |||
@@ -173,6 +173,9 @@ md5sum.c | |||
173 | 173 | ||
174 | #if (PLUGIN_BUFFER_SIZE >= 0x80000) | 174 | #if (PLUGIN_BUFFER_SIZE >= 0x80000) |
175 | lua.c | 175 | lua.c |
176 | #ifdef HAVE_TOUCHSCREEN | ||
177 | boomshine.lua | ||
178 | #endif | ||
176 | #endif | 179 | #endif |
177 | 180 | ||
178 | #ifdef USB_ENABLE_HID | 181 | #ifdef USB_ENABLE_HID |
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 | |||
25 | require "actions" | ||
26 | |||
27 | local CYCLETIME = rb.HZ / 50 | ||
28 | |||
29 | local 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 | |||
45 | local Ball = { | ||
46 | size = 10, | ||
47 | exploded = false, | ||
48 | implosion = false | ||
49 | } | ||
50 | |||
51 | function 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 | ||
71 | end | ||
72 | |||
73 | function 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) | ||
81 | end | ||
82 | |||
83 | function 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 | ||
104 | end | ||
105 | |||
106 | function 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 | ||
124 | end | ||
125 | |||
126 | function 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) | ||
129 | end | ||
130 | |||
131 | function 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 | ||
225 | end | ||
226 | |||
227 | -- Helper function to display a message | ||
228 | function 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 | ||
244 | end | ||
245 | |||
246 | rb.touchscreen_set_mode(rb.TOUCHSCREEN_POINT) | ||
247 | rb.backlight_force_on() | ||
248 | |||
249 | local idx, highscore = 1, 0 | ||
250 | while 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 | ||
272 | end | ||
273 | |||
274 | display_message(string.format("You made it till level %d with %d points!", idx, highscore)) | ||
275 | |||
276 | -- Restore user backlight settings | ||
277 | rb.backlight_use_settings() | ||
278 | |||