invaders: remove useless shots table
[awesome.git] / lib / invaders.lua.in
blobd20317b9fb2bcde36c3b215898455177ba86edfe
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 widget = widget
27 local awful = require("awful")
28 local beautiful = require("awful.beautiful")
29 local image = image
30 local capi = { screen = screen, mouse = mouse, keygrabber = keygrabber }
32 local tonumber = tonumber
33 local table = table
34 local math = math
35 local os = os
36 local io = io
38 --- Space Invaders look-alike
39 module("invaders")
41 local gamedata = { }
42 gamedata.field = { }
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.ammo_max = 1
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({ position = "floating",
66 bg = gamedata.solidbg or "#12345600" })
67 p:geometry({ width = 24,
68 height = 16,
69 x = gamedata.field.x + (gamedata.field.w / 2),
70 y = gamedata.field.y + gamedata.field.h - (16 + 5) })
71 p.screen = 1
73 w = widget({ type = "imagebox", name = "player" })
74 w.image = image("@AWESOME_ICON_PATH@/invaders/player.png")
75 p.widgets = w
77 return p
78 end
80 function player.move(x)
81 local gb = gamedata.player:geometry()
83 if x < 0 and gb.x > gamedata.field.x then
84 gb.x = gb.x + x
85 elseif x > 0 and gb.x < gamedata.field.x + gamedata.field.w - 30 then
86 gb.x = gb.x + x
87 end
89 gamedata.player:geometry(gb)
90 end
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")
98 end
99 end
101 function shots.fire (x, y, color)
102 local s = wibox({ position = "floating",
103 bg = color })
104 s:geometry({ width = 4,
105 height = 10,
106 x = x,
107 y = y })
108 s.screen = 1
110 if not gamedata.shot or gamedata.shot.screen == nil then
111 gamedata.shot = s
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",
119 bg = color })
120 s:geometry({ width = 4,
121 height = 10,
122 x = x,
123 y = y })
124 s.screen = 1
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
128 break
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
145 s.screen = nil
146 gamedata.ammo = gamedata.ammo + 1
147 else
148 g.y = g.y - 6
149 s:geometry(g)
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
165 s.screen = nil
166 gamedata.enemies.shots.fired = gamedata.enemies.shots.fired - 1
167 else
168 g.y = g.y + 3
169 s:geometry(g)
171 if game.collide(gamedata.player, s) then
172 game.over()
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 })
185 e.screen = 1
186 w = widget({ type = "imagebox",
187 name = "enemy"..t })
188 w.image = gamedata.enemies[t]
190 e.widgets = w
191 return e
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]
231 if e.screen then
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))
237 e:geometry(g)
238 if game.collide(gamedata.player, e) or g.y > gamedata.field.y + gamedata.field.h - 20 then
239 game.over()
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
246 e.screen = nil
247 s.screen = nil
249 if (y % 3) == 0 then
250 gamedata.score = gamedata.score + 15
251 elseif (y % 3) == 1 then
252 gamedata.score = gamedata.score + 10
253 else
254 gamedata.score = gamedata.score + 5
257 gamedata.field.status.text = gamedata.score.." | "..gamedata.ammo.." "
258 break
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,
270 "#FF0000")
274 if gamedata.enemies.number == 0 then
275 enemies.setup()
276 if gamedata.enemies.speed > 1 then gamedata.enemies.speed = gamedata.enemies.speed - 1 end
277 return false
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 .. "|"
304 else
305 if key == "Left" then
306 player.move(-10)
307 elseif key == "Right" then
308 player.move(10)
309 elseif key == "q" then
310 game.quit()
311 return false
312 elseif key == " " then
313 player.fire()
314 elseif key == "s" then
315 awful.util.spawn("import -window root "..gamedata.cachedir.."/awesome/invaders-"..os.time()..".png")
318 return true
321 function game.collide(o1, o2)
322 g1 = o1:geometry()
323 g2 = o2:geometry()
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
328 return true
331 return false
334 function game.over ()
335 gamedata.running = false
336 game.highscore(gamedata.score)
339 function game.quit()
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,
383 width = 200,
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"
394 for i = 1, 5 do
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")
402 if not fh then
403 return false
406 for i = 1, 5 do
407 fh:write(gamedata.highscore[i].."\n")
410 fh:close()
413 function game.highscore_add (score, name)
414 local t = gamedata.highscore
416 for i = 5, 1, -1 do
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")
429 if fh then
430 for i = 1, 5 do
431 gamedata.highscore[i] = fh:read("*line")
433 fh:close()
434 else
435 for i = 1, 5 do
436 gamedata.highscore[i] = ((6-i)*20).." foo"
440 local newentry = false
441 for i = 1, 5 do
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,
450 width = 300,
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 name = "foobar" })
457 gamedata.namebox.text = " Name: |"
458 gamedata.highscore.window.widgets = gamedata.namebox
460 if newentry then
461 gamedata.name = ""
462 gamedata.highscore.getkeys = true
463 else
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.
474 function run(args)
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)
479 if args then
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
485 gamedata.score = 0
486 gamedata.name = ""
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,
506 height = 15,
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",
512 name = "status",
513 align = "right" })
514 gamedata.field.status.text = gamedata.score.." | "..gamedata.ammo .. " "
516 gamedata.field.caption = widget({ type = "textbox",
517 name = "caption",
518 align = "left" })
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,
527 height = 5,
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
551 enemies.setup()
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)