OpenAI o1-previewでテトリス作ってみた

ブラウザで動作するシンプルなテトリスゲームを作成しました。以下のコードを使用して実装できます。


HTMLコード(index.html)

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>テトリス</title>
    <style>
        canvas {
            border: 1px solid #555;
            background-color: #000;
        }
        #score {
            color: #fff;
            font-size: 20px;
            position: absolute;
            top: 10px;
            left: 260px;
        }
    </style>
</head>
<body>
    <canvas id="tetris" width="240" height="400"></canvas>
    <div id="score">スコア: 0</div>
    <script src="tetris.js"></script>
</body>
</html>

JavaScriptコード(tetris.js)

// キャンバスとコンテキストの取得
const canvas = document.getElementById('tetris');
const context = canvas.getContext('2d');

// スケールを設定(ブロックのサイズを調整)
context.scale(20, 20);

// ブロックの色の配列
const colors = [
    null,        // インデックス0は使用しない
    '#FF0D72',   // T
    '#0DC2FF',   // I
    '#0DFF72',   // S
    '#F538FF',   // Z
    '#FF8E0D',   // L
    '#FFE138',   // O
    '#3877FF',   // J
];

// ラインが揃ったときに消去する関数
function arenaSweep() {
    let rowCount = 1;
    outer: for (let y = arena.length -1; y > 0; --y) {
        // 横一列が埋まっているか確認
        for (let x = 0; x < arena[y].length; ++x) {
            if (arena[y][x] === 0) {
                continue outer; // 埋まっていない場合は次の行へ
            }
        }

        // ラインを削除し、新しい空の行を上に追加
        arena.splice(y, 1);
        arena.unshift(new Array(arena[0].length).fill(0));
        ++y;

        // スコアの更新
        player.score += rowCount * 10;
        rowCount *= 2;
    }
    updateScore();
}

// 衝突を検出する関数
function collide(arena, player) {
    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 &&        // ブロックが存在し
               (arena[y + o.y] &&       // フィールド内にあり
                arena[y + o.y][x + o.x]) !== 0) { // 衝突している場合
                return true;
            }
        }
    }
    return false;
}

// フィールド(矩形配列)を作成する関数
function createMatrix(w, h) {
    const matrix = [];
    while (h--) {
        matrix.push(new Array(w).fill(0)); // 幅w、高さhの2次元配列を作成
    }
    return matrix;
}

// テトリミノ(ブロック)を作成する関数
function createPiece(type) {
    switch(type) {
        case 'T':
            return [
                [0, 0, 0],
                [1, 1, 1],
                [0, 1, 0],
            ];
        case 'O':
            return [
                [6, 6],
                [6, 6],
            ];
        case 'L':
            return [
                [0, 5, 0],
                [0, 5, 0],
                [0, 5, 5],
            ];
        case 'J':
            return [
                [0, 7, 0],
                [0, 7, 0],
                [7, 7, 0],
            ];
        case 'I':
            return [
                [0, 2, 0, 0],
                [0, 2, 0, 0],
                [0, 2, 0, 0],
                [0, 2, 0, 0],
            ];
        case 'S':
            return [
                [0, 3, 3],
                [3, 3, 0],
                [0, 0, 0],
            ];
        case 'Z':
            return [
                [4, 4, 0],
                [0, 4, 4],
                [0, 0, 0],
            ];
    }
}

// 指定された位置にブロックを描画する関数
function drawMatrix(matrix, offset) {
    matrix.forEach((row, y) => {
        row.forEach((value, x) => {
            if(value !== 0) {
                context.fillStyle = colors[value]; // ブロックの色を設定
                context.fillRect(x + offset.x,
                                 y + offset.y,
                                 1, 1); // ブロックを描画
            }
        });
    });
}

// 全体を描画する関数
function draw() {
    // キャンバスをクリア
    context.fillStyle = '#000';
    context.fillRect(0, 0, canvas.width, canvas.height);

    // フィールドと現在のブロックを描画
    drawMatrix(arena, {x:0, y:0});
    drawMatrix(player.matrix, player.pos);
}

// ブロックをフィールドに固定する関数
function merge(arena, player) {
    player.matrix.forEach((row, y) => {
        row.forEach((value, x) => {
            if(value !== 0) {
                arena[y + player.pos.y][x + player.pos.x] = value;
            }
        });
    });
}

// ブロックを回転する関数
function rotate(matrix, dir) {
    // 転置
    for(let y = 0; y < matrix.length; ++y) {
        for(let x = 0; x < y; ++x) {
            [matrix[x][y], matrix[y][x]] = 
            [matrix[y][x], matrix[x][y]];
        }
    }

    // 行または列を反転
    if(dir > 0) {
        matrix.forEach(row => row.reverse()); // 右回転
    } else {
        matrix.reverse(); // 左回転
    }
}

// ブロックを下に移動する関数
function playerDrop() {
    player.pos.y++;
    if(collide(arena, player)) {
        player.pos.y--; // 衝突したら位置を戻す
        merge(arena, player); // ブロックを固定
        playerReset(); // 新しいブロックを生成
        arenaSweep(); // ラインが揃っているか確認
    }
    dropCounter = 0;
}

// ブロックを左右に移動する関数
function playerMove(dir) {
    player.pos.x += dir;
    if(collide(arena, player)) {
        player.pos.x -= dir; // 衝突したら位置を戻す
    }
}

// 新しいブロックを生成する関数
function playerReset() {
    const pieces = 'ILJOTSZ';
    // ランダムにブロックを選択
    player.matrix = createPiece(pieces[Math.floor(pieces.length * Math.random())]);
    player.pos.y = 0; // 上部から開始
    player.pos.x = (arena[0].length / 2 | 0) -
                   (player.matrix[0].length / 2 | 0); // 中央に配置
    // ゲームオーバーの判定
    if(collide(arena, player)) {
        arena.forEach(row => row.fill(0)); // フィールドをクリア
        player.score = 0; // スコアをリセット
        updateScore();
    }
}

// ブロックを回転させる関数
function playerRotate(dir) {
    const pos = player.pos.x;
    let offset = 1;
    rotate(player.matrix, dir); // ブロックを回転
    // 壁を突き抜けないように調整
    while(collide(arena, player)) {
        player.pos.x += offset;
        offset = -(offset + (offset > 0 ? 1 : -1));
        if(offset > player.matrix[0].length) {
            rotate(player.matrix, -dir); // 回転を元に戻す
            player.pos.x = pos;
            return;
        }
    }
}

// 自動的にブロックを落下させるための変数
let dropCounter = 0;
let dropInterval = 1000; // 落下間隔(ミリ秒)

let lastTime = 0;

// フレームごとに更新する関数
function update(time = 0) {
    const deltaTime = time - lastTime;
    lastTime = time;

    dropCounter += deltaTime;
    if(dropCounter > dropInterval) {
        playerDrop(); // 一定時間ごとにブロックを落下
    }

    draw(); // 描画を更新
    requestAnimationFrame(update); // 次のフレームを要求
}

// スコアを更新する関数
function updateScore() {
    document.getElementById('score').innerText = 'スコア: ' + player.score;
}

// フィールド(12x20の配列)を作成
const arena = createMatrix(12, 20);

// プレイヤー情報の初期化
const player = {
    pos: {x:0, y:0}, // ブロックの位置
    matrix: null,    // ブロックの形状
    score: 0,        // スコア
};

// キーボード入力の処理
document.addEventListener('keydown', event => {
    if(event.keyCode === 37) {
        playerMove(-1); // 左移動
    } else if(event.keyCode === 39) {
        playerMove(1); // 右移動
    } else if(event.keyCode === 40) {
        playerDrop(); // 下移動(高速落下)
    } else if(event.keyCode === 81) {
        playerRotate(-1); // 左回転
    } else if(event.keyCode === 87) {
        playerRotate(1); // 右回転
    }
});

// ゲームの開始
playerReset();
updateScore();
update();

説明

  • HTML部分
  • <canvas>要素でゲーム画面を表示します。
  • <div id="score">でスコアを表示します。
  • スタイルシートでキャンバスの見た目を調整しています。
  • JavaScript部分
  • ゲームロジック:テトリスの動作をすべてJavaScriptで実装しています。
    • createPiece関数でテトリミノ(ブロック)を生成。
    • draw関数でブロックやスコアの描画。
    • playerMoveplayerDropplayerRotate関数でブロックの操作。
    • arenaSweep関数でラインが揃った際の処理。
    • キーボードイベントでユーザー入力を処理。

操作方法

  • 移動:左矢印キー(←)、右矢印キー(→)
  • 回転Qキー(左回転)、Wキー(右回転)
  • 高速落下:下矢印キー(↓)

使用手順

  1. ファイルの準備
  • 上記のHTMLコードをindex.htmlとして保存。
  • JavaScriptコードをtetris.jsとして保存。
  1. ファイルの配置
  • 同じフォルダ内にindex.htmltetris.jsを配置。
  1. ゲームの起動
  • index.htmlをブラウザで開く。
  1. ゲームのプレイ
  • キーボードでブロックを操作してテトリスを楽しんでください。

注意:このコードは学習目的のための簡易的な実装です。実際のゲーム開発では、追加の機能やエラーチェックが必要になる場合があります。

テトリスはこちら

以前のバージョンでは一発で動かす事はできなかったので、凄い進化!

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です