ChatGPT を用いて開発を加速しよう!
言いたいこと
- ChatGPT はガンガン使おう.
- コード書けない(読めない)人間がプロダクトを作れるようにはならない.
- どう使うかが肝.ChatGPT で重要なのは日本語(母国語)の運用能力である.
概要
- お題を与えて ChatGPT にコード書いてもらう.
- コードを見て修正してみる.
- ほしいコードを書いてもらうにはどう向き合えばいいのかを考える.
お題
HTML,CSS,JavaScript でじゃんけんのゲームをつくってみる.
やりたいことを日本語で書いてみる
下記のように ChatGPT に指示を出した.
入力
HTML,CSS,JavaScript を用いてじゃんけんのゲームを実装するにはどのように考えたら良いですか.
仕様は下記とします.
- 画面に「グー」「チョキ」「パー」の3つのボタンが存在する.
- いずれかのボタンをクリックすると,コンピュータがランダムな手を出して対戦する.
- コンピュータの出した手と対戦結果を画面に表示する.
- 対戦の履歴を画面に表示する.履歴には「プレイヤーの出した手」「コンピュータの出した手」「対戦結果」の3つを表示する.
出力(一部省略)
じゃんけんゲームを HTML、CSS、JavaScript を使って実装するには、次の手順に従ってください。
HTMLの作成
じゃんけんゲームのボタン、結果表示領域、対戦履歴表示領域を作成します。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>じゃんけんゲーム</title>
</head>
<body>
<h1>じゃんけんゲーム</h1>
<button id="rock">グー</button>
<button id="scissors">チョキ</button>
<button id="paper">パー</button>
<h2>結果</h2>
<div id="result"></div>
<h2>対戦履歴</h2>
<ul id="history"></ul>
<script src="janken.js"></script>
</body>
</html>
JavaScript の作成
じゃんけんゲームのロジックを実装します。janken.js ファイルを作成し、HTML ファイルにリンクを追加します。
janken.js の例:
const choices = ['グー', 'チョキ', 'パー'];
function getRandomChoice() {
return choices[Math.floor(Math.random() * choices.length)];
}
function getWinner(playerChoice, computerChoice) {
if (playerChoice === computerChoice) {
return '引き分け';
}
if (
(playerChoice === 'グー' && computerChoice === 'チョキ') ||
(playerChoice === 'チョキ' && computerChoice === 'パー') ||
(playerChoice === 'パー' && computerChoice === 'グー')
) {
return 'プレイヤー';
}
return 'コンピュータ';
}
function play(playerChoice) {
const computerChoice = getRandomChoice();
const winner = getWinner(playerChoice, computerChoice);
const result = document.getElementById('result');
result.textContent = `プレイヤー: ${playerChoice}, コンピュータ: ${computerChoice},結果: ${winner}`;
const history = document.getElementById('history');
const historyItem = document.createElement('li');
historyItem.textContent = `プレイヤー: ${playerChoice}, コンピュータ: ${computerChoice}, 結果: ${winner}`;
history.prepend(historyItem);
}
document.getElementById('rock').addEventListener('click', () => play('グー'));
document.getElementById('scissors').addEventListener('click', () => play('チョキ'));
document.getElementById('paper').addEventListener('click', () => play('パー'));
だいたい良さそうだけどいくつか気に入らない点がある.
- 結果は配列で持ちたい.
- 条件分岐が多くて見づらい.
- ロジックと画面表示が明確に分けられていない.
修正してみる
入力
履歴の部分を配列で保存し,対戦するごとに「結果を追加した新しい配列」を作成して表示するようにできますか.
出力
もちろんできます。履歴を配列で保存し、対戦するごとに結果を追加した新しい配列を作成して表示するように、janken.js を次のように変更します。
const choices = ['グー', 'チョキ', 'パー'];
const history = [];
function getRandomChoice() {
return choices[Math.floor(Math.random() * choices.length)];
}
function getWinner(playerChoice, computerChoice) {
if (playerChoice === computerChoice) {
return '引き分け';
}
if (
(playerChoice === 'グー' && computerChoice === 'チョキ') ||
(playerChoice === 'チョキ' && computerChoice === 'パー') ||
(playerChoice === 'パー' && computerChoice === 'グー')
) {
return 'プレイヤー';
}
return 'コンピュータ';
}
function updateHistory(playerChoice, computerChoice, winner) {
const newHistory = { playerChoice, computerChoice, winner };
history.unshift(newHistory);
}
function displayHistory() {
const historyElement = document.getElementById('history');
historyElement.innerHTML = '';
history.forEach(item => {
const listItem = document.createElement('li');
listItem.textContent = `プレイヤー: ${item.playerChoice}, コンピュータ: ${item.computerChoice}, 結果: ${item.winner}`;
historyElement.appendChild(listItem);
});
}
function play(playerChoice) {
const computerChoice = getRandomChoice();
const winner = getWinner(playerChoice, computerChoice);
const result = document.getElementById('result');
result.textContent = `プレイヤー: ${playerChoice}, コンピュータ: ${computerChoice}, 結果: ${winner}`;
updateHistory(playerChoice, computerChoice, winner);
displayHistory();
}
document.getElementById('rock').addEventListener('click', () => play('グー'));
document.getElementById('scissors').addEventListener('click', () => play('チョキ'));
document.getElementById('paper').addEventListener('click', () => play('パー'));
この変更では、履歴をhistory配列に保存し、対戦するごとに updateHistory 関数で結果を追加しています。
そして、displayHistory 関数を使って履歴を表示します。
この関数は、履歴配列をループして、対戦履歴を表示するリストアイテムを作成し、history 要素に追加します。
play 関数が呼び出されるたびに、displayHistory 関数も呼び出され、最新の履歴が表示されます。
違う,そうじゃない(結果配列は破壊的に扱ってほしくない).
このように,処理全体を丸投げすると「動くけど実装の方針はおまかせ」な感じになる.
もう少し自分で考えてみる
大枠は良さそう(HTML 部分など)なので,処理の流れを自分で考えてみる.⭐の部分を chatGPT に作成してもらうこととする.
├── 画面に「グー」「チョキ」「パー」ボタンを表示する
├── ボタンをクリックしたら指定の処理を動かす処理
├── プレイヤーが選択したボタンの値を取得する処理
├── 最小値と最大値を入力するとその間のランダムな数値を出力する処理
├── 数値を入力するとじゃんけんの手を出力する処理
├── プレイヤーの手(数値)と敵の手(数値)を入力すると勝敗を出力する処理
├── ⭐ 「プレイヤーの手,敵の手,勝敗のオブジェクト」と「履歴の配列」を入力すると新しい履歴の配列を出力する処理
└── 結果と履歴を画面に表示する処理
入力
履歴を保存する配列を作成します.
「結果のオブジェクト」と「履歴の配列」を入力すると結果のオブジェクトを履歴の配列に追加した新しい配列を出力する関数を JavaScript で作成してください.
結果のオブジェクトは下記の形式です.
{
myHand: 'グー',
enemyHand: 'チョキ',
winner: 'プレイヤー',
}
履歴の配列は下記の形式です.
[
{
myHand: 'グー',
enemyHand: 'チョキ',
winner: 'プレイヤー',
},
{
myHand: 'グー',
enemyHand: 'チョキ',
winner: 'プレイヤー',
},
{
myHand: 'グー',
enemyHand: 'チョキ',
winner: 'プレイヤー',
},
]
なお,作成する際には,配列の破壊的処理を行わない実装にしてください.
出力
破壊的処理を行わない方法で、結果のオブジェクトを履歴の配列に追加する関数を作成するには、以下のように実装します。
function addToHistory(history, result) {
return [result, ...history];
}
const result = {
myHand: 'グー',
enemyHand: 'チョキ',
winner: 'プレイヤー',
};
const history = [
{
myHand: 'グー',
enemyHand: 'チョキ',
winner: 'プレイヤー',
},
{
myHand: 'グー',
enemyHand: 'チョキ',
winner: 'プレイヤー',
},
{
myHand: 'グー',
enemyHand: 'チョキ',
winner: 'プレイヤー',
},
];
const updatedHistory = addToHistory(history, result);
console.log(updatedHistory);
この実装では、addToHistory関数は現在の履歴配列 (history) と新しい結果のオブジェクト (result) を引数に取り、
新しい結果のオブジェクトを履歴配列の先頭に追加した新しい配列を返します。
スプレッド演算子...を使用して、元の配列を新しい配列に展開し、破壊的処理を回避しています。
ここでのポイントは以下である.
- 実現したいことを理解していないとうまくいかない.
- 処理の流れ(ロジック)は自分で考えたほうが良い(丸投げだとうまく追加修正できない).
- 「入力と出力を明確にした関数」に分割して ChatGPT に投げる.
ちなみに
同様の関数をRustで実装してください.
fn add_to_history(history: Vec<(String, String, String)>, result: (String, String, String)) -> Vec<(String, String, String)> {
let mut new_history = vec![result];
new_history.extend(history);
new_history
}
fn main() {
let result = (
String::from("グー"),
String::from("チョキ"),
String::from("プレイヤー"),
);
let history = vec![
(
String::from("グー"),
String::from("チョキ"),
String::from("プレイヤー"),
),
(
String::from("グー"),
String::from("チョキ"),
String::from("プレイヤー"),
),
(
String::from("グー"),
String::from("チョキ"),
String::from("プレイヤー"),
),
];
let updated_history = add_to_history(history, result);
println!("{:?}", updated_history);
}
ああ~いいっすね~
まとめ
- 丸投げはちょっとキツい.
- 「やりたいこと」「手順」を明確にする.処理を関数単位に分割するとうまくいきやすい.
- 考え方の道筋が立てられないと有効活用できないかも.
- 判断するのは人間.
- (ググり能力の高い人は特に強い)