xxxxxxxxxx
366
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Jelly Tetris Softbody Screensaver (Fixed)</title>
<script src="https://cdn.jsdelivr.net/npm/p5@1.6.0/lib/p5.min.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>/************************
* Jelly Tetris w/ Fixes
************************/
let BG_COLOR = [20, 20, 80];
let LIGHT_DIR = [0.3, -0.7, 0.6];
let JELLY_AMPLITUDE = 0.05;
let TRANSPARENCY = 0.9;
let USE_RAYMARCHING = false;
let RAYMARCHING_STEPS = 64;
let RAYMARCHING_MAX_DIST = 5.0;
let GRAVITY = 0.03;
let BOUNCE_FACTOR = 0.3;
let SPAWN_INTERVAL = 333;
let MAX_PIECES = 20;
const COLS = 20;
const ROWS = 40;
let cubeSize = 40;
// Tetris shapes (relative coordinates)
const TETRIS_SHAPES = [
[{x:0,y:0},{x:0,y:-1},{x:0,y:1},{x:0,y:2}],
[{x:0,y:0},{x:1,y:0},{x:0,y:1},{x:1,y:1}],
[{x:0,y:0},{x:-1,y:0},{x:1,y:0},{x:0,y:-1}],
[{x:0,y:0},{x:1,y:0},{x:0,y:-1},{x:-1,y:-1}],
[{x:0,y:0},{x:-1,y:0},{x:0,y:-1},{x:1,y:-1}],
[{x:0,y:0},{x:0,y:-1},{x:0,y:-2},{x:-1,y:-2}],
[{x:0,y:0},{x:0,y:-1},{x:0,y:-2},{x:1,y:-2}]
];
let pieces = [];
let grid = [];
for (let y = 0; y < ROWS; y++) {
grid[y] = [];
for (let x = 0; x < COLS; x++) {
grid[y][x] = null;
}
}
let jellyShader;
// Simple vertex/fragment shaders for the “jelly” effect
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;
varying vec3 vNormal;
varying vec3 vPos;
void main() {
// Slight displacement for the "jelly" effect
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 bool uUseRaymarching;
uniform vec3 uColor;
uniform float uTransparency;
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);
}
`;
// For blinking/pause effect when grid is full
let gameOver = false;
let gameOverStart = 0;
let gameOverDelay = 2000; // 2 seconds of blinking
function setup() {
// Scale so the entire well fits in the window
cubeSize = floor(min(windowWidth / COLS, windowHeight / ROWS));
if (cubeSize < 10) cubeSize = 10; // minimal size
createCanvas(windowWidth, windowHeight, WEBGL);
noStroke();
jellyShader = createShader(vert, frag);
setInterval(spawnPiece, SPAWN_INTERVAL);
}
function draw() {
background(BG_COLOR[0], BG_COLOR[1], BG_COLOR[2]);
// If the top is filled, do blinking
if (gameOver) {
let elapsed = millis() - gameOverStart;
// Blink every 200 ms
let blinkOn = floor(elapsed / 200) % 2 === 0;
// Render the well only half the time for blinking
if (blinkOn) renderWell();
// If blink time is over, flush and reset
if (elapsed > gameOverDelay) {
flushGrid();
gameOver = false;
}
return;
}
// Normal update + rendering
updatePieces();
checkTopAndFlush(); // see if top is blocked, triggers gameOver
renderWell();
}
function renderWell() {
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("uTransparency", TRANSPARENCY);
let wellWidth = COLS * cubeSize;
let wellHeight = ROWS * cubeSize;
translate(-wellWidth / 2, -wellHeight / 2, 0);
// Slight tilt
let angleX = sin(millis() * 0.0005) * 0.2;
let angleY = sin(millis() * 0.0003) * 0.2;
rotateY(angleY);
rotateX(angleX);
// Draw locked grid
for (let gy = 0; gy < ROWS; gy++) {
for (let gx = 0; gx < COLS; gx++) {
let c = grid[gy][gx];
if (c !== null) {
jellyShader.setUniform("uColor", c);
push();
translate(gx * cubeSize + cubeSize / 2, gy * cubeSize + cubeSize / 2, 0);
box(cubeSize * 0.9);
pop();
}
}
}
// Draw active pieces
for (let p of pieces) {
jellyShader.setUniform("uColor", p.color);
for (let b of p.shape) {
push();
let bx = p.x + b.x;
let by = p.floatY + b.y;
translate(bx * cubeSize + cubeSize / 2, by * cubeSize + cubeSize / 2, 0);
box(cubeSize * 0.9);
pop();
}
}
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}
// ================ Piece Spawning ================
function spawnPiece() {
if (gameOver) return; // Don't spawn if we're in blink/pause
let shape = random(TETRIS_SHAPES);
let minX = Infinity;
let maxX = -Infinity;
for (let b of shape) {
if (b.x < minX) minX = b.x;
if (b.x > maxX) maxX = b.x;
}
let pieceWidth = maxX - minX + 1;
let startX = floor(random(0, COLS - pieceWidth + 1)) - minX;
let startY = -2;
let pieceColor = [random(0.2,1), random(0.2,1), random(0.2,1)];
// If we can't place at top, we consider it "full"
if (wouldCollideAt(startX, startY, shape)) {
// Trigger top-block (goes to blinking)
triggerGameOver();
return;
}
pieces.push({
shape: shape,
x: startX,
y: startY,
floatY: startY,
vy: 0,
color: pieceColor
});
}
// ================ Update & Collision ================
function updatePieces() {
for (let i = pieces.length - 1; i >= 0; i--) {
let p = pieces[i];
let oldY = p.floatY;
// Move piece
p.vy += GRAVITY;
let newY = p.floatY + p.vy;
// Check collision at newY
if (wouldCollideAtFloat(p, newY)) {
// Revert
p.vy = -p.vy * BOUNCE_FACTOR;
// If bounce too small, lock
if (Math.abs(p.vy) < 0.01) {
p.floatY = Math.round(oldY);
lockPiece(p);
pieces.splice(i,1);
clearFullRows();
} else {
// minimal bounce
p.floatY = oldY;
}
} else {
// No collision, apply newY
p.floatY = newY;
}
}
}
function wouldCollideAt(px, py, shape) {
// If shape is out-of-bounds or hits existing tile, that's collision
for (let b of shape) {
let x = px + b.x;
let y = py + b.y;
// Bound check
if (x < 0 || x >= COLS || y >= ROWS) {
if (y >= 0) return true;
} else {
// Grid check
if (y >= 0 && grid[y][x]) return true;
}
}
return false;
}
function wouldCollideAtFloat(piece, testY) {
// Test each block at (piece.x + b.x, testY + b.y)
for (let b of piece.shape) {
let x = piece.x + b.x;
let y = testY + b.y;
// If below bottom
if (y >= ROWS) return true;
// If out of left/right
if (x < 0 || x >= COLS) return true;
// If within grid and hits existing tile
if (y >= 0 && grid[Math.floor(y)][x]) return true;
}
return false;
}
function lockPiece(p) {
let finalY = Math.round(p.floatY);
for (let b of p.shape) {
let gx = p.x + b.x;
let gy = finalY + b.y;
if (gy >= 0 && gy < ROWS && gx >= 0 && gx < COLS) {
grid[gy][gx] = p.color;
}
}
}
// ================ Row Clearing ================
function clearFullRows() {
for (let y = ROWS - 1; y >= 0; y--) {
let full = true;
for (let x = 0; x < COLS; x++) {
if (!grid[y][x]) {
full = false;
break;
}
}
if (full) {
// Shift everything down
for (let yy = y; yy > 0; yy--) {
for (let xx = 0; xx < COLS; xx++) {
grid[yy][xx] = grid[yy - 1][xx];
}
}
// Empty top row
for (let xx = 0; xx < COLS; xx++) {
grid[0][xx] = null;
}
// recheck this row
y++;
}
}
}
// ================ Detect Full Top & Flush ================
function checkTopAndFlush() {
// If top row is blocked, trigger blinking
for (let x = 0; x < COLS; x++) {
if (grid[0][x] !== null) {
triggerGameOver();
return;
}
}
// Or if too many pieces
if (pieces.length > MAX_PIECES) {
triggerGameOver();
}
}
function triggerGameOver() {
// Start blinking/pause
gameOver = true;
gameOverStart = millis();
}
function flushGrid() {
// Clear entire grid & pieces
for (let y = 0; y < ROWS; y++) {
for (let x = 0; x < COLS; x++) {
grid[y][x] = null;
}
}
pieces = [];
}
</script>
</body>
</html>