2 Ruben's Pyramid - a minigame is a part of POCA - a puzzle game
4 POCA is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This software is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>.
23 hijacked_love_functions
= {
30 draw
= function() -- hijacked function
32 love
.graphics
.setColor(1,1,1, 1)
34 love
.graphics
.draw(rp
.rich_canvas
, rp
.bigx
, rp
. bigy
)
35 else -- draw rotating triangles over canvas with these traingles missing
36 love
.graphics
.draw(rp
.poor_canvas
, rp
.bigx
, rp
. bigy
)
38 love
.graphics
.draw(rp
.meshes
[a
], rp
.bigx
, rp
.bigy
)
40 -- restore selected triangle to hide weird shapes that appear when rotating
42 local x
= rp
.bigx
+ p
[p
.selectedr
][p
.selectedt
].x
43 local y
= rp
.bigy
+ p
[p
.selectedr
][p
.selectedt
].y
44 rp
.draw_triangle(x
, y
, p
[p
.selectedr
][p
.selectedt
].color
)
47 rp
:draw_pyramid_cursor()
53 -- height of equilateral triangle
59 draw_button_image
= function(name
, selected
, disabled
)
62 love
.graphics
.line(.5,1, .5,0)
63 love
.graphics
.line(0,.5, 1,.5)
66 love
.graphics
.line(0,.5, 1,.5)
69 love
.graphics
.line(0,0, 1,1)
70 love
.graphics
.line(1,0, 0,1)
73 love
.graphics
.polygon('line', 0,.1, .5,rp
.h(1)+.1, 1,.1)
76 if rp
.no_anim
then love
.graphics
.setColor(1,1,1, .4) end
77 love
.graphics
.rectangle('line', 0,0, .2,.2)
78 love
.graphics
.rectangle('line', 0,.4, .2,.2)
79 love
.graphics
.rectangle('line', 0,.8, .2,.2)
83 -- draw button background
84 love
.graphics
.setColor(.4,.4,.4, 1)
86 love
.graphics
.rectangle('fill', 0, 0, 1, 1, .1)
88 love
.graphics
.rectangle('line', 0, 0, 1, 1, .1)
90 love
.graphics
.setLineWidth(.03)
91 love
.graphics
.rectangle('line', 0, 0, 1, 1, .1)
94 love
.graphics
.translate(.1, .1)
95 love
.graphics
.scale(.8)
97 love
.graphics
.setColor(1,1,1, .4)
99 love
.graphics
.setColor(1,1,1, 1)
108 x
, y
, image
= 'less',
110 rp
.size
= math
.max(rp
.min_size
, rp
.size
- 1)
115 x
, y
, image
= 'more',
117 rp
.size
= math
.min(rp
.max_size
, rp
.size
+ 1)
122 x
, y
, image
= 'fast',
124 rp
.no_anim
= not rp
.no_anim
128 x
, y
, image
= 'shuffle',
139 draw
= function(self
)
142 love
.graphics
.translate(self
[a
].x
, self
[a
].y
)
143 love
.graphics
.scale(rp
.buttonsize
)
144 rp
.draw_button_image(self
[a
].image
, self
.selected
== a
, self
[a
].disabled
)
154 local function last(psize
)
155 return psize
* (psize
+ 1) / 2
158 local rnd
= math
.random(last(rp
.size
))
159 for row
= 1, rp
.size
do
160 if last(row
) >= rnd
then
161 rp
:pyramid_click(row
, rnd
- last(row
- 1), true) -- no animation
166 -- remove last click indicator
167 rp
.pyramid
.selectedr
, rp
.pyramid
.selectedt
= nil, nil
173 resize
= function() -- hijacked function
174 if current
.phase
== 'rubens_anim' then return end
176 if screen
.h
< rp
.h(screen
.w
) then
177 rp
.margin
= screen
.h
/ 10
178 rp
.bigh
= screen
.h
- rp
.margin
179 rp
.bigw
= rp
.bigh
* 1.1547
181 rp
.margin
= screen
.w
/ 10
182 rp
.bigw
= screen
.w
- rp
.margin
183 rp
.bigh
= rp
.h(screen
.w
- rp
.margin
)
185 rp
.bigx
= (screen
.w
- rp
.bigw
) / 2
186 rp
.bigy
= (screen
.h
- rp
.bigh
) / 2
187 rp
.buttonsize
= rp
.bigw
/ 7
188 local buttony
= rp
.bigy
/ 2
190 buttonx
[2] = rp
.bigx
/ 2
191 buttonx
[3] = buttonx
[2] + rp
.buttonsize
+ rp
.margin
/ 3
192 buttonx
[5] = screen
.w
- rp
.buttonsize
- rp
.bigx
/ 2
193 buttonx
[4] = buttonx
[5] - rp
.buttonsize
- rp
.margin
/ 3
194 for a
= 2, #buttonx
do
195 rp
.buttons
[a
].x
, rp
.buttons
[a
].y
= buttonx
[a
], buttony
197 rp
.buttons
[1].x
= buttonx
[2]
198 rp
.buttons
[1].y
= buttony
+ rp
.buttonsize
+ rp
.margin
/ 3
199 rp
.labelwidth
= rp
.buttonsize
200 rp
.smallw
= rp
.bigw
/ rp
.size
201 rp
.smallh
= rp
.h(rp
.smallw
)
202 rp
.inpyramid(rp
.pyramid
, function(p
, row
, tri
)
203 p
[row
][tri
].x
= rp
.bigw
/ 2 + (tri
- 1 - row
/ 2) * rp
.smallw
204 p
[row
][tri
].y
= (row
- 1) * rp
.smallh
210 update_canvases
= function(self
)
211 if not self
.poor_canvas
then
212 self
.poor_canvas
= love
.graphics
.newCanvas(self
.bigw
, self
.bigh
)
213 -- without mirrored triangles
215 if not self
.rich_canvas
then
216 self
.rich_canvas
= love
.graphics
.newCanvas(self
.bigw
, self
.bigh
)
217 -- with mirrored triangles
219 love
.graphics
.setCanvas(self
.poor_canvas
)
220 self
:draw_pyramid(true) -- exclude mirrored triangles
221 love
.graphics
.setCanvas(self
.rich_canvas
)
223 love
.graphics
.setCanvas()
227 pyramid_click
= function(self
, r
, t
, no_anim
)
228 local function mirror(p
, r
, t
)
229 for row
= r
+ 1, rp
.size
do -- skip first
230 for tri
= t
, t
+ math
.floor((row
- r
) / 2) do
231 -- (row - r - 1) to exclude central triangles which go nowhere
232 -- but they need to be marked as mirrored nevertheless
233 local tempcolor
= p
[row
][tri
].color
234 p
[row
][tri
].color
= p
[row
][2 * t
+ row
- r
- tri
].color
235 p
[row
][2 * t
+ row
- r
- tri
].color
= tempcolor
236 p
[row
][tri
].mirrored
= true
237 p
[row
][2 * t
+ row
- r
- tri
].mirrored
= true
241 self
:pyramid_clear_mirrored()
242 self
.pyramid
.selectedr
= r
243 self
.pyramid
.selectedt
= t
246 mirror(self
.pyramid
, self
.pyramid
.selectedr
, self
.pyramid
.selectedt
)
247 self
.pyramid
= rp
.rotate_pyramid(self
.pyramid
)
249 self
:update_canvases()
250 if not (no_anim
or self
.no_anim
) then animate_pyramid() end
254 pyramid_clear_mirrored
= function(self
)
255 self
.inpyramid(self
.pyramid
, function(p
, row
, tri
)
256 p
[row
][tri
].mirrored
= nil
261 mousepressed
= function(mx
, my
, button
) -- hijacked function
262 if current
.phase
== 'rubens_anim' then return end
263 rp
.pyramid
.selectedr
= nil
264 rp
.pyramid
.selectedt
= nil
265 rp
.buttons
.selected
= nil
266 -- check for button clicks
267 for a
= 1, #rp
.buttons
do
269 isin(mx
, my
, rp
.buttons
[a
].x
, rp
.buttons
[a
].y
, rp
.buttonsize
, rp
.buttonsize
) and
270 not rp
.buttons
[a
].disabled
272 rp
.buttons
[a
].onclick()
276 -- check for pyramid clicks
277 rp
.inpyramid(rp
.pyramid
, function(p
, r
, t
)
278 if isin(mx
- rp
.bigx
, my
- rp
.bigy
, p
[r
][t
].x
, p
[r
][t
].y
, rp
.smallw
, rp
.smallh
) then
279 rp
:pyramid_click(r
,t
)
287 cursor_move
= function(dir
)
289 if not rp
.pyramid
.selectedr
then
290 rp
.pyramid
.selectedr
= rp
.pyramid
.selectedr
or 1
291 rp
.pyramid
.selectedt
= rp
.pyramid
.selectedt
or 1
292 elseif dir
== 'up' then
293 -- jump to third button from the tip of the pyramid
294 if rp
.pyramid
.selectedr
== 1 then
295 rp
.pyramid
.selectedr
= 0
296 rp
.pyramid
.selectedt
= 3
297 -- special case: move to another arrow button
298 elseif rp
.pyramid
.selectedr
== 0 and rp
.pyramid
.selectedt
== 1 then
299 rp
.pyramid
.selectedt
= 2
300 -- shound't do anything at 0th row (doesn't move to -1 and left)
302 rp
.pyramid
.selectedr
= rp
.pyramid
.selectedr
- 1
304 (rp
.pyramid
.selectedr
% 2) == 1 and
305 rp
.pyramid
.selectedr
> 1
307 rp
.pyramid
.selectedt
= rp
.pyramid
.selectedt
- 1
310 elseif dir
== 'down' then
311 if rp
.pyramid
.selectedr
== 0 and rp
.pyramid
.selectedt
== 2 then
312 -- special case: move to another arrow button
313 rp
.pyramid
.selectedt
= 1
315 -- shound't do anything at last row
316 rp
.pyramid
.selectedr
= rp
.pyramid
.selectedr
+ 1
318 (rp
.pyramid
.selectedr
% 2) == 0 and
319 rp
.pyramid
.selectedr
<= rp
.size
321 rp
.pyramid
.selectedt
= rp
.pyramid
.selectedt
+ 1
324 elseif dir
== 'left' then
325 --jump to buttons from 1
326 if rp
.pyramid
.selectedr
== 1 then
327 rp
.pyramid
.selectedr
= 0
328 rp
.pyramid
.selectedt
= 3
330 rp
.pyramid
.selectedt
= rp
.pyramid
.selectedt
- 1
332 elseif dir
== 'right' then
333 --jump to buttons from 1
334 if rp
.pyramid
.selectedr
== 1 then
335 rp
.pyramid
.selectedr
= 0
336 rp
.pyramid
.selectedt
= 4
338 rp
.pyramid
.selectedt
= rp
.pyramid
.selectedt
+ 1
341 rp
.pyramid
.selectedr
= math
.max(0, rp
.pyramid
.selectedr
)
342 rp
.pyramid
.selectedr
= math
.min(rp
.size
, rp
.pyramid
.selectedr
)
343 rp
.pyramid
.selectedt
= math
.max(1, rp
.pyramid
.selectedt
)
344 if rp
.pyramid
.selectedr
> 0 then
345 rp
.pyramid
.selectedt
= math
.min(rp
.pyramid
.selectedr
, rp
.pyramid
.selectedt
)
346 rp
.buttons
.selected
= nil
348 rp
.pyramid
.selectedt
= math
.min(#rp
.buttons
, rp
.pyramid
.selectedt
)
349 rp
.buttons
.selected
= rp
.pyramid
.selectedt
354 keypressed
= function(k
) -- hijacked function
355 if current
.phase
== 'rubens_anim' then return end
357 love
.event
.push('quit')
358 elseif k
== 'escape' then
360 elseif k
== 'up' or k
== 'down' or k
== 'left' or k
== 'right' then
362 elseif k
== 'return' and rp
.pyramid
.selectedr
then
363 if rp
.pyramid
.selectedr
> 0 then
364 rp
:pyramid_click(rp
.pyramid
.selectedr
, rp
.pyramid
.selectedt
)
366 local clicked_button
= rp
.pyramid
.selectedt
367 if not rp
.buttons
[rp
.pyramid
.selectedt
].disabled
then
368 rp
.buttons
[rp
.pyramid
.selectedt
].onclick()
370 -- preserve selection
371 rp
.pyramid
.selectedr
= 0
372 rp
.pyramid
.selectedt
= clicked_button
378 -- init or go through pyramid
379 inpyramid
= function(p
, f
, lastrow
)
380 for row
= 1, lastrow
or p
.size
do
381 p
[row
] = p
[row
] or {}
382 for triangle
= 1, row
do
383 p
[row
][triangle
] = p
[row
][triangle
] or {color
= 0}
384 if f
then f(p
, row
, triangle
) end
390 rotate_pyramid
= function(p
)
391 local newp
= {size
= p
.size
}
392 rp
.inpyramid(newp
, function(np
, r
, t
)
393 np
[r
][t
].x
, np
[r
][t
].y
= p
[r
][t
].x
, p
[r
][t
].y
394 end) -- just init and copy coordinates
395 rp
.inpyramid(newp
, function(np
, r
, t
)
396 np
[np
.size
- t
+ 1][r
- t
+ 1].color
= p
[r
][t
].color
397 np
[np
.size
- t
+ 1][r
- t
+ 1].mirrored
= p
[r
][t
].mirrored
398 -- preserve selection
399 if p
.selectedr
== r
and p
.selectedt
== t
then
400 np
.selectedr
= np
.size
- t
+ 1
401 np
.selectedt
= r
- t
+ 1
408 init_pyramid
= function(size
)
409 -- init white and paint pyramid top red, then rotate...
410 local p
= {size
= size
}
411 rp
.inpyramid(p
) --just init
412 local function paint_top(color
, height
)
413 rp
.inpyramid(p
, function(p
, row
, triangle
)
414 p
[row
][triangle
].color
= color
415 end, height
) -- paint until here
418 paint_top(a
, size
/ 2)
419 p
= rp
.rotate_pyramid(p
)
425 -- initialize big triangles for animation
426 init_meshes
= function(self
)
427 if self
.no_anim
then return end
428 local p
= self
.pyramid
429 if not (p
.selectedr
and p
.selectedr
> 0) then
434 local sr
= p
.selectedr
435 local st
= p
.selectedt
437 local tx1
= p
[sr
][st
].x
+ self
.smallw
/ 2
438 local ty1
= p
[sr
][st
].y
439 local bottomy
= p
[self
.size
][st
].y
+ self
.smallh
440 local dx1
= tx1
- p
[self
.size
][st
].x
442 tx1
, ty1
, tx1
- dx1
, bottomy
, tx1
+ dx1
, bottomy
445 local tx2
= p
[sr
][st
].x
+ self
.smallw
446 local ty2
= p
[sr
][st
].y
+ self
.smallh
447 local dx2
= tx2
- p
[sr
][1].x
449 tx2
, ty2
, p
[sr
][1].x
, ty2
, tx2
- dx2
/ 2, ty2
- self
.h(dx2
)
452 local tx3
= p
[sr
][st
].x
453 local dx3
= p
[sr
][sr
].x
- p
[sr
][st
].x
+ self
.smallw
455 tx3
, ty2
, tx3
+ dx3
, ty2
, tx3
+ dx3
/ 2, ty2
- self
.h(dx3
)
459 self
.meshes
[a
] = love
.graphics
.newMesh({
460 { v
[a
][1], v
[a
][2], v
[a
][1] / self
.bigw
, v
[a
][2] / self
.bigh
},
461 { v
[a
][3], v
[a
][4], v
[a
][3] / self
.bigw
, v
[a
][4] / self
.bigh
},
462 { v
[a
][5], v
[a
][6], v
[a
][5] / self
.bigw
, v
[a
][6] / self
.bigh
}
464 self
.meshes
[a
]:setTexture(self
.rich_canvas
) -- sample triangles from the whole picture
470 -- exchange places of two last vertices
471 anim_triangles
= function(self
, phase
)
472 -- sometimes meshes get unset at the last step of animation and this
473 -- needs to be checked to prevent crashes
474 if not self
.meshes
then return end
475 local v
= self
.vertices
477 local dx
= (v
[a
][5] - v
[a
][3]) * phase
478 local dy
= (v
[a
][6] - v
[a
][4]) * phase
479 self
.meshes
[a
]:setVertices({
480 { v
[a
][3] + dx
, v
[a
][4] + dy
, v
[a
][3] / self
.bigw
, v
[a
][4] / self
.bigh
},
481 { v
[a
][5] - dx
, v
[a
][6] - dy
, v
[a
][5] / self
.bigw
, v
[a
][6] / self
.bigh
}
487 draw_triangle
= function(x
, y
, color
) -- upper left
489 love
.graphics
.setColor(.9,.9,.9, 1)
490 elseif color
== 1 then
491 love
.graphics
.setColor(.9,.1,.1, 1)
492 elseif color
== 2 then
493 love
.graphics
.setColor(.1,.9,.1, 1)
494 elseif color
== 3 then
495 love
.graphics
.setColor(.1,.1,.9, 1)
497 love
.graphics
.polygon('fill',
499 x
+ rp
.smallw
/ 2, y
,
500 x
+ rp
.smallw
, y
+ rp
.smallh
502 love
.graphics
.setColor(1,1,1, 1)
506 -- the pyramid without some triangles will be drawn on poor_canvas.
507 -- although the poor_canvas will be cut out before animation,
508 -- to remove background, it's still better to exclude these triangles,
509 -- because canvas-to meshes mapping is not perfect
510 draw_pyramid
= function(self
, exclude_mirrored
)
511 -- pyramid background
512 love
.graphics
.setColor(.2,.2,.2, 1)
513 love
.graphics
.polygon('fill',
515 0 + rp
.bigw
, 0 + rp
.bigh
,
518 local p
= self
.pyramid
519 rp
.inpyramid(p
, function(p
, r
, t
)
520 if not (exclude_mirrored
and p
[r
][t
].mirrored
) then
521 rp
.draw_triangle(p
[r
][t
].x
, p
[r
][t
].y
, p
[r
][t
].color
)
527 draw_pyramid_cursor
= function(self
)
528 local p
= self
.pyramid
529 local r
= p
.selectedr
530 local t
= p
.selectedt
532 love
.graphics
.setColor(0,0,0, .5)
533 love
.graphics
.circle('fill',
534 rp
.bigx
+ p
[r
][t
].x
+ rp
.smallw
/ 2, rp
.bigy
+ p
[r
][t
].y
+ rp
.smallh
* 2 / 3,
541 -- called when changing the height
542 init
= function(self
)
543 self
.pyramid
= self
.init_pyramid(self
.size
)
544 self
.buttons
[2].disabled
= self
.size
>= self
.max_size
545 self
.buttons
[1].disabled
= self
.size
<= self
.min_size
550 main
= function(self
)
551 self
.old_speed
= settings
.speed
552 if settings
.speed
== 2 then
564 quit
= function(self
)
567 change_screen('menu_objects')
568 settings
.speed
= self
.old_speed