xxxxxxxxxx
294
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Jelly Tetris Softbody Screensaver</title>
<script src="https://cdn.jsdelivr.net/npm/p5@1.6.0/lib/p5.js"></script>
<script src="https://cdn.jsdelivr.net/npm/p5@1.6.0/lib/addons/p5.dom.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/p5@1.6.0/lib/addons/p5.sound.min.js"></script>
</head>
<body style="margin:0;padding:0;overflow:hidden;">
<script>
/*
DISCLAIMER
CHATGPT DID MOST OF THE HEAVY LIFTING HERE. THATS WHY ITS SO BAD :') jk
Jelly Tetris Softbody Screensaver with random falling pieces, stacking, and flushing.
- Random Tetris-like pieces (I, O, T, S, Z, J, L) spawn from the top.
- They fall down under gravity and land on others, forming a stack.
- When stack is too high, flush them all.
- Simple "bounce" when landing to make it feel more dynamic.
- Locked to a 2D plane for physics, but rendered in 3D with jelly effect.
*/
// -----------------------------------
// Adjustable Parameters
// -----------------------------------
let BG_COLOR = [20, 20, 40];
let LIGHT_DIR = [0.3, -0.7, 0.6];
// Jelly effect parameters
let JELLY_AMPLITUDE = 0.05;
let TRANSPARENCY = 0.9;
let USE_RAYMARCHING = false;
let RAYMARCHING_STEPS = 64;
let RAYMARCHING_MAX_DIST = 5.0;
// Physics parameters
let GRAVITY = 0.05; // fall speed increment per frame
let BOUNCE_FACTOR = 0.2; // how much they bounce back up on landing
let SPAWN_INTERVAL = 2000; // spawn new piece every 2 seconds
let FLUSH_HEIGHT = 400; // if stacked height exceeds this, flush everything
// Each Tetris piece shape is defined by relative coordinates of its cubes
// We'll choose from these sets randomly
const TETRIS_SHAPES = [
// I piece
[{x:0,y:0},{x:0,y:-1},{x:0,y:1},{x:0,y:2}],
// O piece
[{x:0,y:0},{x:1,y:0},{x:0,y:1},{x:1,y:1}],
// T piece
[{x:0,y:0},{x:-1,y:0},{x:1,y:0},{x:0,y:-1}],
// S piece
[{x:0,y:0},{x:1,y:0},{x:0,y:-1},{x:-1,y:-1}],
// Z piece
[{x:0,y:0},{x:-1,y:0},{x:0,y:-1},{x:1,y:-1}],
// J piece
[{x:0,y:0},{x:0,y:-1},{x:0,y:-2},{x:-1,y:-2}],
// L piece
[{x:0,y:0},{x:0,y:-1},{x:0,y:-2},{x:1,y:-2}]
];
// Each piece is stored as an object: {shape:[], x: num, y: num, vy: num (fall speed)}
let pieces = [];
// Shader variable
let jellyShader;
const vert = `
precision mediump float;
attribute vec3 aPosition;
attribute vec3 aNormal;
uniform mat4 uProjectionMatrix;
uniform mat4 uModelViewMatrix;
uniform mat3 uNormalMatrix;
uniform float uTime;
uniform float uJellyAmplitude;
uniform bool uUseRaymarching;
varying vec3 vNormal;
varying vec3 vPos;
void main() {
float displacement = sin(uTime * 2.0 + aPosition.x * 2.0 + aPosition.y * 2.0) * uJellyAmplitude;
vec3 newPosition = aPosition + aNormal * displacement;
vNormal = normalize(uNormalMatrix * aNormal);
vec4 pos = uModelViewMatrix * vec4(newPosition, 1.0);
vPos = pos.xyz;
gl_Position = uProjectionMatrix * pos;
}
`;
const frag = `
precision mediump float;
varying vec3 vNormal;
varying vec3 vPos;
uniform vec3 uLightDir;
uniform float uTime;
uniform bool uUseRaymarching;
uniform vec3 uColor;
uniform float uTransparency;
uniform vec3 uBackgroundColor;
uniform int uRaySteps;
uniform float uRayMaxDist;
void main() {
vec3 norm = normalize(vNormal);
float diff = max(dot(norm, normalize(uLightDir)), 0.0);
if(uUseRaymarching) {
diff *= 0.5;
}
vec3 baseColor = uColor * diff + uColor * 0.2;
gl_FragColor = vec4(baseColor, uTransparency);
}
`;
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
noStroke();
jellyShader = createShader(vert, frag);
// Schedule piece spawns
setInterval(spawnPiece, SPAWN_INTERVAL);
}
function draw() {
background(BG_COLOR[0], BG_COLOR[1], BG_COLOR[2]);
// Update physics
updatePhysics();
// Flush if too high
checkAndFlush();
// Render scene
shader(jellyShader);
jellyShader.setUniform("uTime", millis() * 0.001);
jellyShader.setUniform("uJellyAmplitude", JELLY_AMPLITUDE);
jellyShader.setUniform("uLightDir", LIGHT_DIR);
jellyShader.setUniform("uUseRaymarching", USE_RAYMARCHING);
jellyShader.setUniform("uColor", [0.4, 0.7, 1.0]);
jellyShader.setUniform("uTransparency", TRANSPARENCY);
jellyShader.setUniform("uBackgroundColor", [BG_COLOR[0]/255, BG_COLOR[1]/255, BG_COLOR[2]/255]);
jellyShader.setUniform("uRaySteps", RAYMARCHING_STEPS);
jellyShader.setUniform("uRayMaxDist", RAYMARCHING_MAX_DIST);
// Rotate scene for a dynamic view
rotateY(millis() * 0.0003);
rotateX(millis() * 0.0002);
// Draw all pieces
let cubeSize = 40; // smaller cubes for clarity
for (let piece of pieces) {
for (let b of piece.shape) {
push();
translate((piece.x + b.x) * cubeSize, (piece.y + b.y) * cubeSize, 0);
box(cubeSize * 0.9);
pop();
}
}
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}
// Spawn a random piece at the top
function spawnPiece() {
let shape = random(TETRIS_SHAPES);
// Start somewhere random horizontally
let startX = floor(random(-4,4));
let startY = -10;
pieces.push({
shape: shape,
x: startX,
y: startY,
vy: 0 // initial vertical speed
});
}
function updatePhysics() {
// For each piece, apply gravity and check collisions
for (let i = 0; i < pieces.length; i++) {
let p = pieces[i];
p.vy += GRAVITY;
p.y += p.vy;
// Check collision with ground (y ~ some lower limit)
// Let's define ground at y = 10 (arbitrary)
if (checkCollisionWithGround(p)) {
p.y = getAdjustedYOnGround(p);
p.vy = -p.vy * BOUNCE_FACTOR;
if (abs(p.vy) < 0.01) p.vy = 0; // settle
}
// Check collision with other pieces
for (let j = 0; j < pieces.length; j++) {
if (i === j) continue;
if (checkPieceCollision(p, pieces[j])) {
// Move p back up and apply bounce
p.y -= p.vy;
p.vy = -p.vy * BOUNCE_FACTOR;
if (abs(p.vy) < 0.01) p.vy = 0; // settle
}
}
}
}
// Simple ground check
function checkCollisionWithGround(p) {
// We'll say ground is at y=10 (lowest visible area)
// If any block of p is below y=10, that's a collision
for (let b of p.shape) {
if (p.y + b.y > 10) {
return true;
}
}
return false;
}
// Adjust piece Y so it just sits on ground
function getAdjustedYOnGround(p) {
// Move it so its lowest block is at y=10
let maxBlockY = -Infinity;
for (let b of p.shape) {
let by = p.y + b.y;
if (by > maxBlockY) maxBlockY = by;
}
let offset = 10 - maxBlockY;
return p.y + offset;
}
// Check collision between two pieces
function checkPieceCollision(p1, p2) {
// If they overlap in the y-direction and x-direction:
// We'll just do a simple discrete check block by block
for (let b1 of p1.shape) {
let bx1 = p1.x + b1.x;
let by1 = p1.y + b1.y;
for (let b2 of p2.shape) {
let bx2 = p2.x + b2.x;
let by2 = p2.y + b2.y;
// If blocks overlap or are extremely close (we'll say exact match)
if (floor(bx1) === floor(bx2) && floor(by1) === floor(by2)) {
// They occupy the same cell
if (by1 > by2) {
// p1 is on top of p2, so collision
return true;
}
}
}
}
return false;
}
// Check if pieces stacked too high
function checkAndFlush() {
// Find the highest piece
let highest = Infinity;
for (let p of pieces) {
for (let b of p.shape) {
let by = p.y + b.y;
if (by < highest) highest = by;
}
}
// If the top is higher than a certain threshold, flush
if (highest < -FLUSH_HEIGHT/calcCubeSizeFactor()) {
pieces = [];
}
}
// This is a helper to scale flush condition based on cubeSize if needed
function calcCubeSizeFactor() {
return 1; // currently just 1, but you can adapt if changing cubeSize dynamically
}
</script>
</body>
</html>