awful.titlebar: add signals for various properties (FS#609)
[awesome.git] / lib / invaders.lua.in
blobeb50197babf1aec30d957a3de280b45ec2fd9dc2
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 timer = timer
27 local widget = widget
28 local awful = require("awful")
29 local beautiful = require("beautiful")
30 local image = image
31 local capi = { screen = screen, mouse = mouse, keygrabber = keygrabber }
33 local tonumber = tonumber
34 local table = table
35 local math = math
36 local os = os
37 local io = io
39 --- Space Invaders look-alike
40 module("invaders")
42 local gamedata = { }
43 gamedata.field = { }
44 gamedata.field.x = 100
45 gamedata.field.y = 100
46 gamedata.field.h = 400
47 gamedata.field.w = 600
48 gamedata.running = false
49 gamedata.highscore = { }
50 gamedata.enemies = { }
51 gamedata.enemies.h = 10
52 gamedata.enemies.w = 20
53 gamedata.enemies.rows = 5
54 gamedata.enemies.count = gamedata.enemies.rows * 6
55 gamedata.enemies[1] = image("@AWESOME_ICON_PATH@/invaders/enemy_1.png")
56 gamedata.enemies[2] = image("@AWESOME_ICON_PATH@/invaders/enemy_2.png")
57 gamedata.enemies[3] = image("@AWESOME_ICON_PATH@/invaders/enemy_3.png")
59 local player = { }
60 local game = { }
61 local shots = { }
62 local enemies = { }
64 function player.new ()
65 p = wibox({ bg = gamedata.solidbg or "#12345600",
66 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 = gamedata.screen
72 w = widget({ type = "imagebox" })
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 if not gamedata.running then return false end
82 if x < 0 and gamedata.player.x > gamedata.field.x then
83 gamedata.player.x = gamedata.player.x + x
84 elseif x > 0 and gamedata.player.x < gamedata.field.x + gamedata.field.w - 30 then
85 gamedata.player.x = gamedata.player.x + x
86 end
87 end
89 function player.fire()
90 if not gamedata.running then return false end
91 if gamedata.ammo == 1 then
92 gamedata.ammo = 0
93 shots.fire(gamedata.player.x + 9, gamedata.player.y - 10, "#00FF00")
94 end
95 end
97 function shots.fire (x, y, color)
98 local s = wibox({ bg = color,
99 width = 4,
100 height = 10,
101 x = x,
102 y = y })
103 s.screen = gamedata.screen
105 if not gamedata.shot or gamedata.shot.screen == nil then
106 gamedata.shot = s
110 function shots.fire_enemy (x, y, color)
111 if gamedata.enemies.shots.fired < gamedata.enemies.shots.max then
112 gamedata.enemies.shots.fired = gamedata.enemies.shots.fired + 1
113 local s = wibox({ bg = color,
114 width = 4,
115 height = 10,
116 x = x,
117 y = y })
118 s.screen = gamedata.screen
119 for i = 1, gamedata.enemies.shots.max do
120 if not gamedata.enemies.shots[i] or gamedata.enemies.shots[i].screen == nil then
121 gamedata.enemies.shots[i] = s
122 break
128 function shots.handle()
129 if not gamedata.running then return false end
130 if gamedata.ammo == 1 then return false end
132 local s = gamedata.shot
133 if s and s.screen then
134 gamedata.ammo = 0
135 if s.y < gamedata.field.y + 15 then
136 s.screen = nil
137 gamedata.ammo = 1
138 else
139 s.y = s.y - 6
145 function shots.handle_enemy ()
146 if not gamedata.running then return false end
147 if gamedata.enemies.shots.fired == 0 then return false end
149 for i = 1, gamedata.enemies.shots.max do
150 local s = gamedata.enemies.shots[i]
151 if s and s.screen then
152 if s.y > gamedata.field.y + gamedata.field.h - 15 then
153 s.screen = nil
154 gamedata.enemies.shots.fired = gamedata.enemies.shots.fired - 1
155 else
156 s.y = s.y + 3
158 if game.collide(gamedata.player, s) then
159 game.over()
165 function enemies.new (t)
166 e = wibox({ bg = gamedata.solidbg or "#12345600",
167 height = gamedata.enemies.h,
168 width = gamedata.enemies.w,
169 x = gamedata.field.x,
170 y = gamedata.field.y })
171 e.screen = gamedata.screen
172 w = widget({ type = "imagebox" })
173 w.image = gamedata.enemies[t]
175 e.widgets = w
176 return e
179 function enemies.setup()
180 gamedata.enemies.data = { }
181 gamedata.enemies.x = 10
182 gamedata.enemies.y = 5
183 gamedata.enemies.dir = 1
184 if not gamedata.enemies.shots then gamedata.enemies.shots = { } end
185 gamedata.enemies.shots.max = 10
186 gamedata.enemies.shots.fired = 0
188 gamedata.enemies.speed_count = 0
190 for y = 1, gamedata.enemies.rows do
191 gamedata.enemies.data[y] = { }
192 for x = 1, math.ceil((gamedata.enemies.count / gamedata.enemies.rows) + 1) do
193 gamedata.enemies.data[y][x] = enemies.new((y % 3) + 1)
197 if gamedata.shot then
198 gamedata.shot.screen = nil
201 for i = 1, gamedata.enemies.shots.max do
202 if gamedata.enemies.shots[i] then
203 gamedata.enemies.shots[i].screen = nil
208 function enemies.handle ()
209 if not gamedata.running then return false end
211 gamedata.enemies.number = 0
213 for y = 1, #gamedata.enemies.data do
214 for x = 1, #gamedata.enemies.data[y] do
215 local e = gamedata.enemies.data[y][x]
216 if e.screen then
217 gamedata.enemies.number = gamedata.enemies.number + 1
218 if gamedata.enemies.speed_count == (gamedata.enemies.speed - 1) then
219 e.y = math.floor(gamedata.field.y + gamedata.enemies.y + ((y - 1) * gamedata.enemies.h * 2))
220 e.x = math.floor(gamedata.field.x + gamedata.enemies.x + ((x - 1) * gamedata.enemies.w * 2))
221 if game.collide(gamedata.player, e) or e.y > gamedata.field.y + gamedata.field.h - 20 then
222 game.over()
225 if gamedata.ammo == 0 then
226 local s = gamedata.shot
227 if s and s.screen and game.collide(e, s) then
228 gamedata.enemies.number = gamedata.enemies.number - 1
229 gamedata.ammo = 1
230 e.screen = nil
231 s.screen = nil
233 if (y % 3) == 0 then
234 gamedata.score = gamedata.score + 15
235 elseif (y % 3) == 1 then
236 gamedata.score = gamedata.score + 10
237 else
238 gamedata.score = gamedata.score + 5
240 gamedata.field.status.text = gamedata.score.." | "..gamedata.round.." "
246 local x = math.random(1, gamedata.enemies.count / gamedata.enemies.rows)
247 if gamedata.enemies.speed_count == (gamedata.enemies.speed - 1)
248 and math.random(0, 150) <= 1
249 and gamedata.enemies.data[y][x].screen then
250 shots.fire_enemy(math.floor(gamedata.field.x + gamedata.enemies.x + ((x - 1) * gamedata.enemies.w)),
251 gamedata.field.y + gamedata.enemies.y + gamedata.enemies.h,
252 "#FF0000")
256 if gamedata.enemies.number == 0 then
257 enemies.setup()
258 gamedata.round = gamedata.round + 1
259 gamedata.field.status.text = gamedata.score.." | "..gamedata.round.." "
260 if gamedata.enemies.speed > 1 then gamedata.enemies.speed = gamedata.enemies.speed - 1 end
261 return false
264 gamedata.enemies.speed_count = gamedata.enemies.speed_count + 1
265 if gamedata.enemies.speed_count < gamedata.enemies.speed then return false end
266 gamedata.enemies.speed_count = 0
267 gamedata.enemies.x = gamedata.enemies.x + math.floor((gamedata.enemies.w * gamedata.enemies.dir) / 4)
268 if gamedata.enemies.x > gamedata.field.w - (2 * gamedata.enemies.w * (gamedata.enemies.count / gamedata.enemies.rows + 1)) + 5
269 or gamedata.enemies.x <= 10 then
270 gamedata.enemies.y = gamedata.enemies.y + gamedata.enemies.h
271 gamedata.enemies.dir = gamedata.enemies.dir * (-1)
275 function keyhandler(mod, key, event)
276 if event ~= "press" then return true end
277 if gamedata.highscore.getkeys then
278 if key:len() == 1 and gamedata.name:len() < 20 then
279 gamedata.name = gamedata.name .. key
280 elseif key == "BackSpace" then
281 gamedata.name = gamedata.name:sub(1, gamedata.name:len() - 1)
282 elseif key == "Return" then
283 gamedata.highscore.window.screen = nil
284 game.highscore_add(gamedata.score, gamedata.name)
285 game.highscore_show()
286 gamedata.highscore.getkeys = false
288 gamedata.namebox.text = " Name: " .. gamedata.name .. "|"
289 else
290 if key == "Left" then
291 player.move(-10)
292 elseif key == "Right" then
293 player.move(10)
294 elseif key == "q" then
295 game.quit()
296 return false
297 elseif key == " " then
298 player.fire()
299 elseif key == "s" then
300 awful.util.spawn("import -window root "..gamedata.cachedir.."/invaders-"..os.time()..".png")
301 elseif key == "p" then
302 gamedata.running = not gamedata.running
305 return true
308 function game.collide(o1, o2)
309 --check if o2 is inside o1
310 if o2.x >= o1.x and o2.x <= o1.x + o1.width
311 and o2.y >= o1.y and o2.y <= o1.y + o1.height then
312 return true
315 return false
318 function game.over ()
319 gamedata.running = false
320 game.highscore(gamedata.score)
323 function game.quit()
324 gamedata.running = false
326 if gamedata.highscore.window then
327 gamedata.highscore.window.screen = nil
328 gamedata.highscore.window.widgets = nil
331 if gamedata.field.background then
332 gamedata.field.background.screen = nil
335 gamedata.player.screen = nil
336 gamedata.player.widgets = nil
337 gamedata.player = nil
339 gamedata.field.north.screen = nil
340 gamedata.field.north = nil
342 gamedata.field.south.screen = nil
343 gamedata.field.south = nil
345 gamedata.field.west.screen = nil
346 gamedata.field.west = nil
348 gamedata.field.east.screen = nil
349 gamedata.field.east = nil
351 for y = 1, #gamedata.enemies.data do
352 for x = 1, #gamedata.enemies.data[y] do
353 gamedata.enemies.data[y][x].screen = nil
354 gamedata.enemies.data[y][x].widgets = nil
358 if gamedata.shot then gamedata.shot.screen = nil end
360 for i = 1, gamedata.enemies.shots.max do
361 if gamedata.enemies.shots[i] then gamedata.enemies.shots[i].screen = nil end
365 function game.highscore_show ()
366 gamedata.highscore.table = widget({ type = "textbox" })
368 gamedata.highscore.table.text = "\tHighscores:\t\n"
370 for i = 1, 5 do
371 gamedata.highscore.table.text = gamedata.highscore.table.text .. "\n\t" .. gamedata.highscore[i]
374 gamedata.highscore.table.text = gamedata.highscore.table.text .. "\n\n Press Q to quit"
376 gamedata.highscore.window:geometry(gamedata.highscore.table:extents())
377 gamedata.highscore.window:geometry({ x = gamedata.field.x + math.floor(gamedata.field.w / 2) - 100,
378 y = gamedata.field.y + math.floor(gamedata.field.h / 2) - 55 })
379 gamedata.highscore.window.screen = gamedata.screen
381 gamedata.highscore.window.widgets = gamedata.highscore.table
383 local fh = io.open(gamedata.cachedir.."/highscore_invaders", "w")
385 if not fh then
386 return false
389 for i = 1, 5 do
390 fh:write(gamedata.highscore[i].."\n")
393 fh:close()
396 function game.highscore_add (score, name)
397 local t = gamedata.highscore
399 for i = 5, 1, -1 do
400 if tonumber(t[i]:match("(%d+) ")) <= score then
401 if t[i+1] then t[i+1] = t[i] end
402 t[i] = score .. " " .. name
406 gamedata.highscore = t
409 function game.highscore (score)
410 if gamedata.highscore.window and gamedata.highscore.window.screen then return false end
411 local fh = io.open(gamedata.cachedir.."/highscore_invaders", "r")
413 if fh then
414 for i = 1, 5 do
415 gamedata.highscore[i] = fh:read("*line")
417 fh:close()
418 else
419 for i = 1, 5 do
420 gamedata.highscore[i] = ((6-i)*20).." foo"
424 local newentry = false
425 for i = 1, 5 do
426 local s = tonumber(gamedata.highscore[i]:match("(%d+) .*"))
427 if s <= score then newentry = true end
430 gamedata.highscore.window = wibox({ bg = gamedata.btheme.bg_focus or "#333333",
431 fg = gamedata.btheme.fg_focus or "#FFFFFF",
432 height = 20,
433 width = 300,
434 x = gamedata.field.x + math.floor(gamedata.field.w / 2) - 150,
435 y = gamedata.field.y + math.floor(gamedata.field.h / 2) })
436 gamedata.highscore.window.screen = gamedata.screen
438 gamedata.namebox = widget({ type = "textbox" })
439 gamedata.namebox.text = " Name: |"
440 gamedata.highscore.window.widgets = gamedata.namebox
442 if newentry then
443 gamedata.name = ""
444 gamedata.highscore.getkeys = true
445 else
446 game.highscore_show()
450 --- Run Awesome Invaders
451 -- @param args A table with parameters.
452 -- x the X coordinate of the playing field.
453 -- y the Y coordinate of the playing field.
454 -- if either of these is left out, the game is placed in the center of the focused screen.
455 -- solidbg the background color of the playing field. If none is given, the playing field is transparent.
456 function run(args)
457 gamedata.screen = capi.screen[capi.mouse.screen]
458 gamedata.field.x = gamedata.screen.geometry.x + math.floor((gamedata.screen.geometry.width - gamedata.field.w) / 2)
459 gamedata.field.y = gamedata.screen.geometry.y + math.floor((gamedata.screen.geometry.height - gamedata.field.h) / 2)
460 gamedata.screen = capi.mouse.screen
462 if args then
463 if args['x'] then gamedata.field.x = args['x'] end
464 if args['y'] then gamedata.field.y = args['y'] end
465 if args['solidbg'] then gamedata.solidbg = args['solidbg'] end
468 gamedata.score = 0
469 gamedata.name = ""
470 gamedata.ammo = 1
471 gamedata.round = 1
472 gamedata.btheme = beautiful.get()
474 gamedata.cachedir = awful.util.getdir("cache")
476 if gamedata.solidbg then
477 gamedata.field.background = wibox({ bg = gamedata.solidbg,
478 x = gamedata.field.x,
479 y = gamedata.field.y,
480 height = gamedata.field.h,
481 width = gamedata.field.w })
482 gamedata.field.background.screen = gamedata.screen
485 gamedata.field.status = widget({ type = "textbox" })
486 gamedata.field.status.text = gamedata.score.." | "..gamedata.round .. " "
488 gamedata.field.caption = widget({ type = "textbox" })
489 gamedata.field.caption.text = " Awesome Invaders"
491 gamedata.field.north = wibox({ bg = gamedata.btheme.bg_focus or "#333333",
492 fg = gamedata.btheme.fg_focus or "#FFFFFF",
493 width = gamedata.field.w + 10,
494 height = gamedata.field.caption:extents()["height"],
495 x = gamedata.field.x - 5,
496 y = gamedata.field.y - gamedata.field.caption:extents()["height"] })
497 gamedata.field.north.screen = gamedata.screen
499 gamedata.field.north.widgets = {
501 gamedata.field.caption,
502 ["layout"] = awful.widget.layout.horizontal.leftright
505 gamedata.field.status,
506 ["layout"] = awful.widget.layout.horizontal.rightleft
508 ["layout"] = awful.widget.layout.horizontal.rightleft
511 gamedata.field.south = wibox({ bg = gamedata.btheme.bg_focus or "#333333",
512 fg = gamedata.btheme.fg_focus or "#FFFFFF",
513 width = gamedata.field.w,
514 height = 5,
515 x = gamedata.field.x,
516 y = gamedata.field.y + gamedata.field.h - 5 })
517 gamedata.field.south.screen = gamedata.screen
519 gamedata.field.west = wibox({ bg = gamedata.btheme.bg_focus or "#333333",
520 fg = gamedata.btheme.fg_focus or "#FFFFFF",
521 width = 5,
522 height = gamedata.field.h,
523 x = gamedata.field.x - 5,
524 y = gamedata.field.y })
525 gamedata.field.west.screen = gamedata.screen
527 gamedata.field.east = wibox({ bg = gamedata.btheme.bg_focus or "#333333",
528 fg = gamedata.btheme.fg_focus or "#FFFFFF",
529 width = 5,
530 height = gamedata.field.h,
531 x = gamedata.field.x + gamedata.field.w,
532 y = gamedata.field.y })
533 gamedata.field.east.screen = gamedata.screen
535 gamedata.enemies.speed = 5
536 enemies.setup()
538 gamedata.player = player.new()
539 capi.keygrabber.run(keyhandler)
540 gamedata.running = true
543 -- Start timers
544 local t = timer { timeout = 0.02 }
545 t:add_signal("timeout", shots.handle)
546 t:start()
547 local t = timer { timeout = 0.03 }
548 t:add_signal("timeout", shots.handle_enemy)
549 t:start()
550 local t = timer { timeout = 0.01 }
551 t:add_signal("timeout", enemies.handle)
552 t:start()