Don't transfer devil status and rotate arrow which is just passing into memory.
[poca-love.git] / main.lua
blobc742d95b4ee5b0dc6687cbec9a85a996b0e29ba0
1 --[[
2 POCA - a game of logic
3 Copyright 2019 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 require('images')
22 require('levels')
23 require('anim')
24 require('menu')
25 require('trinkets')
27 function love.load()
29 directions = {
30 {1, 0}, {1, -1}, {0, -1}, {-1, -1},
31 {-1, 0}, {-1, 1}, {0, 1}, {1, 1}
34 game = {
35 screen = 'playfield', -- 'playfield', 'menu_objects', 'story_beginning' ...
36 shown_screens = {},
37 change_screen = function(self, newscreen, only_once)
38 if only_once and self.shown_screens[newscreen] then return end
39 self.shown_screens[newscreen] = true
40 menu_elements.drawn = nil
41 self.screen = newscreen
42 if newscreen == 'playfield' then
43 screen.bgcanvas = nil
44 elseif not screen.bgcanvas then
45 screen.bgcanvas = love.graphics.newCanvas(screen.width, screen.height)
46 screen.bgcanvas:renderTo(draw_playfield)
47 end
48 end,
49 phase = 'play', -- see next_phase()
50 timer = 0,
51 temppurple = false,
52 purple = false,
53 died = false,
54 showkill = false,
55 devil = true,
56 underpill = true,
57 events = function(self, e)
58 if e == 'pill on' then
59 self.underpill = true
60 setuplevel(level_labels.pill)
61 elseif e == 'pill off' then
62 self.underpill = false
63 self.devil = false
64 setuplevel(level_labels.devil)
65 elseif e == 'devil on' then
66 self.devil = true
67 end
68 end ,
71 screen = {}
72 love.resize()
74 playfield = {{}, {}, {}, {}}
75 setuplevel(96)
77 end
80 function explode_clicked()
81 local p = playfield[playfield.clickedx][playfield.clickedy]
82 if p.kind == 'pill' then
83 game:events('pill on')
84 return
85 end
86 if p.kind == 'memory' then -- remember
87 playfield[playfield.clickedx][playfield.clickedy] = p.memory
88 playfield.remembered = true
89 return
90 end
91 -- mark all purple arrow tiles if a purple arrow is clicked
92 -- even if only a memory
93 if p.kind == 'purple' then
94 game.temppurple = not game.temppurple
95 inplayfield(function (x, y, p) if p.kind == 'purple' or (p.memory and p.memory.kind == 'purple') then
96 playfield[x][y].hit = true
97 local pf = playfield[x][y].memory or playfield[x][y]
98 pf.center = 'dot'
99 end end)
102 -- clicked arrows should explode, even if player dies
103 -- to explain turning of yellow arrows, and just for fun
104 -- first, copy them to new table
105 local newtile = recognizeink('0')
106 if p.shape == 'x' or p.shape == '+' then -- perhaps needs more conditions later
107 newtile.arrows = {}
108 newtile.color = images[p.name].color
109 newtile.shape = p.shape
110 newtile.gift = p.gift
111 newtile.arrowgift = p.arrowgift
112 if p.sleep then
113 newtile.sleep = 4
114 newtile.sleeping_name = p.sleeping_name or p.name
117 for dir, arr in ipairs(p.arrows) do
118 -- arrows are false or integer: distance from tile center
119 newtile.arrows[dir] = arr
120 if arr then
121 local discard, distance, pf = check_direction(playfield.clickedx, playfield.clickedy, directions[dir][1], directions[dir][2])
122 local target
123 if pf and pf.kind == 'yellow' then
124 pf.hit = true
125 target = pf
126 end -- mark yellow arrows for rotation
127 explode_arrow(playfield.clickedx, playfield.clickedy, dir, distance, false, target)
131 if game.underpill then return end
132 playfield[playfield.clickedx][playfield.clickedy] = newtile
136 function forget_clicked()
137 --only when underpill, transform clicked (and exploded) arrow to the memory
138 if not game.underpill then return end
139 if playfield.remembered then playfield.remembered = false ; return end
140 local p = playfield[playfield.clickedx][playfield.clickedy]
141 local p_d = p.devil -- to preserve devil after becoming a memory
142 playfield[playfield.clickedx][playfield.clickedy] = recognizeink(get_memory_name(p.name))
143 if playfield[playfield.clickedx][playfield.clickedy].memory then
144 playfield[playfield.clickedx][playfield.clickedy].memory.devil = p_d
148 function check_player_death()
149 inplayfield(function(x, y, p)
150 if not p.arrows then return end
151 for direction, arrow in ipairs(p.arrows) do
152 if not p.backgrounded and arrow then
153 local death, distance = check_direction(x, y,
154 directions[direction][1], directions[direction][2])
155 if death then
156 explode_arrow(x, y, direction, distance, true) -- kill=true
157 game.died = true
161 end)
165 function influence_and_rotate()
166 -- devil influence on yellow arrows
167 if game.devil then
168 local dx, dy
169 local yellow_arrows = {}
170 inplayfield(function(x, y, p)
171 if p.kind == 'yellow' then
172 if p.devil then
173 -- don't insert last influenced yellow arrow
174 playfield[x][y].devil = nil -- unset but remember in case there are no more arrows
175 dx, dy = x, y
176 elseif not (not playfield.remembered and
177 playfield.clickedx == x and playfield.clickedy == y) then
178 -- don't insert arrow just passing into memory
179 table.insert(yellow_arrows, {x, y})
182 end)
183 if #yellow_arrows > 0 then
184 -- choose among available yellow arrows
185 dx, dy = unpack(yellow_arrows[math.random(#yellow_arrows)])
186 playfield[dx][dy].center = 'dot'
187 if playfield[dx][dy].hit then
188 rotate_arrows(dx, dy, 0) -- will just remove dot
189 else
190 rotate_arrows(dx, dy, -1)
192 playfield[dx][dy].hit = false -- so won't be rotated later
194 if dx then --set devil on some of the arrows in foreground
195 playfield[dx][dy].devil = true
196 -- unset any devil in memories
197 inplayfield(function(x, y, p)
198 if p.memory and p.memory.devil then
199 playfield[x][y].memory.devil = nil
201 end)
202 end -- new or last dx, dy in case there was only 1
204 -- turn yellow and purple arrows
205 inplayfield(function(x, y, p)
206 if (p.kind == 'yellow' or p.kind == 'purple' or (p.kind == 'memory' and p.memory.kind == 'purple')) and p.hit then
207 rotate_arrows(x, y)
208 p.hit = false
210 end)
211 -- wake regenerating arrows up
212 inplayfield(function(x, y, p)
213 if p.sleep and p.sleep > 0 then
214 p.sleep = p.sleep - 1
215 if p.sleep == 0 then
216 playfield[x][y] = recognizeink(p.sleeping_name)
217 grow_arrows(x, y)
220 end)
221 -- rotate red arrows
222 inplayfield(function(x, y, p)
223 if p.kind == 'red' or p.kind == 'loadred' then rotate_arrows(x, y) end
224 end)
228 function next_phase()
229 local nextphases = {'rotate', 'forget', 'die', 'level', 'play', 'explode'}
230 setmetatable(nextphases, {__index = function (table, key)
231 for a = 1, #table do
232 if rawget(table, a) == key then return table[a + 1] or table[1] end
234 end})
236 game.phase = nextphases[game.phase] or 'play' -- nextphases['opengifts'] == nil , etc.
237 if game.phase == 'explode' then -- 1. explode clicked arrow
238 explode_clicked()
239 elseif game.phase == 'rotate' then -- 2.
240 influence_and_rotate()
241 elseif game.phase == 'forget' then -- 3. pass to memory if underpill
242 forget_clicked()
243 elseif game.phase == 'die' then -- 4. check for player death
244 check_player_death()
245 elseif game.phase == 'level' then -- 5. check for level end
246 check_level()
247 elseif game.phase == 'play' then -- 6. await next click
252 function remember_arrows()
253 if game.underpill then return end
254 playfield.clickedx, playfield.clickedy = nil, nil
255 inplayfield(function (x, y, p)
256 if p.memory then playfield[x][y] = p.memory end
257 end)
261 function love.resize()
262 screen.w = love.graphics.getWidth()
263 screen.h = love.graphics.getHeight()
264 screen.playfieldsize = math.min(screen.w, screen.h) * .9
265 screen.tilesize = screen.playfieldsize / 4
266 screen.playfieldx = (screen.w / screen.playfieldsize - 1) * 2
267 screen.playfieldy = (screen.h / screen.playfieldsize - 1) * 2
268 if screen.bgcanvas then
269 screen.bgcanvas:renderTo(love.graphics.clear)
270 screen.bgcanvas:renderTo(draw_playfield)
272 screen.defaultfont = love.graphics.newFont(screen.tilesize / 5)
276 function love.update(dt)
277 if game.screen ~= 'playfield' then return end
278 if dt < 1/50 then
279 love.timer.sleep(1/30 - dt)
281 game.timer = game.timer + dt
282 update_anim(dt)
286 function osd()
287 love.graphics.setColor(1, 1, 1)
288 love.graphics.print(game.currentlevel, 1, 2) --debug
289 if anim[1] then love.graphics.print(anim[1].step, 1, 12) end
293 function inplayfield(f)
294 for y = 1, 4 do for x = 1, 4 do
295 f(x, y, playfield[x][y])
296 end end
300 function love.draw()
301 if game.screen == 'playfield' then
302 osd()
303 draw_playfield()
304 else
305 draw_menu()
310 function check_direction(x, y, dx, dy)
311 local p, xx, yy
312 for a = 1, 4 do
313 xx, yy = x + dx * a, y + dy * a
314 if not playfield[xx] then return false, a end -- arrow hits playfield edge
315 p = playfield[xx][yy]
316 if not p then return false, a end --||--
317 if xx == playfield.clickedx and yy == playfield.clickedy then return true, a end -- arrow hits player
318 if not p.passive then return false, a, p end -- arrow hits another object. return that object
320 return false, 3
324 function check_level()
325 -- checks for the level end (when all arrows are eliminated)
326 -- game should generally check for arrows not shapes!
327 local anymemories, anyactivearrows = false, false
328 inplayfield(function(x, y, p)
329 anymemories = anymemories or p.memory
330 if p.arrows then for direction, arrow in ipairs(p.arrows) do
331 anyactivearrows = anyactivearrows or arrow
332 end end
333 end)
334 if game.underpill then
335 if not anymemories and not game.died then
336 game.purple = game.temppurple --!!
337 setuplevel(game.currentlevel - 1)
339 else
340 if not anyactivearrows and not open_gifts() then
341 if anymemories then
342 remember_arrows()
343 else
344 game.purple = game.temppurple
345 setuplevel(game.currentlevel + 1)
352 function open_gifts()
353 if game.underpill then return end
354 local opened = false
355 inplayfield(function(x, y, p)
356 if p.name == 'J' or p.name == 'K' then
357 if p.name == 'K' then game:events('devil on') end
358 playfield[x][y] = recognizeink(p.gift)
359 playfield[x][y].gift = p.gift
360 playfield[x][y].ingift = true
361 opened = true
363 end)
364 if opened then
365 animate_gifts()
366 --playfield.clickedx, playfield.clickedy = nil, nil
367 --necessary to preserve clicked for opening arrowgifts
369 return opened
373 function clicked(mx, my)
374 if game.screen ~= 'playfield' then
375 return
377 if game.phase ~= 'play' then
378 return
381 -- click after death
382 if game.died then
383 setuplevel()
384 return
386 -- clickedx, clickedy from previous turn
387 if playfield.clickedx and
388 playfield[playfield.clickedx][playfield.clickedy].arrowgift and
389 not (mx == playfield.clickedx and my == playfield.clickedy) and
390 not game.underpill then
391 local gift = playfield[playfield.clickedx][playfield.clickedy].gift
392 -- open arrowgift
393 animate_arrow_gift(playfield.clickedx, playfield.clickedy)
394 -- preserve gift
395 playfield[playfield.clickedx][playfield.clickedy].gift = gift
397 -- new clickedx, clickedy
398 playfield.clickedx, playfield.clickedy = mx, my
399 if playfield[mx][my].onclick then playfield[mx][my].onclick() end
401 next_phase()
405 function isin(mx, my, x, y, w, h)
406 return mx >= x and mx <= x + w and my >= y and my <= y + h
410 function love.mousepressed(mx, my, button)
411 if game.screen == 'playfield' then
412 inplayfield(function (x, y, p)
413 if isin(mx, my, (screen.playfieldx + x - 1) * screen.tilesize, (screen.playfieldy + y - 1) * screen.tilesize, screen.tilesize, screen.tilesize) then
414 clicked(x, y)
416 end)
417 else --menu
418 menu_click(mx, my)
423 function love.keypressed(k)
424 if k == 'escape' then
425 if game.screen ~= 'playfield' then
426 game:change_screen('playfield')
427 else
428 love.event.push('quit')
430 elseif k == 'q' then
431 love.event.push('quit')
432 elseif k == 'n' then
433 setuplevel(game.currentlevel + 1)
434 elseif k == 'p' then
435 setuplevel(game.currentlevel - 1)
436 elseif k == 'r' then
437 game.purple = not game.purple
438 print('purple', game.purple)
439 elseif k == 'u' then
440 game.underpill = not game.underpill
441 print('underpill', game.underpill)
442 elseif k == 'd' then
443 game.devil = not game.devil
444 print('devil', game.devil)
445 elseif k == 'm' then
446 if game.screen ~= 'playfield' then
447 game:change_screen('playfield')
448 else
449 game:change_screen('menu_levels')