Some work on implementing underpill.
[poca-love.git] / main.lua
blob33c51c1653cf0686ea947c5ec53bf8d2dd2bff83
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 = false,
56 underpill = false,
59 screen = {}
60 love.resize()
62 playfield = {{}, {}, {}, {}}
63 setuplevel(96)
65 end
68 function explode_clicked()
69 local p = playfield[playfield.clickedx][playfield.clickedy]
70 if p.kind == 'memory' then
71 playfield[playfield.clickedx][playfield.clickedy] = p.memory --recognizeink(p.of)
72 return
73 end
74 -- clicked arrows should explode, even if player dies
75 -- to explain turning of yellow arrows, and just for fun
76 -- first, copy them to new table
77 local newtile = recognizeink('0')
78 if p.shape == 'x' or p.shape == '+' then -- perhaps needs more conditions later
79 newtile.arrows = {}
80 newtile.color = images[p.name].color
81 newtile.shape = p.shape
82 newtile.gift = p.gift
83 newtile.arrowgift = p.arrowgift
84 if p.sleep then
85 newtile.sleep = 4
86 newtile.sleeping_name = p.sleeping_name or p.name
87 end
89 for dir, arr in ipairs(p.arrows) do
90 -- arrows are false or integer: distance from tile center
91 newtile.arrows[dir] = arr
92 if arr then
93 local discard, distance, pf = check_direction(playfield.clickedx, playfield.clickedy, directions[dir][1], directions[dir][2])
94 local target
95 if pf and pf.kind == 'yellow' then
96 pf.hit = true
97 target = pf
98 end -- mark yellow arrows for rotation
99 explode_arrow(playfield.clickedx, playfield.clickedy, dir, distance, false, target)
103 -- mark all purple arrow tiles if a purple arrow is clicked
104 -- even if only a memory --!!
105 if p.kind == 'purple' then
106 game.temppurple = not game.temppurple
107 inplayfield(function (x, y, p) if p.kind == 'purple' or (p.memory and p.memory.kind == 'purple') then
108 playfield[x][y].hit = true
109 local pf = playfield[x][y].memory or playfield[x][y]
110 pf.center = 'dot'
111 end end)
113 playfield[playfield.clickedx][playfield.clickedy] = newtile
117 function check_player_death()
118 inplayfield(function(x, y, p)
119 if not p.arrows then return end
120 for direction, arrow in ipairs(p.arrows) do
121 if not p.backgrounded and arrow then
122 local death, distance = check_direction(x, y,
123 directions[direction][1], directions[direction][2])
124 if death then
125 explode_arrow(x, y, direction, distance, true) -- kill=true
126 game.died = true
130 end)
134 function influence_and_rotate()
135 -- devil influence on yellow arrows
136 if game.devil then
137 local dx, dy
138 local yellow_arrows = {}
139 inplayfield(function(x, y, p)
140 if p.kind == 'yellow' then
141 if p.devil then -- don't insert last influenced yellow arrow
142 playfield[x][y].devil = nil -- unset but remember in case there are no more arrows
143 dx, dy = x, y
144 else
145 table.insert(yellow_arrows, {x, y})
148 end)
149 if #yellow_arrows > 0 then
150 -- choose among available yellow arrows
151 dx, dy = unpack(yellow_arrows[math.random(#yellow_arrows)])
152 playfield[dx][dy].center = 'dot'
153 if playfield[dx][dy].hit then
154 rotate_arrows(dx, dy, 0) -- will just remove dot
155 else
156 rotate_arrows(dx, dy, -1)
158 playfield[dx][dy].hit = false -- so won't be rotated later
160 if dx then playfield[dx][dy].devil = true end -- new or last dx, dy in case there was only 1
162 -- turn yellow and purple arrows
163 inplayfield(function(x, y, p)
164 if (p.kind == 'yellow' or p.kind == 'purple' or (p.kind == 'memory' and p.memory.kind == 'purple')) and p.hit then
165 rotate_arrows(x, y)
166 p.hit = false
168 end)
169 -- wake regenerating arrows up
170 inplayfield(function(x, y, p)
171 if p.sleep and p.sleep > 0 then
172 p.sleep = p.sleep - 1
173 if p.sleep == 0 then
174 playfield[x][y] = recognizeink(p.sleeping_name)
175 grow_arrows(x, y)
178 end)
179 -- rotate red arrows
180 inplayfield(function(x, y, p)
181 if p.kind == 'red' or p.kind == 'loadred' then rotate_arrows(x, y) end
182 end)
186 function next_phase()
187 local nextphases = {'rotate', 'die', 'level', 'play', 'explode'}
188 setmetatable(nextphases, {__index = function (table, key)
189 for a = 1, #table do
190 if rawget(table, a) == key then return table[a + 1] or table[1] end
192 end})
194 game.phase = nextphases[game.phase] or 'play' -- nextphases['opengifts'] == nil , etc.
195 if game.phase == 'explode' then -- 1. explode clicked arrow
196 explode_clicked()
197 elseif game.phase == 'rotate' then
198 influence_and_rotate()
199 elseif game.phase == 'die' then -- 2. check for player death
200 check_player_death()
201 elseif game.phase == 'level' then -- 3. check for level end
202 check_level()
203 elseif game.phase == 'play' then -- 4. await next click
208 function remember_arrows()
209 playfield.clickedx, playfield.clickedy = nil, nil
210 inplayfield(function (x, y, p)
211 if p.memory then playfield[x][y] = p.memory end
212 end)
216 function love.resize()
217 screen.w = love.graphics.getWidth()
218 screen.h = love.graphics.getHeight()
219 screen.playfieldsize = math.min(screen.w, screen.h) * .9
220 screen.tilesize = screen.playfieldsize / 4
221 screen.playfieldx = (screen.w / screen.playfieldsize - 1) * 2
222 screen.playfieldy = (screen.h / screen.playfieldsize - 1) * 2
223 if screen.bgcanvas then
224 screen.bgcanvas:renderTo(love.graphics.clear)
225 screen.bgcanvas:renderTo(draw_playfield)
227 screen.defaultfont = love.graphics.newFont(screen.tilesize / 5)
231 function love.update(dt)
232 if game.screen ~= 'playfield' then return end
233 if dt < 1/50 then
234 love.timer.sleep(1/30 - dt)
236 game.timer = game.timer + dt
237 update_anim(dt)
241 function osd()
242 love.graphics.setColor(1, 1, 1)
243 love.graphics.print(game.currentlevel, 1, 2) --debug
244 if anim[1] then love.graphics.print(anim[1].step, 1, 12) end
248 function inplayfield(f)
249 for y = 1, 4 do for x = 1, 4 do
250 f(x, y, playfield[x][y])
251 end end
255 function love.draw()
256 if game.screen == 'playfield' then
257 osd()
258 draw_playfield()
259 else
260 draw_menu()
265 function check_direction(x, y, dx, dy)
266 local p, xx, yy
267 for a = 1, 4 do
268 xx, yy = x + dx * a, y + dy * a
269 if not playfield[xx] then return false, a end -- arrow hits playfield edge
270 p = playfield[xx][yy]
271 if not p then return false, a end --||--
272 if xx == playfield.clickedx and yy == playfield.clickedy then return true, a end -- arrow hits player
273 if not p.passive then return false, a, p end -- arrow hits another object. return that object
275 return false, 3
279 function check_level()
280 -- checks for the level end (when all arrows are eliminated)
281 -- game should generally check for arrows not shapes!
282 local anymemories, anyactivearrows = false, false
283 inplayfield(function(x, y, p)
284 anymemories = anymemories or p.memory
285 if p.arrows then for direction, arrow in ipairs(p.arrows) do
286 anyactivearrows = anyactivearrows or arrow
287 end end
288 end)
289 if not anyactivearrows and not open_gifts() then
290 if anymemories then
291 remember_arrows()
292 else
293 game.purple = game.temppurple
294 setuplevel(game.currentlevel + 1)
300 function open_gifts()
301 local opened = false
302 inplayfield(function(x, y, p)
303 if p.name == 'J' or p.name == 'K' then
304 if p.name == 'K' then game.devil = true end
305 playfield[x][y] = recognizeink(p.gift)
306 playfield[x][y].gift = p.gift
307 playfield[x][y].ingift = true
308 opened = true
310 end)
311 if opened then
312 animate_gifts()
313 --playfield.clickedx, playfield.clickedy = nil, nil
314 --necessary to preserve clicked for opening arrowgifts
316 return opened
320 function clicked(mx, my)
321 if game.screen ~= 'playfield' then
322 return
324 if game.phase ~= 'play' then
325 return
328 -- click after death
329 if game.died then
330 setuplevel()
331 return
333 -- clickedx, clickedy from previous turn
334 if playfield.clickedx and
335 playfield[playfield.clickedx][playfield.clickedy].arrowgift and
336 not (mx == playfield.clickedx and my == playfield.clickedy) then
337 local gift = playfield[playfield.clickedx][playfield.clickedy].gift
338 -- open arrowgift
339 animate_arrow_gift(playfield.clickedx, playfield.clickedy)
340 -- preserve gift
341 playfield[playfield.clickedx][playfield.clickedy].gift = gift
343 -- new clickedx, clickedy
344 playfield.clickedx, playfield.clickedy = mx, my
345 if playfield[mx][my].onclick then playfield[mx][my].onclick() end
347 next_phase()
351 function isin(mx, my, x, y, w, h)
352 return mx >= x and mx <= x + w and my >= y and my <= y + h
356 function love.mousepressed(mx, my, button)
357 if game.screen == 'playfield' then
358 inplayfield(function (x, y, p)
359 if isin(mx, my, (screen.playfieldx + x - 1) * screen.tilesize, (screen.playfieldy + y - 1) * screen.tilesize, screen.tilesize, screen.tilesize) then
360 clicked(x, y)
362 end)
363 else --menu
364 menu_click(mx, my)
369 function love.keypressed(k)
370 if k == 'escape' then
371 if game.screen ~= 'playfield' then
372 game:change_screen('playfield')
373 else
374 love.event.push('quit')
376 elseif k == 'q' then
377 love.event.push('quit')
378 elseif k == 'n' then
379 setuplevel(game.currentlevel + 1)
380 elseif k == 'p' then
381 setuplevel(game.currentlevel - 1)
382 elseif k == 'r' then
383 game.temppurple = not game.temppurple
384 print('purple', game.temppurple)
385 elseif k == 'u' then
386 game.underpill = not game.underpill
387 print('underpill', game.underpill)
388 elseif k == 'd' then
389 game.devil = not game.devil
390 print('devil', game.devil)
391 elseif k == 'm' then
392 if game.screen ~= 'playfield' then
393 game:change_screen('playfield')
394 else
395 game:change_screen('menu_levels')