Enemies shoot after preset time.
[batto.git] / main.lua
blob60a557e8423a94ef62020fa3670147878dd51084
1 --[[
2 Batto! A clicking arcade game
3 Copyright 2018 Pajo <xpio at tut dot by>
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>.
19 -- love 11.1
21 function love.load()
22 love.window.setMode(1080/2.5, 1920/2.5)
23 screen = {
24 w = love.graphics.getWidth(),
25 h = love.graphics.getHeight()
28 player = {
29 x = screen.w / 2,
30 y = screen.h * 3 / 4,
31 size = screen.w / 6,
32 draw = function(self)
33 love.graphics.setColor(.5, .5, .8)
34 love.graphics.circle("fill", self.x, self.y, self.size / 2)
35 end,
36 move = function(self, x, y)
37 if trails[1] then return end -- no movement until all trails expire
38 trails:make(x, y, self.x, self.y)
39 player.x = x
40 player.y = y
41 end,
42 destroy = function(self)
43 game.score = game.score - 10
44 explosions:make(self.x, self.y, self.size)
45 end,
48 trails = {
49 min = 1,
50 max = 40,
51 duration = 1 / 20,
52 timer = 0,
53 make = function(self, x, y, sx, sy)
54 local expiration = 10
55 local d = ((x - sx) ^ 2 + (y - sy) ^ 2) ^ 0.5
56 local howmany = math.max(math.min(d / expiration, self.max), self.min)
57 local step
58 for a = self.min, howmany do
59 step = 1 - a / howmany
60 trails[a] = {
61 size = player.size,
62 x = sx - (sx - x) * step,
63 y = sy - (sy - y) * step
65 end
66 end,
67 draw = function(self)
68 love.graphics.setColor(1, .5, 0)
69 for a = 1, #self do
70 love.graphics.circle("line", self[a].x, self[a].y, self[a].size / 2)
71 end
72 end,
73 expire = function(self, dt)
74 self.timer = self.timer + dt
75 if self.timer > self.duration and #self > 0 then
76 self[#self] = nil
77 self.timer = self.timer - self.duration
78 end
79 end,
80 check = function(self)
81 for a = 1, #self do for b = 1, #enemies do
82 if not enemies[b].dying and collides(trails[a], enemies[b]) then
83 if enemies[b].isbullet then
84 player:destroy()
85 else
86 enemies[b]:die()
87 end
88 end
89 end end
90 end,
91 purge = function(self)
92 while #self > 0 do self[#self] = nil end
93 end,
96 enemies = {
97 movements = {
98 pass = function(self)
99 self.x = self.x + self.speedx
100 self.y = self.y + self.speedy
101 self.dying = self.dying or
102 (self.x < 1 or self.x > screen.w or self.y < 1 or self.y > screen.h)
103 return self
104 end,
105 wander = function(self)
106 self.x = self.x + self.speedx
107 self.y = self.y + self.speedy
108 if self.x < 1 or self.x > screen.w then
109 self.speedx = - self.speedx
111 if self.y < 1 or self.y > screen.h then
112 self.speedy = - self.speedy
114 return self
115 end,
116 chase = function(self)
117 self.speedx = self.speedx * 199/200 + (player.x - self.x) / 2000
118 self.speedy = self.speedy * 199/200 + (player.y - self.y) / 2000
119 self.speedx = self.speedx / math.abs(self.speedx) * math.min(3, math.abs(self.speedx))
120 self.speedy = self.speedy / math.abs(self.speedy) * math.min(3, math.abs(self.speedy))
121 self.x = self.x + self.speedx
122 self.y = self.y + self.speedy
123 return self
124 end,
126 draw = function(self)
127 for a = 1, #self do
128 love.graphics.setColor(self[a].kind.color)
129 love.graphics.rectangle("fill", self[a].x - self[a].size / 2, self[a].y - self[a].size / 2, self[a].size, self[a].size)
131 end,
132 make = function(self, kind, x, y, speedx, speedy)
133 self[#self + 1] = {
134 kind = kind, x = x, y = y, speedx = speedx, speedy = speedy,
135 size = kind.size,
136 dying = false,
137 die = function(self)
138 explosions:make(self.x, self.y, self.size)
139 game.score = game.score + 1
140 self.dying = true
141 -- will be disposed of in cleanup
142 end,
144 end,
145 move = function(self)
146 for a = 1, #self do
147 self[a] = self[a].kind.movement(self[a])
149 end,
150 check = function(self)
151 for a = 1, #self do
152 if not self[a].dying and collides(player, self[a]) then
153 player:destroy()
154 self[a]:die()
156 if self[a].shoots_at and game.timer >= self[a].shoots_at then
157 self:shoot(a)
158 self[a].shoots_at = nil
161 if #self == 0 and game.happened == #scenario[game.level] then
162 endlevel()
164 end,
165 shoot = function(self, a)
166 print(a, "shoots")
167 end,
168 cleanup = function(self)
169 for a = #self, 1, -1 do if self[a].dying then
170 self[a] = self[#self]
171 self[#self] = nil
172 end end
173 end,
176 enemies.kinds = {
177 wanderer = {
178 color = {.8, 0, .4},
179 movement = enemies.movements.wander,
180 size = player.size,
182 chaser = {
183 color = {1, 0, 0},
184 movement = enemies.movements.chase,
185 size = player.size,
187 passer = {
188 color = {0, .5, .8},
189 movement = enemies.movements.pass,
190 size = player.size,
192 bullet = {
193 color = {1, 1, 1},
194 movement = enemies.movements.pass,
195 size = 5,
199 explosions = {
200 maxsteps = 15,
201 make = function(self, x, y, size)
202 self[#self + 1] = {
203 step = 0,
204 x = x,
205 y = y,
206 size = size,
208 end,
209 grow = function(self)
210 -- must go backwards not to disrupt the loop
211 for a = #self, 1, -1 do
212 self[a].step = self[a].step + 1
213 if self[a].step > self.maxsteps then
214 self:destroy(a)
217 end,
218 destroy = function(self, a)
219 self[a] = self[#self]
220 self[#self] = nil
221 end,
222 draw = function(self)
223 love.graphics.setColor(1,1,0)
224 for a = 1, #self do
225 love.graphics.rectangle("line", self[a].x - self[a].size / 2 - self[a].step * 2, self[a].y - self[a].size / 2 - self[a].step * 2, self[a].size + self[a].step * 4, self[a].size + self[a].step * 4)
227 end,
228 purge = function(self)
229 while #self > 0 do self[#self] = nil end
230 end,
233 origins = {
234 ul = { x = 1, y = 1 },
235 u = { x = screen.w / 2, y = 1 },
236 ur = { x = screen.w - 1, y = 1 },
237 l = { x = 1, y = screen.h / 2 },
238 c = { x = screen.w / 2, y = screen.h / 2 },
239 r = { x = screen.w - 1, y = screen.h / 2 },
240 dl = { x = 1, y = screen.h - 1 },
241 d = { x = screen.w / 2, y = screen.h - 1 },
242 dr = { x = screen.w - 1, y = screen.h - 1 },
245 scenario = {
248 title = "Wave 1",
249 player_origin = origins.c,
250 --1st enemy
251 { time = 0, origin = origins.ul, speed = { 1, 1 }, kind = enemies.kinds.passer },
252 --2nd enemy
253 { time = 2, origin = origins.l, speed = { 1, 0 }, kind = enemies.kinds.passer },
254 --3rd enemy
255 { time = 4, origin = origins.dl, speed = { 1, -1 }, kind = enemies.kinds.passer },
256 --4th enemy
257 { time = 6, origin = origins.d, speed = { 0, -1 }, kind = enemies.kinds.passer },
261 title = "Wave 2",
262 player_origin = origins.d,
263 --1st enemy
264 { time = 0, origin = origins.ul, speed = { 1, 1 }, kind = enemies.kinds.wanderer },
265 --2nd enemy
266 { time = 1, origin = origins.r, speed = { -5, 0 }, kind = enemies.kinds.bullet },
267 --3rd enemy
268 { time = 2, origin = origins.l, speed = { 1, 0 }, kind = enemies.kinds.wanderer },
272 title = "Wave 3",
273 player_origin = origins.c,
274 --1st enemy
275 { time = 2, origin = origins.u, speed = { 1, 1 }, kind = enemies.kinds.chaser },
276 --2nd enemy
277 { time = 2, origin = origins.dr, speed = { 1, 0 }, kind = enemies.kinds.chaser },
278 --3rd enemy
279 { time = 3, origin = origins.dl, speed = { 1, 0 }, kind = enemies.kinds.chaser },
281 --4 - enemies shoot
283 title = "Wave 4",
284 player_origin = origins.d,
285 --1st enemy
286 { time = 0, origin = origins.ul, speed = { 0, 0 }, kind = enemies.kinds.passer, shoots_in = 2 },
287 --2nd enemy
288 { time = 0, origin = origins.ur, speed = { 0, 0 }, kind = enemies.kinds.passer, shoots_in = 2.5 },
289 --3rd enemy
290 { time = 0, origin = origins.c, speed = { 0, 0 }, kind = enemies.kinds.passer, shoots_in = 3 },
291 -- shoots_after?
292 -- range?
296 game = {
297 timer = 0,
298 level = 4,
299 happened = 0,
300 score = 0,
301 mode = "title",
306 function collides(obj1, obj2)
307 local o1x, o1y, o2x, o2y, d
308 o1x = obj1.x + obj1.size / 2
309 o1y = obj1.y + obj1.size / 2
310 o2x = obj2.x + obj2.size / 2
311 o2y = obj2.y + obj2.size / 2
312 d = obj1.size / 2 + obj2.size / 2
313 return (o1x - o2x) ^ 2 + (o1y - o2y) ^ 2 < d ^ 2
317 function beginlevel()
318 game.timer = 0
319 game.mode = "play"
320 game.happened = 0
321 player.x = scenario[game.level].player_origin.x
322 player.y = scenario[game.level].player_origin.y
326 function happen(number)
327 game.happened = number
328 local s = scenario[game.level][number]
329 enemies:make(s.kind, s.origin.x, s.origin.y, s.speed[1], s.speed[2])
330 enemies[#enemies].isbullet = s.kind == enemies.kinds.bullet
331 enemies[#enemies].shoots_at = game.timer + s.shoots_in
335 function endlevel()
336 trails:purge()
337 explosions:purge()
338 game.timer = 0
339 game.level = game.level + 1
340 if game.level > #scenario then
341 game.mode = "theend"
342 game.level = 1
343 else
344 game.mode = "title"
349 function love.update(dt)
350 if dt < 1/50 then
351 love.timer.sleep(1/30 - dt)
353 game.timer = game.timer + dt
354 if game.mode == "play" then
355 trails:expire(dt)
356 enemies:move()
357 enemies:check()
358 enemies:cleanup()
359 explosions:grow()
360 for a = game.happened + 1, #scenario[game.level] do
361 if game.timer > scenario[game.level][a].time then
362 happen(a)
365 elseif game.mode == "title" then
366 if game.timer > 3 then
367 beginlevel()
373 function osd()
374 love.graphics.setColor(1,1,1)
375 love.graphics.print(math.floor(game.timer) .. ':' .. game.score, 1, 2)
376 love.graphics.print('game.mode = ' .. game.mode, 1, 20)
377 love.graphics.print('#enemies = ' .. #enemies, 1, 30)
381 function love.draw()
382 if game.mode == "play" then
383 trails:draw()
384 player:draw()
385 enemies:draw()
386 explosions:draw()
387 elseif game.mode == "title" then
388 love.graphics.setColor(1,1,1)
389 love.graphics.print(scenario[game.level].title, 100, 100)
390 elseif game.mode == "theend" then
391 love.graphics.setColor(1,1,1)
392 love.graphics.print('The End', 100, 100)
394 osd()
398 function love.mousepressed(x, y, button)
399 if game.mode == "title" or game.mode == "theend" then
400 beginlevel()
401 elseif game.mode == "play" then
402 player:move(x, y)
403 trails:check()
408 function love.keypressed(k)
409 if k == "escape" or k == "q" then
410 love.event.push("quit")