お久しぶりです。前回投稿が2022-08-15なので、サボりすぎですね…
情報のインプットは好奇心から頑張れるというか知識を得るのが楽しいのでやるんですが、情報のアウトプットって既に知っている知識を使うことなので気が進まないものですよね…定着させるには大事なんですが、特に定着させるモチベーションもなく…
ただ、やっとそのアウトプットを勝手にやってくれる時代がやってきました。そうChatGPTです。こやつにコンテンツを作ってもらう取り組みがどれくらい続くかチャレンジをやってみようと思い、ChatGTP Plus課金しちゃいました!
こやつを使って今日はテトリス作っちゃいます!
完成形
テトリスって、どんなブロックがあってどういうふうに回転してどこで壁にぶつかってとか、まじめに実装すると考えることいっぱいあるのですが、そんなことを考えずに一瞬で作ってくれました。天才です。
が、それを作ってから手直ししていく過程が大変だったので、会話の過程をご覧いただけたらと思います。
会話highlight
一瞬でコード書けるスーパーエンジニア
## USER
# 命令書:
あなたはフロントエンドエンジニアです。
以下の制約条件と入力文をもとにゲームのソースコードを出力してください。
# 制約条件:
* HTML、CSS、Javascriptのみで動く
# 入力文:
テトリス
# 出力文:
深津式プロンプト(https://chatgpt-lab.com/n/ne1643b7f10e6)を使ってみようとしたのですが、大して制約条件を書くことなく出力しちゃいました。
index.html
を作って動かしたらこれ
もうエンジニアいらんやん…
すぐ解決策に走る初心者エンジニア
なんか右の画面いっぱいまでブロックが動かせなくて質問するのですが…
これやっても直りませんでした。どこが問題かを分析せず解決策に走る初心者エンジニアみたいです。でも、根気よく質問すると…
ちゃんとうごきました!
「コード見せて」といってくるベテランエンジニア
「次のブロックを表示」という機能追加もちょっとバグっていて、いろいろ質問して直そうとしたのですが
なかなか直らないとみたやコード全体を要求してきました。今度はこっちがなかなかバグを治せない初心者エンジニアみたいです…
かわいい
最後にお礼にコードレビューしてみると…?
GPT-4の使用上限にひっかかって、GPT-3.5になりました。。。コンテキストをちゃんと解釈してくれてない回答感ありますよね…KY…
ChatGPTと喋った内容
Takaaki
# 命令書:
あなたはフロントエンドエンジニアです。
以下の制約条件と入力文をもとにゲームのソースコードを出力してください。
# 制約条件:
* HTML、CSS、Javascriptのみで動く
# 入力文:
テトリス
# 出力文:
ChatGPT
以下は、HTML、CSS、Javascriptを使用してテトリスゲームを実装するための簡単なソースコードです。このコードをhtmlファイルにコピーして実行することができます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>テトリス</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
canvas {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas id="game" width="320" height="640"></canvas>
<script>
const canvas = document.getElementById("game");
const context = canvas.getContext("2d");
const scale = 20;
context.scale(scale, scale);
const arenaSweep = () => {
outer: for (let y = arena.length - 1; y > 0; y--) {
for (let x = 0; x < arena[y].length; x++) {
if (!arena[y][x]) {
continue outer;
}
}
const row = arena.splice(y, 1)[0].fill(0);
arena.unshift(row);
++y;
}
};
const 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] && (arena[y + o.y] && arena[y + o.y][x + o.x]) !== 0) {
return true;
}
}
}
return false;
};
const createMatrix = (w, h) => {
const matrix = [];
while (h--) {
matrix.push(new Array(w).fill(0));
}
return matrix;
};
const draw = () => {
context.fillStyle = "#000";
context.fillRect(0, 0, canvas.width, canvas.height);
drawMatrix(arena, { x: 0, y: 0 });
drawMatrix(player.matrix, player.pos);
};
const drawMatrix = (matrix, offset) => {
matrix.forEach((row, y) => {
row.forEach((value, x) => {
if (value) {
context.fillStyle = "red";
context.fillRect(x + offset.x, y + offset.y, 1, 1);
}
});
});
};
const merge = (arena, player) => {
player.matrix.forEach((row, y) => {
row.forEach((value, x) => {
if (value) {
arena[y + player.pos.y][x + player.pos.x] = value;
}
});
});
};
const playerDrop = () => {
player.pos.y++;
if (collide(arena, player)) {
player.pos.y--;
merge(arena, player);
playerReset();
arenaSweep();
}
dropCounter = 0;
};
const playerMove = (offset) => {
player.pos.x += offset;
if (collide(
#### Takaaki
続けてください
#### ChatGPT
arena, player)) {
player.pos.x -= offset;
}
};
const playerReset = () => {
const pieces = "TJLOSZI";
player.matrix = createPiece(pieces[(pieces.length * Math.random()) | 0]);
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));
alert("ゲームオーバー");
}
};
const 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;
}
}
};
const 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();
}
};
const createPiece = (type) => {
if (type === "T") {
return [
[1, 1, 1],
[0, 1, 0],
[0, 0, 0]
];
} else if (type === "J") {
return [
[0, 1, 0],
[0, 1, 0],
[1, 1, 0]
];
} else if (type === "L") {
return [
[0, 1, 0],
[0, 1, 0],
[0, 1, 1]
];
} else if (type === "O") {
return [
[1, 1],
[1, 1]
];
} else if (type === "S") {
return [
[0, 1, 1],
[1, 1, 0],
[0, 0, 0]
];
} else if (type === "Z") {
return [
[1, 1, 0],
[0, 1, 1],
[0, 0, 0]
];
} else if (type === "I") {
return [
[0, 1, 0, 0],
[0, 1, 0, 0],
[0, 1, 0, 0],
[0, 1, 0, 0]
];
}
};
let dropCounter = 0;
let dropInterval = 1000;
let lastTime = 0;
const update = (time = 0) => {
const deltaTime = time - lastTime;
lastTime = time;
dropCounter += deltaTime;
if (dropCounter > dropInterval) {
playerDrop();
}
draw();
requestAnimationFrame(update);
};
#### Takaaki
続けてください
#### ChatGPT
const arena = createMatrix(10, 20);
const player = {
pos: { x: 0, y: 0 },
matrix: null
};
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 || event.keyCode === 38) {
playerRotate(1);
}
});
playerReset();
update();
</script>
</body>
</html>
このコードは、テトリスゲームの基本的な機能を実装しています。プレイヤーは矢印キーを使ってピースを操作し、Qキーで反時計回りに回転、Wキーまたは上矢印キーで時計回りに回転させることができます。行が埋まると消え、ゲームが続行します。ピースがスタックして画面上部に達すると、ゲームオーバーになります。このコードを自由にカスタマイズして、さらに機能を追加したり、見た目を改善したりすることができます。
Takaaki
早速ありがとうございます、コードを貼り付けて動かしてみるとちゃんとブロックが落ちてきたので感動しています!
1点、ブロックが左から10列の範囲しか動かせず行が埋まることがなくなってしまっているのですがどこをどうやって直せばいいでしょうか?
ChatGPT
お力になれてうれしいです!ご指摘の問題について、playerMove()
関数内の衝突判定が適切に行われていないことが原因と思われます。collide()
関数を呼び出す前に、移動後のプレイヤーの位置が適切な範囲にあることを確認するために、追加の条件を追加してください。以下に修正を加えた playerMove()
関数を示します。
const playerMove = (offset) => {
player.pos.x += offset;
if (collide(arena, player) || player.pos.x < 0 || player.pos.x + player.matrix[0].length > arena[0].length) {
player.pos.x -= offset;
}
};
この修正により、プレイヤーがブロックを左右に動かすときに、画面外や既存のブロックとの衝突が適切に判定されます。これでブロックが10列の範囲内で適切に動作し、行が埋まるようになるはずです。
Takaaki
あれ、おっしゃった修正を加えると壁に衝突していないのに左に動かせない場合がでてきまいました…
これでブロックが10列の範囲内で適切に動作し、行が埋まるようになるはずです。
ゲーム画面は10列以上あるようにみえていて、右にブロックが置けない範囲があるのを直したいです!
ChatGPT
すみません、私の解釈が誤っていました。画面の幅に関する問題について、createMatrix()
関数で作成された arena の幅が適切でない可能性があります。10 列の代わりに、画面全体をカバーするために幅を 16 列に変更してみてください。
const arena = createMatrix(16, 20);
また、playerMove()
関数の修正に関しては、以下のように、壁に衝突していない場合だけ左右に動かせるようになります。
const playerMove = (offset) => {
player.pos.x += offset;
if (collide(arena, player)) {
player.pos.x -= offset;
}
};
これで、ゲーム画面が16列になり、左右の壁にぶつからずにブロックを動かせるはずです。また、行が埋まるようになります。
Takaaki
ちゃんと動きました、ありがとうございます〜!
ちょっと実際にゲームしていると次にどんなブロックが来るかわからないのが辛くて、次にどんなブロックが落ちてくるのか表示するようにしてもらえませんか?
ChatGPT
もちろんです!次に来るブロックを表示するために、次の手順を実行してください。
- 次のブロック用のキャンバスを追加します。
- 次のブロックを生成し、表示するための関数を追加します。
- ブロックがリセットされるたびに、次のブロックを表示します。
それでは、以下の手順に従ってコードを修正していきましょう。
Step 1: 次のブロック用のキャンバスを追加
まず、HTML部分に次のブロック用のキャンバスを追加し、CSSで適切にスタイルを調整します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>テトリス</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
canvas {
border: 1px solid #000;
}
#game {
margin-right: 20px;
}
.container {
display: flex;
align-items: center;
}
</style>
</head>
<body>
<div class="container">
<canvas id="game" width="320" height="640"></canvas>
<div>
<p>次のブロック:</p>
<canvas id="next" width="80" height="80"></canvas>
</div>
</div>
<script>
// 続くJavaScriptコード
</script>
</body>
</html>
Step 2: 次のブロックを生成し、表示するための関数を追加
次に、JavaScript部分に次のブロックを生成し、表示するための関数を追加します。
const nextCanvas = document.getElementById("next");
const nextContext = nextCanvas.getContext("2d");
nextContext.scale(scale, scale);
let nextPiece = null;
const generateNextPiece = () => {
const pieces = "TJLOSZI";
nextPiece = createPiece(pieces[(pieces.length * Math.random()) | 0]);
};
const drawNextPiece = () => {
nextContext.fillStyle = "#000";
nextContext.fillRect(0, 0, nextCanvas.width, nextCanvas.height);
drawMatrix(nextPiece, { x: (nextCanvas.width / scale - nextPiece[0].length) / 2, y: (nextCanvas.height / scale - nextPiece.length) / 2 }, nextContext);
};
const drawMatrix = (matrix, offset, ctx = context) => {
matrix.forEach((row, y) => {
row.forEach((value, x) => {
if (value) {
ctx.fillStyle = "red";
ctx.fillRect(x + offset.x, y + offset.y, 1, 1);
}
});
});
};
Step 3: ブロックがリセットされるたびに、次のブロックを表示
最後に
Takaaki
続けてください
ChatGPT
最後に、playerReset()
関数を修正して、ブロックがリセットされるたびに次のブロックを表示し、新しい次のブロックを生成します。
const playerReset = () => {
if (!nextPiece) {
generateNextPiece();
}
player.matrix = nextPiece;
generateNextPiece();
drawNextPiece();
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));
alert("ゲームオーバー");
}
};
これで、ゲーム画面の横に次に落ちてくるブロックが表示されるようになりました。ゲームが始まるときに最初の次のブロックを生成するために、playerReset()
を初期化前に一度呼び出してください。
playerReset();
update();
これで、次にどんなブロックが落ちてくるかが表示されるようになりました。この機能により、プレイヤーは次のブロックを考慮して戦略を立てることができます。
Takaaki
やってみたのですが、次のブロックの箱は表示されるのですが中になんのブロックも表示されず…
ChatGPT
すみません、コードに不足がありました。drawNextPiece()
関数で nextPiece
が null
の場合に対応する必要があります。以下のように drawNextPiece()
関数を修正してください。
const drawNextPiece = () => {
nextContext.fillStyle = "#000";
nextContext.fillRect(0, 0, nextCanvas.width, nextCanvas.height);
if (nextPiece) {
drawMatrix(nextPiece, { x: (nextCanvas.width / scale - nextPiece[0].length) / 2, y: (nextCanvas.height / scale - nextPiece.length) / 2 }, nextContext);
}
};
さらに、最初の次のブロックを表示するために、update()
関数の前に drawNextPiece()
を呼び出して初期化してください。
playerReset();
drawNextPiece();
update();
これで、次のブロックが表示されるはずです。もしまだ問題がある場合はお知らせください。
Takaaki
あれ、まだ表示されませんでした…
ChatGPT
申し訳ありません。理由がわかりました。playerReset()
関数内でplayer.matrix
にnextPiece
を代入した後で、generateNextPiece()
を呼び出すことで、次のブロックを生成する前にnextPiece
がnull
になってしまっています。
playerReset()
関数を以下のように修正してください。
const playerReset = () => {
if (!nextPiece) {
generateNextPiece();
}
player.matrix = nextPiece;
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));
alert("ゲームオーバー");
}
generateNextPiece();
drawNextPiece();
};
これで、次のブロックが正しく表示されるはずです。もしまだ問題がある場合は、お知らせください。
Takaaki
1つまえの修正は戻した方がいいです?
ChatGPT
はい、前の修正に戻して、playerReset()
関数の修正に集中しましょう。以下の修正を行ってください。
drawNextPiece()
関数の修正を元に戻す(nextPiece
がnull
の場合の処理を削除する)。playerReset()
関数を修正し、次のブロックを生成し、次のブロックを表示する順序を適切に設定する。
修正後の関数は以下のようになります。
const drawNextPiece = () => {
nextContext.fillStyle = "#000";
nextContext.fillRect(0, 0, nextCanvas.width, nextCanvas.height);
drawMatrix(nextPiece, { x: (nextCanvas.width / scale - nextPiece[0].length) / 2, y: (nextCanvas.height / scale - nextPiece.length) / 2 }, nextContext);
};
const playerReset = () => {
if (!nextPiece) {
generateNextPiece();
}
player.matrix = nextPiece;
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));
alert("ゲームオーバー");
}
generateNextPiece();
drawNextPiece();
};
これで、次のブロックが正しく表示されるはずです。もしまだ問題がある場合はお知らせください。
Takaaki
んー表示されないです…ステップバイステップで修正方法を考えてもらえますか?
ChatGPT
申し訳ありません。もう一度見直して、ステップバイステップで修正方法を提案させていただきます。
Step 1: 次のブロック用の変数と関数を追加
まず、次のブロック用の変数と関数を追加します。nextPiece
変数を定義し、generateNextPiece()
関数を作成します。
let nextPiece = null;
const generateNextPiece = () => {
const pieces = "TJLOSZI";
nextPiece = createPiece(pieces[(pieces.length * Math.random()) | 0]);
};
Step 2: 次のブロックを表示する関数を追加
次に、次のブロックを表示するための関数を追加します。drawNextPiece()
関数を作成し、nextPiece
が null
でない場合に描画を行います。
const drawNextPiece = () => {
nextContext.fillStyle = "#000";
nextContext.fillRect(0, 0, nextCanvas.width, nextCanvas.height);
if (nextPiece) {
drawMatrix(nextPiece, { x: (nextCanvas.width / scale - nextPiece[0].length) / 2, y: (nextCanvas.height / scale - nextPiece.length) / 2 }, nextContext);
}
};
Step 3: playerReset()
関数を修正
playerReset()
関数を修正して、現在の nextPiece
をプレイヤーのブロックに設定し、新しい次のブロックを生成して表示します。また、最初の次のブロックを生成するために、playerReset()
を初期化前に一度呼び出します。
const playerReset = () => {
if (!nextPiece) {
generateNextPiece();
}
player.matrix = nextPiece;
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));
alert("ゲームオーバー");
}
generateNextPiece();
drawNextPiece();
};
playerReset();
update();
Step 4: draw()
関数を修正
draw()
関数を修正して、drawNextPiece()
関数が正しく呼び出されるようにします。draw()
関数内で drawNextPiece()
を呼び出すことで、次のブロックが常に更新されます。
const draw = () => {
context.fillStyle = "#000";
context.fillRect(0, 0, canvas.width, canvas.height);
drawMatrix(arena, { x: 0, y: 0 });
drawMatrix(player.matrix, player.pos);
drawNextPiece();
};
これらの修正を適用すると、次のブロックが正しく表示されるはずです。もしまだ問
Takaaki
あ、コードの変更ではなくチェックして欲しい箇所を教えてください
ChatGPT
失礼いたしました。既存のコードをチェックして問題がないかを確認させていただきます。
以下の箇所をチェックしてみてください:
nextCanvas
およびnextContext
の定義が正しいか確認してください。以下のように定義されていることを確認してください。
const nextCanvas = document.getElementById("next");
const nextContext = nextCanvas.getContext("2d");
nextCanvas.width = 4 * scale;
nextCanvas.height = 4 * scale;
nextContext.scale(scale, scale);
generateNextPiece()
関数で、次のブロックが正しく生成されていることを確認してください。
const generateNextPiece = () => {
const pieces = "TJLOSZI";
nextPiece = createPiece(pieces[(pieces.length * Math.random()) | 0]);
};
drawNextPiece()
関数が正しく実装されていることを確認してください。
const drawNextPiece = () => {
nextContext.fillStyle = "#000";
nextContext.fillRect(0, 0, nextCanvas.width, nextCanvas.height);
drawMatrix(nextPiece, { x: (nextCanvas.width / scale - nextPiece[0].length) / 2, y: (nextCanvas.height / scale - nextPiece.length) / 2 }, nextContext);
};
playerReset()
関数内で、次のブロックをプレイヤーのブロックに設定し、新しい次のブロックを生成して表示していることを確認してください。
const playerReset = () => {
if (!nextPiece) {
generateNextPiece();
}
player.matrix = nextPiece;
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));
alert("ゲームオーバー");
}
generateNextPiece();
drawNextPiece();
};
draw()
関数内で、drawNextPiece()
関数が呼び出されていることを確認してください。
const draw = () => {
context.fillStyle = "#000";
context.fillRect(0, 0, canvas.width, canvas.height);
drawMatrix(arena, { x: 0, y: 0 });
drawMatrix(player.matrix, player.pos);
drawNextPiece();
};
これらの箇所が正しく実装されていれば、次のブロックが表示されるはずです。もし問題が解決しない場合は、お手数ですがコード全体をお知らせいただけると、より具体的な解決策を提案できるかと思います。
Takaaki
あ、次のブロックが出てきたんですけど描画位置が違いますね…
すいませんどこがおかしいですか…?
const canvas = document.getElementById("game");
const context = canvas.getContext("2d");
const scale = 20;
context.scale(scale, scale);
const nextCanvas = document.getElementById("next");
const nextContext = nextCanvas.getContext("2d");
nextCanvas.width = 4 * scale;
nextCanvas.height = 4 * scale;
nextContext.scale(scale, scale);
const arenaSweep = () => {
outer: for (let y = arena.length - 1; y > 0; y--) {
for (let x = 0; x < arena[y].length; x++) {
if (!arena[y][x]) {
continue outer;
}
}
const row = arena.splice(y, 1)[0].fill(0);
arena.unshift(row);
++y;
}
};
const 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] && (arena[y + o.y] && arena[y + o.y][x + o.x]) !== 0) {
return true;
}
}
}
return false;
};
const createMatrix = (w, h) => {
const matrix = [];
while (h--) {
matrix.push(new Array(w).fill(0));
}
return matrix;
};
const draw = () => {
context.fillStyle = "#000";
context.fillRect(0, 0, canvas.width, canvas.height);
drawMatrix(arena, { x: 0, y: 0 });
drawMatrix(player.matrix, player.pos);
drawNextPiece();
};
const drawMatrix = (matrix, offset) => {
matrix.forEach((row, y) => {
row.forEach((value, x) => {
if (value) {
context.fillStyle = "red";
context.fillRect(x + offset.x, y + offset.y, 1, 1);
}
});
});
};
const merge = (arena, player) => {
player.matrix.forEach((row, y) => {
row.forEach((value, x) => {
if (value) {
arena[y + player.pos.y][x + player.pos.x] = value;
}
});
});
};
const playerDrop = () => {
player.pos.y++;
if (collide(arena, player)) {
player.pos.y--;
merge(arena, player);
playerReset();
arenaSweep();
}
dropCounter = 0;
};
const playerMove = (offset) => {
player.pos.x += offset;
if (collide(arena, player)) {
player.pos.x -= offset;
}
};
let nextPiece = null;
const generateNextPiece = () => {
const pieces = "TJLOSZI";
nextPiece = createPiece(pieces[(pieces.length * Math.random()) | 0]);
};
const drawNextPiece = () => {
nextContext.fillStyle = "#000";
nextContext.fillRect(0, 0, nextCanvas.width, nextCanvas.height);
drawMatrix(
nextPiece,
{
x: (nextCanvas.width / scale - nextPiece[0].length) / 2,
y: (nextCanvas.height / scale - nextPiece.length) / 2,
},
nextContext
);
};
const playerReset = () => {
if (!nextPiece) {
generateNextPiece();
}
player.matrix = nextPiece;
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));
alert("ゲームオーバー");
}
generateNextPiece();
drawNextPiece();
};
const 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;
}
}
};
const 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();
}
};
const createPiece = (type) => {
if (type === "T") {
return [
[1, 1, 1],
[0, 1, 0],
[0, 0, 0],
];
} else if (type === "J") {
return [
[0, 1, 0],
[0, 1, 0],
[1, 1, 0],
];
} else if (type === "L") {
return [
[0, 1, 0],
[0, 1, 0],
[0, 1, 1],
];
} else if (type === "O") {
return [
[1, 1],
[1, 1],
];
} else if (type === "S") {
return [
[0, 1, 1],
[1, 1, 0],
[0, 0, 0],
];
} else if (type === "Z") {
return [
[1, 1, 0],
[0, 1, 1],
[0, 0, 0],
];
} else if (type === "I") {
return [
[0, 1, 0, 0],
[0, 1, 0, 0],
[0, 1, 0, 0],
[0, 1, 0, 0],
];
}
};
let dropCounter = 0;
let dropInterval = 1000;
let lastTime = 0;
const update = (time = 0) => {
const deltaTime = time - lastTime;
lastTime = time;
dropCounter += deltaTime;
if (dropCounter > dropInterval) {
playerDrop();
}
draw();
requestAnimationFrame(update);
};
const arena = createMatrix(16, 20);
const player = {
pos: { x: 0, y: 0 },
matrix: null,
};
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 || event.keyCode === 38) {
playerRotate(1);
}
});
playerReset();
update();
ChatGPT
コードを確認させていただきました。drawMatrix
関数の呼び出し時に、nextContext
が渡されていないために、次のブロックが正しく描画されていないようです。drawNextPiece()
関数内のdrawMatrix()
関数の呼び出しで、nextContext
を渡すように変更してみてください。
以下のように修正してみてください。
const drawNextPiece = () => {
nextContext.fillStyle = "#000";
nextContext.fillRect(0, 0, nextCanvas.width, nextCanvas.height);
drawMatrix(
nextPiece,
{
x: (nextCanvas.width / scale - nextPiece[0].length) / 2,
y: (nextCanvas.height / scale - nextPiece.length) / 2,
},
nextContext
);
};
そして、drawMatrix
関数のシグネチャを以下のように変更してください。
const drawMatrix = (matrix, offset, context) => {
// 以前のコードはここに残ります。
};
これで次のブロックが正しく表示されるはずです。お試しください。
Takaaki
おー次のブロックのようなものが表示されました! が、落ちてくるブロックが表示されなくなっちゃいました…
ChatGPT
失礼しました。draw()
関数内のdrawMatrix()
関数の呼び出しにもcontext
を渡す必要があります。以下のように修正してみてください。
const draw = () => {
context.fillStyle = "#000";
context.fillRect(0, 0, canvas.width, canvas.height);
drawMatrix(arena, { x: 0, y: 0 }, context);
drawMatrix(player.matrix, player.pos, context);
drawNextPiece();
};
これで、ゲーム画面に落ちてくるブロックが再び表示されるはずです。お試しください。
Takaaki
いけました! いやーなかなか大変でしたね笑
ChatGPT
うまくいってよかったです!お手伝いできてうれしいです。確かに、コードの修正やデバッグは時々大変ですね。しかし、うまく機能するときはとても達成感があります!
今後も何か質問があれば、どんなことでもお気軽にお聞きください。お手伝いできることがあれば喜んで対応いたします。お楽しみください!
Takaaki
画面の一番下までブロックが落ちないのですがどこか設定を変更する必要がありますか?
ChatGPT
すみません、問題が発生していることに気づかなかったです。アリーナの高さが20に設定されているので、キャンバスの高さもそれに合わせて調整する必要があります。canvas
の高さを変更することで、ブロックが画面の一番下まで落ちるようになります。
以下のように、canvas
の高さを20倍のスケールに設定してみてください。
const canvas = document.getElementById("game");
const context = canvas.getContext("2d");
const scale = 20;
canvas.width = 16 * scale;
canvas.height = 20 * scale; // ここを変更します
context.scale(scale, scale);
これで、ブロックが画面の一番下まで落ちるはずです。お試しください。
Takaaki
あ、画面全体の大きさはさっきのままがいいです!
ChatGPT
失礼しました。画面全体の大きさを維持しつつ、ブロックが画面の一番下まで落ちるようにするには、アリーナの高さを調整する必要があります。
createMatrix
関数でアリーナを作成する際の高さを、現在のキャンバスの高さに合わせましょう。以下のようにcreateMatrix
関数を呼び出す際に、高さをcanvas.height / scale
に設定してみてください。
const arena = createMatrix(16, canvas.height / scale); // ここを変更します
これで、ブロックが画面の一番下まで落ちるはずです。お試しください。
Takaaki
できました! 最後に、ブロックを動かしている時にどこ列にいるのかわかりずらいのでいい感じにデザイン変えてもらえますか?
ChatGPT
もちろんです!ブロックがどの列にあるかをわかりやすくするために、ゲーム画面にグリッド線を追加してみましょう。以下のdrawGrid
関数を追加し、draw
関数内で呼び出してください。
const drawGrid = () => {
context.strokeStyle = "rgba(255, 255, 255, 0.1)"; // グリッド線の色と透明度を設定
context.lineWidth = 0.05; // グリッド線の太さを設定
// 縦線を描画
for (let x = 1; x < 16; x++) {
context.beginPath();
context.moveTo(x, 0);
context.lineTo(x, canvas.height / scale);
context.stroke();
}
// 横線を描画
for (let y = 1; y < canvas.height / scale; y++) {
context.beginPath();
context.moveTo(0, y);
context.lineTo(16, y);
context.stroke();
}
};
const draw = () => {
context.fillStyle = "#000";
context.fillRect(0, 0, canvas.width, canvas.height);
drawGrid(); // グリッド線を描画
drawMatrix(arena, { x: 0, y: 0 });
drawMatrix(player.matrix, player.pos);
drawNextPiece();
};
この変更により、ゲーム画面にグリッド線が表示され、ブロックがどの列にあるかがわかりやすくなるはずです。お試しください。
Takaaki
グリッド戦は表示されたのですが、落ちてくるブロックがまた消えちゃいました…
ChatGPT
すみません、グリッド線の描画がブロックの描画を上書きしてしまっているようです。drawGrid
関数をdraw
関数の最初に移動して、背景とグリッド線を先に描画してからブロックを描画するようにしましょう。
const draw = () => {
context.fillStyle = "#000";
context.fillRect(0, 0, canvas.width, canvas.height);
drawGrid(); // グリッド線を描画
drawMatrix(arena, { x: 0, y: 0 });
drawMatrix(player.matrix, player.pos);
drawNextPiece();
};
これで落ちてくるブロックが表示されるはずです。お試しください。
Takaaki
あれ、表示されないです…
const drawGrid = () => {
context.strokeStyle = "rgba(255, 255, 255, 0.1)"; // グリッド線の色と透明度を設定
context.lineWidth = 0.05; // グリッド線の太さを設定
// 縦線を描画
for (let x = 1; x < 16; x++) {
context.beginPath();
context.moveTo(x, 0);
context.lineTo(x, canvas.height / scale);
context.stroke();
}
// 横線を描画
for (let y = 1; y < canvas.height / scale; y++) {
context.beginPath();
context.moveTo(0, y);
context.lineTo(16, y);
context.stroke();
}
};
const draw = () => {
context.fillStyle = "#000";
context.fillRect(0, 0, canvas.width, canvas.height);
drawGrid(); // グリッド線を描画
drawMatrix(arena, { x: 0, y: 0 });
drawMatrix(player.matrix, player.pos);
drawNextPiece();
};
であってますか?
ChatGPT
申し訳ありません、私の説明が不十分でした。drawMatrix
関数を呼び出す前に、context.save()
を追加して、描画状態を保存し、drawMatrix
関数が終わった後にcontext.restore()
を呼び出して、描画状態を元に戻しましょう。
以下のように変更してください。
const draw = () => {
context.fillStyle = "#000";
context.fillRect(0, 0, canvas.width, canvas.height);
drawGrid(); // グリッド線を描画
context.save(); // 描画状態を保存
drawMatrix(arena, { x: 0, y: 0 });
drawMatrix(player.matrix, player.pos);
context.restore(); // 描画状態を元に戻す
drawNextPiece();
};
これでブロックが表示されるはずです。お試しください。
Takaaki
そんなメソッドは実装していないので動かなさそうですね…
ChatGPT
申し訳ありません。私がコードを見直したところ、誤解があったようです。drawGrid
内でcontext.lineWidth
を設定していたことが原因でした。ブロックの描画に影響が出ていました。context.lineWidth
をdrawGrid
の最後で元の値に戻すことで、問題が解決するはずです。
drawGrid
関数を以下のように修正してください。
const drawGrid = () => {
context.strokeStyle = "rgba(255, 255, 255, 0.1)"; // グリッド線の色と透明度を設定
const originalLineWidth = context.lineWidth;
context.lineWidth = 0.05; // グリッド線の太さを設定
// 縦線を描画
for (let x = 1; x < 16; x++) {
context.beginPath();
context.moveTo(x, 0);
context.lineTo(x, canvas.height / scale);
context.stroke();
}
// 横線を描画
for (let y = 1; y < canvas.height / scale; y++) {
context.beginPath();
context.moveTo(0, y);
context.lineTo(16, y);
context.stroke();
}
context.lineWidth = originalLineWidth; // 元の lineWidth に戻す
};
これでブロックが表示されるはずです。再度お試しください。
Takaaki
だめでした…
const drawGrid = () => {
context.strokeStyle = "rgba(255, 255, 255, 0.1)"; // グリッド線の色と透明度を設定
const originalLineWidth = context.lineWidth;
context.lineWidth = 0.05; // グリッド線の太さを設定
// 縦線を描画
for (let x = 1; x < 16; x++) {
context.beginPath();
context.moveTo(x, 0);
context.lineTo(x, canvas.height / scale);
context.stroke();
}
// 横線を描画
for (let y = 1; y < canvas.height / scale; y++) {
context.beginPath();
context.moveTo(0, y);
context.lineTo(16, y);
context.stroke();
}
context.lineWidth = originalLineWidth; // 元の lineWidth に戻す
};
const draw = () => {
context.fillStyle = "#000";
context.fillRect(0, 0, canvas.width, canvas.height);
drawGrid(); // グリッド線を描画
drawMatrix(arena, { x: 0, y: 0 });
drawMatrix(player.matrix, player.pos);
drawNextPiece();
};
ChatGPT
申し訳ありません。問題を見つけました。context.lineWidth
は問題ではなかったようです。drawMatrix
関数を呼び出す際に、nextContext
を引数として渡す必要があります。drawNextPiece
関数を以下のように修正してください。
const drawNextPiece = () => {
nextContext.fillStyle = "#000";
nextContext.fillRect(0, 0, nextCanvas.width, nextCanvas.height);
drawMatrix(
nextPiece,
{
x: (nextCanvas.width / scale - nextPiece[0].length) / 2,
y: (nextCanvas.height / scale - nextPiece.length) / 2,
},
nextContext
);
};
これで、ブロックが表示されるはずです。再度お試しください。
Takaaki
だめですね…
チェックポイントを教えてください!
ChatGPT
大変失礼しました。drawMatrix
関数の定義を修正する必要があります。第3引数として ctx
を追加してください。そして、ctx.fillStyle
と ctx.fillRect
を使用して、drawMatrix
内での描画を適切なコンテキストで行います。
以下のように修正してください。
const drawMatrix = (matrix, offset, ctx = context) => {
matrix.forEach((row, y) => {
row.forEach((value, x) => {
if (value) {
ctx.fillStyle = "red";
ctx.fillRect(x + offset.x, y + offset.y, 1, 1);
}
});
});
};
これで、落ちてくるブロックが表示されるはずです。もう一度お試しください。
Takaaki
お、表示されました! 何が原因だったですか?
ChatGPT
素晴らしいです!表示されて良かったです。
原因は、drawMatrix
関数にデフォルトのcontext
が設定されていなかったことです。このため、drawNextPiece()
関数でdrawMatrix()
を呼び出す際に、nextContext
を渡していましたが、draw()
関数内でdrawMatrix()
を呼び出す際に、コンテキストが指定されていませんでした。
修正により、drawMatrix
関数にctx
という第3引数が追加され、デフォルト値としてcontext
が設定されました。これによって、draw()
関数内でdrawMatrix()
を呼び出す際にコンテキストが適切に設定されるようになり、落ちてくるブロックが正常に表示されるようになりました。
Takaaki
なるほど! デフォルトの引数でグローバル変数を渡すのは怖いので、draw関数内でdrawMatrixを呼び出す時に明示的にcontextを渡してあげた方が安全かなと思ったのですが、難しそうですか?
ChatGPT
関数内で引数としてコンテキストを渡すことは可能ですが、この場合、呼び出し元で引数としてコンテキストを渡さなければなりません。これは、関数を使う側にも知識を要求するため、あまり良い方法ではありません。また、コンテキストを渡すことが忘れられた場合や、誤ったコンテキストが渡された場合、エラーが発生する可能性があります。
代わりに、draw関数内で、グローバルなコンテキスト変数を参照する方法もあります。これにより、呼び出し元がコンテキストを指定する必要がなくなります。
以下は、この方法を使用したdrawMatrix関数の例です。
const drawMatrix = (matrix, offset) => {
matrix.forEach((row, y) => {
row.forEach((value, x) => {
if (value) {
context.fillStyle = "red";
context.fillRect(x + offset.x, y + offset.y, 1, 1);
}
});
});
};
このように、グローバル変数であるcontext
を使用することで、コンテキストを関数呼び出し元で渡す必要がなくなります。
まとめ
こやつにコンテンツ生成してもらう取り組みいつまで続くのか…Plus契約したから頑張りたい…