Block-packing game
Drag shapes to place them on the grid. Complete lines to score points. Use points to progress through a series of ever-more-expensive inversions of the grid, called "flips".
Can you reach a total score of more than 50 points?
This might be my most-polished p5.js game. It's also some of the worst code I've ever written, to the point that I've used it as an exercise in navigating unreadable code. The worst part of it is the code responsible for the adaptable layout; it's scattered in multiple places and stubbornly refuses to use an explict representation of rectangles.
I could rewrite it with better classes/modules/datatypes... but where's the fun in that? 😂
function setup() {
createCanvas(windowWidth, windowHeight);
}
var windowResized = setup
setTimeout(windowResized, 100)
let grid = {}
let piece
let reserved
let drag
let score = 0
let message = ''
let mT = 0
let over = 0
let flips = 0
function draw() {
background(255);
const g = 10
if (piece == undefined) {
piece = {}
let parts = 0
let x = 0
let y = 0
let bounds = [x,y,x,y] // limits
let dir = [0, 0]
while (parts < 5) {
bounds[0] = min(bounds[0], x) // ??
bounds[1] = min(bounds[1], y)
bounds[2] = max(bounds[2], x)
bounds[3] = max(bounds[3], y)
if (!piece[[x,y]]) {
piece[[x,y]] = true
parts ++
}
dir = random([dir, [1,0], [-1,0], [0,1], [0,-1]])
let [dx,dy] = dir
x += dx
y += dy
}
piece.bounds = bounds
}
if (piece.possible == undefined) {
piece.possible = false
a: for (let i = -piece.bounds[0]; i < g-piece.bounds[2]; i++) {
for (let j = -piece.bounds[1]; j < g-piece.bounds[3]; j++) {
piece.possible = true
b: for (let k = piece.bounds[0]; k <= piece.bounds[2]; k++) {
for (let l = piece.bounds[1]; l <= piece.bounds[3]; l++) {
if (piece[[k, l]] && grid[[i + k, j + l]] > 0) {
piece.possible = false
break b
}
}
}
if (piece.possible) {
break a
}
}
}
}
if (!piece.possible && !over) {
over = 1
} else if (piece.possible && over) {
over = 0
}
let minSize = min(width, height)
let maxSize = max(width, height)
let ps = min(max(abs(width - height), 200), minSize/3)
let s = min(maxSize - ps, minSize)
let boundsI = piece.bounds[2] - piece.bounds[0] + 1
let boundsJ = piece.bounds[3] - piece.bounds[1] + 1
let maxBounds = max(boundsI, boundsJ)
let sqS = drag ? s/g : ps/maxBounds
let ox = maxSize == width ? 0 : (width - s) / 2
let oy = maxSize == height ? 0 : (height - s) / 2
let pox = (maxSize == width ? s : (width - ps) / 2)
let poy = (maxSize == height ? s : (height - ps) / 2)
let pcx = sqS * (maxBounds - boundsI) / 2
let pcy = sqS * (maxBounds - boundsJ) / 2
let mouseI = drag && round((mouseX - drag.sx + pox + pcx - ox) / (s/g) - piece.bounds[0])
let mouseJ = drag && round((mouseY - drag.sy + poy + pcy - oy) / (s/g) - piece.bounds[1])
translate(ox, oy)
for (let i = 0; i < g; i++) {
for (let j = 0; j < g; j++) {
stroke(255)
let t = 0
if ((i + j) % 2) {
fill(240)
} else {
fill(230)
}
if (grid[[i, j]]) {
fill(50)
if (grid[[i, j]] > 1) {
t = grid[[i, j]]
grid[[i, j]] ++
}
if (grid[[i, j]] > 20) {
grid[[i, j]] = 1
}
if (grid[[i, j]] < 0) {
t = -grid[[i, j]]
grid[[i, j]] --
}
if (grid[[i, j]] < -20) {
delete grid[[i, j]]
}
}
if (piece[[i - mouseI, j - mouseJ]]) {
if (grid[[i, j]]) {
noStroke()
fill(250, 0, 0)
} else {
fill(200, 150, 150)
}
}
rect(s/g*(i + (t/10)/2), s/g*(j + (t/10)/2), s/g * (1 - t/10), s/g * (1 - t/10))
}
}
mT += 5
if (over && !drag) {
over ++
fill(0, min(over - 100, 160))
rect(0, 0, s, s)
noStroke()
fill(255, over - 100)
textSize(30)
textAlign(CENTER, CENTER)
let cost = flips * 2 + 1
if (score >= cost) {
text(`Can't move.\nFlip?\nCost: ${cost}`, s/2, s/2)
if (over > 60 && mouseIsPressed && mouseX > ox && mouseX < ox + s && mouseY > oy && mouseY < oy + s) {
for (let i = 0; i < g; i++) {
for (let j = 0; j < g; j++) {
if (grid[[i, j]] && grid[[i, j]] > 0) {
grid[[i, j]] = -1
} else {
grid[[i, j]] = 2
}
}
}
score -= cost
if (piece) piece.possible = undefined
over = 0
flips ++
mouseIsPressed = false
}
} else {
text(`Game over!\nNext flip costs: ${cost}\nPoints: ${score}\nTotal score: ${score + flips * flips}`, s/2, s/2)
if (over > 160 && mouseIsPressed && mouseX > ox && mouseX < ox + s && mouseY > oy && mouseY < oy + s) {
grid = {}
piece = undefined
reserved = undefined
score = 0
message = ''
over = 0
flips = 0
}
}
} else if (mT < 255) {
stroke(255)
fill(0, 255 - mT)
textSize(10 + sqrt(mT))
textAlign(CENTER, CENTER)
text(message, s/2, s/2)
}
resetMatrix()
let rox = maxSize == width ? pox : pox - ps
let roy = maxSize == height ? poy : poy - ps
translate(rox, roy) // Reserved
if (reserved) {
let boundsI = reserved.bounds[2] - reserved.bounds[0] + 1
let boundsJ = reserved.bounds[3] - reserved.bounds[1] + 1
let maxBounds = max(boundsI, boundsJ)
let sqS = ps/maxBounds
let pcx = sqS * (maxBounds - boundsI) / 2
let pcy = sqS * (maxBounds - boundsJ) / 2
translate(pcx, pcy)
for (let i = reserved.bounds[0]; i <= reserved.bounds[2]; i++) {
for (let j = reserved.bounds[1]; j <= reserved.bounds[3]; j++) {
if (reserved[[i, j]]) {
stroke(255)
fill(150, 150, 200)
rect(sqS*(i - reserved.bounds[0]), sqS*(j - reserved.bounds[1]), sqS, sqS)
}
}
}
}
resetMatrix()
fill(0)
noStroke()
textSize(15)
textAlign(CENTER, CENTER)
translate(rox, roy)
text(`Reserve`, ps / 2, ps / 2)
resetMatrix()
translate(pox, poy)
noFill()
rect(0, 0, ps, ps)
if (drag == undefined && mouseIsPressed && mouseX > pox && mouseX < pox + ps && mouseY > poy && mouseY < poy + ps) {
drag = {
sx: mouseX + (mouseX - pox) * (1 / (ps/maxBounds) * s/g - 1),
sy: mouseY + (mouseY - poy) * (1 / (ps/maxBounds) * s/g - 1),
}
}
if (drag && !mouseIsPressed) { // когато пуснеш?
if (mouseI + piece.bounds[0] >= 0 && mouseJ + piece.bounds[1] >= 0 && mouseI + piece.bounds[2] < g && mouseJ + piece.bounds[3] < g) {
let allowed = true
for (let i = piece.bounds[0]; i <= piece.bounds[2]; i++) {
for (let j = piece.bounds[1]; j <= piece.bounds[3]; j++) {
if (piece[[i, j]] && grid[[mouseI + i, mouseJ + j]]) {
allowed = false
}
}
}
if (allowed) {
let checkI = new Set()
let checkJ = new Set()
for (let i = piece.bounds[0]; i <= piece.bounds[2]; i++) {
for (let j = piece.bounds[1]; j <= piece.bounds[3]; j++) {
if (piece[[i, j]]) {
grid[[mouseI + i, mouseJ + j]] = piece[[i, j]]
checkJ.add(mouseJ + j)
checkI.add(mouseI + i)
}
}
}
let matchI = new Set([...checkI].filter(i => {
for (let j = 0; j < g; j++) {
if (grid[[i, j]] != 1) {
return false;
}
}
return true;
}))
let matchJ = new Set([...checkJ].filter(j => {
for (let i = 0; i < g; i++) {
if (grid[[i, j]] != 1) {
return false;
}
}
return true;
}))
for (let i of matchI) {
for (let j = 0; j < g; j++) {
grid[[i, j]] = -1;
}
}
for (let j of matchJ) {
for (let i = 0; i < g; i++) {
grid[[i, j]] = -1;
}
}
let matched = matchI.size + matchJ.size
if (matched) {
message = `x${matched}!`
mT = 0
score += matched * matched
}
mouseIsPressed = false
piece = undefined
}
}
drag = undefined
}
if (drag) {
translate(mouseX - drag.sx, mouseY - drag.sy)
}
if (piece) {
translate(pcx, pcy)
for (let i = piece.bounds[0]; i <= piece.bounds[2]; i++) {
for (let j = piece.bounds[1]; j <= piece.bounds[3]; j++) {
if (piece[[i, j]]) {
stroke(255)
fill(150, 100, 100)
rect(sqS*(i - piece.bounds[0]), sqS*(j - piece.bounds[1]), sqS, sqS)
}
}
}
}
resetMatrix()
if (mouseIsPressed && !drag && mouseX > rox && mouseX < rox + ps && mouseY > roy && mouseY < roy + ps) {
[reserved, piece] = [piece, reserved]
if (piece) piece.possible = undefined
mouseIsPressed = false
}
fill(0)
noStroke()
textSize(15)
textAlign(CENTER, CENTER)
translate(maxSize == width ? pox : pox + ps, maxSize == height ? poy : poy + ps)
text(`Points: ${score}\nFlips: ${flips}`, ps / 2, ps / 2)
resetMatrix()
}(Originally seen at https://editor.p5js.org/bojidar-bg/sketches/hydiwBCb2)
Browse more articles?
← Scale Experiments tagged p5 (64/85) De Rahm curves →
← Scale Experiments tagged game (8/11) 3D Maze →
← Scale Experiments on this site (64/85) De Rahm curves →