|
<!DOCTYPE html> |
|
<html lang="ja"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>ゼリーテトリス</title> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<style> |
|
body { |
|
font-family: 'Arial', sans-serif; |
|
background: linear-gradient(135deg, #1a1a2e, #16213e); |
|
color: white; |
|
height: 100vh; |
|
margin: 0; |
|
display: flex; |
|
flex-direction: column; |
|
align-items: center; |
|
justify-content: center; |
|
overflow: hidden; |
|
} |
|
|
|
#game-container { |
|
position: relative; |
|
width: 300px; |
|
height: 600px; |
|
background-color: rgba(0, 0, 0, 0.3); |
|
border: 2px solid rgba(255, 255, 255, 0.1); |
|
border-radius: 8px; |
|
overflow: hidden; |
|
} |
|
|
|
canvas { |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
} |
|
|
|
.jelly-block { |
|
position: absolute; |
|
border-radius: 50%; |
|
box-shadow: 0 0 10px rgba(255, 255, 255, 0.3); |
|
transition: all 0.3s ease; |
|
transform-origin: center; |
|
filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.5)); |
|
} |
|
|
|
.jelly-animation { |
|
animation: jelly 0.8s infinite alternate; |
|
} |
|
|
|
@keyframes jelly { |
|
0% { transform: scale(1, 1); border-radius: 30%; } |
|
25% { transform: scale(0.95, 1.05); border-radius: 35%; } |
|
50% { transform: scale(1.05, 0.95); border-radius: 40%; } |
|
75% { transform: scale(0.98, 1.02); border-radius: 35%; } |
|
100% { transform: scale(1, 1); border-radius: 30%; } |
|
} |
|
|
|
.jelly-drop { |
|
animation: jellyDrop 0.5s; |
|
} |
|
|
|
@keyframes jellyDrop { |
|
0% { transform: scale(1, 1); } |
|
50% { transform: scale(1.2, 0.8); } |
|
100% { transform: scale(1, 1); } |
|
} |
|
|
|
.jelly-rotate { |
|
animation: jellyRotate 0.5s; |
|
} |
|
|
|
@keyframes jellyRotate { |
|
0% { transform: rotate(0deg) scale(1, 1); } |
|
25% { transform: rotate(5deg) scale(0.9, 1.1); } |
|
50% { transform: rotate(0deg) scale(1.1, 0.9); } |
|
75% { transform: rotate(-5deg) scale(0.95, 1.05); } |
|
100% { transform: rotate(0deg) scale(1, 1); } |
|
} |
|
|
|
.jelly-move { |
|
animation: jellyMove 0.3s; |
|
} |
|
|
|
@keyframes jellyMove { |
|
0% { transform: translateX(0) scale(1, 1); } |
|
50% { transform: translateX(2px) scale(0.95, 1.05); } |
|
100% { transform: translateX(0) scale(1, 1); } |
|
} |
|
|
|
.score-container { |
|
margin-top: 20px; |
|
font-size: 1.5rem; |
|
text-align: center; |
|
} |
|
|
|
.controls { |
|
margin-top: 20px; |
|
display: flex; |
|
gap: 10px; |
|
} |
|
|
|
button { |
|
background: rgba(255, 255, 255, 0.1); |
|
border: 1px solid rgba(255, 255, 255, 0.2); |
|
color: white; |
|
padding: 8px 16px; |
|
border-radius: 20px; |
|
cursor: pointer; |
|
transition: all 0.2s; |
|
font-weight: bold; |
|
letter-spacing: 1px; |
|
} |
|
|
|
button:hover { |
|
background: rgba(255, 255, 255, 0.2); |
|
transform: scale(1.05); |
|
} |
|
|
|
.title { |
|
font-size: 2.5rem; |
|
margin-bottom: 20px; |
|
background: linear-gradient(90deg, #ff6b6b, #feca57, #1dd1a1, #54a0ff); |
|
-webkit-background-clip: text; |
|
background-clip: text; |
|
color: transparent; |
|
animation: gradient 5s ease infinite; |
|
background-size: 400% 400%; |
|
text-shadow: 0 0 10px rgba(255, 255, 255, 0.3); |
|
} |
|
|
|
@keyframes gradient { |
|
0% { background-position: 0% 50%; } |
|
50% { background-position: 100% 50%; } |
|
100% { background-position: 0% 50%; } |
|
} |
|
|
|
.game-over { |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
background: rgba(0, 0, 0, 0.7); |
|
display: flex; |
|
flex-direction: column; |
|
justify-content: center; |
|
align-items: center; |
|
z-index: 10; |
|
font-size: 2rem; |
|
animation: fadeIn 0.5s; |
|
} |
|
|
|
@keyframes fadeIn { |
|
from { opacity: 0; } |
|
to { opacity: 1; } |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<h1 class="title">ゼリーテトリス</h1> |
|
<div id="game-container"> |
|
<canvas id="game-canvas" width="300" height="600"></canvas> |
|
<div id="game-over" class="game-over hidden"> |
|
<div>ゲームオーバー</div> |
|
<button id="restart-btn" class="mt-4">もう一度遊ぶ</button> |
|
</div> |
|
</div> |
|
<div class="score-container"> |
|
スコア: <span id="score">0</span> |
|
</div> |
|
<div class="controls"> |
|
<button id="start-btn">スタート</button> |
|
<button id="pause-btn">ポーズ</button> |
|
<button id="reset-btn">リセット</button> |
|
</div> |
|
|
|
<audio id="move-sound" src="https://assets.mixkit.co/sfx/preview/mixkit-arcade-game-jump-coin-216.mp3"></audio> |
|
<audio id="rotate-sound" src="https://assets.mixkit.co/sfx/preview/mixkit-unlock-game-notification-253.mp3"></audio> |
|
<audio id="drop-sound" src="https://assets.mixkit.co/sfx/preview/mixkit-quick-jump-arcade-game-239.mp3"></audio> |
|
<audio id="clear-sound" src="https://assets.mixkit.co/sfx/preview/mixkit-winning-chimes-2015.mp3"></audio> |
|
<audio id="gameover-sound" src="https://assets.mixkit.co/sfx/preview/mixkit-retro-arcade-lose-2027.mp3"></audio> |
|
<audio id="jelly-sound" src="https://assets.mixkit.co/sfx/preview/mixkit-sci-fi-positive-interface-beep-257.mp3"></audio> |
|
|
|
<script> |
|
document.addEventListener('DOMContentLoaded', () => { |
|
const canvas = document.getElementById('game-canvas'); |
|
const ctx = canvas.getContext('2d'); |
|
const scoreElement = document.getElementById('score'); |
|
const startBtn = document.getElementById('start-btn'); |
|
const pauseBtn = document.getElementById('pause-btn'); |
|
const resetBtn = document.getElementById('reset-btn'); |
|
const restartBtn = document.getElementById('restart-btn'); |
|
const gameOverElement = document.getElementById('game-over'); |
|
|
|
|
|
const moveSound = document.getElementById('move-sound'); |
|
const rotateSound = document.getElementById('rotate-sound'); |
|
const dropSound = document.getElementById('drop-sound'); |
|
const clearSound = document.getElementById('clear-sound'); |
|
const gameoverSound = document.getElementById('gameover-sound'); |
|
const jellySound = document.getElementById('jelly-sound'); |
|
|
|
|
|
const COLS = 10; |
|
const ROWS = 20; |
|
const BLOCK_SIZE = 30; |
|
const COLORS = [ |
|
null, |
|
'#FF6B6B', |
|
'#FECA57', |
|
'#1DD1A1', |
|
'#54A0FF', |
|
'#5F27CD', |
|
'#FF9FF3', |
|
'#00D2D3' |
|
]; |
|
|
|
|
|
const SHAPES = [ |
|
null, |
|
[[0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0]], |
|
[[2, 0, 0], [2, 2, 2], [0, 0, 0]], |
|
[[0, 0, 3], [3, 3, 3], [0, 0, 0]], |
|
[[0, 4, 4], [0, 4, 4], [0, 0, 0]], |
|
[[0, 5, 5], [5, 5, 0], [0, 0, 0]], |
|
[[0, 6, 0], [6, 6, 6], [0, 0, 0]], |
|
[[7, 7, 0], [0, 7, 7], [0, 0, 0]] |
|
]; |
|
|
|
let score = 0; |
|
let gameOver = false; |
|
let isPaused = false; |
|
let dropCounter = 0; |
|
let dropInterval = 1000; |
|
let lastTime = 0; |
|
let board = createMatrix(COLS, ROWS); |
|
let player = { |
|
pos: {x: 0, y: 0}, |
|
matrix: null, |
|
score: 0 |
|
}; |
|
|
|
|
|
function reset() { |
|
board = createMatrix(COLS, ROWS); |
|
score = 0; |
|
scoreElement.textContent = score; |
|
gameOver = false; |
|
isPaused = false; |
|
gameOverElement.classList.add('hidden'); |
|
playerReset(); |
|
} |
|
|
|
|
|
function playerReset() { |
|
const piece = Math.floor(Math.random() * 7) + 1; |
|
player.matrix = SHAPES[piece]; |
|
player.pos.y = 0; |
|
player.pos.x = Math.floor(COLS / 2) - Math.floor(player.matrix[0].length / 2); |
|
|
|
if (collide()) { |
|
gameOver = true; |
|
gameOverElement.classList.remove('hidden'); |
|
gameoverSound.play(); |
|
} |
|
} |
|
|
|
|
|
function createMatrix(w, h) { |
|
const matrix = []; |
|
while (h--) { |
|
matrix.push(new Array(w).fill(0)); |
|
} |
|
return matrix; |
|
} |
|
|
|
|
|
function drawJellyBlock(x, y, color, size = BLOCK_SIZE) { |
|
|
|
ctx.fillStyle = color; |
|
ctx.beginPath(); |
|
ctx.ellipse( |
|
x + size/2, |
|
y + size/2, |
|
size/2 * 0.9, |
|
size/2 * 0.9, |
|
0, 0, Math.PI * 2 |
|
); |
|
ctx.fill(); |
|
|
|
|
|
const gradient = ctx.createRadialGradient( |
|
x + size/3, y + size/3, size/10, |
|
x + size/3, y + size/3, size/3 |
|
); |
|
gradient.addColorStop(0, 'rgba(255, 255, 255, 0.4)'); |
|
gradient.addColorStop(1, 'rgba(255, 255, 255, 0)'); |
|
|
|
ctx.fillStyle = gradient; |
|
ctx.beginPath(); |
|
ctx.ellipse( |
|
x + size/3, |
|
y + size/3, |
|
size/4, |
|
size/4, |
|
0, 0, Math.PI * 2 |
|
); |
|
ctx.fill(); |
|
|
|
|
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.2)'; |
|
ctx.beginPath(); |
|
ctx.ellipse( |
|
x + size/2, |
|
y + size/2 + 2, |
|
size/2 * 0.8, |
|
size/2 * 0.4, |
|
0, 0, Math.PI * 2 |
|
); |
|
ctx.fill(); |
|
} |
|
|
|
|
|
function draw() { |
|
|
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'; |
|
ctx.fillRect(0, 0, canvas.width, canvas.height); |
|
|
|
|
|
drawMatrix(board, {x: 0, y: 0}); |
|
|
|
|
|
drawMatrix(player.matrix, player.pos); |
|
} |
|
|
|
|
|
function drawMatrix(matrix, offset) { |
|
matrix.forEach((row, y) => { |
|
row.forEach((value, x) => { |
|
if (value !== 0) { |
|
drawJellyBlock( |
|
(x + offset.x) * BLOCK_SIZE, |
|
(y + offset.y) * BLOCK_SIZE, |
|
COLORS[value] |
|
); |
|
} |
|
}); |
|
}); |
|
} |
|
|
|
|
|
function collide() { |
|
const [m, o] = [player.matrix, player.pos]; |
|
for (let y = 0; y < m.length; ++y) { |
|
for (let x = 0; x < m[y].length; ++x) { |
|
if (m[y][x] !== 0 && |
|
(board[y + o.y] && |
|
board[y + o.y][x + o.x]) !== 0) { |
|
return true; |
|
} |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
|
|
function merge() { |
|
player.matrix.forEach((row, y) => { |
|
row.forEach((value, x) => { |
|
if (value !== 0) { |
|
board[y + player.pos.y][x + player.pos.x] = value; |
|
} |
|
}); |
|
}); |
|
} |
|
|
|
|
|
function rotate() { |
|
const matrix = player.matrix; |
|
const N = matrix.length; |
|
|
|
|
|
for (let y = 0; y < N; ++y) { |
|
for (let x = 0; x < y; ++x) { |
|
[matrix[x][y], matrix[y][x]] = [matrix[y][x], matrix[x][y]]; |
|
} |
|
} |
|
|
|
|
|
for (let y = 0; y < N; ++y) { |
|
matrix[y].reverse(); |
|
} |
|
|
|
|
|
rotateSound.play(); |
|
jellySound.play(); |
|
document.getElementById('game-container').classList.add('jelly-rotate'); |
|
setTimeout(() => { |
|
document.getElementById('game-container').classList.remove('jelly-rotate'); |
|
}, 500); |
|
} |
|
|
|
|
|
function playerMove(dir) { |
|
player.pos.x += dir; |
|
if (collide()) { |
|
player.pos.x -= dir; |
|
} else { |
|
moveSound.play(); |
|
jellySound.play(); |
|
document.getElementById('game-container').classList.add('jelly-move'); |
|
setTimeout(() => { |
|
document.getElementById('game-container').classList.remove('jelly-move'); |
|
}, 300); |
|
} |
|
} |
|
|
|
|
|
function playerDrop() { |
|
player.pos.y++; |
|
if (collide()) { |
|
player.pos.y--; |
|
merge(); |
|
playerReset(); |
|
arenaSweep(); |
|
dropSound.play(); |
|
jellySound.play(); |
|
document.getElementById('game-container').classList.add('jelly-drop'); |
|
setTimeout(() => { |
|
document.getElementById('game-container').classList.remove('jelly-drop'); |
|
}, 500); |
|
} |
|
dropCounter = 0; |
|
} |
|
|
|
|
|
function playerHardDrop() { |
|
while (!collide()) { |
|
player.pos.y++; |
|
} |
|
player.pos.y--; |
|
merge(); |
|
playerReset(); |
|
arenaSweep(); |
|
dropSound.play(); |
|
jellySound.play(); |
|
document.getElementById('game-container').classList.add('jelly-drop'); |
|
setTimeout(() => { |
|
document.getElementById('game-container').classList.remove('jelly-drop'); |
|
}, 500); |
|
} |
|
|
|
|
|
function arenaSweep() { |
|
let rowCount = 0; |
|
outer: for (let y = board.length - 1; y >= 0; --y) { |
|
for (let x = 0; x < board[y].length; ++x) { |
|
if (board[y][x] === 0) { |
|
continue outer; |
|
} |
|
} |
|
|
|
|
|
const row = board.splice(y, 1)[0].fill(0); |
|
board.unshift(row); |
|
++y; |
|
|
|
rowCount++; |
|
} |
|
|
|
if (rowCount > 0) { |
|
score += rowCount * 100; |
|
scoreElement.textContent = score; |
|
clearSound.play(); |
|
jellySound.play(); |
|
|
|
|
|
dropInterval = Math.max(100, dropInterval - 20); |
|
|
|
|
|
document.getElementById('game-container').classList.add('jelly-animation'); |
|
setTimeout(() => { |
|
document.getElementById('game-container').classList.remove('jelly-animation'); |
|
}, 800); |
|
} |
|
} |
|
|
|
|
|
function update(time = 0) { |
|
if (gameOver || isPaused) return; |
|
|
|
const deltaTime = time - lastTime; |
|
lastTime = time; |
|
|
|
dropCounter += deltaTime; |
|
if (dropCounter > dropInterval) { |
|
playerDrop(); |
|
} |
|
|
|
draw(); |
|
requestAnimationFrame(update); |
|
} |
|
|
|
|
|
document.addEventListener('keydown', event => { |
|
if (gameOver) return; |
|
|
|
switch (event.keyCode) { |
|
case 37: |
|
playerMove(-1); |
|
break; |
|
case 39: |
|
playerMove(1); |
|
break; |
|
case 40: |
|
playerDrop(); |
|
break; |
|
case 38: |
|
rotate(); |
|
break; |
|
case 32: |
|
playerHardDrop(); |
|
break; |
|
case 80: |
|
isPaused = !isPaused; |
|
if (!isPaused) { |
|
update(); |
|
} |
|
break; |
|
} |
|
}); |
|
|
|
|
|
startBtn.addEventListener('click', () => { |
|
if (gameOver) { |
|
reset(); |
|
} |
|
isPaused = false; |
|
update(); |
|
}); |
|
|
|
pauseBtn.addEventListener('click', () => { |
|
isPaused = !isPaused; |
|
if (!isPaused) { |
|
update(); |
|
} |
|
}); |
|
|
|
resetBtn.addEventListener('click', reset); |
|
restartBtn.addEventListener('click', reset); |
|
|
|
|
|
let touchStartX = 0; |
|
let touchStartY = 0; |
|
|
|
document.addEventListener('touchstart', e => { |
|
touchStartX = e.touches[0].clientX; |
|
touchStartY = e.touches[0].clientY; |
|
}, false); |
|
|
|
document.addEventListener('touchmove', e => { |
|
if (!touchStartX || !touchStartY || gameOver || isPaused) return; |
|
|
|
const touchEndX = e.touches[0].clientX; |
|
const touchEndY = e.touches[0].clientY; |
|
|
|
const diffX = touchStartX - touchEndX; |
|
const diffY = touchStartY - touchEndY; |
|
|
|
if (Math.abs(diffX) > Math.abs(diffY)) { |
|
|
|
if (diffX > 5) { |
|
playerMove(-1); |
|
} else if (diffX < -5) { |
|
playerMove(1); |
|
} |
|
} else { |
|
|
|
if (diffY > 5) { |
|
playerHardDrop(); |
|
} else if (diffY < -5) { |
|
rotate(); |
|
} |
|
} |
|
|
|
touchStartX = 0; |
|
touchStartY = 0; |
|
}, false); |
|
|
|
|
|
reset(); |
|
}); |
|
</script> |
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - <a href="https://enzostvs-deepsite.hf.space?remix=akys/jellytet" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body> |
|
</html> |