AIの助けを借りてテトリスを作ってみました!
使用AIはCopilotとGemini
まずは完成したゲーム
AIへの指示(プロンプト)を書く。実際の開発では要件定義と言うらしい。
要件が曖昧だとAIも不完全なものを出力するため、ここが重要。
細かいロジックの実装(設計とコーディング)はAIが良い感じにやってくれる。
プロンプト
テトリスのJavaScript実装:
基本操作:
左右キーでブロックの移動
スペースキーでブロックの回転
下キーでブロック落下の加速
ゲームボード:
マス目は横10、縦20
行がすべて埋まったら該当行を削除し、行分詰める
最上段のマスをブロックが超えたらゲームオーバー
フレームレートは3fps
ブロックの形と色:
I、J、L、O、S、T、Z型のブロック
各ブロックごとに色分け
スコア計算:
1列消すごとに100点追加
10秒ごとに基礎スコアに100点追加
追加機能:
次のブロックをボードの右に表示し、その下にSCOREを表示
ブロック設置後に0.5秒のインターバルを設ける
操作方法をフッターに表示
完成したコード
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>JavaScript Tetris</title>
<style>
body {
display: flex;
flex-direction: column;
align-items: center;
}
.container {
display: flex;
}
canvas {
border: 1px solid black;
margin-top: 10px;
}
#next-container {
margin-left: 20px;
display: flex;
flex-direction: column;
align-items: center;
}
footer {
margin-top: 20px;
}
</style>
</head>
<body>
<h1>Tetris</h1>
<div class="container">
<canvas id="gameBoard" width="300" height="600"></canvas>
<div id="next-container">
<p>Next Block:</p>
<canvas id="nextBlock" width="100" height="100"></canvas>
<p>SCORE: <span id="score">0</span></p>
</div>
</div>
<footer>
<p>左右キーでブロックの移動 / スペースキーでブロックの回転 / 下キーでブロック落下の加速</p>
</footer>
<script>
// ゲームボードのキャンバスとコンテキストを取得
const canvas = document.getElementById('gameBoard');
const context = canvas.getContext('2d');
// 次のブロック表示用のキャンバスとコンテキストを取得
const nextCanvas = document.getElementById('nextBlock');
const nextContext = nextCanvas.getContext('2d');
// スコア表示用の要素を取得
const scoreElement = document.getElementById('score');
// 定数の定義
const ROWS = 20;
const COLS = 10;
const BLOCK_SIZE = 30;
const FPS = 3;
const COLORS = ['cyan', 'blue', 'orange', 'yellow', 'green', 'purple', 'red'];
// ブロックの形状を定義
const SHAPES = [
[[1, 1, 1, 1]], // I
[[1, 0, 0], [1, 1, 1]], // J
[[0, 0, 1], [1, 1, 1]], // L
[[1, 1], [1, 1]], // O
[[0, 1, 1], [1, 1, 0]], // S
[[0, 1, 0], [1, 1, 1]], // T
[[1, 1, 0], [0, 1, 1]] // Z
];
// ゲームボードを初期化(すべてのセルを0にする)
let board = Array.from({ length: ROWS }, () => Array(COLS).fill(0));
// 現在のブロックと次のブロックをランダムに設定
let currentShape = getRandomShape();
let nextShape = getRandomShape();
let score = 0;
let gameInterval;
// ランダムなブロックを取得する関数
function getRandomShape() {
const shapeIndex = Math.floor(Math.random() * SHAPES.length);
return { shape: SHAPES[shapeIndex], color: COLORS[shapeIndex], x: COLS / 2 - 1, y: 0 };
}
// ゲームボードを描画する関数
function drawBoard() {
context.clearRect(0, 0, canvas.width, canvas.height);
for (let row = 0; row < ROWS; row++) {
for (let col = 0; col < COLS; col++) {
if (board[row][col]) {
context.fillStyle = COLORS[board[row][col] - 1];
context.fillRect(col * BLOCK_SIZE, row * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
context.strokeRect(col * BLOCK_SIZE, row * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
}
}
}
// 現在のブロックを描画
drawShape(currentShape);
}
// ブロックを描画する関数
function drawShape(shape) {
shape.shape.forEach((row, y) => {
row.forEach((cell, x) => {
if (cell) {
context.fillStyle = shape.color;
context.fillRect((shape.x + x) * BLOCK_SIZE, (shape.y + y) * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
context.strokeRect((shape.x + x) * BLOCK_SIZE, (shape.y + y) * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
}
});
});
}
// 次のブロックを描画する関数
function drawNextShape() {
nextContext.clearRect(0, 0, nextCanvas.width, nextCanvas.height);
nextShape.shape.forEach((row, y) => {
row.forEach((cell, x) => {
if (cell) {
nextContext.fillStyle = nextShape.color;
nextContext.fillRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
nextContext.strokeRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
}
});
});
}
// ブロックを移動する関数
function moveShape(offsetX, offsetY) {
const newShape = { ...currentShape, x: currentShape.x + offsetX, y: currentShape.y + offsetY };
if (!isCollision(newShape)) {
currentShape = newShape;
} else if (offsetY === 1) {
mergeShape();
currentShape = nextShape;
nextShape = getRandomShape();
drawNextShape();
if (isCollision(currentShape)) {
clearInterval(gameInterval);
alert('Game Over!');
}
}
drawBoard(); // ゲームボードの再描画
}
// ブロックを回転させる関数
function rotateShape() {
const rotatedShape = {
...currentShape,
shape: currentShape.shape[0].map((_, index) => currentShape.shape.map(row => row[index]).reverse())
};
if (!isCollision(rotatedShape)) {
currentShape = rotatedShape;
drawBoard(); // ゲームボードの再描画
}
}
// 衝突判定の関数
function isCollision(shape) {
return shape.shape.some((row, y) =>
row.some((cell, x) =>
cell &&
(shape.x + x < 0 || shape.x + x >= COLS || shape.y + y >= ROWS || board[shape.y + y] && board[shape.y + y][shape.x + x])
)
);
}
// ブロックをゲームボードに統合する関数
function mergeShape() {
currentShape.shape.forEach((row, y) => {
row.forEach((cell, x) => {
if (cell) {
board[currentShape.y + y][currentShape.x + x] = COLORS.indexOf(currentShape.color) + 1;
}
});
});
clearLines();
}
// 行をクリアする関数
function clearLines() {
let clearedLines = 0;
board = board.filter(row => {
if (row.every(cell => cell)) {
clearedLines++;
return false;
}
return true;
});
while (board.length < ROWS) {
board.unshift(Array(COLS).fill(0));
}
score += clearedLines * 100;
scoreElement.textContent = score;
}
// キーボードイベントリスナーを追加
document.addEventListener('keydown', event => {
if (['ArrowLeft', 'ArrowRight', 'ArrowDown', ' '].includes(event.key)) {
event.preventDefault(); // 矢印キーとスペースキーのデフォルト動作を無効化
}
switch (event.key) {
case 'ArrowLeft':
moveShape(-1, 0);
break;
case 'ArrowRight':
moveShape(1, 0);
break;
case 'ArrowDown':
moveShape(0, 1);
break;
case ' ':
rotateShape();
break;
}
});
// ゲームループを設定
gameInterval = setInterval(() => {
moveShape(0, 1);
}, 1000 / FPS);
drawNextShape();
drawBoard();
</script>
</body>
</html>
昔からあるゲームではあるけれど、ゼロから組むには難易度が高いと思う。
AIを使うと5分ばかりでできるだけでなくコードの解説もしてくれるため学習教材として優秀!