BardとChatGPTでパックマンもどきの初期画面を作ってみた
GoogleのAIであるBardが2023年5月11日から日本語も含めて180カ国対応となったので使ってみました。
簡単なゲームをと思い、パックマンの画面をお任せで作成するコードを生成してみました。ChatGPT3.5との比較もあるのでご興味のある方はぜひご覧ください。
ChatGPT:https://openai.com/blog/chatgpt
Bardでパックマンもどきの画面生成
まずは初期コードを出力するまで
まずはBardがパックマンを知っているかどうか確認するために質問をしてみました。
Q:パックマンがどのようなゲームなのか教えて下さい
A:パックマンは、1980年にナムコ(現在のバンダイナムコエンターテインメント)からリリースされたアーケードゲームです。このゲームは、4つのゴーストから追われながら、パックマンというキャラクターが迷路を移動してドットを食べるというシンプルなゲームプレイです。パックマンは、パワーエサを食べることでゴーストを食べることもできます。パックマンは、ビデオゲーム史上最も人気があり影響力のあるゲームの1つです。
一応パックマンのことを認識しているようなので、実際にコードを出力してもらいます。
Q:パックマンをJavaScriptで制作したいです。コードを教えてください
A:はい。パックマンの基本的な JavaScript コードを次に示します。
コード出力後のBardによる説明がこちら。
基本的な動作といいながらもパックマンの操作やゴーストがパックマンを追いかけ、ゲームオーバーといった記述まであります。
こうして出力されたのが次のコードです。
<!DOCTYPE html>
<html>
<head>
<title>Pacman</title>
</head>
<body>
<canvas id="canvas" width="500" height="500"></canvas>
<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
// パックマンの画像を読み込む
var pacmanImage = new Image();
pacmanImage.src = "pacman.png";
// ゴーストの画像を読み込む
var ghostImage = new Image();
ghostImage.src = "ghost.png";
// パックマンの位置と方向を設定します。
var pacmanX = 200;
var pacmanY = 200;
var pacmanDir = "up";
// ゴーストの位置と方向を設定します。
var ghost1X = 100;
var ghost1Y = 100;
var ghost1Dir = "up";
var ghost2X = 300;
var ghost2Y = 100;
var ghost2Dir = "up";
var ghost3X = 100;
var ghost3Y = 300;
var ghost3Dir = "up";
var ghost4X = 300;
var ghost4Y = 300;
var ghost4Dir = "up";
// 描画関数
function draw() {
// キャンバスを黒で塗りつぶします。
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// パックマンを描画します。
ctx.drawImage(pacmanImage, pacmanX, pacmanY);
// ゴーストを描画します。
ctx.drawImage(ghostImage, ghost1X, ghost1Y);
ctx.drawImage(ghostImage, ghost2X, ghost2Y);
ctx.drawImage(ghostImage, ghost3X, ghost3Y);
ctx.drawImage(ghostImage, ghost4X, ghost4Y);
// パックマンを更新します。
switch (pacmanDir) {
case "up":
pacmanY -= 5;
break;
case "down":
pacmanY += 5;
break;
case "left":
pacmanX -= 5;
break;
case "right":
pacmanX += 5;
break;
}
// ゴーストを更新します。
ghost1X += 5;
ghost2X -= 5;
ghost3Y += 5;
ghost4Y -= 5;
// キーボード入力を監視します。
var keyCode = event.keyCode;
if (keyCode == 38) { // 上矢印キー
pacmanDir = "up";
} else if (keyCode == 40) { // 下矢印キー
pacmanDir = "down";
} else if (keyCode == 37) { // 左矢印キー
pacmanDir = "left";
} else if (keyCode == 39) { // 右矢印キー
pacmanDir = "right";
}
// 10ミリ秒後に再描画します。
requestAnimationFrame(draw);
}
// ページがロードされたときに描画関数を呼び出します。
window.addEventListener("load", draw);
</script>
</body>
</html>
コードを見る限り、ゴーストがパックマンを追いかけたり、触れるとゲームオーバーになるような機能は実装されていません。
また、pacman.pngとghost.pngの2つの画像が別途必要となるようです。そこで画像の入手をお願いしてみたところ以下の回答でした。
著作権抵触の可能性ありで教えてくれないので、パブリックドメインとなっている画像素材から適当にパックマンとゴーストの画像を入手してpacman.pngとghost.pngとしました。
初期コードの出力画面
そこで表示されたのが以下の画面です。
パックマンと4匹のゴーストが表示されるものの動かず、エラーが出ていました。
Uncaught TypeError: Cannot read properties of undefined (reading 'keyCode’)
初期コードのエラーを解消
そこでエラーの解消をBardにお願いしました。
こうして出力されたのが以下のコードです。
// キーボード入力を監視します。
document.addEventListener("keydown", function(event) {
// キーコードを読み取ります。
var keyCode = event.keyCode;
// キーコードに応じてアクションを実行します。
if (keyCode == 38) { // 上矢印キー
pacmanDir = "up";
} else if (keyCode == 40) { // 下矢印キー
pacmanDir = "down";
} else if (keyCode == 37) { // 左矢印キー
pacmanDir = "left";
} else if (keyCode == 39) { // 右矢印キー
pacmanDir = "right";
}
});
キーボードの矢印キーでパックマンの動きを操作するコードを修正してきています。
しかしエラー解消に必要なところだけを表示しているので、慣れていない人だとどこにこのコードを挿入してよいのかわからないかもですね。その場合は、もう一度フルコードで教えて、とお願いすればよいのでしょう。
とりあえずこれでエラーを解消して表示されたのが次。
エラー解消後の出力画面
まず、最初にパックマンとゴーストが表示されますが、それぞれ動いて画面の外に出て行ってしまいます。そこで矢印キーを操作してパックマンだけ画面内に戻し、少し動かしてみました。
※下記gif動画では動きにカクカク感がありますが、ファイルサイズ縮小のために5freme/秒としているためです。実際はもっとなめらかに動きます。以降のgif動画も同じです。
さらに進めようとしたが・・・
その後、画面外に出て消えてしまうのを防止するため、次のお願いをしてコードを出力してみました。
出力されたコードは以下の通り。
// パックマンがキャンバスの外に出てしまわないようにします。
if (pacmanX < 0) {
pacmanX = canvas.width;
} else if (pacmanX > canvas.width) {
pacmanX = 0;
}
if (pacmanY < 0) {
pacmanY = canvas.height;
} else if (pacmanY > canvas.height) {
pacmanY = 0;
}
// ゴーストがキャンバスの外に出てしまわないようにします。
for (var i = 0; i < ghosts.length; i++) {
var ghost = ghosts[i];
if (ghost.x < 0) {
ghost.x = canvas.width;
} else if (ghost.x > canvas.width) {
ghost.x = 0;
}
if (ghost.y < 0) {
ghost.y = canvas.height;
} else if (ghost.y > canvas.height) {
ghost.y = 0;
}
}
たしかにそれらしいコードなのですが、元のコードで宣言していないghostsという配列を使用しているため、undefinedのエラーが出て当然動きません。
しかしこれを解消させるためにエラーの内容をBardに伝えて進めようとしましたが、なぜかうまく進みませんでした。
何度か生成し直して出てきたのがこのコード。
<!DOCTYPE html>
<html>
<head>
<title>Pacman</title>
</head>
<body>
<canvas id="canvas" width="500" height="500"></canvas>
<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
// パックマンの画像を読み込む
var pacmanImage = new Image();
pacmanImage.src = "pacman.png";
// ゴーストの画像を読み込む
var ghostImage = [
new Image(),
new Image(),
new Image(),
new Image()
];
// パックマンの位置と方向を設定します。
var pacmanX = 200;
var pacmanY = 200;
var pacmanDir = "up";
// ゴーストの位置と方向を設定します。
var ghost1X = 100;
var ghost1Y = 100;
var ghost1Dir = "up";
var ghost2X = 300;
var ghost2Y = 100;
var ghost2Dir = "up";
var ghost3X = 100;
var ghost3Y = 300;
var ghost3Dir = "up";
var ghost4X = 300;
var ghost4Y = 300;
var ghost4Dir = "up";
// 描画関数
function draw() {
// キャンバスを黒で塗りつぶします。
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// パックマンを描画します。
ctx.drawImage(pacmanImage, pacmanX, pacmanY);
// ゴーストを描画します。
for (var i = 0; i < ghosts.length; i++) {
var ghost = ghosts[i];
ctx.drawImage(ghostImage[i], ghost.x, ghost.y);
}
// パックマンを更新します。
switch (pacmanDir) {
case "up":
if (pacmanY > 0) {
pacmanY -= 5;
}
break;
case "down":
if (pacmanY < canvas.height - pacmanImage.height) {
pacmanY += 5;
}
break;
case "left":
if (pacmanX > 0) {
pacmanX -= 5;
}
break;
case "right":
if (pacmanX < canvas.width - pacmanImage.width) {
pacmanX += 5;
}
break;
}
// ゴーストを更新します。
for (var i = 0; i < ghosts.length; i++) {
var ghost = ghosts[i];
if (ghost.x < 0) {
ghost.x = canvas.width;
} else if (ghost.x > canvas.width) {
ghost.x = 0;
}
if (ghost.y < 0) {
ghost.y = canvas.height;
} else if (ghost.y > canvas.height) {
ghost.y = 0;
}
ghost.x += 5;
}
// 10ミリ秒後に再描画します。
requestAnimationFrame(draw);
}
// ページがロードされたときに描画関数を呼び出します。
window.addEventListener("load", draw);
</script>
</body>
</html>
- ghost.pngの読み込み記述がなくなっている
- 相変わらず配列ghostsが定義されていない
- パックマンのキーボード制御がなくなっている
などなど、正直なぜこうなるの?というデキです。これならまだ最初のコードの方がマシでした。
他に何度かあったのが、コード記述が途中で止まるので『続きを書いてください』とお願いすると、もう一度最初からすべて書き始め、また途中までしか出力されない、といったことでした。少しコードが長くなると対処できないのでしょうか?ずいぶん時間を取られました。
自分でコード修正することに
で、結局自分でコードを修正したほうが速い、ということで修正したのがこちら。
<!DOCTYPE html>
<html>
<head>
<title>Pacman</title>
</head>
<body>
<canvas id="canvas" width="500" height="500"></canvas>
<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
// パックマンの画像を読み込む
var pacmanImage = new Image();
pacmanImage.src = "pacman.png";
// ゴーストの画像を読み込む
var ghostImage = new Image();
ghostImage.src = "ghost.png";
var ghostImage = [
ghostImage,
ghostImage,
ghostImage,
ghostImage
];
// パックマンの位置と方向を設定します。
var pacmanX = 200;
var pacmanY = 200;
var pacmanDir = "up";
// ゴーストの位置と方向を設定します。
const ghosts = [
{
x: 100,
y: 100,
dir: "down"
},
{
x: 300,
y: 100,
dir: "left"
},
{
x: 100,
y: 300,
dir: "right"
},
{
x: 300,
y: 300,
dir: "up"
}
];
// 描画関数
function draw() {
// キャンバスを黒で塗りつぶします。
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// パックマンを描画します。
ctx.drawImage(pacmanImage, pacmanX, pacmanY);
// ゴーストを描画します。
for (var i = 0; i < ghostImage.length; i++) {
// var ghost = ghosts[i];
ctx.drawImage(ghostImage[i], ghosts[i].x, ghosts[i].y);
}
// パックマンを更新します。
switch (pacmanDir) {
case "up":
pacmanY -= 2;
if (pacmanY < 0) {
pacmanY = canvas.height;
}
break;
case "down":
pacmanY += 2;
if (pacmanY > canvas.height) {
pacmanY = 0;
}
break;
case "left":
pacmanX -= 2;
if (pacmanX < 0) {
pacmanX = canvas.width;
}
break;
case "right":
pacmanX += 2;
if (pacmanX > canvas.width) {
pacmanX = 0;
}
break;
}
// ゴーストを更新します。
for (let i = 0; i < ghostImage.length; i++) {
ghost = ghosts[i];
switch (ghost.dir) {
case "up":
ghost.y -= 2;
if (ghost.y < 0) {
ghost.y = canvas.height;
}
break;
case "down":
ghost.y += 2;
if (ghost.y > canvas.height) {
ghost.y = 0;
}
break;
case "left":
ghost.x -= 2;
if (ghost.x < 0) {
ghost.x = canvas.width;
}
break;
case "right":
ghost.x += 2;
if (ghost.x > canvas.width) {
ghost.x = 0;
}
break;
}
}
// キーボード入力を監視します。
document.addEventListener("keydown", function (event) {
// キーコードを読み取ります。
var keyCode = event.keyCode;
// キーコードに応じてアクションを実行します。
if (keyCode == 38) { // 上矢印キー
pacmanDir = "up";
} else if (keyCode == 40) { // 下矢印キー
pacmanDir = "down";
} else if (keyCode == 37) { // 左矢印キー
pacmanDir = "left";
} else if (keyCode == 39) { // 右矢印キー
pacmanDir = "right";
}
});
// 10ミリ秒後に再描画します。
requestAnimationFrame(draw);
}
// ページがロードされたときに描画関数を呼び出します。
window.addEventListener("load", draw);
</script>
</body>
</html>
その画面がこちら。途中からキーボード操作でパックマンを動かしています。
なんとか目論見通りといったところです。何度も修正をお願いして結局できなかったので、なんだかんだでここまで40分程度はかかりましたかね。
ChatGPT3.5でパックマンもどきの画面生成
まずは初期コードを出力するまで
Bardと同じく、まずはパックマンを知っているかどうか確認しました。Bardより詳しく教えてくれます。
Q:パックマンがどのようなゲームなのか教えて下さい
A:パックマンは、1980年にナムコ(現バンダイナムコエンターテインメント)からリリースされたアーケードゲームです。プレイヤーは、パックマンという円形のキャラクターを操作して、迷路の中を進みながら点を集め、敵であるゴーストから逃げる、またはゴーストを食べることが目的のゲームです。
迷路内には、小さな点(ドット)や大きな点(パワーペルレット)があります。プレイヤーがこれらを集めることで、得点が加算されます。一方、敵であるゴーストが追いかけてきて、プレイヤーに触れると失敗になります。しかし、パワーペルレットを集めることで、一時的にゴーストを弱らせ、食べることができます。ゲームを進めると、ゴーストの速度が上がり、プレイヤーにとってより挑戦的なゲームプレイが要求されます。
パックマンは、当時のアーケードゲームの中でも大ヒットし、その後も様々なプラットフォームでリリースされ、世界中で愛されるクラシックゲームとなりました。
それではBardにしたのと同じプロンプトでコード生成を依頼です。
コード表示後の説明文です。まずは基本的な動作のみを描画するコードである旨の説明があります。
出てきたコードがこちら。
<!DOCTYPE html>
<html>
<head>
<title>Pacman Game</title>
<style>
canvas {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas id="canvas" width="500" height="500"></canvas>
<script>
// ゲーム要素の初期化
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius = 20;
var startAngle = 0.25 * Math.PI;
var endAngle = 1.75 * Math.PI;
var counterClockwise = false;
// ゲームの描画
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(x, y, radius, startAngle, endAngle, counterClockwise);
ctx.lineTo(x, y);
ctx.fillStyle = "#FF0";
ctx.fill();
ctx.closePath();
}
// ゲームの更新
function update() {
x += 1;
if (x > canvas.width) {
x = 0;
}
}
// ゲームループの実行
function gameLoop() {
draw();
update();
window.requestAnimationFrame(gameLoop);
}
// ゲームループの開始
gameLoop();
</script>
</body>
</html>
Bardではpng画像を別途必要でしたが、ChtaGPTはパックマンを自分で描画するようです。
初期コードの出力画面
なので、これをそのままhtmlとしてブラウザで表示したのがこちら。ゴーストは存在せず、パックマンのみ右へ動くだけのシンプルなコードです。しかし画面外に出たまま消えてしまうことはありません。
Bardは最初からゴーストを表示したりパックマンのキーボード操作を盛り込もうとしてエラーが出ていましたが、ChatGPTはとりあえず簡単なところから始め、必要ならば要素を追加していってください、という主旨のようです。
ゴーストの追加とパックマンのキーボード操作を追加
なので要素の追加をお願いしました。とりあえずBardと同じ機能を付与するようお願いです。ゴーストの描画にはghost.pngを使用するように指示しました。
コードが途中で切れたので、『続きを書いてください』とお願いすると素直に続きを書いてくれました。コード表示の後の説明文がこちらです。
<!DOCTYPE html>
<html>
<head>
<title>Pacman Game</title>
<style>
canvas {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas id="canvas" width="500" height="500"></canvas>
<script>
// ゲーム要素の初期化
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius = 20;
var startAngle = 0.25 * Math.PI;
var endAngle = 1.75 * Math.PI;
var counterClockwise = false;
var speed = 2;
var direction = null;
var ghostImages = [];
// Ghostの情報
var ghosts = [
{ x: 100, y: 100, speed: 1, direction: "right" },
{ x: 200, y: 200, speed: 1.5, direction: "down" },
{ x: 300, y: 300, speed: 2, direction: "left" },
{ x: 400, y: 400, speed: 2.5, direction: "up" }
];
// 画像の読み込み
function loadImages() {
var imageCount = 0;
var totalImages = 4;
for (var i = 0; i < totalImages; i++) {
ghostImages[i] = new Image();
ghostImages[i].src = "ghost.png";
ghostImages[i].onload = function() {
imageCount++;
if (imageCount === totalImages) {
gameLoop();
}
};
}
}
// ゲームの描画
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Ghostの描画
for (var i = 0; i < ghosts.length; i++) {
var ghost = ghosts[i];
ctx.drawImage(ghostImages[i], ghost.x, ghost.y);
}
// パックマンの描画
ctx.beginPath();
ctx.arc(x, y, radius, startAngle, endAngle, counterClockwise);
ctx.lineTo(x, y);
ctx.fillStyle = "#FF0";
ctx.fill();
ctx.closePath();
}
// ゲームの更新
function update() {
// Ghostの移動
for (var i = 0; i < ghosts.length; i++) {
var ghost = ghosts[i];
if (ghost.direction === "right") {
ghost.x += ghost.speed;
if (ghost.x > canvas.width) {
ghost.x = 0;
}
} else if (ghost.direction === "down") {
ghost.y += ghost.speed;
if (ghost.y > canvas.height) {
ghost.y = 0;
}
} else if (ghost.direction === "left") {
ghost.x -= ghost.speed;
if (ghost.x < 0) {
ghost.x = canvas.width;
}
} else if (ghost.direction === "up") {
ghost.y -= ghost.speed;
if (ghost.y < 0) {
ghost.y = canvas.height;
}
}
}
// パックマンの移動
if (direction === "right") {
x += speed;
if (x > canvas.width) {
x = 0;
}
} else if (direction === "down") {
y += speed;
if (y > canvas.height) {
y = 0;
}
} else if (direction === "left") {
x -= speed;
if (x < 0) {
x = canvas.width;
}
} else if (direction === "up") {
y -= speed;
if (y < 0) {
y = canvas.height;
}
}
}
// キーボードのイベントリスナー
document.addEventListener("keydown", function(event) {
if (event.keyCode === 37) {
direction = "left"; // 左キー
} else if (event.keyCode === 38) {
direction = "up"; // 上キー
} else if (event.keyCode === 39) {
direction = "right"; // 右キー
} else if (event.keyCode === 40) {
direction = "down"; // 下キー
}
});
// ゲームループの実行
function gameLoop() {
draw();
update();
window.requestAnimationFrame(gameLoop);
}
// 画像の読み込みを開始
loadImages();
</script>
</body>
</html>
これをブラウザで表示させたのがこちら。パックマンは最初は左に単純移動していますが、途中からキーボード操作で動かしています。ゴーストのスピードもそれぞれ少し違う設定になっています。
指示どおり一発でエラーもなく表示できました。ChatGPTの方は、ここまでせいぜい10分程度、という感じでしょうか。
これならどんどん機能を追加していけそうです。
まとめ
BardとChatGPTを使ってパックマンもどきの画面を作成してみました。
Bardのほうは結局思惑通りのコードを得られず、時間を浪費する結果となりました。また長めのコード生成時に途中で中断したときに続きを書いてもらうには、何か適切なプロンプトが必要なのかもしれません。
Bardにはこれからどんどんアップデートしていくことを期待したいと思います。
ChatGPTのほうは3.5でも十分優秀な結果でした。最近私もコード生成にはお世話になっているので、お願いするプロンプトがそこそこ的を得ているのかもしれません。