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("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" })
73 w
.image
= image("@AWESOME_ICON_PATH@/invaders/player.png")
79 function player
.move(x
)
80 if not gamedata
.running
then return false end
81 local g
= gamedata
.player
:geometry()
83 if x
< 0 and g
.x
> gamedata
.field
.x
then
85 elseif x
> 0 and g
.x
< gamedata
.field
.x
+ gamedata
.field
.w
- 30 then
89 gamedata
.player
:geometry(g
)
92 function player
.fire()
93 if not gamedata
.running
then return false end
94 if gamedata
.ammo
== 1 then
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
== 1 then return false end
138 local s
= gamedata
.shot
139 if s
and s
.screen
then
141 local g
= s
:geometry()
142 if g
.y
< gamedata
.field
.y
+ 15 then
153 function shots
.handle_enemy ()
154 if not gamedata
.running
then return false end
155 if gamedata
.enemies
.shots
.fired
== 0 then return false end
157 for i
= 1, gamedata
.enemies
.shots
.max do
158 local s
= gamedata
.enemies
.shots
[i
]
159 if s
and s
.screen
then
160 local g
= s
:geometry()
161 if g
.y
> gamedata
.field
.y
+ gamedata
.field
.h
- 15 then
163 gamedata
.enemies
.shots
.fired
= gamedata
.enemies
.shots
.fired
- 1
168 if game
.collide(gamedata
.player
, s
) then
175 function enemies
.new (t
)
176 e
= wibox({ position
= "floating",
177 bg
= gamedata
.solidbg
or "#12345600" })
178 e
:geometry({ height
= gamedata
.enemies
.h
,
179 width
= gamedata
.enemies
.w
,
180 x
= gamedata
.field
.x
,
181 y
= gamedata
.field
.y
})
183 w
= widget({ type = "imagebox" })
184 w
.image
= gamedata
.enemies
[t
]
190 function enemies
.setup()
191 gamedata
.enemies
.data
= { }
192 gamedata
.enemies
.x
= 10
193 gamedata
.enemies
.y
= 5
194 gamedata
.enemies
.dir
= 1
195 if not gamedata
.enemies
.shots
then gamedata
.enemies
.shots
= { } end
196 gamedata
.enemies
.shots
.max = 10
197 gamedata
.enemies
.shots
.fired
= 0
199 gamedata
.enemies
.speed_count
= 0
201 for y
= 1, gamedata
.enemies
.rows
do
202 gamedata
.enemies
.data
[y
] = { }
203 for x
= 1, math
.ceil((gamedata
.enemies
.count
/ gamedata
.enemies
.rows
) + 1) do
204 gamedata
.enemies
.data
[y
][x
] = enemies
.new((y
% 3) + 1)
208 if gamedata
.shot
then
209 gamedata
.shot
.screen
= nil
212 for i
= 1, gamedata
.enemies
.shots
.max do
213 if gamedata
.enemies
.shots
[i
] then
214 gamedata
.enemies
.shots
[i
].screen
= nil
219 function enemies
.handle ()
220 if not gamedata
.running
then return false end
222 gamedata
.enemies
.number = 0
224 for y
= 1, #gamedata
.enemies
.data
do
225 for x
= 1, #gamedata
.enemies
.data
[y
] do
226 local e
= gamedata
.enemies
.data
[y
][x
]
228 local g
= e
:geometry()
229 gamedata
.enemies
.number = gamedata
.enemies
.number + 1
230 if gamedata
.enemies
.speed_count
== (gamedata
.enemies
.speed
- 1) then
231 g
.y
= math
.floor(gamedata
.field
.y
+ gamedata
.enemies
.y
+ ((y
- 1) * gamedata
.enemies
.h
* 2))
232 g
.x
= math
.floor(gamedata
.field
.x
+ gamedata
.enemies
.x
+ ((x
- 1) * gamedata
.enemies
.w
* 2))
234 if game
.collide(gamedata
.player
, e
) or g
.y
> gamedata
.field
.y
+ gamedata
.field
.h
- 20 then
238 if gamedata
.ammo
== 0 then
239 local s
= gamedata
.shot
240 if s
and s
.screen
and game
.collide(e
, s
) then
241 gamedata
.enemies
.number = gamedata
.enemies
.number - 1
247 gamedata
.score
= gamedata
.score
+ 15
248 elseif (y
% 3) == 1 then
249 gamedata
.score
= gamedata
.score
+ 10
251 gamedata
.score
= gamedata
.score
+ 5
253 gamedata
.field
.status
.text
= gamedata
.score
.." | "..gamedata
.round
.." "
259 local x
= math
.random(1, gamedata
.enemies
.count
/ gamedata
.enemies
.rows
)
260 if gamedata
.enemies
.speed_count
== (gamedata
.enemies
.speed
- 1)
261 and math
.random(0, 150) <= 1
262 and gamedata
.enemies
.data
[y
][x
].screen
then
263 shots
.fire_enemy(math
.floor(gamedata
.field
.x
+ gamedata
.enemies
.x
+ ((x
- 1) * gamedata
.enemies
.w
)),
264 gamedata
.field
.y
+ gamedata
.enemies
.y
+ gamedata
.enemies
.h
,
269 if gamedata
.enemies
.number == 0 then
271 gamedata
.round
= gamedata
.round
+ 1
272 gamedata
.field
.status
.text
= gamedata
.score
.." | "..gamedata
.round
.." "
273 if gamedata
.enemies
.speed
> 1 then gamedata
.enemies
.speed
= gamedata
.enemies
.speed
- 1 end
277 gamedata
.enemies
.speed_count
= gamedata
.enemies
.speed_count
+ 1
278 if gamedata
.enemies
.speed_count
< gamedata
.enemies
.speed
then return false end
279 gamedata
.enemies
.speed_count
= 0
280 gamedata
.enemies
.x
= gamedata
.enemies
.x
+ math
.floor((gamedata
.enemies
.w
* gamedata
.enemies
.dir
) / 4)
281 if gamedata
.enemies
.x
> gamedata
.field
.w
- (2 * gamedata
.enemies
.w
* (gamedata
.enemies
.count
/ gamedata
.enemies
.rows
+ 1)) + 5
282 or gamedata
.enemies
.x
<= 10 then
283 gamedata
.enemies
.y
= gamedata
.enemies
.y
+ gamedata
.enemies
.h
284 gamedata
.enemies
.dir
= gamedata
.enemies
.dir
* (-1)
288 function keyhandler(mod, key
, event
)
289 if event
~= "press" then return true end
290 if gamedata
.highscore
.getkeys
then
291 if key
:len() == 1 and gamedata
.name
:len() < 20 then
292 gamedata
.name
= gamedata
.name
.. key
293 elseif key
== "BackSpace" then
294 gamedata
.name
= gamedata
.name
:sub(1, gamedata
.name
:len() - 1)
295 elseif key
== "Return" then
296 gamedata
.highscore
.window
.screen
= nil
297 game
.highscore_add(gamedata
.score
, gamedata
.name
)
298 game
.highscore_show()
299 gamedata
.highscore
.getkeys
= false
301 gamedata
.namebox
.text
= " Name: " .. gamedata
.name
.. "|"
303 if key
== "Left" then
305 elseif key
== "Right" then
307 elseif key
== "q" then
310 elseif key
== " " then
312 elseif key
== "s" then
313 awful
.util
.spawn("import -window root "..gamedata
.cachedir
.."/invaders-"..os
.time()..".png")
314 elseif key
== "p" then
315 gamedata
.running
= not gamedata
.running
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 gamedata
.highscore
.window
.widgets
= gamedata
.highscore
.table
391 gamedata
.highscore
.table.text
= " Highscores:\n"
394 gamedata
.highscore
.table.text
= gamedata
.highscore
.table.text
.. "\n\t" .. gamedata
.highscore
[i
]
397 gamedata
.highscore
.table.text
= gamedata
.highscore
.table.text
.. "\n\n Press Q to quit"
399 local fh
= io
.open(gamedata
.cachedir
.."/highscore_invaders", "w")
406 fh
:write(gamedata
.highscore
[i
].."\n")
412 function game
.highscore_add (score
, name
)
413 local t
= gamedata
.highscore
416 if tonumber(t
[i
]:match("(%d+) ")) <= score
then
417 if t
[i
+1] then t
[i
+1] = t
[i
] end
418 t
[i
] = score
.. " " .. name
422 gamedata
.highscore
= t
425 function game
.highscore (score
)
426 if gamedata
.highscore
.window
and gamedata
.highscore
.window
.screen
then return false end
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" })
456 gamedata
.namebox
.text
= " Name: |"
457 gamedata
.highscore
.window
.widgets
= gamedata
.namebox
461 gamedata
.highscore
.getkeys
= true
463 game
.highscore_show()
467 --- Run Awesome Invaders
468 -- @param args A table with parameters.
469 -- x the X coordinate of the playing field.
470 -- y the Y coordinate of the playing field.
471 -- if either of these is left out, the game is placed in the center of the focused screen.
472 -- solidbg the background color of the playing field. If none is given, the playing field is transparent.
474 gamedata
.screen
= capi
.screen
[capi
.mouse
.screen
]
475 gamedata
.field
.x
= gamedata
.screen
.geometry
.x
+ math
.floor((gamedata
.screen
.geometry
.width
- gamedata
.field
.w
) / 2)
476 gamedata
.field
.y
= gamedata
.screen
.geometry
.y
+ math
.floor((gamedata
.screen
.geometry
.height
- gamedata
.field
.h
) / 2)
479 if args
['x'] then gamedata
.field
.x
= args
['x'] end
480 if args
['y'] then gamedata
.field
.y
= args
['y'] end
481 if args
['solidbg'] then gamedata
.solidbg
= args
['solidbg'] end
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",
513 gamedata
.field
.status
.text
= gamedata
.score
.." | "..gamedata
.round
.. " "
515 gamedata
.field
.caption
= widget({ type = "textbox",
517 gamedata
.field
.caption
.text
= " Awesome Invaders"
519 gamedata
.field
.north
.widgets
= { gamedata
.field
.caption
, gamedata
.field
.status
}
521 gamedata
.field
.south
= wibox({ position
= "floating",
522 bg
= gamedata
.btheme
.bg_focus
or "#333333",
523 fg
= gamedata
.btheme
.fg_focus
or "#FFFFFF" })
524 gamedata
.field
.south
:geometry({ width
= gamedata
.field
.w
,
526 x
= gamedata
.field
.x
,
527 y
= gamedata
.field
.y
+ gamedata
.field
.h
- 5 })
528 gamedata
.field
.south
.screen
= 1
530 gamedata
.field
.west
= wibox({ position
= "floating",
531 bg
= gamedata
.btheme
.bg_focus
or "#333333",
532 fg
= gamedata
.btheme
.fg_focus
or "#FFFFFF" })
533 gamedata
.field
.west
:geometry({ width
= 5,
534 height
= gamedata
.field
.h
,
535 x
= gamedata
.field
.x
- 5,
536 y
= gamedata
.field
.y
})
537 gamedata
.field
.west
.screen
= 1
539 gamedata
.field
.east
= wibox({ position
= "floating",
540 bg
= gamedata
.btheme
.bg_focus
or "#333333",
541 fg
= gamedata
.btheme
.fg_focus
or "#FFFFFF" })
542 gamedata
.field
.east
:geometry({ width
= 5,
543 height
= gamedata
.field
.h
,
544 x
= gamedata
.field
.x
+ gamedata
.field
.w
,
545 y
= gamedata
.field
.y
})
546 gamedata
.field
.east
.screen
= 1
548 gamedata
.enemies
.speed
= 5
551 gamedata
.player
= player
.new()
552 capi
.keygrabber
.run(keyhandler
)
553 gamedata
.running
= true
556 awful
.hooks
.timer
.register(0.02, shots
.handle
)
557 awful
.hooks
.timer
.register(0.03, shots
.handle_enemy
)
558 awful
.hooks
.timer
.register(0.01, enemies
.handle
)