jellytet / index.html
akys's picture
Add 2 files
8391ef8 verified
<!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', // I
'#FECA57', // J
'#1DD1A1', // L
'#54A0FF', // O
'#5F27CD', // S
'#FF9FF3', // T
'#00D2D3' // Z
];
// ブロックの形状
const SHAPES = [
null,
[[0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0]], // I
[[2, 0, 0], [2, 2, 2], [0, 0, 0]], // J
[[0, 0, 3], [3, 3, 3], [0, 0, 0]], // L
[[0, 4, 4], [0, 4, 4], [0, 0, 0]], // O
[[0, 5, 5], [5, 5, 0], [0, 0, 0]], // S
[[0, 6, 0], [6, 6, 6], [0, 0, 0]], // T
[[7, 7, 0], [0, 7, 7], [0, 0, 0]] // Z
];
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: // P
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>