GeminiにテトリスのPythonソースコードをJavaScriptに書き換えてもらいました。
極悪テトリミノ"X"がいきなり出現して脂肪しますんで、真面目にプレイせず、ギャグとして軽く流して下さいw
※Geminiさんがメタタグを忘れたんで、文字化けしてしまいました。
もちろん修正してあります。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>テトリス</title>
<style>
body {
font-family: sans-serif;
display: flex;
flex-direction: column;
align-items: center;
background-color: #000;
color: #fff;
margin: 20px;
}
canvas {
border: 2px solid #fff;
background-color: #222;
}
.controls {
display: flex;
gap: 10px;
margin-top: 20px;
}
.controls button {
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
background-color: #333;
color: #fff;
border: 1px solid #555;
border-radius: 5px;
}
.controls button:active {
background-color: #555;
}
#score {
margin-top: 10px;
font-size: 18px;
}
.game-over {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.8);
padding: 20px;
border-radius: 5px;
text-align: center;
}
.game-over h1 {
margin-top: 0;
}
.game-over button {
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
background-color: green;
color: #fff;
border: none;
border-radius: 5px;
margin-top: 10px;
}
.game-over button:active {
background-color: darkgreen;
}
</style>
</head>
<body>
<canvas id="tetrisCanvas" width="300" height="700"></canvas>
<div class="controls">
<button id="leftButton">←</button>
<button id="rightButton">→</button>
<button id="rotateButton">回転</button>
<button id="downButton">↓</button>
<div id="score">スコア: 0</div>
</div>
<script>
const canvas = document.getElementById('tetrisCanvas');
const ctx = canvas.getContext('2d');
const scoreElement = document.getElementById('score');
const leftButton = document.getElementById('leftButton');
const rightButton = document.getElementById('rightButton');
const rotateButton = document.getElementById('rotateButton');
const downButton = document.getElementById('downButton');
const BLOCK_SIZE = 30;
const GRID_WIDTH = 10;
const GRID_HEIGHT = 20;
const BUTTON_AREA_HEIGHT = 100;
const SCREEN_WIDTH = BLOCK_SIZE * GRID_WIDTH;
const SCREEN_HEIGHT = BLOCK_SIZE * GRID_HEIGHT + BUTTON_AREA_HEIGHT;
const GAME_AREA_LEFT = 0;
const SHAPES = [
[[1, 1, 1, 1]], // I
[[1, 1], [1, 1]], // O
[[1, 1, 1], [0, 1, 0]], // T
[[1, 1, 1], [1, 0, 0]], // L
[[1, 1, 1], [0, 0, 1]], // J
[[0, 1, 1], [1, 1, 0]], // S
[[1, 1, 0], [0, 1, 1]], // Z
[[1, 0 ,1], [0, 1, 0],[1, 0, 1]] // X
];
const COLORS = ['cyan', 'yellow', 'purple', 'orange', 'blue', 'lime', 'red', 'white'];
let grid = createGrid();
let currentTetrimino = createTetrimino();
let nextTetrimino = createTetrimino();
let score = 0;
let fallInterval = 500; // ミリ秒
let lastFallTime = 0;
let isGameOver = false;
function createGrid() {
return Array(GRID_HEIGHT).fill(null).map(() => Array(GRID_WIDTH).fill(0));
}
function createTetrimino() {
const shapeIndex = Math.floor(Math.random() * SHAPES.length);
const shape = SHAPES[shapeIndex];
const color = COLORS[shapeIndex];
const x = Math.floor(GRID_WIDTH / 2) - Math.floor(shape[0].length / 2);
const y = 0;
return { shape, color, x, y };
}
function rotate(tetrimino) {
const rows = tetrimino.shape.length;
const cols = tetrimino.shape[0].length;
const rotatedShape = Array(cols).fill(null).map(() => Array(rows).fill(0));
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
rotatedShape[j][rows - 1 - i] = tetrimino.shape[i][j];
}
}
return { ...tetrimino, shape: rotatedShape };
}
function drawBlock(x, y, color) {
ctx.fillStyle = color;
ctx.fillRect(GAME_AREA_LEFT + x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
ctx.strokeStyle = '#fff';
ctx.strokeRect(GAME_AREA_LEFT + x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
}
function drawGrid() {
grid.forEach((row, y) => {
row.forEach((value, x) => {
if (value !== 0) {
drawBlock(x, y, COLORS[value - 1]);
} else {
ctx.strokeStyle = '#fff';
ctx.strokeRect(GAME_AREA_LEFT + x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
}
});
});
}
function drawTetrimino() {
currentTetrimino.shape.forEach((row, y) => {
row.forEach((cell, x) => {
if (cell) {
drawBlock(currentTetrimino.x + x, currentTetrimino.y + y, currentTetrimino.color);
}
});
});
}
function isValidMove(tetrimino, newX, newY, newShape = tetrimino.shape) {
for (let y = 0; y < newShape.length; y++) {
for (let x = 0; x < newShape[y].length; x++) {
if (newShape[y][x]) {
const gridX = newX + x;
const gridY = newY + y;
if (gridX < 0 || gridX >= GRID_WIDTH || gridY >= GRID_HEIGHT || (gridY >= 0 && grid[gridY][gridX] !== 0)) {
return false;
}
}
}
}
return true;
}
function moveTetrimino(dx, dy) {
const newX = currentTetrimino.x + dx;
const newY = currentTetrimino.y + dy;
if (isValidMove(currentTetrimino, newX, newY)) {
currentTetrimino.x = newX;
currentTetrimino.y = newY;
} else if (dy > 0) {
freezeTetrimino();
}
}
function rotateTetrimino() {
const rotated = rotate(currentTetrimino);
if (isValidMove(rotated, currentTetrimino.x, currentTetrimino.y, rotated.shape)) {
currentTetrimino = rotated;
}
}
function freezeTetrimino() {
currentTetrimino.shape.forEach((row, y) => {
row.forEach((cell, x) => {
if (cell) {
grid[currentTetrimino.y + y][currentTetrimino.x + x] = COLORS.indexOf(currentTetrimino.color) + 1;
}
});
});
currentTetrimino = nextTetrimino;
nextTetrimino = createTetrimino();
checkClearRows();
if (!isValidMove(currentTetrimino, currentTetrimino.x, currentTetrimino.y)) {
gameOver();
}
}
function checkClearRows() {
let rowsCleared = 0;
for (let y = GRID_HEIGHT - 1; y >= 0; y--) {
if (grid[y].every(cell => cell !== 0)) {
rowsCleared++;
grid.splice(y, 1);
grid.unshift(Array(GRID_WIDTH).fill(0));
}
}
updateScore(rowsCleared);
}
function updateScore(rows) {
if (rows === 1) score += 100;
else if (rows === 2) score += 300;
else if (rows === 3) score += 500;
else if (rows === 4) score += 800;
scoreElement.textContent = `スコア: ${score}`;
}
function gameOver() {
isGameOver = true;
const gameOverDiv = document.createElement('div');
gameOverDiv.classList.add('game-over');
gameOverDiv.innerHTML = `
<h1>ゲームオーバー!</h1>
<p>最終スコア: ${score}</p>
<button id="restartButton">再プレイ</button>
`;
document.body.appendChild(gameOverDiv);
document.getElementById('restartButton').addEventListener('click', restartGame);
}
function restartGame() {
grid = createGrid();
currentTetrimino = createTetrimino();
nextTetrimino = createTetrimino();
score = 0;
scoreElement.textContent = `スコア: 0`;
lastFallTime = Date.now();
isGameOver = false;
const gameOverDiv = document.querySelector('.game-over');
if (gameOverDiv) {
gameOverDiv.remove();
}
gameLoop();
}
function gameLoop() {
if (!isGameOver) {
const currentTime = Date.now();
if (currentTime - lastFallTime > fallInterval) {
moveTetrimino(0, 1);
lastFallTime = currentTime;
}
ctx.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
drawGrid();
drawTetrimino();
requestAnimationFrame(gameLoop);
}
}
// イベントリスナー
document.addEventListener('keydown', (e) => {
if (!isGameOver) {
switch (e.key) {
case 'ArrowLeft':
moveTetrimino(-1, 0);
break;
case 'ArrowRight':
moveTetrimino(1, 0);
break;
case 'ArrowUp':
rotateTetrimino();
break;
case 'ArrowDown':
moveTetrimino(0, 1);
break;
}
}
});
leftButton.addEventListener('click', () => !isGameOver && moveTetrimino(-1, 0));
rightButton.addEventListener('click', () => !isGameOver && moveTetrimino(1, 0));
rotateButton.addEventListener('click', () => !isGameOver && rotateTetrimino());
downButton.addEventListener('click', () => !isGameOver && moveTetrimino(0, 1));
// ゲーム開始
gameLoop();
</script>
</body>
</html>

Comments
clickイベントは押した要素を放した瞬間に発火する性質上、こういったゲームではタイミングが微妙にずれがちですので、要素を押した瞬間に発火するpointerdownイベントを使った方が良いかと思います。Let's comment your feelings that are more than good