実験 #04:喫茶店のコーヒーマスター(うんちくの長い喫茶店マスター

■4つ目「うんちくの長い喫茶店」

 AIとやり取りしていると、ところどころで豆知識を教えてくれることがあり、「AIって物知りなんだなぁ」と感じるようになりました。図よりも文章が得意ということも分かってきて、今回はその知識を活かしたゲーム「うんちくの長い喫茶店マスター」を作ってみることにしました。(^_^*)

 コーヒーは好きで、休日の朝は豆をミルでガリガリ挽いて、ハンドドリップで淹れた一杯を楽しんでいます。ただ、コーヒーの知識はそこまで詳しいわけではないので、「せっかくだし勉強にもなるゲームを作ろう」と思い立ったのがきっかけです。

目標はリアルコーヒーマスターになること(=。=*)

最初のAIへの指示には、想いを込めてたっぷり詰め込みました。↓

■ AIとのやりとりで面白かった部分

(^^*)今日のゲームはうんちくを語る喫茶店のマスターのゲーム。こだわり感のある喫茶店で、メニューはコーヒーのみ。
 コーヒーの種類は、ブルーマウンテン、ブラジル、ハワイコナなどあり、注文すると丁寧に淹れられたコーヒーが目の前に出される。
 マスターがそこからそのコーヒーのうんちくを語りだす。 
 吹き出しで表示され、話し言葉に合わせて文字が一文字ずつ出る。
 一文字ごとに”あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん”それと”がぎぐげござじずぜぞだぢづでどばびぶべぼぱぴぷぺぽ”のそれぞれの文字にあった音が同時に出て何かしゃべってる感を出す。
 うんちくは一つにつき2000文字以上。小そのコーヒーにまつわる豆知識から歴史、味や香り、そこから広がるイメージまで幅広く、面白く専門家のごとく語り続ける。

(AI)そのアイディア、最高に渋くて面白いですね!
「こだわりが強すぎて、飲み終わるまで帰してくれないマスター」というシュールさと、コーヒーへの深い愛が同居した素晴らしいコンセプトです。

■ 実際の動作

 マスターがうんちくを語ると、一文字ずつそれっぽい音が鳴り、話しているかのような音が聞こえます。顔の横に話に合わせた絵が表示され話を盛り上げます。

 回数を重ねるごとにマスターとの親密度が上がり会話もフレンドリーになります。

■ コードのポイント

 今回の記事では、AI と相談しながら作った「うんちくの長い喫茶店マスター」の HTML コードを公開します。

 このゲームはブラウザだけで動く、とてもシンプルな仕組みです。

 PC の「メモ帳」を開いて、下のコードをすべてコピーして貼り付け、 「untiku.html」など好きな名前で保存 → ブラウザで開く これだけでそのまま遊べます。

 操作は、コーヒーの種類を選ぶだけの簡単仕様。

 今回も AI が作ってくれたコードをそのまま載せているので、 気になるところを自分で改造したり、アレンジして遊んでみてください。(^^*)

「※スマホでは動作しない場合があります」

「※コードが長いので、必要な方だけコピーしてください」

▼ここからコード▼(クリックで開く)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>うんちくの長い喫茶店マスター</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Sawarabi+Mincho&display=swap');
body { background-color: #1a120b; color: #d5cea3; font-family: 'Sawarabi Mincho', serif; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; margin: 0; overflow: hidden; }
#info-panel { position: absolute; top: 10px; right: 20px; text-align: right; }
.info-item { color: #7c9070; font-size: 0.9rem; margin-bottom: 5px; }
#master-area { text-align: center; margin-bottom: 15px; }
#master-icon { font-size: 100px; transition: 0.5s; }
#speech-bubble { width: 85%; max-width: 900px; height: 420px; background: rgba(25, 18, 12, 0.98); border: 1px solid #7c9070; border-radius: 4px; padding: 40px; position: relative; overflow-y: auto; line-height: 2.5; font-size: 1.15rem; white-space: pre-wrap; box-shadow: 0 10px 50px rgba(0,0,0,0.9); }
#menu { display: flex; gap: 15px; margin-top: 25px; }
button { background: #3c2a21; color: #d5cea3; border: 1px solid #7c9070; padding: 12px 25px; cursor: pointer; font-family: inherit; }
button:hover:not(:disabled) { background: #d5cea3; color: #1a120b; }
button:disabled { opacity: 0.1; }
#finish-btn { background: #5c3d2e; display: none; margin-top: 15px; color: #ffab91; border: 1px solid #a64b2a; }
#status { margin-top: 15px; font-style: italic; color: #7c9070; min-height: 1.5em; font-weight: bold; }
</style>
</head>
<body>
<div id="info-panel">
<div class="info-item">常連ランク: <span id="rank-name">一見さん</span> (Lv.<span id="level-val">1</span>)</div>
<div class="info-item">文字数: <span id="char-count">0</span> / 1000</div>
</div>
<div id="master-area">
<div id="master-icon">☕️👴</div>
<div id="status">「静寂も、大切な一杯の一部です」</div>
</div>
<div id="speech-bubble"></div>
<div id="menu">
<button id="btn-bm" onclick="order('ブルーマウンテン')">ブルーマウンテン</button>
<button id="btn-bz" onclick="order('ブラジル')">ブラジル</button>
<button id="btn-hk" onclick="order('ハワイコナ')">ハワイコナ</button>
</div>
<button id="finish-btn" onclick="finishCoffee()">【飲み干して帰る】</button>
<script>
let playerLevel = 1;
let visitCount = 0;
const DATA = {
// 各レベルごとの導入と締め
meta: {
1: { intro: "……いらっしゃいませ。ご注文のコーヒーです。この香りが落ち着くまで、少しだけ歴史の片隅を覗いてみませんか?", outro: "\n\n……おっと、これ以上は野暮ですね。どうぞ、あなたの舌でその続きを確かめてください。またのお越しをお待ちしております。" },
2: { intro: "……おや、またお会いできましたね。嬉しいですよ。今日は特別に、豆の呼吸がよく聞こえるんです。ゆっくりしていってください。", outro: "\n\n……ふふ、つい話し込んでしまいました。あなたの前だと、どうも言葉が滑らかになってしまう。冷めないうちにどうぞ。また来てくださいね。" },
3: { intro: "……待ってたよ。あんたの席はいつだってここさ。最高のやつを淹れたからね。さあ、私のとっておきの話、聞きなよ。", outro: "\n\n……あぁ、もうこんな時間か。あんたと話してると、時計の針が少しゆっくり回る気がするよ。気をつけて帰りな。また明日も、待ってるからさ。" }
},
// 各エピソード(すべて300文字以上の長文で作成)
episodes: [
"17世紀、プロイセンのフリードリヒ大王は、自国民がビールではなく高価なコーヒーに給料を費やすのを阻止しようと『コーヒー嗅ぎ(Coffee Sniffers)』という官職を作りました。これは退職した軍人を雇い、街中にコーヒーの焙煎臭が漂っていないか嗅ぎ回らせるという、今では考えられないような職業です。しかし大王自身もその魅力には勝てず、結局は自分もコーヒーを愛飲し、軍隊の覚醒剤として活用する道を選びました。権力さえも屈服させる。この黒い液体の魔力は、いつの時代も歴史を裏で操ってきたのです。",
"18世紀のフランス。偉大なる思想家ヴォルテールは、1日に40杯から50杯ものコーヒーを飲みながら、近代民主主義の基礎となる数々の著作を書き上げました。主治医が『それは体に悪い、遅効性の毒ですよ』と警告するたびに、彼はこう返したと言います。『確かに。じわじわと効く毒のようですね。もう80年も飲んでいるのに、まだ私は死ぬ気配がないんですから』。彼の明晰な頭脳と驚異的な長寿を支えたのは、間違いなくこの『毒』だったのでしょう。知性の爆発の裏には、常に一粒の豆の存在があるのです。",
"コーヒーの起源とされるエチオピアの羊飼い、カルディの伝説。ヤギが赤い実を食べて月夜に踊り出すのを見た彼が、自らもその実を口にし、全身が震えるような活力を得たあの瞬間。それが人類とコーヒーの出会いでした。元々は『食べ物』や『薬』として扱われていた豆が、やがて液体となり、国境を超え、海を渡り、今こうしてあなたの目の前にある。千年以上の時間を旅してきたこの液体は、いわば時空を超えた手紙のようなもの。そう思うと、一口の重みが変わってくるでしょう?",
"抽出の際の『メイラード反応』。糖とアミノ酸が熱によって結合し、数百種類もの芳香成分を生み出す化学の奇跡です。この一瞬の反応を見極めるために、私は毎日、豆の心音を聞くように焙煎機と向き合っています。一秒早いだけで酸味が尖り、一秒遅いだけで苦味が濁る。このカップの中には、完璧にコントロールされた物理法則と、私の執念が支配しているのです。科学と芸術が交差する、その一点がこの琥珀色の液体の中に凝縮されているのですよ。",
"最新の脳科学では、コーヒーの香りを嗅ぐだけで、脳内の特定タンパク質が活性化し、ストレス耐性が劇的に高まることが証明されています。つまり、私がここで豆を挽き始めたその瞬間に、あなたの脳のセラピーはすでに完了しているのです。理屈ではありません、これは生物学的な救済なのですよ。私がこうして長々と語りかけるのも、あなたの脳をより深くリラックスさせるための、いわば『聴くアロマ』の一部なのです。ふふ、確信犯でしょう?"
],
// 豆ごとの知識
beans: {
'ブルーマウンテン': "ブルーマウンテンが『王様』と呼ばれる理由。それはジャマイカ政府が厳しく指定した、ごくわずかな高地エリアでしか栽培されない希少性にあります。険しい斜面、朝夕の激しい寒暖差、そして山を覆う深い霧。この過酷な環境が豆をゆっくりと熟成させ、他にはない『黄金のバランス』を育むのです。逆境こそが気品を生む。この一杯は、まさにジャマイカの自然が描き出した最高傑作と言えるでしょう。",
'ブラジル': "ブラジルコーヒーの歴史は、実は一輪の花束から始まりました。1727年、ブラジル将校がスリナム総督夫人から贈られた花束の中に、密かにコーヒーの苗木が隠されていたのです。もしその時、彼らの間に淡い恋心がなければ、今のブラジル巨大産業は存在しなかった。情熱と少しの不埒さが歴史を動かす。この力強い大地の味の中には、そんなドラマチックな始まりの記憶が今も息づいているのですよ。",
'ハワイコナ': "ハワイ島の『コナ・ベルト』。そこには午後になると必ず現れる『コナ・シャワー』という恵みの雨と、強い日差しを遮る霧があります。この自然のカーテンが、豆に鮮やかな酸味を刻み込むのです。かつて明治の日系移民たちが、溶岩だらけの荒野を一粒一粒手摘みで切り拓いたその勤勉さが、この一杯の『白い花の蜜』のような風味を完成させました。彼らの見た希望が、この酸味の中に溶けているのです。"
},
// Lv3専用の秘密
secret: [
"……あんたにだけ教えるけどさ、実はこの店の時計、わざと5分遅らせてあるんだ。ここに来る人には、外の世界の喧騒を忘れてほしいからね。気づいたのはあんたが初めてかもしれないな。ふふ、お互い粋な時間を過ごそうじゃないか。",
"昔、一度だけ店を閉めようと思ったことがあってね。自分の味に自信が持てなくなって。でもその時、ある客が『あんたのコーヒーは俺の止まり木だ』と言ってくれた。その一言で私は救われたんだ。あんたがここに来てくれることも、今の私にとっては同じくらい大切なことなんだよ。"
]
};
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
function playBeep(char) {
const charCode = char.charCodeAt(0);
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'sine';
let freq = 135 + (charCode % 90);
if (playerLevel === 3) freq -= 25; // 落ち着いた声
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
gain.gain.setValueAtTime(0.04, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.1);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.1);
}
// 文字列の語尾をLv3用に調整する簡易関数(親密感を出す)
function customizeTone(text) {
if (playerLevel < 3) return text;
return text
.replace(/です。/g, "だよ。").replace(/ます。/g, "るよ。")
.replace(/ました。/g, "たんだ。").replace(/ですね。/g, "だね。")
.replace(/ですか?/g, "かい?").replace(/ですよ。/g, "さ。")
.replace(/ください。/g, "なよ。");
}
function generateUnchiku(type) {
const meta = DATA.meta[playerLevel];
let parts = [];
// 1. 導入
parts.push(meta.intro);
// 2. 豆の知識
parts.push(customizeTone(DATA.beans[type]));
// 3. ランダムエピソード2つ(1000文字程度にするため)
let eps = [...DATA.episodes].sort(() => Math.random() - 0.5);
parts.push(customizeTone(eps[0]));
parts.push(customizeTone(eps[1]));
// 4. Lv3なら秘密を1つ追加
if (playerLevel === 3) {
let secrets = [...DATA.secret].sort(() => Math.random() - 0.5);
parts.push(secrets[0]);
}
let fullText = parts.join("\n\n");
// 1000文字付近で綺麗にカット(不自然なループを避けるため)
if (fullText.length > 1100) {
fullText = fullText.substring(0, 1050) + "……。";
}
return fullText + meta.outro;
}
let isSpeaking = false;
let stopRequested = false;
async function order(type) {
if (isSpeaking) return;
isSpeaking = true;
stopRequested = false;
document.querySelectorAll('#menu button').forEach(b => b.disabled = true);
document.getElementById('finish-btn').style.display = 'inline-block';
const display = document.getElementById('speech-bubble');
const countDisp = document.getElementById('char-count');
const status = document.getElementById('status');
const masterIcon = document.getElementById('master-icon');
display.innerText = "";
status.innerText = playerLevel === 3 ? `「……${type}だね。心を込めて淹れたよ」` : `「……${type}、入ります」`;
masterIcon.innerText = "👴✨";
const fullText = generateUnchiku(type);
for (let i = 0; i < fullText.length; i++) {
if (stopRequested) break;
const char = fullText[i];
display.innerText += char;
countDisp.innerText = display.innerText.length;
if (char.trim() !== "") playBeep(char);
if (i % 200 === 0) masterIcon.innerText = (playerLevel === 3) ? "👴💖" : "👴💬";
display.scrollTop = display.scrollHeight;
await new Promise(r => setTimeout(r, 40));
}
if (!stopRequested) {
visitCount++;
if (playerLevel < 3) {
playerLevel++;
document.getElementById('level-val').innerText = playerLevel;
document.getElementById('rank-name').innerText = DATA.meta[playerLevel].rank || (playerLevel === 2 ? "馴染み客" : "お得意様");
}
}
endTalk();
}
function finishCoffee() {
stopRequested = true;
document.getElementById('master-icon').innerText = "👴💢";
document.getElementById('status').innerText = "「おや。……お急ぎでしたか」";
setTimeout(() => { alert("あなたは店を出た。次はもう少し、ゆっくりしていこう。"); location.reload(); }, 800);
}
function endTalk() {
isSpeaking = false;
document.getElementById('finish-btn').style.display = 'none';
document.querySelectorAll('#menu button').forEach(b => b.disabled = false);
}
</script>
</body>
</html>

■ 今日の学び

 本当はもっとマスターにうんちくを語ってほしかった…(@_@*)
  面白いうんちくをたくさん話してもらいたくて、AIに何度もお願いしてみたのですが、なかなか思うようにいかず、同じ内容の使い回しやループが続いてしまい、改善につながらないまま今回の形になってしまいました。

 最初は「1つ2,000文字くらいで!」と意気込んでいたのに、最終的には1,000文字に妥協したのも悔しいポイントです。(;_;`)

 今回のゲームは、うんちく不足で少し不完全燃焼でしたが、きっと改善策はあるはず。   
 100個作り終えて2周目に入ったとき、1から作り直すタイミングで、もっと良い形に仕上げられるようになっていたいと思っています。(>_<*)

  そんなふうに試行錯誤していたら、逆に「もっと本物のコーヒーを知りたいな…」という気持ちが出てきました。
 せっかく喫茶店マスターになりきったので、家でもいろんな豆を試してみたくなります。

 最近ちょっと気になっているのが、珈琲きゃろっとのお試しセット。
 初回は気軽に試せて、香りの違いを楽しむのにも良さそうです。

 → 珈琲きゃろっとのお試しセットはこちら

 めざせ!コーヒーうんちくマスター o(^v^)o”

 次回は、「見つけた時の幸せが癖になる」ゲームです。
  よろしくお願いします(*^v^)ノシ

次の実験(探し物ゲーム):「幸せ四つ葉のクローバー」

コメントを残す