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.
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,
16 -- Anyway, have fun :)
18 -- Start this by adding
19 -- require("invaders")
20 -- to the top of your rc.lua and adding
22 -- to the bottom of rc.lua or as a keybinding
27 local awful
= require("awful")
28 local beautiful
= require("awful.beautiful")
30 local capi
= { screen
= screen
, mouse
= mouse
, keygrabber
= keygrabber
}
32 local tonumber = tonumber
38 --- Space Invaders look-alike
43 gamedata
.field
.x
= 100
44 gamedata
.field
.y
= 100
45 gamedata
.field
.h
= 400
46 gamedata
.field
.w
= 600
47 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")
64 function player
.new ()
65 p
= wibox({ position
= "floating",
66 bg
= gamedata
.solidbg
or "#12345600" })
67 p
:geometry({ width
= 24,
69 x
= gamedata
.field
.x
+ (gamedata
.field
.w
/ 2),
70 y
= gamedata
.field
.y
+ gamedata
.field
.h
- (16 + 5) })
73 w
= widget({ type = "imagebox", name
= "player" })
74 w
.image
= image("@AWESOME_ICON_PATH@/invaders/player.png")
80 function player
.move(x
)
81 local gb
= gamedata
.player
:geometry()
83 if x
< 0 and gb
.x
> gamedata
.field
.x
then
85 elseif x
> 0 and gb
.x
< gamedata
.field
.x
+ gamedata
.field
.w
- 30 then
89 gamedata
.player
:geometry(gb
)
92 function player
.fire()
93 if gamedata
.ammo
> 0 then
94 gamedata
.ammo
= gamedata
.ammo
- 1
95 gamedata
.field
.status
.text
= gamedata
.score
.." | "..gamedata
.ammo
.. " "
96 local gb
= gamedata
.player
:geometry()
97 shots
.fire(gb
.x
+ 9, gb
.y
- 10, "#00FF00")
101 function shots
.fire (x
, y
, color
)
102 local s
= wibox({ position
= "floating",
104 s
:geometry({ width
= 4,
110 if not gamedata
.shot
or gamedata
.shot
.screen
== nil then
115 function shots
.fire_enemy (x
, y
, color
)
116 if gamedata
.enemies
.shots
.fired
< gamedata
.enemies
.shots
.max then
117 gamedata
.enemies
.shots
.fired
= gamedata
.enemies
.shots
.fired
+ 1
118 local s
= wibox({ position
= "floating",
120 s
:geometry({ width
= 4,
125 for i
= 1, gamedata
.enemies
.shots
.max do
126 if not gamedata
.enemies
.shots
[i
] or gamedata
.enemies
.shots
[i
].screen
== nil then
127 gamedata
.enemies
.shots
[i
] = s
134 function shots
.handle()
135 if not gamedata
.running
then return false end
136 if gamedata
.ammo
== gamedata
.ammo_max
then return false end
138 gamedata
.ammo
= gamedata
.ammo_max
140 local s
= gamedata
.shot
141 if s
and s
.screen
then
142 gamedata
.ammo
= gamedata
.ammo
- 1
143 local g
= s
:geometry()
144 if g
.y
< gamedata
.field
.y
+ 15 then
146 gamedata
.ammo
= gamedata
.ammo
+ 1
152 gamedata
.field
.status
.text
= gamedata
.score
.." | "..gamedata
.ammo
.. " "
156 function shots
.handle_enemy ()
157 if not gamedata
.running
then return false end
158 if gamedata
.enemies
.shots
.fired
== 0 then return false end
160 for i
= 1, gamedata
.enemies
.shots
.max do
161 local s
= gamedata
.enemies
.shots
[i
]
162 if s
and s
.screen
then
163 local g
= s
:geometry()
164 if g
.y
> gamedata
.field
.y
+ gamedata
.field
.h
- 15 then
166 gamedata
.enemies
.shots
.fired
= gamedata
.enemies
.shots
.fired
- 1
171 if game
.collide(gamedata
.player
, s
) then
178 function enemies
.new (t
)
179 e
= wibox({ position
= "floating",
180 bg
= gamedata
.solidbg
or "#12345600" })
181 e
:geometry({ height
= gamedata
.enemies
.h
,
182 width
= gamedata
.enemies
.w
,
183 x
= gamedata
.field
.x
,
184 y
= gamedata
.field
.y
})
186 w
= widget({ type = "imagebox",
188 w
.image
= gamedata
.enemies
[t
]
194 function enemies
.setup()
195 gamedata
.enemies
.data
= { }
196 gamedata
.enemies
.x
= 10
197 gamedata
.enemies
.y
= 5
198 gamedata
.enemies
.dir
= 1
199 if not gamedata
.enemies
.shots
then gamedata
.enemies
.shots
= { } end
200 gamedata
.enemies
.shots
.max = 10
201 gamedata
.enemies
.shots
.fired
= 0
203 gamedata
.enemies
.speed_count
= 0
205 for y
= 1, gamedata
.enemies
.rows
do
206 gamedata
.enemies
.data
[y
] = { }
207 for x
= 1, math
.ceil((gamedata
.enemies
.count
/ gamedata
.enemies
.rows
) + 1) do
208 gamedata
.enemies
.data
[y
][x
] = enemies
.new((y
% 3) + 1)
212 if gamedata
.shot
then
213 gamedata
.shot
.screen
= nil
216 for i
= 1, gamedata
.enemies
.shots
.max do
217 if gamedata
.enemies
.shots
[i
] then
218 gamedata
.enemies
.shots
[i
].screen
= nil
223 function enemies
.handle ()
224 if not gamedata
.running
then return false end
226 gamedata
.enemies
.number = 0
228 for y
= 1, #gamedata
.enemies
.data
do
229 for x
= 1, #gamedata
.enemies
.data
[y
] do
230 local e
= gamedata
.enemies
.data
[y
][x
]
232 local g
= e
:geometry()
233 gamedata
.enemies
.number = gamedata
.enemies
.number + 1
234 if gamedata
.enemies
.speed_count
== (gamedata
.enemies
.speed
- 1) then
235 g
.y
= math
.floor(gamedata
.field
.y
+ gamedata
.enemies
.y
+ ((y
- 1) * gamedata
.enemies
.h
* 2))
236 g
.x
= math
.floor(gamedata
.field
.x
+ gamedata
.enemies
.x
+ ((x
- 1) * gamedata
.enemies
.w
* 2))
238 if game
.collide(gamedata
.player
, e
) or g
.y
> gamedata
.field
.y
+ gamedata
.field
.h
- 20 then
242 if gamedata
.ammo
== 0 then
243 local s
= gamedata
.shot
244 if s
and s
.screen
and game
.collide(e
, s
) then
245 gamedata
.enemies
.number = gamedata
.enemies
.number - 1
250 gamedata
.score
= gamedata
.score
+ 15
251 elseif (y
% 3) == 1 then
252 gamedata
.score
= gamedata
.score
+ 10
254 gamedata
.score
= gamedata
.score
+ 5
257 gamedata
.field
.status
.text
= gamedata
.score
.." | "..gamedata
.ammo
.." "
264 local x
= math
.random(1, gamedata
.enemies
.count
/ gamedata
.enemies
.rows
)
265 if gamedata
.enemies
.speed_count
== (gamedata
.enemies
.speed
- 1)
266 and math
.random(0, 150) <= 1
267 and gamedata
.enemies
.data
[y
][x
].screen
then
268 shots
.fire_enemy(math
.floor(gamedata
.field
.x
+ gamedata
.enemies
.x
+ ((x
- 1) * gamedata
.enemies
.w
)),
269 gamedata
.field
.y
+ gamedata
.enemies
.y
+ gamedata
.enemies
.h
,
274 if gamedata
.enemies
.number == 0 then
276 if gamedata
.enemies
.speed
> 1 then gamedata
.enemies
.speed
= gamedata
.enemies
.speed
- 1 end
280 gamedata
.enemies
.speed_count
= gamedata
.enemies
.speed_count
+ 1
281 if gamedata
.enemies
.speed_count
< gamedata
.enemies
.speed
then return false end
282 gamedata
.enemies
.speed_count
= 0
283 gamedata
.enemies
.x
= gamedata
.enemies
.x
+ math
.floor((gamedata
.enemies
.w
* gamedata
.enemies
.dir
) / 4)
284 if gamedata
.enemies
.x
> gamedata
.field
.w
- (2 * gamedata
.enemies
.w
* (gamedata
.enemies
.count
/ gamedata
.enemies
.rows
+ 1)) + 5
285 or gamedata
.enemies
.x
<= 10 then
286 gamedata
.enemies
.y
= gamedata
.enemies
.y
+ gamedata
.enemies
.h
287 gamedata
.enemies
.dir
= gamedata
.enemies
.dir
* (-1)
291 function keyhandler(mod, key
)
292 if gamedata
.highscore
.getkeys
then
293 if key
:len() == 1 and gamedata
.name
:len() < 20 then
294 gamedata
.name
= gamedata
.name
.. key
295 elseif key
== "BackSpace" then
296 gamedata
.name
= gamedata
.name
:sub(1, gamedata
.name
:len() - 1)
297 elseif key
== "Return" then
298 gamedata
.highscore
.window
.screen
= nil
299 game
.highscore_add(gamedata
.score
, gamedata
.name
)
300 game
.highscore_show()
301 gamedata
.highscore
.getkeys
= false
303 gamedata
.namebox
.text
= " Name: " .. gamedata
.name
.. "|"
305 if key
== "Left" then
307 elseif key
== "Right" then
309 elseif key
== "q" then
312 elseif key
== " " then
314 elseif key
== "s" then
315 awful
.util
.spawn("import -window root "..gamedata
.cachedir
.."/awesome/invaders-"..os
.time()..".png")
321 function game
.collide(o1
, o2
)
325 --check if o2 is inside o1
326 if g2
.x
>= g1
.x
and g2
.x
<= g1
.x
+ g1
.width
327 and g2
.y
>= g1
.y
and g2
.y
<= g1
.y
+ g1
.height
then
334 function game
.over ()
335 gamedata
.running
= false
336 game
.highscore(gamedata
.score
)
340 gamedata
.running
= false
342 if gamedata
.highscore
.window
then
343 gamedata
.highscore
.window
.screen
= nil
344 gamedata
.highscore
.window
.widgets
= nil
347 if gamedata
.field
.background
then
348 gamedata
.field
.background
.screen
= nil
351 gamedata
.player
.screen
= nil
352 gamedata
.player
.widgets
= nil
353 gamedata
.player
= nil
355 gamedata
.field
.north
.screen
= nil
356 gamedata
.field
.north
= nil
358 gamedata
.field
.south
.screen
= nil
359 gamedata
.field
.south
= nil
361 gamedata
.field
.west
.screen
= nil
362 gamedata
.field
.west
= nil
364 gamedata
.field
.east
.screen
= nil
365 gamedata
.field
.east
= nil
367 for y
= 1, #gamedata
.enemies
.data
do
368 for x
= 1, #gamedata
.enemies
.data
[y
] do
369 gamedata
.enemies
.data
[y
][x
].screen
= nil
370 gamedata
.enemies
.data
[y
][x
].widgets
= nil
374 if gamedata
.shot
then gamedata
.shot
.screen
= nil end
376 for i
= 1, gamedata
.enemies
.shots
.max do
377 if gamedata
.enemies
.shots
[i
] then gamedata
.enemies
.shots
[i
].screen
= nil end
381 function game
.highscore_show ()
382 gamedata
.highscore
.window
:geometry({ height
= 140,
384 x
= gamedata
.field
.x
+ math
.floor(gamedata
.field
.w
/ 2) - 100,
385 y
= gamedata
.field
.y
+ math
.floor(gamedata
.field
.h
/ 2) - 55 })
386 gamedata
.highscore
.window
.screen
= 1
388 gamedata
.highscore
.table = widget({ type = "textbox",
389 name
= "highscore" })
390 gamedata
.highscore
.window
.widgets
= gamedata
.highscore
.table
392 gamedata
.highscore
.table.text
= " Highscores:\n"
395 gamedata
.highscore
.table.text
= gamedata
.highscore
.table.text
.. "\n\t" .. gamedata
.highscore
[i
]
398 gamedata
.highscore
.table.text
= gamedata
.highscore
.table.text
.. "\n\n Press Q to quit"
400 local fh
= io
.open(gamedata
.cachedir
.."/highscore_invaders", "w")
407 fh
:write(gamedata
.highscore
[i
].."\n")
413 function game
.highscore_add (score
, name
)
414 local t
= gamedata
.highscore
417 if tonumber(t
[i
]:match("(%d+) ")) <= score
then
418 if t
[i
+1] then t
[i
+1] = t
[i
] end
419 t
[i
] = score
.. " " .. name
423 gamedata
.highscore
= t
426 function game
.highscore (score
)
427 local fh
= io
.open(gamedata
.cachedir
.."/highscore_invaders", "r")
431 gamedata
.highscore
[i
] = fh
:read("*line")
436 gamedata
.highscore
[i
] = ((6-i
)*20).." foo"
440 local newentry
= false
442 local s
= tonumber(gamedata
.highscore
[i
]:match("(%d+) .*"))
443 if s
<= score
then newentry
= true end
446 gamedata
.highscore
.window
= wibox({ position
= "floating",
447 bg
= gamedata
.btheme
.bg_focus
or "#333333",
448 fg
= gamedata
.btheme
.fg_focus
or "#FFFFFF" })
449 gamedata
.highscore
.window
:geometry({ height
= 20,
451 x
= gamedata
.field
.x
+ math
.floor(gamedata
.field
.w
/ 2) - 150,
452 y
= gamedata
.field
.y
+ math
.floor(gamedata
.field
.h
/ 2) })
453 gamedata
.highscore
.window
.screen
= 1
455 gamedata
.namebox
= widget({ type = "textbox",
457 gamedata
.namebox
.text
= " Name: |"
458 gamedata
.highscore
.window
.widgets
= gamedata
.namebox
462 gamedata
.highscore
.getkeys
= true
464 game
.highscore_show()
468 --- Run Awesome Invaders
469 -- @param args A table with parameters.
470 -- x the X coordinate of the playing field.
471 -- y the Y coordinate of the playing field.
472 -- if either of these is left out, the game is placed in the center of the focused screen.
473 -- solidbg the background color of the playing field. If none is given, the playing field is transparent.
475 gamedata
.screen
= capi
.screen
[capi
.mouse
.screen
]
476 gamedata
.field
.x
= gamedata
.screen
.geometry
.x
+ math
.floor((gamedata
.screen
.geometry
.width
- gamedata
.field
.w
) / 2)
477 gamedata
.field
.y
= gamedata
.screen
.geometry
.y
+ math
.floor((gamedata
.screen
.geometry
.height
- gamedata
.field
.h
) / 2)
480 if args
['x'] then gamedata
.field
.x
= args
['x'] end
481 if args
['y'] then gamedata
.field
.y
= args
['y'] end
482 if args
['solidbg'] then gamedata
.solidbg
= args
['solidbg'] end
487 gamedata
.ammo
= gamedata
.ammo_max
488 gamedata
.btheme
= beautiful
.get()
490 gamedata
.cachedir
= awful
.util
.getdir("cache")
492 if gamedata
.solidbg
then
493 gamedata
.field
.background
= wibox({ position
= "floating",
494 bg
= gamedata
.solidbg
})
495 gamedata
.field
.background
:geometry({ x
= gamedata
.field
.x
,
496 y
= gamedata
.field
.y
,
497 height
= gamedata
.field
.h
,
498 width
= gamedata
.field
.w
})
499 gamedata
.field
.background
.screen
= 1
502 gamedata
.field
.north
= wibox({ position
= "floating",
503 bg
= gamedata
.btheme
.bg_focus
or "#333333",
504 fg
= gamedata
.btheme
.fg_focus
or "#FFFFFF" })
505 gamedata
.field
.north
:geometry({ width
= gamedata
.field
.w
+ 10,
507 x
= gamedata
.field
.x
- 5,
508 y
= gamedata
.field
.y
- 15 })
509 gamedata
.field
.north
.screen
= 1
511 gamedata
.field
.status
= widget({ type = "textbox",
514 gamedata
.field
.status
.text
= gamedata
.score
.." | "..gamedata
.ammo
.. " "
516 gamedata
.field
.caption
= widget({ type = "textbox",
519 gamedata
.field
.caption
.text
= " Awesome Invaders"
521 gamedata
.field
.north
.widgets
= { gamedata
.field
.caption
, gamedata
.field
.status
}
523 gamedata
.field
.south
= wibox({ position
= "floating",
524 bg
= gamedata
.btheme
.bg_focus
or "#333333",
525 fg
= gamedata
.btheme
.fg_focus
or "#FFFFFF" })
526 gamedata
.field
.south
:geometry({ width
= gamedata
.field
.w
,
528 x
= gamedata
.field
.x
,
529 y
= gamedata
.field
.y
+ gamedata
.field
.h
- 5 })
530 gamedata
.field
.south
.screen
= 1
532 gamedata
.field
.west
= wibox({ position
= "floating",
533 bg
= gamedata
.btheme
.bg_focus
or "#333333",
534 fg
= gamedata
.btheme
.fg_focus
or "#FFFFFF" })
535 gamedata
.field
.west
:geometry({ width
= 5,
536 height
= gamedata
.field
.h
,
537 x
= gamedata
.field
.x
- 5,
538 y
= gamedata
.field
.y
})
539 gamedata
.field
.west
.screen
= 1
541 gamedata
.field
.east
= wibox({ position
= "floating",
542 bg
= gamedata
.btheme
.bg_focus
or "#333333",
543 fg
= gamedata
.btheme
.fg_focus
or "#FFFFFF" })
544 gamedata
.field
.east
:geometry({ width
= 5,
545 height
= gamedata
.field
.h
,
546 x
= gamedata
.field
.x
+ gamedata
.field
.w
,
547 y
= gamedata
.field
.y
})
548 gamedata
.field
.east
.screen
= 1
550 gamedata
.enemies
.speed
= 5
553 gamedata
.player
= player
.new()
554 capi
.keygrabber
.run(keyhandler
)
555 gamedata
.running
= true
558 awful
.hooks
.timer
.register(0.02, shots
.handle
)
559 awful
.hooks
.timer
.register(0.03, shots
.handle_enemy
)
560 awful
.hooks
.timer
.register(0.01, enemies
.handle
)