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
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")
63 function player
.new ()
64 p
= wibox({ position
= "floating",
65 bg
= gamedata
.solidbg
or "#12345600" })
66 p
:geometry({ width
= 24,
68 x
= gamedata
.field
.x
+ (gamedata
.field
.w
/ 2),
69 y
= gamedata
.field
.y
+ gamedata
.field
.h
- (16 + 5) })
72 w
= widget({ type = "imagebox", name
= "player" })
73 w
.image
= image("@AWESOME_ICON_PATH@/invaders/player.png")
79 function player
.move(x
)
80 local g
= gamedata
.player
:geometry()
82 if x
< 0 and g
.x
> gamedata
.field
.x
then
84 elseif x
> 0 and g
.x
< gamedata
.field
.x
+ gamedata
.field
.w
- 30 then
88 gamedata
.player
:geometry(g
)
91 function player
.fire()
92 if gamedata
.ammo
== 1 then
94 local gb
= gamedata
.player
:geometry()
95 shots
.fire(gb
.x
+ 9, gb
.y
- 10, "#00FF00")
99 function shots
.fire (x
, y
, color
)
100 local s
= wibox({ position
= "floating",
102 s
:geometry({ width
= 4,
108 if not gamedata
.shot
or gamedata
.shot
.screen
== nil then
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",
118 s
:geometry({ width
= 4,
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
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
139 local g
= s
:geometry()
140 if g
.y
< gamedata
.field
.y
+ 15 then
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
161 gamedata
.enemies
.shots
.fired
= gamedata
.enemies
.shots
.fired
- 1
166 if game
.collide(gamedata
.player
, s
) then
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
})
181 w
= widget({ type = "imagebox",
183 w
.image
= gamedata
.enemies
[t
]
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
]
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))
233 if game
.collide(gamedata
.player
, e
) or g
.y
> gamedata
.field
.y
+ gamedata
.field
.h
- 20 then
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
246 gamedata
.score
= gamedata
.score
+ 15
247 elseif (y
% 3) == 1 then
248 gamedata
.score
= gamedata
.score
+ 10
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
,
268 if gamedata
.enemies
.number == 0 then
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
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
.. "|"
301 if key
== "Left" then
303 elseif key
== "Right" then
305 elseif key
== "q" then
308 elseif key
== " " then
310 elseif key
== "s" then
311 awful
.util
.spawn("import -window root "..gamedata
.cachedir
.."/awesome/invaders-"..os
.time()..".png")
317 function game
.collide(o1
, o2
)
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
330 function game
.over ()
331 gamedata
.running
= false
332 game
.highscore(gamedata
.score
)
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,
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"
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")
403 fh
:write(gamedata
.highscore
[i
].."\n")
409 function game
.highscore_add (score
, name
)
410 local t
= gamedata
.highscore
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")
427 gamedata
.highscore
[i
] = fh
:read("*line")
432 gamedata
.highscore
[i
] = ((6-i
)*20).." foo"
436 local newentry
= false
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,
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",
453 gamedata
.namebox
.text
= " Name: |"
454 gamedata
.highscore
.window
.widgets
= gamedata
.namebox
458 gamedata
.highscore
.getkeys
= true
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.
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)
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
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,
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",
511 gamedata
.field
.status
.text
= gamedata
.score
.." | "..gamedata
.round
.. " "
513 gamedata
.field
.caption
= widget({ type = "textbox",
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
,
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
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
)