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
28 local awful
= require("awful")
29 local beautiful
= require("beautiful")
31 local capi
= { screen
= screen
, mouse
= mouse
, keygrabber
= keygrabber
}
33 local tonumber = tonumber
39 --- Space Invaders look-alike
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")
64 function player
.new ()
65 p
= wibox({ bg
= gamedata
.solidbg
or "#12345600",
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")
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
89 function player
.fire()
90 if not gamedata
.running
then return false end
91 if gamedata
.ammo
== 1 then
93 shots
.fire(gamedata
.player
.x
+ 9, gamedata
.player
.y
- 10, "#00FF00")
97 function shots
.fire (x
, y
, color
)
98 local s
= wibox({ bg
= color
,
103 s
.screen
= gamedata
.screen
105 if not gamedata
.shot
or gamedata
.shot
.screen
== nil then
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
,
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
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
135 if s
.y
< gamedata
.field
.y
+ 15 then
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
154 gamedata
.enemies
.shots
.fired
= gamedata
.enemies
.shots
.fired
- 1
158 if game
.collide(gamedata
.player
, s
) then
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
]
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
]
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
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
234 gamedata
.score
= gamedata
.score
+ 15
235 elseif (y
% 3) == 1 then
236 gamedata
.score
= gamedata
.score
+ 10
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
,
256 if gamedata
.enemies
.number == 0 then
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
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
.. "|"
290 if key
== "Left" then
292 elseif key
== "Right" then
294 elseif key
== "q" then
297 elseif key
== " " then
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
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
318 function game
.over ()
319 gamedata
.running
= false
320 game
.highscore(gamedata
.score
)
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"
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")
390 fh
:write(gamedata
.highscore
[i
].."\n")
396 function game
.highscore_add (score
, name
)
397 local t
= gamedata
.highscore
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")
415 gamedata
.highscore
[i
] = fh
:read("*line")
420 gamedata
.highscore
[i
] = ((6-i
)*20).." foo"
424 local newentry
= false
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",
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
444 gamedata
.highscore
.getkeys
= true
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.
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
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
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
,
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",
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",
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
538 gamedata
.player
= player
.new()
539 capi
.keygrabber
.run(keyhandler
)
540 gamedata
.running
= true
544 local t
= timer
{ timeout
= 0.02 }
545 t
:add_signal("timeout", shots
.handle
)
547 local t
= timer
{ timeout
= 0.03 }
548 t
:add_signal("timeout", shots
.handle_enemy
)
550 local t
= timer
{ timeout
= 0.01 }
551 t
:add_signal("timeout", enemies
.handle
)