Introduced invulnerable "bullet" enemy.
[batto.git] / main.lua
blobdb21a44a42d92a8cc22148efd09ce2dcb9989528
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 not enemies[b].isbullet then enemies[b]:die() end
84 end
85 end end
86 end,
87 purge = function(self)
88 while #self > 0 do self[#self] = nil end
89 end,
92 enemies = {
93 movements = {
94 pass = function(self)
95 self.x = self.x + self.speedx
96 self.y = self.y + self.speedy
97 self.dying = self.dying or
98 (self.x < 1 or self.x > screen.w or self.y < 1 or self.y > screen.h)
99 return self
100 end,
101 wander = function(self)
102 self.x = self.x + self.speedx
103 self.y = self.y + self.speedy
104 if self.x < 1 or self.x > screen.w then
105 self.speedx = - self.speedx
107 if self.y < 1 or self.y > screen.h then
108 self.speedy = - self.speedy
110 return self
111 end,
112 chase = function(self)
113 self.speedx = self.speedx * 199/200 + (player.x - self.x) / 2000
114 self.speedy = self.speedy * 199/200 + (player.y - self.y) / 2000
115 self.speedx = self.speedx / math.abs(self.speedx) * math.min(3, math.abs(self.speedx))
116 self.speedy = self.speedy / math.abs(self.speedy) * math.min(3, math.abs(self.speedy))
117 self.x = self.x + self.speedx
118 self.y = self.y + self.speedy
119 return self
120 end,
122 draw = function(self)
123 for a = 1, #self do
124 love.graphics.setColor(self[a].kind.color)
125 love.graphics.rectangle("fill", self[a].x - self[a].size / 2, self[a].y - self[a].size / 2, self[a].size, self[a].size)
127 end,
128 make = function(self, kind, x, y, speedx, speedy)
129 self[#self + 1] = {
130 kind = kind, x = x, y = y, speedx = speedx, speedy = speedy,
131 size = kind.size,
132 dying = false,
133 die = function(self)
134 explosions:make(self.x, self.y, self.size)
135 game.score = game.score + 1
136 self.dying = true
137 -- will be disposed of in cleanup
138 end,
140 end,
141 move = function(self)
142 for a = 1, #self do
143 self[a] = self[a].kind.movement(self[a])
145 end,
146 check = function(self)
147 for a = 1, #self do
148 if not self[a].dying and collides(player, self[a]) then
149 player:destroy()
150 self[a]:die()
153 if #self == 0 and game.happened == #scenario[game.level] then
154 endlevel()
156 end,
157 cleanup = function(self)
158 for a = #self, 1, -1 do if self[a].dying then
159 self[a] = self[#self]
160 self[#self] = nil
161 end end
162 end,
165 enemies.kinds = {
166 wanderer = {
167 color = {.8, 0, .4},
168 movement = enemies.movements.wander,
169 size = player.size,
171 chaser = {
172 color = {1, 0, 0},
173 movement = enemies.movements.chase,
174 size = player.size,
176 passer = {
177 color = {0, .5, .8},
178 movement = enemies.movements.pass,
179 size = player.size,
181 bullet = {
182 color = {1, 1, 1},
183 movement = enemies.movements.pass,
184 size = 5,
188 explosions = {
189 maxsteps = 15,
190 make = function(self, x, y, size)
191 self[#self + 1] = {
192 step = 0,
193 x = x,
194 y = y,
195 size = size,
197 end,
198 grow = function(self)
199 -- must go backwards not to disrupt the loop
200 for a = #self, 1, -1 do
201 self[a].step = self[a].step + 1
202 if self[a].step > self.maxsteps then
203 self:destroy(a)
206 end,
207 destroy = function(self, a)
208 self[a] = self[#self]
209 self[#self] = nil
210 end,
211 draw = function(self)
212 love.graphics.setColor(1,1,0)
213 for a = 1, #self do
214 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)
216 end,
217 purge = function(self)
218 while #self > 0 do self[#self] = nil end
219 end,
222 origins = {
223 ul = { x = 0, y = 0 },
224 u = { x = screen.w / 2, y = 0 },
225 ur = { x = screen.w, y = 0 },
226 l = { x = 0, y = screen.h / 2 },
227 c = { x = screen.w / 2, y = screen.h / 2 },
228 r = { x = screen.w, y = screen.h / 2 },
229 dl = { x = 0, y = screen.h },
230 d = { x = screen.w / 2, y = screen.h },
231 dr = { x = screen.w, y = screen.h },
234 scenario = {
237 title = "Wave 1",
238 player_origin = origins.c,
239 --1st enemy
240 { time = 0, origin = origins.ul, speed = { 1, 1 }, kind = enemies.kinds.passer },
241 --2nd enemy
242 { time = 2, origin = origins.l, speed = { 1, 0 }, kind = enemies.kinds.passer },
243 --3rd enemy
244 { time = 4, origin = origins.dl, speed = { 1, -1 }, kind = enemies.kinds.passer },
245 --4th enemy
246 { time = 6, origin = origins.d, speed = { 0, -1 }, kind = enemies.kinds.passer },
250 title = "Wave 2",
251 player_origin = origins.d,
252 --1st enemy
253 { time = 0, origin = origins.ul, speed = { 1, 1 }, kind = enemies.kinds.wanderer },
254 --2nd enemy
255 { time = 1, origin = origins.r, speed = { -5, 0 }, kind = enemies.kinds.bullet },
256 --3rd enemy
257 { time = 2, origin = origins.l, speed = { 1, 0 }, kind = enemies.kinds.wanderer },
261 title = "Wave 3",
262 player_origin = origins.c,
263 --1st enemy
264 { time = 1, origin = origins.u, speed = { 1, 1 }, kind = enemies.kinds.chaser },
265 --2nd enemy
266 { time = 2, origin = origins.dr, speed = { 1, 0 }, kind = enemies.kinds.chaser },
267 --3rd enemy
268 { time = 3, origin = origins.dl, speed = { 1, 0 }, kind = enemies.kinds.chaser },
272 game = {
273 timer = 0,
274 level = 2,
275 happened = 0,
276 score = 0,
277 mode = "title",
282 function collides(obj1, obj2)
283 local o1x, o1y, o2x, o2y, d
284 o1x = obj1.x + obj1.size / 2
285 o1y = obj1.y + obj1.size / 2
286 o2x = obj2.x + obj2.size / 2
287 o2y = obj2.y + obj2.size / 2
288 d = obj1.size / 2 + obj2.size / 2
289 return (o1x - o2x) ^ 2 + (o1y - o2y) ^ 2 < d ^ 2
293 function beginlevel()
294 game.timer = 0
295 game.mode = "play"
296 game.happened = 0
297 player.x = scenario[game.level].player_origin.x
298 player.y = scenario[game.level].player_origin.y
302 function happen(number)
303 game.happened = number
304 local s = scenario[game.level][number]
305 enemies:make(s.kind, s.origin.x, s.origin.y, s.speed[1], s.speed[2])
306 enemies[#enemies].isbullet = s.kind == enemies.kinds.bullet
310 function endlevel()
311 trails:purge()
312 explosions:purge()
313 game.timer = 0
314 game.level = game.level + 1
315 if game.level > #scenario then
316 game.mode = "theend"
317 game.level = 1
318 else
319 game.mode = "title"
324 function love.update(dt)
325 if dt < 1/50 then
326 love.timer.sleep(1/30 - dt)
328 game.timer = game.timer + dt
329 if game.mode == "play" then
330 trails:expire(dt)
331 enemies:move()
332 enemies:check()
333 enemies:cleanup()
334 explosions:grow()
335 for a = game.happened + 1, #scenario[game.level] do
336 if game.timer > scenario[game.level][a].time then
337 happen(a)
340 elseif game.mode == "title" then
341 if game.timer > 3 then
342 beginlevel()
348 function osd()
349 love.graphics.setColor(1,1,1)
350 love.graphics.print(math.floor(game.timer) .. ':' .. game.score, 1, 2)
351 love.graphics.print('game.mode = ' .. game.mode, 1, 20)
352 love.graphics.print('#enemies = ' .. #enemies, 1, 30)
356 function love.draw()
357 if game.mode == "play" then
358 trails:draw()
359 player:draw()
360 enemies:draw()
361 explosions:draw()
362 elseif game.mode == "title" then
363 love.graphics.setColor(1,1,1)
364 love.graphics.print(scenario[game.level].title, 100, 100)
365 elseif game.mode == "theend" then
366 love.graphics.setColor(1,1,1)
367 love.graphics.print('The End', 100, 100)
369 osd()
373 function love.mousepressed(x, y, button)
374 if game.mode == "title" or game.mode == "theend" then
375 beginlevel()
376 elseif game.mode == "play" then
377 player:move(x, y)
378 trails:check()
383 function love.keypressed(k)
384 if k == "escape" or k == "q" then
385 love.event.push("quit")