6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ChatGPTで、一人用ピンポンゲームが30分で出来た

Last updated at Posted at 2023-03-19

話題のChatGPTを使って、一人用ピンポンゲームを作ったら30分でできた。すごいぞ。ちゃんと遊べる。

ぷよぷよを作ろうとしている人とかいたので、もっと簡単なものを作ってみた。

gpt-pong3.png

Q1.Javascriptを使って、一人用のPONGのプログラムを書いて

A. はい、PONGゲームのプログラムをJavaScriptで書いてみましょう!

以下は、一人用のシンプルなPONGゲームのサンプルコードです。このコードは、HTML5のcanvas要素を使用してゲームを描画し、requestAnimationFrame()メソッドを使用してゲームループを制御します。また、簡単な衝突判定を実装しています。

// ゲームの初期化
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

var ball = { x: canvas.width/2, y: canvas.height/2, dx: 5, dy: 5, radius: 10 };
var paddle = { x: canvas.width/2, y: canvas.height-20, width: 80, height: 10 };
var leftPressed = false;
var rightPressed = false;

document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);

function keyDownHandler(e) {
    if(e.keyCode == 37) {
        leftPressed = true;
    }
    else if(e.keyCode == 39) {
        rightPressed = true;
    }
}

function keyUpHandler(e) {
    if(e.keyCode == 37) {
        leftPressed = false;
    }
    else if(e.keyCode == 39) {
        rightPressed = false;
    }
}

function drawBall() {
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI*2);
    ctx.fillStyle = "#0095DD";
    ctx.fill();
    ctx.closePath();
}

function drawPaddle() {
    ctx.beginPath();
    ctx.rect(paddle.x - paddle.width/2, paddle.y, paddle.width, paddle.height);
    ctx.fillStyle = "#0095DD";
    ctx.fill();
    ctx.closePath();
}

function draw() {
    // ボールの移動
    ball.x += ball.dx;
    ball.y += ball.dy;

    // 壁に当たった場合は反射
    if(ball.x + ball.radius > canvas.width || ball.x - ball.radius < 0) {
        ball.dx = -ball.dx;
    }
    if(ball.y - ball.radius < 0) {
        ball.dy = -ball.dy;
    }
    else if(ball.y + ball.radius > canvas.height) {
        alert("GAME OVER");
        document.location.reload();
    }

    // パドルとの衝突判定
    if(ball.y + ball.dy > paddle.y - ball.radius &&
       ball.x > paddle.x - paddle.width/2 &&
       ball.x < paddle.x + paddle.width/2) {
        ball.dy = -ball.dy;
    }

    // パドルの移動
    if(leftPressed && paddle.x - paddle.width/2 > 0) {
        paddle.x -= 7;
    }
    else if(rightPressed && paddle.x + paddle.width/2 < canvas.width) {
        paddle.x += 7;
    }

    // ゲーム画面のクリア
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // ボールとパドルの描画
    drawBall();
    drawPaddle();

    // ゲームループの再帰呼び出し
    requestAnimationFrame(draw);
}

// ゲームル

おお!すごい。なんかコードが出てきた。途中までしかないけど、いちおう動かしてみたい。

Q2.このコードをpong.jsというファイル名として、pong.jsを読み込むHTMLファイルを作って

A.以下は、先程のJavaScriptコードをpong.jsというファイル名で保存し、それを読み込むHTMLファイルの例です。

このHTMLファイルには<canvas>要素と、<script>要素が含まれています。<script>要素でJavaScriptファイルを読み込み、ゲームの開始を開始するためにdraw()関数を呼び出します。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>PONG</title>
    <style>
        canvas {
            border: 1px solid black;
        }
    </style>
</head>
<body>
    <canvas id="canvas" width="480" height="320"></canvas>
    <script src="pong.js"></script>
</body>
</html>

上記のHTMLファイルをブラウザで開くと、pong.jsファイルが読み込まれ、PONGゲームが表示されます。ただし、pong.jsファイルとHTMLファイルが同じフォルダにある必要があります。また、要素の幅と高さは、任意の値に設定できます。

HTMLファイルもできた。でも、これだけでは動かないなぁ。どういうコードになっているのかな。

Q3.このpong.jsの動作について、コメントを分かりやすく追加して

A.はい、コメントを追加して、PONGゲームの動作をさらに分かりやすく説明してみましょう。

// ゲームの初期化
var canvas = document.getElementById("canvas");  // canvas要素を取得
var ctx = canvas.getContext("2d");  // canvasの2Dコンテキストを取得

var ball = { x: canvas.width/2, y: canvas.height/2, dx: 5, dy: 5, radius: 10 };  // ボールの初期位置と速度、半径
var paddle = { x: canvas.width/2, y: canvas.height-20, width: 80, height: 10 };  // パドルの初期位置とサイズ
var leftPressed = false;  // 左キーが押されているかどうかのフラグ
var rightPressed = false;  // 右キーが押されているかどうかのフラグ

// キーイベントのリスナーを追加
document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);

// キーが押された時の処理
function keyDownHandler(e) {
    if(e.keyCode == 37) {  // 左キー
        leftPressed = true;
    }
    else if(e.keyCode == 39) {  // 右キー
        rightPressed = true;
    }
}

// キーが離された時の処理
function keyUpHandler(e) {
    if(e.keyCode == 37) {  // 左キー
        leftPressed = false;
    }
    else if(e.keyCode == 39) {  // 右キー
        rightPressed = false;
    }
}

// ボールを描画する関数
function drawBall() {
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI*2);  // 円を描画
    ctx.fillStyle = "#0095DD";
    ctx.fill();
    ctx.closePath();
}

// パドルを描画する関数
function drawPaddle() {
    ctx.beginPath();
    ctx.rect(paddle.x - paddle.width/2, paddle.y, paddle.width, paddle.height);  // 長方形を描画
    ctx.fillStyle = "#0095DD";
    ctx.fill();
    ctx.closePath();
}

// ゲームループの関数
function draw() {
    // ボールの移動
    ball.x += ball.dx;
    ball.y += ball.dy;

    // 壁に当たった場合は反射
    if(ball.x + ball.radius > canvas.width || ball.x - ball.radius < 0) {
        ball.dx = -ball.dx;
    }
    if(ball.y - ball.radius < 0) {
        ball.dy = -ball.dy;
    }
    else if(ball.y + ball.radius > canvas.height) {  // 下側の壁に当たった場合はゲームオーバー
        alert("GAME OVER");
        document.location.reload();  // ページをリロードしてリセット
    }

    // パドルとの衝突判定
    if(ball.y + ball.dy >

どうみても、コードが最後までない。

Q4.pong.jsは、draw()が途中になっています。最後まで記述してください

A.すみません、前回の回答の最後の部分が切れていました。以下が、PONGゲームのdraw()関数の最後の部分です。

    // パドルとの衝突判定
    if(ball.y + ball.dy > canvas.height - ball.radius - paddle.height && ball.x > paddle.x - paddle.width/2 && ball.x < paddle.x + paddle.width/2) {
        ball.dy = -ball.dy;
    }

    // パドルの移動
    if(leftPressed && paddle.x > paddle.width/2) {
        paddle.x -= 7;
    }
    else if(rightPressed && paddle.x < canvas.width - paddle.width/2) {
        paddle.x += 7;
    }

    // 画面のクリア
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // ボールとパドルを描画
    drawBall();
    drawPaddle();

    // 次のフレームを描画
    requestAnimationFrame(draw);
}

// ゲームを開始する
draw();

draw()関数の最後に、requestAnimationFrame()を呼び出して、次のフレームを描画するようにしています。これにより、ゲームループが実現されます。また、clearRect()を使用して、前のフレームで描画されたボールとパドルを消去しています。最後に、drawBall()とdrawPaddle()を呼び出して、ボールとパドルを描画しています。

requestAnimationFrame()は、ブラウザが次の再描画をする前に、アニメーションを更新するために使用されるメソッドです。これにより、フレームレートをブラウザに任せることができ、よりスムーズなアニメーションが実現されます。

手動で修正

と、ここまで作ってコードを結合しても、ちゃんと動かない。そこで、コードを手動で修正。

「else if」であるべきところが「if」になってる。インデントもちょっとおかしい。

これでゲームが遊べるようになったけど、すぐにゲームオーバーになるので、ボールの初期値を修正。

gpt-pong.png

だいたい、ここまでで30分くらい。

pong.jsの完全版はこちら。

// JS PONG Game for single player by ChatGPT and Me

// ゲームの初期化
var canvas = document.getElementById("canvas");  // canvas要素を取得
var ctx = canvas.getContext("2d");  // canvasの2Dコンテキストを取得

var ball = { x: canvas.width/2, y: canvas.height/2, dx: 5, dy: -5, radius: 10 };  // ボールの初期位置と速度、半径
var paddle = { x: canvas.width/2, y: canvas.height-20, width: 80, height: 10 };  // パドルの初期位置とサイズ
var leftPressed = false;  // 左キーが押されているかどうかのフラグ
var rightPressed = false;  // 右キーが押されているかどうかのフラグ

// キーイベントのリスナーを追加
document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);

// キーが押された時の処理
function keyDownHandler(e) {
    if(e.keyCode == 37) {  // 左キー
        leftPressed = true;
    }
    else if(e.keyCode == 39) {  // 右キー
        rightPressed = true;
    }
}

// キーが離された時の処理
function keyUpHandler(e) {
    if(e.keyCode == 37) {  // 左キー
        leftPressed = false;
    }
    else if(e.keyCode == 39) {  // 右キー
        rightPressed = false;
    }
}

// ボールを描画する関数
function drawBall() {
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI*2);  // 円を描画
    ctx.fillStyle = "#0095DD";
    ctx.fill();
    ctx.closePath();
}

// パドルを描画する関数
function drawPaddle() {
    ctx.beginPath();
    ctx.rect(paddle.x - paddle.width/2, paddle.y, paddle.width, paddle.height);  // 長方形を描画
    ctx.fillStyle = "#0095DD";
    ctx.fill();
    ctx.closePath();
}

// ゲームループの関数
function draw() {
    // ボールの移動
    ball.x += ball.dx;
    ball.y += ball.dy;

    // 壁に当たった場合は反射
    if(ball.x + ball.radius > canvas.width || ball.x - ball.radius < 0) {
        ball.dx = -ball.dx;
        } else if(ball.y - ball.radius < 0) {
            ball.dy = -ball.dy;
        } else if(ball.y + ball.radius > canvas.height) {  
            // 下側の壁に当たった場合はゲームオーバー
            alert("GAME OVER");
            document.location.reload();  // ページをリロードしてリセット
        }

    // パドルとの衝突判定
    if(ball.y + ball.dy > canvas.height - ball.radius - paddle.height && ball.x > paddle.x - paddle.width/2 && ball.x < paddle.x + paddle.width/2) {
        ball.dy = -ball.dy;
    }

    // パドルの移動
    if(leftPressed && paddle.x > paddle.width/2) {
        paddle.x -= 7;
    } else if(rightPressed && paddle.x < canvas.width - paddle.width/2) {
        paddle.x += 7;
    }

    // 画面のクリア
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // ボールとパドルを描画
    drawBall();
    drawPaddle();

    // 次のフレームを描画
    requestAnimationFrame(draw);
}

// ゲームを開始する
draw();

Q5.このpong.jsとHTMLファイルでは、白い画面しか表示されません。

このpong.jsとHTMLファイルでは、白い画面しか表示されません。ゲームの背景を明るい灰色、ボールを黄色、パドルを白、HTMLファイルの背景を濃い灰色になるよう、pong.jsとHTMLファイルを作成してください。HTMLファイルのファイル名はindex.htmlです。

A. pong.jsファイルに以下のように変更を加え、背景色と描画色を指定するようにします。

// キャンバスの背景色
ctx.fillStyle = '#f2f2f2';
ctx.fillRect(0, 0, canvas.width, canvas.height);

// ボールの描画色
ctx.fillStyle = '#f1c40f';
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI*2);
ctx.fill();
ctx.closePath();

// パドルの描画色
ctx.fillStyle = '#fff';
ctx.fillRect(paddle.x - paddle.width/2, canvas.height - paddle.height, paddle.width, paddle.height);

次に、index.htmlファイルに以下のように変更を加え、背景色を指定します。

<!DOCTYPE html>
<html>
    <head>
        <title>PONG Game</title>
    </head>
    <body style="background-color: #222;">
        <canvas id="myCanvas" width="480" height="320"></canvas>
        <script src="pong.js"></script>
    </body>
</html>

bodyタグにstyle属性を追加し、背景色を濃い灰色に指定しています。また、キャンバスの背景色はpong.jsファイル内で指定しています。

以上の変更を加えた後、index.htmlファイルをブラウザで開くと、背景色と描画色が変更されたPONGゲームが表示されるはずです。

なんか怪しいような気もするが、色合いを調整して反映。

gpt-pong2.png

Q6.JavaScriptでは、keycodeの使用が非奨励となっているため、次のコードを奨励されている形式に書き直して

let leftPressed = false;  // 左キーが押されているかどうかのフラグ
let rightPressed = false;  // 右キーが押されているかどうかのフラグ

// キーイベントのリスナーを追加
document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);

// キーが押された時の処理
function keyDownHandler(e) {
    if(e.keyCode == 37) {  // 左キー
        leftPressed = true;
    }
    else if(e.keyCode == 39) {  // 右キー
        rightPressed = true;
    }
}

A.次のように、keycodeの代わりにkeyを使用して書き直すことができます。

let leftPressed = false;  // 左キーが押されているかどうかのフラグ
let rightPressed = false;  // 右キーが押されているかどうかのフラグ

// キーイベントのリスナーを追加
document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);

// キーが押された時の処理
function keyDownHandler(e) {
    if(e.key === "ArrowLeft") {  // 左キー
        leftPressed = true;
    }
    else if(e.key === "ArrowRight") {  // 右キー
        rightPressed = true;
    }
}

// キーが離された時の処理
function keyUpHandler(e) {
    if(e.key === "ArrowLeft") {  // 左キー
        leftPressed = false;
    }
    else if(e.key === "ArrowRight") {  // 右キー
        rightPressed = false;
    }
}

これにより、キーコードを使用する代わりに、キーの名前を使用して、キーハンドラー関数を書くことができます。また、キーが離された時の処理を追加することも忘れないでください。

コメント欄で指摘を受けたので修正。へたに調べるより、聞いたほうが早いゾ。キーを離したときに処理まで指摘してされた。

Q7.(さらに改良)次のコードのように一人用PONGゲームがあります。このゲームで、paddleに当たる位置によってballの方向が変わるようにするには、コードのどこを修正すればいいですか。

pong.js
// JS PONG Game for single player by ChatGPT and Me

// ゲームの初期化
let canvas = document.getElementById("canvas");  // canvas要素を取得
let ctx = canvas.getContext("2d");  // canvasの2Dコンテキストを取得

let ball = { x: canvas.width/2, y: canvas.height/2, dx: 5, dy: -5, radius: 10 };  // ボールの初期位置と速度、半径
let paddle = { x: canvas.width/2, y: canvas.height-20, width: 80, height: 10 };  // パドルの初期位置とサイズ

let leftPressed = false;  // 左キーが押されているかどうかのフラグ
let rightPressed = false;  // 右キーが押されているかどうかのフラグ

// キーイベントのリスナーを追加
document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);

// キーが押された時の処理
function keyDownHandler(e) {
    if(e.key === "ArrowLeft") {  // 左キー
        leftPressed = true;
    }
    else if(e.key === "ArrowRight") {  // 右キー
        rightPressed = true;
    }
}

// キーが離された時の処理
function keyUpHandler(e) {
    if(e.key === "ArrowLeft") {  // 左キー
        leftPressed = false;
    }
    else if(e.key === "ArrowRight") {  // 右キー
        rightPressed = false;
    }
}


// 背面を描画する関数
function drawBackground() {
    ctx.fillStyle = '#f2f2f2';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
}

// ボールを描画する関数
function drawBall() {
    ctx.fillStyle = '#f1c40f';
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI*2);
    ctx.fill();
    ctx.closePath();
}

// パドルを描画する関数
function drawPaddle() {
    ctx.beginPath();
    ctx.fillStyle = '#ccc';
    ctx.fillRect(paddle.x - paddle.width/2, canvas.height - paddle.height, paddle.width, paddle.height);
    ctx.closePath();
}

// ゲームループの関数
function draw() {
    // ボールの移動
    ball.x += ball.dx;
    ball.y += ball.dy;

    // 壁に当たった場合は反射
    if(ball.x + ball.radius > canvas.width || ball.x - ball.radius < 0) {
        ball.dx = -ball.dx;
        } else if(ball.y - ball.radius < 0) {
            ball.dy = -ball.dy;
        } else if(ball.y + ball.radius > canvas.height) {  
            // 下側の壁に当たった場合はゲームオーバー
            alert("GAME OVER");
            document.location.reload();  // ページをリロードしてリセット
        }

    // パドルとの衝突判定
    if(ball.y + ball.dy > canvas.height - ball.radius - paddle.height && ball.x > paddle.x - paddle.width/2 && ball.x < paddle.x + paddle.width/2) {
        ball.dy = -ball.dy;
    }

    // パドルの移動
    if(leftPressed && paddle.x > paddle.width/2) {
        paddle.x -= 7;
    } else if(rightPressed && paddle.x < canvas.width - paddle.width/2) {
        paddle.x += 7;
    }

    // 画面のクリア
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // ゲーム要素を描画
    drawBackground();
    drawBall();
    drawPaddle();

    // 次のフレームを描画
    requestAnimationFrame(draw);
}

// ゲームを開始する
draw();

A. ボールの移動処理の後、次の行にパドルとの衝突判定があります。

// パドルとの衝突判定
if(ball.y + ball.dy > canvas.height - ball.radius - paddle.height && ball.x > paddle.x - paddle.width/2 && ball.x < paddle.x + paddle.width/2) {
    ball.dy = -ball.dy;
}

この部分で、ボールがパドルに当たったかどうかが判定されています。パドルの位置によってボールの反射方向を変えるには、この部分でball.dxを変更すれば良いです。

たとえば、パドルの左側に当たった場合はball.dxを負の値に、右側に当たった場合は正の値に変更することができます。具体的には、以下のようになります。

// パドルとの衝突判定
if(ball.y + ball.dy > canvas.height - ball.radius - paddle.height && ball.x > paddle.x - paddle.width/2 && ball.x < paddle.x + paddle.width/2) {
    if (ball.x < paddle.x) {  // パドルの左側に当たった場合
        ball.dx = -Math.abs(ball.dx);
    } else {  // パドルの右側に当たった場合
        ball.dx = Math.abs(ball.dx);
    }
    ball.dy = -ball.dy;
}

このようにすると、パドルの左側に当たった場合はボールが左に反射し、右側に当たった場合はボールが右に反射します。

Q.8このプログラムのHTMLファイルは次のようになっています。ゲームの開始・停止・再開ボタンを追加するにはどうすればいいですか。

index.html
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>gpt-pong</title>
    <style>
        canvas {
            border: 1px solid #505050;
        }
    </style>
</head>

<body style="background-color: #b8b8b8;">
    <h1>gpt-pong</h1>
    <canvas id="canvas" width="480" height="320"></canvas>
    <script src="pong.js"></script>

    <p><a href="https://github.com/ycatch/gpt-pong">Source code</a></p>
</body>

</html>

A. ゲームの開始・停止・再開ボタンを追加するには、以下の手順に従ってください。

HTMLファイルに、ボタンを追加するための要素を作成します。例えば、以下のようにします。

<button id="start-button">Start</button>
<button id="stop-button">Stop</button>
<button id="resume-button">Resume</button>

JavaScriptファイルに、ボタンをクリックしたときに呼び出される関数を追加します。たとえば、以下のようにします。

document.getElementById("start-button").addEventListener("click", function(){
    startGame();
});

document.getElementById("stop-button").addEventListener("click", function(){
    stopGame();
});

document.getElementById("resume-button").addEventListener("click", function(){
    resumeGame();
});

JavaScriptファイルに、ボタンを押したときに呼び出される関数を実装します。以下は、startGame、stopGame、resumeGameの例です。

function startGame() {
    // ゲームを開始するための処理を実装します。
}

function stopGame() {
    // ゲームを停止するための処理を実装します。
}

function resumeGame() {
    // ゲームを再開するための処理を実装します。
}

startGame、stopGame、resumeGame関数に、ゲームの開始・停止・再開に必要な処理を追加します。
以上の手順を実行することで、ゲームの開始・停止・再開ボタンを追加することができます。

Q9.ゲームの開始・停止・再開に必要なstartGame、stopGame、resumeGame関数を書いてください

以下のように、startGame、stopGame、resumeGame関数を追加することができます。

A.ゲームの開始・停止・再開に必要なstartGame、stopGame、resumeGame関数を書いてください

以下のように、startGame、stopGame、resumeGame関数を追加することができます。

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>gpt-pong</title>
    <style>
        canvas {
            border: 1px solid #505050;
        }
    </style>
</head>

<body style="background-color: #b8b8b8;">
    <h1>gpt-pong</h1>
    <canvas id="canvas" width="480" height="320"></canvas>
    <script src="pong.js"></script>

    <p><a href="https://github.com/ycatch/gpt-pong">Source code</a></p>

    <button onclick="startGame()">Start Game</button>
    <button onclick="stopGame()">Stop Game</button>
    <button onclick="resumeGame()">Resume Game</button>

    <script>
        var intervalId;

        function startGame() {
            if (!intervalId) {
                intervalId = setInterval(update, 10);
            }
        }

        function stopGame() {
            clearInterval(intervalId);
            intervalId = null;
        }

        function resumeGame() {
            if (!intervalId) {
                intervalId = setInterval(update, 10);
            }
        }
    </script>
</body>

</html>

これにより、画面にStart Game、Stop Game、Resume Gameの3つのボタンが追加され、それぞれがstartGame、stopGame、resumeGame関数を呼び出します。また、intervalIdという変数を使用して、ゲームの更新を停止、再開することができます。

Q10.setInterval(update, 10)の役割を教えて

A.setInterval(update, 10) は、10ミリ秒ごとに update 関数を呼び出すように設定するメソッドです。このメソッドは、ゲームのループを作成するために使用されます。

具体的には、ゲーム内の状態を更新し、描画を更新するために update 関数が呼び出されます。10ミリ秒ごとに update 関数が呼び出されるため、ゲームは60FPSで実行されます(1秒あたり1000ミリ秒 / 10ミリ秒 = 100、1秒あたり100回呼び出されるため、60FPSになります)。

したがって、 setInterval(update, 10) を使用することで、定期的に update 関数を呼び出すことができ、ゲームを実行することができます。

Q11.一人用PONGゲームにはupdate関数はありません。setIntervalでどの関数を呼び出せばいいですか

A.一人用PONGゲームでは、update() 関数の代わりに gameLoop() 関数が用意されています。

setInterval() メソッドで gameLoop() 関数を繰り返し実行することができます。以下のようにコードを書き換えることができます。

// gameLoop() 関数を定義する
function gameLoop() {
  movePaddle();
  moveBall();
  collisionDetection();
  draw();
}

// setInterval() メソッドで gameLoop() 関数を繰り返し実行する
setInterval(gameLoop, 10);

これにより、gameLoop() 関数が10ミリ秒ごとに繰り返し実行されるようになります。

手動で修正

setIntervalで呼び出す関数名は、drawなので手動で修正。

また、drawを繰り返し実行する方法も変更。

pong.js
// JS PONG Game for single player by ChatGPT and Me

// ゲームの初期化
let canvas = document.getElementById("canvas");  // canvas要素を取得
let ctx = canvas.getContext("2d");  // canvasの2Dコンテキストを取得

let ball = { x: canvas.width/2, y: canvas.height/2, dx: 5, dy: -5, radius: 10 };  // ボールの初期位置と速度、半径
let paddle = { x: canvas.width/2, y: canvas.height-20, width: 80, height: 10 };  // パドルの初期位置とサイズ

let leftPressed = false;  // 左キーが押されているかどうかのフラグ
let rightPressed = false;  // 右キーが押されているかどうかのフラグ

let intervalId;     //開始・停止・再開のフラグ

// キーイベントのリスナーを追加
document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);


// ボタンイベントのリスナーを追加
document.getElementById("start-button").addEventListener("click", function(){
    startGame();
});

document.getElementById("stop-button").addEventListener("click", function(){
    stopGame();
});

document.getElementById("resume-button").addEventListener("click", function(){
    resumeGame();
});


// キーが押された時の処理
function keyDownHandler(e) {
    if(e.key === "ArrowLeft") {  // 左キー
        leftPressed = true;
    }
    else if(e.key === "ArrowRight") {  // 右キー
        rightPressed = true;
    }
}

// キーが離された時の処理
function keyUpHandler(e) {
    if(e.key === "ArrowLeft") {  // 左キー
        leftPressed = false;
    }
    else if(e.key === "ArrowRight") {  // 右キー
        rightPressed = false;
    }
}

// ボタンが押された時の処理

// ゲームを開始
function startGame() {
    if (!intervalId) {
        intervalId = setInterval(draw, 10);
    }
}

// ゲームを停止
function stopGame() {
    clearInterval(intervalId);
    intervalId = null;
}

// ゲームを再開
function resumeGame() {
    if (!intervalId) {
        intervalId = setInterval(draw, 10);
    }
}

// 背面を描画する関数
function drawBackground() {
    ctx.fillStyle = '#f2f2f2';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
}

// ボールを描画する関数
function drawBall() {
    ctx.fillStyle = '#f1c40f';
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI*2);
    ctx.fill();
    ctx.closePath();
}

// パドルを描画する関数
function drawPaddle() {
    ctx.beginPath();
    ctx.fillStyle = '#ccc';
    ctx.fillRect(paddle.x - paddle.width/2, canvas.height - paddle.height, paddle.width, paddle.height);
    ctx.closePath();
}

// ゲームループの関数
function draw() {
    // ボールの移動
    ball.x += ball.dx;
    ball.y += ball.dy;

    // 壁に当たった場合は反射
    if(ball.x + ball.radius > canvas.width || ball.x - ball.radius < 0) {
        ball.dx = -ball.dx;
        } else if(ball.y - ball.radius < 0) {
            ball.dy = -ball.dy;
        } else if(ball.y + ball.radius > canvas.height) {  
            // 下側の壁に当たった場合はゲームオーバー
            alert("GAME OVER");
            document.location.reload();  // ページをリロードしてリセット
        }

    // パドルとの衝突判定
    if(ball.y + ball.dy > canvas.height - ball.radius - paddle.height && ball.x > paddle.x - paddle.width/2 && ball.x < paddle.x + paddle.width/2) {
        if (ball.x < paddle.x) {  // パドルの左側に当たった場合
            ball.dx = -Math.abs(ball.dx);
        } else {  // パドルの右側に当たった場合
            ball.dx = Math.abs(ball.dx);
        }
        ball.dy = -ball.dy;
    }

    // パドルの移動
    if(leftPressed && paddle.x > paddle.width/2) {
        paddle.x -= 7;
    } else if(rightPressed && paddle.x < canvas.width - paddle.width/2) {
        paddle.x += 7;
    }

    // 画面のクリア
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // ゲーム要素を描画
    drawBackground();
    drawBall();
    drawPaddle();
}

CSSフレームワークを適用

さらに、手動でCSSフレームワークのmilligramを適用。こういうのは自分でやったほうが早いかな。

index.html
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/milligram/1.4.1/milligram.css">
    <title>gpt-pong</title>
    <style>
        canvas {
            border: 1px solid #505050;
        }
    </style>
</head>

<body style="background-color:white;">
    <div class="container">
        <div class="row">
            <div class="column column-100">
                <h1>gpt-pong</h1>
                <canvas id="canvas" width="480" height="320"></canvas>
            </div>
        </div>
        <div class="row">
            <div class="column column-100">
                <button id="start-button">Start</button>
                <button id="stop-button">Stop</button>
                <button id="resume-button">Resume</button>
                <hr>
                <p><a href="https://github.com/ycatch/gpt-pong">Source code</a></p>
                <script src="pong.js"></script>
            </div>
        </div>
    </div>
</body>

</html>

gpt-pong3.png

感想

  • ここまで出来るのはすごい。

  • コードの出力量に制限があるみたいで、完全版が出力されない場合が多々ある。

  • ある程度コードを把握して自分で組み立てる羽目になった。

  • else ifが漏れるのはなんでやねん。

  • あと、varで宣言されてた。直すのを忘れていた。

  • このあとブロック崩しに進化させようとしたけど、コードの断片しか出力してくれない。

  • アプリ開発ツールの中に組み込んで、コードリポジトリの一部みたいに使うのは有効そう

  • ノーコードというかローコードツールだね

関連ページ

6
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?