Amazon Q Developer for CLIを使って、自然言語オンリーでモグラ叩きゲームを作ったよ、という話
今日はJAWS-UG佐賀さんのイベント、 Amazon Q Developer for CLI De GameをつくらNight に参加して、気になっていた Amazon Q Developer for CLI でゲーム作成してきました。
ひたすら自然言語のみ、という縛りで、40分余りでこれが完成してしまいました。恐るべし。
Amazon Q Developer for CLIって何?
AWSが提供している、AIによるコーディングアシスタントです。 インストールすれば、コマンドラインから利用が可能です。 Claude4.0sonnetが使えて、無料(2025.6.11現在)でエージェントモードでバリバリコーディングしてくれます。 太っ腹すぎる!!!
今回やったこと
1.AWS Builder IDを取得(設定に必要)
2.Amazon Q Developer CLIのインストール
3.ターミナルからAmazon Q Developer CLIを呼び出す。
q chat
で呼び出すだけです。 あとは素材だけ与えて、 「モグラ叩き作って」 「点数システム変えて」 みたいな感じで仕様を詰めていきました。
概要もまとめてもらいました。
葉隠モグラ叩き塾(Hagakure Mogura)
製品概要
「葉隠モグラ叩き塾」は、日本の江戸時代をテーマにした伝統的なモグラ叩きゲームのウェブアプリケーションです。プレイヤーは制限時間内に画面から出現する様々なキャラクターを叩いて 高得点を目指します。
主な特徴
• 和風テーマ: 侍、忍者、町娘などの江戸時代風キャラクターが登場 • コンボシステム: プラスポイントを連続で獲得するとコンボが増加し、得点倍率がアップ • レベル進行: プレイ時間の経過とともにレベルが上がり、キャラクターの出現速度が上昇 • 多様なキャラクター: 異なる得点価値を持つ4種類のキャラクター • レスポンシブデザイン: スマートフォンからデスクトップまで様々なデバイスに対応
キャラクター
- 侍: +50点(レア出現)
- 忍者(男): +10点(高頻度で出現)
- 忍者(女): -20点(中頻度で出現)
- 町娘: -100点(レア出現、大きなペナルティ)
ゲームシステム
• 制限時間: 60秒 • コンボ倍率: 10コンボごとに倍率が上昇(×2、×3、×4...) • レベルアップ: 10秒ごとにレベルが上昇し、キャラクターの出現速度が増加 • ペナルティ: マイナスポイントのキャラクターを叩くとコンボがリセット
技術仕様
• 開発言語: HTML, CSS, JavaScript • 外部依存性: なし(純粋なフロントエンド実装) • ファイルサイズ: 約22KB(HTML)、画像ファイル複数 • デプロイ要件: 標準的なウェブサーバー
ビジュアル要素
• 和風の背景デザイン • 城と木のフッターイラスト • 伝統的な日本風のキャラクターイラスト • 得点エフェクトとコンボ表示アニメーション
今後の展開予定
• 追加キャラクターの実装 • 難易度選択機能 • ハイスコアの保存と共有機能 • サウンドエフェクトと背景音楽の追加
このゲームは、伝統的な「モグラ叩き」のゲームプレイに日本の歴史的要素を組み合わせた、シンプルながらも中毒性のある娯楽体験を提供します。
MCPもいけるみたい。とりあえずエージェントやってみたい人は、無料で行けるのはこれ一択なんじゃないでしょうか。
以下作らせたソースコード全文
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>葉隠モグラ - Hagakure Mogura</title>
<style>
body {
margin: 0;
padding: 0;
font-family: 'Arial', sans-serif;
background: linear-gradient(180deg, #e8f4f8 0%, #d4e8d4 50%, #c8dcc8 100%);
background-size: cover;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
position: relative;
}
body::before {
content: '';
position: fixed;
bottom: 0;
right: 20px;
width: 200px;
height: 150px;
background: url('images/footer_castle.png') no-repeat center bottom;
background-size: contain;
opacity: 0.2;
pointer-events: none;
z-index: -1;
}
.game-title {
font-size: 2.2em;
color: #8B4513;
text-shadow:
2px 2px 4px rgba(0,0,0,0.8),
-1px -1px 2px rgba(255,255,255,0.9);
margin: 20px 0;
font-weight: bold;
background: rgba(255,255,255,0.9);
padding: 30px 80px;
border-radius: 20px;
border: 3px solid #8B4513;
position: relative;
}
.game-title::before {
content: '';
position: absolute;
left: 15px;
top: 50%;
transform: translateY(-50%);
width: 50px;
height: 50px;
background: url('images/logo.png') no-repeat center;
background-size: contain;
}
.game-title::after {
content: '';
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
width: 50px;
height: 50px;
background: url('images/logo.png') no-repeat center;
background-size: contain;
}
.game-info {
display: flex;
gap: 20px;
margin-bottom: 20px;
font-size: 1.2em;
color: #654321;
flex-wrap: wrap;
justify-content: center;
}
.score, .time, .level, .combo {
background: rgba(255,255,255,0.9);
padding: 10px 20px;
border-radius: 10px;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
border: 2px solid #8B4513;
}
.combo {
background: linear-gradient(45deg, #FFD700, #FFA500);
color: #8B4513;
font-weight: bold;
}
.combo.high-combo {
background: linear-gradient(45deg, #FF6B6B, #FF1493);
color: white;
animation: comboGlow 0.5s ease-in-out infinite alternate;
}
@keyframes comboGlow {
0% { box-shadow: 0 2px 5px rgba(0,0,0,0.2); }
100% { box-shadow: 0 4px 15px rgba(255,107,107,0.6); }
}
.character-guide {
display: flex;
gap: 15px;
margin-bottom: 20px;
flex-wrap: wrap;
justify-content: center;
}
.character-info {
background: rgba(255,255,255,0.9);
padding: 10px;
border-radius: 10px;
text-align: center;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
min-width: 100px;
border: 2px solid #8B4513;
}
.character-info img {
width: 40px;
height: 40px;
object-fit: contain;
display: block;
margin: 0 auto 5px;
}
.character-info .points {
font-weight: bold;
font-size: 1.1em;
}
.positive-points {
color: #2E8B57;
}
.negative-points {
color: #DC143C;
}
.character-info .name {
font-size: 0.9em;
color: #666;
}
.game-board {
display: grid;
grid-template-columns: repeat(3, 150px);
grid-template-rows: repeat(3, 150px);
gap: 20px;
background: rgba(139, 69, 19, 0.1);
padding: 30px;
border-radius: 20px;
box-shadow:
0 5px 15px rgba(0,0,0,0.3),
inset 0 2px 10px rgba(255,255,255,0.2);
border: 4px solid #8B4513;
}
.hole {
width: 150px;
height: 150px;
background: #8B4513;
border-radius: 50%;
position: relative;
cursor: pointer;
box-shadow: inset 0 5px 10px rgba(0,0,0,0.5);
overflow: hidden;
transition: transform 0.1s;
/* タッチデバイス対応 */
-webkit-tap-highlight-color: transparent;
user-select: none;
}
.hole:hover {
transform: scale(1.05);
}
.hole:active {
transform: scale(0.95);
}
.character {
position: absolute;
width: 120px;
height: 120px;
bottom: -120px;
left: 50%;
transform: translateX(-50%);
transition: bottom 0.3s ease-in-out;
cursor: pointer;
}
.character.show {
bottom: 10px;
}
.character img {
width: 100%;
height: 100%;
object-fit: contain;
}
.start-button, .restart-button {
background: linear-gradient(45deg, #8B4513, #D2691E);
color: white;
border: 3px solid #654321;
padding: 15px 30px;
font-size: 1.2em;
border-radius: 25px;
cursor: pointer;
margin: 20px;
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
transition: transform 0.2s;
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
font-weight: bold;
/* タッチデバイス対応 */
-webkit-tap-highlight-color: transparent;
user-select: none;
}
.start-button:hover, .restart-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(0,0,0,0.4);
}
.start-button:active, .restart-button:active {
transform: translateY(0px);
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.game-over {
display: none;
text-align: center;
background: rgba(255,255,255,0.95);
padding: 30px;
border-radius: 15px;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
border: 4px solid #8B4513;
}
.hit-effect {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #FF6B6B;
font-size: 2em;
font-weight: bold;
pointer-events: none;
animation: hitAnimation 0.5s ease-out;
}
@keyframes hitAnimation {
0% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
100% { opacity: 0; transform: translate(-50%, -50%) scale(1.5); }
}
/* レスポンシブデザイン - スマホ対応 */
@media (max-width: 768px) {
.game-title {
font-size: 1.5em;
padding: 20px 60px;
margin: 10px;
}
.game-title::before,
.game-title::after {
width: 35px;
height: 35px;
left: 10px;
right: 10px;
}
.game-info {
gap: 10px;
font-size: 1em;
margin-bottom: 15px;
}
.score, .time, .level, .combo {
padding: 8px 12px;
font-size: 0.9em;
}
.character-guide {
gap: 8px;
margin-bottom: 15px;
}
.character-info {
padding: 8px;
min-width: 80px;
}
.character-info img {
width: 30px;
height: 30px;
}
.character-info .name {
font-size: 0.8em;
}
.character-info .points {
font-size: 1em;
}
.game-board {
grid-template-columns: repeat(3, 100px);
grid-template-rows: repeat(3, 100px);
gap: 15px;
padding: 20px;
margin: 0 10px;
}
.hole {
width: 100px;
height: 100px;
}
.character {
width: 80px;
height: 80px;
bottom: -80px;
}
.character.show {
bottom: 5px;
}
.start-button, .restart-button {
padding: 12px 24px;
font-size: 1em;
margin: 15px 10px;
}
.game-over {
margin: 0 20px;
padding: 20px;
}
body::before {
width: 120px;
height: 90px;
right: 10px;
}
}
@media (max-width: 480px) {
.game-title {
font-size: 1.2em;
padding: 15px 50px;
}
.game-title::before,
.game-title::after {
width: 25px;
height: 25px;
}
.game-info {
flex-direction: column;
align-items: center;
gap: 8px;
}
.character-guide {
flex-direction: column;
align-items: center;
gap: 5px;
}
.character-info {
display: flex;
align-items: center;
gap: 10px;
min-width: 200px;
text-align: left;
}
.character-info img {
width: 25px;
height: 25px;
margin: 0;
}
.game-board {
grid-template-columns: repeat(3, 80px);
grid-template-rows: repeat(3, 80px);
gap: 10px;
padding: 15px;
}
.hole {
width: 80px;
height: 80px;
}
.character {
width: 65px;
height: 65px;
bottom: -65px;
}
.character.show {
bottom: 3px;
}
body::before {
display: none;
}
}
</style>
</head>
<body>
<h1 class="game-title">HAGAKURE モグラ叩き塾</h1>
<div style="text-align: center; margin-bottom: 15px; color: #654321; font-size: 0.9em;">
<strong>コンボシステム:</strong> プラスポイントを連続で獲得すると倍率アップ!<br>
10コンボ毎に得点が×2, ×3, ×4...と倍増。マイナスポイントでコンボリセット。
</div>
<div class="character-guide">
<div class="character-info">
<img src="images/voice-samurai.png" alt="侍">
<div class="name">侍</div>
<div class="points positive-points">+50点</div>
</div>
<div class="character-info">
<img src="images/voice-ninja1.png" alt="忍者(男)">
<div class="name">忍者(男)</div>
<div class="points positive-points">+10点</div>
</div>
<div class="character-info">
<img src="images/voice-ninja2.png" alt="忍者(女)">
<div class="name">忍者(女)</div>
<div class="points negative-points">-20点</div>
</div>
<div class="character-info">
<img src="images/machi-musume.png" alt="町娘">
<div class="name">町娘</div>
<div class="points negative-points">-100点</div>
</div>
</div>
<div class="game-info">
<div class="score">スコア: <span id="score">0</span></div>
<div class="time">時間: <span id="time">60</span>秒</div>
<div class="level">レベル: <span id="level">1</span></div>
<div class="combo" id="comboDisplay">コンボ: <span id="combo">0</span> (×<span id="multiplier">1</span>)</div>
</div>
<div class="game-board" id="gameBoard">
<!-- 穴は JavaScript で生成 -->
</div>
<button class="start-button" id="startButton" onclick="startGame()">ゲーム開始</button>
<div class="game-over" id="gameOver">
<h2>ゲーム終了!</h2>
<p>最終スコア: <span id="finalScore">0</span></p>
<button class="restart-button" onclick="restartGame()">もう一度プレイ</button>
</div>
<script>
// ゲーム変数
let score = 0;
let timeLeft = 60;
let level = 1;
let gameActive = false;
let gameTimer;
let spawnTimer;
let currentSpeed = 800; // キャラクター出現間隔(ミリ秒)
let combo = 0; // コンボカウント
let multiplier = 1; // 得点倍率
// キャラクターごとのポイントと出現頻度
const characterData = {
'voice-samurai.png': { points: 50, weight: 1 }, // 侍: 50点, 時々出現
'voice-ninja1.png': { points: 10, weight: 5 }, // 忍者(男): 10点, 頻出
'voice-ninja2.png': { points: -20, weight: 3 }, // 忍者(女): -20点, まあまあ出現
'machi-musume.png': { points: -100, weight: 1 } // 町娘: -100点, 時々出現
};
// 重み付きランダム選択用の配列を作成
function createWeightedCharacterArray() {
const weightedArray = [];
Object.keys(characterData).forEach(character => {
const weight = characterData[character].weight;
for (let i = 0; i < weight; i++) {
weightedArray.push(character);
}
});
return weightedArray;
}
const weightedCharacters = createWeightedCharacterArray();
// ゲームボード初期化
function initializeBoard() {
const gameBoard = document.getElementById('gameBoard');
gameBoard.innerHTML = '';
for (let i = 0; i < 9; i++) {
const hole = document.createElement('div');
hole.className = 'hole';
hole.id = `hole-${i}`;
const character = document.createElement('div');
character.className = 'character';
character.id = `character-${i}`;
character.onclick = () => hitCharacter(i);
const img = document.createElement('img');
character.appendChild(img);
hole.appendChild(character);
gameBoard.appendChild(hole);
}
}
// ゲーム開始
function startGame() {
gameActive = true;
score = 0;
timeLeft = 60;
level = 1;
currentSpeed = 800;
combo = 0;
multiplier = 1;
updateDisplay();
document.getElementById('startButton').style.display = 'none';
document.getElementById('gameOver').style.display = 'none';
// タイマー開始
gameTimer = setInterval(() => {
timeLeft--;
updateDisplay();
// レベルアップ処理(より頻繁に速度アップ)
if (timeLeft % 10 === 0 && timeLeft > 0) {
level++;
currentSpeed = Math.max(200, currentSpeed - 100);
updateDisplay();
}
if (timeLeft <= 0) {
endGame();
}
}, 1000);
// キャラクター出現開始
spawnCharacters();
}
// キャラクター出現
function spawnCharacters() {
if (!gameActive) return;
// 複数のキャラクターを同時に出現させる可能性
const spawnCount = Math.random() < 0.4 ? 2 : 1; // 40%の確率で2体同時出現
for (let i = 0; i < spawnCount; i++) {
const randomHole = Math.floor(Math.random() * 9);
// 重み付きランダム選択
const randomCharacter = 'images/' + weightedCharacters[Math.floor(Math.random() * weightedCharacters.length)];
showCharacter(randomHole, randomCharacter);
}
// ランダム要素を減らしてより頻繁に出現
spawnTimer = setTimeout(spawnCharacters, currentSpeed + Math.random() * 300);
}
// キャラクター表示
function showCharacter(holeIndex, characterImage) {
const character = document.getElementById(`character-${holeIndex}`);
const img = character.querySelector('img');
// 既に表示されている場合はスキップ
if (character.classList.contains('show')) return;
img.src = characterImage;
character.classList.add('show');
character.dataset.character = characterImage.split('/').pop();
// 表示時間を短縮してよりスピーディに
setTimeout(() => {
character.classList.remove('show');
}, 1000 + Math.random() * 800);
}
// キャラクターを叩く
function hitCharacter(holeIndex) {
const character = document.getElementById(`character-${holeIndex}`);
if (!gameActive || !character.classList.contains('show')) return;
const characterType = character.dataset.character;
const basePoints = characterData[characterType]?.points || 0;
if (basePoints > 0) {
// プラスポイントの場合:コンボ継続、倍率適用
combo++;
multiplier = Math.floor(combo / 10) + 1;
const finalPoints = basePoints * multiplier;
score += finalPoints;
// コンボエフェクト
showHitEffect(holeIndex, `+${finalPoints} (×${multiplier})`, true);
updateComboDisplay();
} else {
// マイナスポイントの場合:コンボリセット、倍率リセット
combo = 0;
multiplier = 1;
score += basePoints; // マイナスポイントはそのまま
// ペナルティエフェクト
showHitEffect(holeIndex, `${basePoints} COMBO RESET!`, false);
updateComboDisplay();
}
character.classList.remove('show');
updateDisplay();
}
// コンボ表示更新
function updateComboDisplay() {
const comboElement = document.getElementById('comboDisplay');
if (combo >= 20) {
comboElement.classList.add('high-combo');
} else {
comboElement.classList.remove('high-combo');
}
}
// ヒットエフェクト表示
function showHitEffect(holeIndex, text, isPositive) {
const hole = document.getElementById(`hole-${holeIndex}`);
const effect = document.createElement('div');
effect.className = 'hit-effect';
effect.textContent = text;
effect.style.color = isPositive ? '#2E8B57' : '#DC143C';
hole.appendChild(effect);
setTimeout(() => {
hole.removeChild(effect);
}, 500);
}
// 表示更新
function updateDisplay() {
document.getElementById('score').textContent = score;
document.getElementById('time').textContent = timeLeft;
document.getElementById('level').textContent = level;
document.getElementById('combo').textContent = combo;
document.getElementById('multiplier').textContent = multiplier;
}
// ゲーム終了
function endGame() {
gameActive = false;
clearInterval(gameTimer);
clearTimeout(spawnTimer);
// 全キャラクターを隠す
for (let i = 0; i < 9; i++) {
document.getElementById(`character-${i}`).classList.remove('show');
}
document.getElementById('finalScore').textContent = score;
document.getElementById('gameOver').style.display = 'block';
}
// ゲーム再開
function restartGame() {
document.getElementById('startButton').style.display = 'inline-block';
document.getElementById('gameOver').style.display = 'none';
}
// 初期化
window.onload = function() {
initializeBoard();
};
</script>
</body>
</html>