lua: color pushlstring gets real len
[awesome.git] / lib / invaders.lua.in
blob5b1c07b922dc32b2a9306de304d4af98e01e5f98
1 ----------------------------------------------------------------------------
2 -- @author Gregor "farhaven" Best
3 -- @copyright 2008 Gregor "farhaven" Best
4 -- @release @AWESOME_VERSION@
5 ----------------------------------------------------------------------------
7 --{{{ Awesome Invaders by Gregor "farhaven" Best
8 -- The ultra-cool retro graphics are done by Andrei "Garoth" Thorp.
9 --
10 -- Use Left and Right to control motion, Space to fire, q quits the game,
11 -- s creates a screenshot in ~/.cache/awesome (needs ImageMagick).
13 -- Maybe there are some huge memory leaks here and there, if you notice one,
14 -- message me.
16 -- Anyway, have fun :)
18 -- Start this by adding
19 -- require("invaders")
20 -- to the top of your rc.lua and adding
21 -- invaders.run()
22 -- to the bottom of rc.lua or as a keybinding
23 --}}}
25 local wibox = wibox
26 local widget = widget
27 local awful = require("awful")
28 local beautiful = require("awful.beautiful")
29 local image = image
30 local capi = { screen = screen, mouse = mouse, keygrabber = keygrabber }
32 local tonumber = tonumber
33 local table = table
34 local math = math
35 local os = os
36 local io = io
38 --- Space Invaders look-alike
39 module("invaders")
41 local gamedata = { }
42 gamedata.field = { }
43 gamedata.field.x = 100
44 gamedata.field.y = 100
45 gamedata.field.h = 400
46 gamedata.field.w = 600
47 gamedata.running = false
48 gamedata.highscore = { }
49 gamedata.enemies = { }
50 gamedata.enemies.h = 10
51 gamedata.enemies.w = 20
52 gamedata.enemies.rows = 5
53 gamedata.enemies.count = gamedata.enemies.rows * 6
54 gamedata.enemies[1] = image("@AWESOME_ICON_PATH@/invaders/enemy_1.png")
55 gamedata.enemies[2] = image("@AWESOME_ICON_PATH@/invaders/enemy_2.png")
56 gamedata.enemies[3] = image("@AWESOME_ICON_PATH@/invaders/enemy_3.png")
58 local player = { }
59 local game = { }
60 local shots = { }
61 local enemies = { }
63 function player.new ()
64 p = wibox({ position = "floating",
65 bg = gamedata.solidbg or "#12345600" })
66 p:geometry({ width = 24,
67 height = 16,
68 x = gamedata.field.x + (gamedata.field.w / 2),
69 y = gamedata.field.y + gamedata.field.h - (16 + 5) })
70 p.screen = 1
72 w = widget({ type = "imagebox", name = "player" })
73 w.image = image("@AWESOME_ICON_PATH@/invaders/player.png")
74 p.widgets = w
76 return p
77 end
79 function player.move(x)
80 local g = gamedata.player:geometry()
82 if x < 0 and g.x > gamedata.field.x then
83 g.x = g.x + x
84 elseif x > 0 and g.x < gamedata.field.x + gamedata.field.w - 30 then
85 g.x = g.x + x
86 end
88 gamedata.player:geometry(g)
89 end
91 function player.fire()
92 if gamedata.ammo == 1 then
93 gamedata.ammo = 0
94 local gb = gamedata.player:geometry()
95 shots.fire(gb.x + 9, gb.y - 10, "#00FF00")
96 end
97 end
99 function shots.fire (x, y, color)
100 local s = wibox({ position = "floating",
101 bg = color })
102 s:geometry({ width = 4,
103 height = 10,
104 x = x,
105 y = y })
106 s.screen = 1
108 if not gamedata.shot or gamedata.shot.screen == nil then
109 gamedata.shot = s
113 function shots.fire_enemy (x, y, color)
114 if gamedata.enemies.shots.fired < gamedata.enemies.shots.max then
115 gamedata.enemies.shots.fired = gamedata.enemies.shots.fired + 1
116 local s = wibox({ position = "floating",
117 bg = color })
118 s:geometry({ width = 4,
119 height = 10,
120 x = x,
121 y = y })
122 s.screen = 1
123 for i = 1, gamedata.enemies.shots.max do
124 if not gamedata.enemies.shots[i] or gamedata.enemies.shots[i].screen == nil then
125 gamedata.enemies.shots[i] = s
126 break
132 function shots.handle()
133 if not gamedata.running then return false end
134 if gamedata.ammo == 1 then return false end
136 local s = gamedata.shot
137 if s and s.screen then
138 gamedata.ammo = 0
139 local g = s:geometry()
140 if g.y < gamedata.field.y + 15 then
141 s.screen = nil
142 gamedata.ammo = 1
143 else
144 g.y = g.y - 6
145 s:geometry(g)
151 function shots.handle_enemy ()
152 if not gamedata.running then return false end
153 if gamedata.enemies.shots.fired == 0 then return false end
155 for i = 1, gamedata.enemies.shots.max do
156 local s = gamedata.enemies.shots[i]
157 if s and s.screen then
158 local g = s:geometry()
159 if g.y > gamedata.field.y + gamedata.field.h - 15 then
160 s.screen = nil
161 gamedata.enemies.shots.fired = gamedata.enemies.shots.fired - 1
162 else
163 g.y = g.y + 3
164 s:geometry(g)
166 if game.collide(gamedata.player, s) then
167 game.over()
173 function enemies.new (t)
174 e = wibox({ position = "floating",
175 bg = gamedata.solidbg or "#12345600" })
176 e:geometry({ height = gamedata.enemies.h,
177 width = gamedata.enemies.w,
178 x = gamedata.field.x,
179 y = gamedata.field.y })
180 e.screen = 1
181 w = widget({ type = "imagebox",
182 name = "enemy"..t })
183 w.image = gamedata.enemies[t]
185 e.widgets = w
186 return e
189 function enemies.setup()
190 gamedata.enemies.data = { }
191 gamedata.enemies.x = 10
192 gamedata.enemies.y = 5
193 gamedata.enemies.dir = 1
194 if not gamedata.enemies.shots then gamedata.enemies.shots = { } end
195 gamedata.enemies.shots.max = 10
196 gamedata.enemies.shots.fired = 0
198 gamedata.enemies.speed_count = 0
200 for y = 1, gamedata.enemies.rows do
201 gamedata.enemies.data[y] = { }
202 for x = 1, math.ceil((gamedata.enemies.count / gamedata.enemies.rows) + 1) do
203 gamedata.enemies.data[y][x] = enemies.new((y % 3) + 1)
207 if gamedata.shot then
208 gamedata.shot.screen = nil
211 for i = 1, gamedata.enemies.shots.max do
212 if gamedata.enemies.shots[i] then
213 gamedata.enemies.shots[i].screen = nil
218 function enemies.handle ()
219 if not gamedata.running then return false end
221 gamedata.enemies.number = 0
223 for y = 1, #gamedata.enemies.data do
224 for x = 1, #gamedata.enemies.data[y] do
225 local e = gamedata.enemies.data[y][x]
226 if e.screen then
227 local g = e:geometry()
228 gamedata.enemies.number = gamedata.enemies.number + 1
229 if gamedata.enemies.speed_count == (gamedata.enemies.speed - 1) then
230 g.y = math.floor(gamedata.field.y + gamedata.enemies.y + ((y - 1) * gamedata.enemies.h * 2))
231 g.x = math.floor(gamedata.field.x + gamedata.enemies.x + ((x - 1) * gamedata.enemies.w * 2))
232 e:geometry(g)
233 if game.collide(gamedata.player, e) or g.y > gamedata.field.y + gamedata.field.h - 20 then
234 game.over()
237 if gamedata.ammo == 0 then
238 local s = gamedata.shot
239 if s and s.screen and game.collide(e, s) then
240 gamedata.enemies.number = gamedata.enemies.number - 1
241 gamedata.ammo = 1
242 e.screen = nil
243 s.screen = nil
245 if (y % 3) == 0 then
246 gamedata.score = gamedata.score + 15
247 elseif (y % 3) == 1 then
248 gamedata.score = gamedata.score + 10
249 else
250 gamedata.score = gamedata.score + 5
252 gamedata.field.status.text = gamedata.score.." | "..gamedata.round.." "
258 local x = math.random(1, gamedata.enemies.count / gamedata.enemies.rows)
259 if gamedata.enemies.speed_count == (gamedata.enemies.speed - 1)
260 and math.random(0, 150) <= 1
261 and gamedata.enemies.data[y][x].screen then
262 shots.fire_enemy(math.floor(gamedata.field.x + gamedata.enemies.x + ((x - 1) * gamedata.enemies.w)),
263 gamedata.field.y + gamedata.enemies.y + gamedata.enemies.h,
264 "#FF0000")
268 if gamedata.enemies.number == 0 then
269 enemies.setup()
270 gamedata.round = gamedata.round + 1
271 gamedata.field.status.text = gamedata.score.." | "..gamedata.round.." "
272 if gamedata.enemies.speed > 1 then gamedata.enemies.speed = gamedata.enemies.speed - 1 end
273 return false
276 gamedata.enemies.speed_count = gamedata.enemies.speed_count + 1
277 if gamedata.enemies.speed_count < gamedata.enemies.speed then return false end
278 gamedata.enemies.speed_count = 0
279 gamedata.enemies.x = gamedata.enemies.x + math.floor((gamedata.enemies.w * gamedata.enemies.dir) / 4)
280 if gamedata.enemies.x > gamedata.field.w - (2 * gamedata.enemies.w * (gamedata.enemies.count / gamedata.enemies.rows + 1)) + 5
281 or gamedata.enemies.x <= 10 then
282 gamedata.enemies.y = gamedata.enemies.y + gamedata.enemies.h
283 gamedata.enemies.dir = gamedata.enemies.dir * (-1)
287 function keyhandler(mod, key)
288 if gamedata.highscore.getkeys then
289 if key:len() == 1 and gamedata.name:len() < 20 then
290 gamedata.name = gamedata.name .. key
291 elseif key == "BackSpace" then
292 gamedata.name = gamedata.name:sub(1, gamedata.name:len() - 1)
293 elseif key == "Return" then
294 gamedata.highscore.window.screen = nil
295 game.highscore_add(gamedata.score, gamedata.name)
296 game.highscore_show()
297 gamedata.highscore.getkeys = false
299 gamedata.namebox.text = " Name: " .. gamedata.name .. "|"
300 else
301 if key == "Left" then
302 player.move(-10)
303 elseif key == "Right" then
304 player.move(10)
305 elseif key == "q" then
306 game.quit()
307 return false
308 elseif key == " " then
309 player.fire()
310 elseif key == "s" then
311 awful.util.spawn("import -window root "..gamedata.cachedir.."/awesome/invaders-"..os.time()..".png")
314 return true
317 function game.collide(o1, o2)
318 g1 = o1:geometry()
319 g2 = o2:geometry()
321 --check if o2 is inside o1
322 if g2.x >= g1.x and g2.x <= g1.x + g1.width
323 and g2.y >= g1.y and g2.y <= g1.y + g1.height then
324 return true
327 return false
330 function game.over ()
331 gamedata.running = false
332 game.highscore(gamedata.score)
335 function game.quit()
336 gamedata.running = false
338 if gamedata.highscore.window then
339 gamedata.highscore.window.screen = nil
340 gamedata.highscore.window.widgets = nil
343 if gamedata.field.background then
344 gamedata.field.background.screen = nil
347 gamedata.player.screen = nil
348 gamedata.player.widgets = nil
349 gamedata.player = nil
351 gamedata.field.north.screen = nil
352 gamedata.field.north = nil
354 gamedata.field.south.screen = nil
355 gamedata.field.south = nil
357 gamedata.field.west.screen = nil
358 gamedata.field.west = nil
360 gamedata.field.east.screen = nil
361 gamedata.field.east = nil
363 for y = 1, #gamedata.enemies.data do
364 for x = 1, #gamedata.enemies.data[y] do
365 gamedata.enemies.data[y][x].screen = nil
366 gamedata.enemies.data[y][x].widgets = nil
370 if gamedata.shot then gamedata.shot.screen = nil end
372 for i = 1, gamedata.enemies.shots.max do
373 if gamedata.enemies.shots[i] then gamedata.enemies.shots[i].screen = nil end
377 function game.highscore_show ()
378 gamedata.highscore.window:geometry({ height = 140,
379 width = 200,
380 x = gamedata.field.x + math.floor(gamedata.field.w / 2) - 100,
381 y = gamedata.field.y + math.floor(gamedata.field.h / 2) - 55 })
382 gamedata.highscore.window.screen = 1
384 gamedata.highscore.table = widget({ type = "textbox",
385 name = "highscore" })
386 gamedata.highscore.window.widgets = gamedata.highscore.table
388 gamedata.highscore.table.text = " Highscores:\n"
390 for i = 1, 5 do
391 gamedata.highscore.table.text = gamedata.highscore.table.text .. "\n\t" .. gamedata.highscore[i]
394 gamedata.highscore.table.text = gamedata.highscore.table.text .. "\n\n Press Q to quit"
396 local fh = io.open(gamedata.cachedir.."/highscore_invaders", "w")
398 if not fh then
399 return false
402 for i = 1, 5 do
403 fh:write(gamedata.highscore[i].."\n")
406 fh:close()
409 function game.highscore_add (score, name)
410 local t = gamedata.highscore
412 for i = 5, 1, -1 do
413 if tonumber(t[i]:match("(%d+) ")) <= score then
414 if t[i+1] then t[i+1] = t[i] end
415 t[i] = score .. " " .. name
419 gamedata.highscore = t
422 function game.highscore (score)
423 local fh = io.open(gamedata.cachedir.."/highscore_invaders", "r")
425 if fh then
426 for i = 1, 5 do
427 gamedata.highscore[i] = fh:read("*line")
429 fh:close()
430 else
431 for i = 1, 5 do
432 gamedata.highscore[i] = ((6-i)*20).." foo"
436 local newentry = false
437 for i = 1, 5 do
438 local s = tonumber(gamedata.highscore[i]:match("(%d+) .*"))
439 if s <= score then newentry = true end
442 gamedata.highscore.window = wibox({ position = "floating",
443 bg = gamedata.btheme.bg_focus or "#333333",
444 fg = gamedata.btheme.fg_focus or "#FFFFFF" })
445 gamedata.highscore.window:geometry({ height = 20,
446 width = 300,
447 x = gamedata.field.x + math.floor(gamedata.field.w / 2) - 150,
448 y = gamedata.field.y + math.floor(gamedata.field.h / 2) })
449 gamedata.highscore.window.screen = 1
451 gamedata.namebox = widget({ type = "textbox",
452 name = "foobar" })
453 gamedata.namebox.text = " Name: |"
454 gamedata.highscore.window.widgets = gamedata.namebox
456 if newentry then
457 gamedata.name = ""
458 gamedata.highscore.getkeys = true
459 else
460 game.highscore_show()
464 --- Run Awesome Invaders
465 -- @param args A table with parameters.
466 -- x the X coordinate of the playing field.
467 -- y the Y coordinate of the playing field.
468 -- if either of these is left out, the game is placed in the center of the focused screen.
469 -- solidbg the background color of the playing field. If none is given, the playing field is transparent.
470 function run(args)
471 gamedata.screen = capi.screen[capi.mouse.screen]
472 gamedata.field.x = gamedata.screen.geometry.x + math.floor((gamedata.screen.geometry.width - gamedata.field.w) / 2)
473 gamedata.field.y = gamedata.screen.geometry.y + math.floor((gamedata.screen.geometry.height - gamedata.field.h) / 2)
475 if args then
476 if args['x'] then gamedata.field.x = args['x'] end
477 if args['y'] then gamedata.field.y = args['y'] end
478 if args['solidbg'] then gamedata.solidbg = args['solidbg'] end
481 gamedata.score = 0
482 gamedata.name = ""
483 gamedata.ammo = 1
484 gamedata.round = 1
485 gamedata.btheme = beautiful.get()
487 gamedata.cachedir = awful.util.getdir("cache")
489 if gamedata.solidbg then
490 gamedata.field.background = wibox({ position = "floating",
491 bg = gamedata.solidbg })
492 gamedata.field.background:geometry({ x = gamedata.field.x,
493 y = gamedata.field.y,
494 height = gamedata.field.h,
495 width = gamedata.field.w })
496 gamedata.field.background.screen = 1
499 gamedata.field.north = wibox({ position = "floating",
500 bg = gamedata.btheme.bg_focus or "#333333",
501 fg = gamedata.btheme.fg_focus or "#FFFFFF" })
502 gamedata.field.north:geometry({ width = gamedata.field.w + 10,
503 height = 15,
504 x = gamedata.field.x - 5,
505 y = gamedata.field.y - 15 })
506 gamedata.field.north.screen = 1
508 gamedata.field.status = widget({ type = "textbox",
509 name = "status",
510 align = "right" })
511 gamedata.field.status.text = gamedata.score.." | "..gamedata.round .. " "
513 gamedata.field.caption = widget({ type = "textbox",
514 name = "caption",
515 align = "left" })
516 gamedata.field.caption.text = " Awesome Invaders"
518 gamedata.field.north.widgets = { gamedata.field.caption, gamedata.field.status }
520 gamedata.field.south = wibox({ position = "floating",
521 bg = gamedata.btheme.bg_focus or "#333333",
522 fg = gamedata.btheme.fg_focus or "#FFFFFF" })
523 gamedata.field.south:geometry({ width = gamedata.field.w,
524 height = 5,
525 x = gamedata.field.x,
526 y = gamedata.field.y + gamedata.field.h - 5 })
527 gamedata.field.south.screen = 1
529 gamedata.field.west = wibox({ position = "floating",
530 bg = gamedata.btheme.bg_focus or "#333333",
531 fg = gamedata.btheme.fg_focus or "#FFFFFF" })
532 gamedata.field.west:geometry({ width = 5,
533 height = gamedata.field.h,
534 x = gamedata.field.x - 5,
535 y = gamedata.field.y })
536 gamedata.field.west.screen = 1
538 gamedata.field.east = wibox({ position = "floating",
539 bg = gamedata.btheme.bg_focus or "#333333",
540 fg = gamedata.btheme.fg_focus or "#FFFFFF" })
541 gamedata.field.east:geometry({ width = 5,
542 height = gamedata.field.h,
543 x = gamedata.field.x + gamedata.field.w,
544 y = gamedata.field.y })
545 gamedata.field.east.screen = 1
547 gamedata.enemies.speed = 5
548 enemies.setup()
550 gamedata.player = player.new()
551 capi.keygrabber.run(keyhandler)
552 gamedata.running = true
555 awful.hooks.timer.register(0.02, shots.handle)
556 awful.hooks.timer.register(0.03, shots.handle_enemy)
557 awful.hooks.timer.register(0.01, enemies.handle)