この記事でわかること
- Markdown AIを使ったオリジナルコンテンツ制作の事例
- 「相関図クイズゲーム」の制作プロセス
- Markdown AIの良かった点&改善希望ポイント
はじめに
突然ですがクイズです。
これはなんの相関図でしょう?
👇
👇
👇
👇
👇
👇
👇
👇
👇
👇
👇
👇
👇
👇
答えは「桃太郎」でした!
👦 - 桃太郎
👴 - おじいさん
👵 - おばあさん
🐕 - 犬
🐒 - 猿
🐦 - キジ
👹 - 鬼
今回私は、Markdown AI を使って「相関図クイズゲーム」という遊べるWebアプリを制作しました!
制作したアプリはこちら です!
この記事では、制作したクイズゲームの内容や、Markdown AIを使った制作プロセスについて詳しく紹介します。Markdown記法を駆使し、ロボットAI やMermaidを活用するアイデアも盛り込みました。
「Markdown AIって何?」という方にもわかりやすく、かつ楽しく読める記事を目指しています。
相関図クイズゲームとは?
「相関図クイズゲーム」は、ある作品のキャラクターや関係性をヒントに、その作品名を当てるシンプルなクイズです。
クイズを解く中で、キャラクター同士の関係性やストーリーの奥深さも楽しむことができます!
例えば、ジャンルを「国民的アニメ」に設定すると、「ドラえもん」や「クレヨンしんちゃん」などが出題される仕組みです。
主な機能
- ジャンルを選んでスタートボタンをクリック!ジャンルは自分で入力することも可能!
- クイズに挑戦(ヒントや相関図を確認しながら楽しめます)
- 答えがわからない場合は答えを見る機能も完備!
制作に使用した技術
Markdown AIを選んだ理由
Markdown AIは、QiitaなどでおなじみのMarkdown記法を使いながら、簡単にWebサイトやツールを作れるツールです。特に以下の点に魅力を感じました:
- HTML/CSS、JavaScript対応で高度なカスタマイズが可能。
- ロボットAIを使ったAIの組み込みがスムーズ。
- サーバーを設定せずにサイトを公開できる。
使用した主な技術や機能
-
Mermaid
キャラクター相関図を作成するのに使用。関係性を視覚的に表現しました。 -
JavaScript
ユーザーインタラクション(スタートボタンや次のクイズ機能)を実装。 -
Markdown AIのロボットAI
クイズ生成プロセスで利用。プロンプトを入力するだけで、関係図やキャラクター情報を自動生成しました。
制作プロセス
-
アイデア出し
- 「みんなが知っている作品をクイズにしたら面白そう」と思い、相関図を使ったゲームを企画。
- ジャンルごとに出題作品を選定。
-
Markdown AI環境で開発
- HTMLとJavaScriptを組み合わせてUIを作成。
- Mermaidを使って相関図の描画に挑戦。
- ロボットAIでクイズ生成を自動化。
-
テストと改善
- クイズ生成時のエラーや、相関図が正しく表示されない問題をデバッグ。
- ユーザーが操作しやすいインターフェースに微調整。
Markdown AIを使った感想
良かった点
-
手軽さとスピード
サイト制作のハードルが大幅に下がりました。特に、サーバー不要で即公開できる点は画期的! -
AIの活用
簡単に生成AI(gpt-4o-mini、Claude、Geminiなど)を組み込んだアプリが作れるのがとてもいいと思いました。 -
無料
AIを使用するためのAPIキーを発行する必要がなく、全て無料で使えるところが非常にありがたいです。
もう少しだった点
-
Mermaidのサポート
Markdown AIでは Mermaid の描画がサポートされているのですが、スクリプトで動的に Mermaid コードを作成した場合は描画されず、個別に JavaScript で Mermaid 描画用のライブラリを CDN からダウンロードして使用する必要がありました。 -
ドキュメントの不足
今はβ版とのことで、まだドキュメントが十分に揃っていないように感じました。最初 Markdown AI で何ができるかがすぐには分からず、Qiitaの記事などを参考に把握することができました。 -
ファイル数の制限
1つのサイトを作成するのに1ファイルだけしか作れないので、全てのコードを1ファイルにまとめる必要があります。
こんな感じで1ファイルにまとめています
# 相関図クイズ!
<p>相関図の作品名を当てるクイズゲーム🤖</p>
<form id="genreForm">
<select id="genre" name="genre">
<option value="" disabled selected style="display:none;">ジャンルを選んでね!</option>
</select>
</form>
<form id="customGenre">
<input type="text" id="customGenreInput" placeholder="自分でジャンルを入力!">
</form>
<button class="button" type="button" id="button-start">スタート!</button>
<div style="display: none;" id="loading-section">
<div class="loader"></div>
<p id="text-loading">
クイズ作成中...
ちょっと待っててね!(もしかしたら1分くらいかかっちゃうかも🥺)
</p>
</div>
<div id="diagram"></div>
<div class="buttons">
<button class="button " type="button" id="button-hint" style="display: none;">💡 グループ分けを見る</button>
<button class="button " type="button" id="button-answer" style="display: none;">👀 答えを見る </button>
</div>
<h3 id="answer"></h3>
<p id="emojiMap"></p>
<button class="button" style="display: none;" type="button" id="button-next">👉 次の問題</button>
<style>
body {
font-family: Arial, sans-serif;
background-color: #F2EFE7;
margin: 0;
padding: 20px;
}
h1 {
color: #777;
font-size: 24px;
margin-bottom: 10px;
}
p {
color: #777;
font-size: 16px;
}
form {
margin: 10px 0;
}
select,
input {
font-size: 16px;
padding: 8px;
margin: 5px;
border: 1px solid #ccc;
border-radius: 5px;
width: 250px;
}
.button {
font-size: 18px;
padding: 10px 20px;
margin: 10px;
border: none;
border-radius: 5px;
background-color: #48A6A7;
color: #F2EFE7;
cursor: pointer;
transition: 0.3s;
}
.button:hover {
background-color: #9ACBD0;
color: #F2EFE7;
}
#loading-section {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-top: 20px;
}
.loader {
border: 5px solid #F2EFE7;
border-top: 5px solid #2973B2;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
#text-loading {
font-size: 14px;
color: #777;
margin-top: 10px;
}
#diagram {
margin-top: 20px;
padding: 10px;
border: 1px solid #ddd;
background: white;
min-height: 100px;
border-radius: 5px;
}
.buttons {
margin-top: 10px;
}
h3 {
color: #777;
font-size: 20px;
margin-top: 20px;
}
</style>
<script>
document.title = "相関図クイズ!";
document.querySelector('meta[property="og:site_name"]').setAttribute('content', '相関図クイズ!');
async function loadMermaid() {
const response = await fetch("https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js");
const scriptContent = await response.text();
const scriptElement = document.createElement("script");
scriptElement.textContent = scriptContent;
document.head.appendChild(scriptElement);
mermaid.initialize({ startOnLoad: true });
const genres = [
"国民的アニメ", "少年漫画", "少女漫画", "スポーツ漫画", "NHKアニメ", "日常系アニメ", "ジブリ", "ディズニー", "ディズニープリンセス", "ピクサー", "日本の昔話", "世界の昔話", "グリム童話", "ことわざ", "四字熟語", "古事成語", "絵本", "児童書", "国語の教科書に載ってる話", "日本の偉人伝", "世界の偉人伝", "ミステリー小説", "国民的ドラマ", "朝ドラ", "月9ドラマ", "90年代人気ドラマ", "00年代人気ドラマ", "2010年代人気ドラマ", "2020年代人気ドラマ", "バラエティ番組", "邦画", "洋画", "ハリウッド名作", "アメリカンコメディドラマ", "数学の定理"
];
const form = document.getElementById('genreForm');
const select = document.getElementById('genre');
genres.forEach((genre) => {
const option = document.createElement('option');
option.value = genre;
option.textContent = genre;
select.appendChild(option);
});
form.appendChild(select);
let quizzes = [];
let currentQuizIndex = 0;
let isCreatingQuiz = false;
function cleanseJson(jsonString) {
jsonString = jsonString.replace(/'/g, '"');
jsonString = jsonString.replace(/,\s*([}\]])/g, '$1');
jsonString = jsonString.replace(/([,:{\[]\s*)([a-zA-Z0-9_]+)\s*(?=\s*:)/g, '$1"$2"');
jsonString = jsonString.replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\t/g, '\\t');
return jsonString;
}
function parseCharacterData(input) {
try {
const jsonStart = input.indexOf("{");
const jsonEnd = input.lastIndexOf("}") + 1;
if (jsonStart === -1 || jsonEnd === 0) {
throw new Error("JSONが見つかりません");
}
const jsonString = input.substring(jsonStart, jsonEnd);
const processedJsonString = cleanseJson(jsonString);
const parsedData = JSON.parse(processedJsonString);
const diagram = parsedData["相関図"] || "";
let diagramWithoutSubgraph = "";
// 相関図のセクションを解析
if (diagram) {
diagramWithoutSubgraph = diagram.replace(/subgraph (.+) \[(.*?)\]/g, (match, p1, p2) => `subgraph ${p1} [ ]`);
}
return {
relationships: parsedData["キャラクター同士の関係性"] || "",
diagram: diagram,
diagramWithoutSubgraph: diagramWithoutSubgraph,
emojiMap: parsedData["キャラクターと絵文字の対応表"] || ""
};
} catch (error) {
console.error("データのパースに失敗しました:", error.message);
return null;
}
}
const startButton = document.getElementById('button-start');
startButton.addEventListener('click', async event => {
isCreatingQuiz = true;
const selectedGenre = select.value;
const customGenreInput = document.getElementById('customGenreInput').value;
if (selectedGenre && customGenreInput) {
alert('ジャンルを一つだけ選んでね!');
} else if (selectedGenre || customGenreInput) {
const genre = selectedGenre || customGenreInput;
const loadingSection = document.getElementById('loading-section');
loadingSection.style.display = 'flex';
startButton.style.display = 'none';
document.getElementById('diagram').innerHTML = "";
const serverAi = new ServerAI();
const prompt = `${genre}の人気の作品を10個教えてください。出力はカンマで区切り、それ以外の文章は加えないでください。出力例:「ドラえもん, クレヨンしんちゃん, ドラゴンボール, サザエさん, ちびまる子ちゃん, キン肉マン, ワンピース, ナルト, 名探偵コナン, あたしんち」`;
const answer = await serverAi.getAnswerText('7M1Cgeu4c9xhSyGwfYMCux', '', prompt);
const stories = answer.split(',').map(story => story.trim());
try {
const quizPromises = stories.map(async (story, index) => {
const storyPrompt = `${genre}の「${story}」ついて、以下の出力例(ドラえもん)と同じ形式で、キャラクター同士の関係性とmaermaidの相関図とキャラクターと絵文字の対応表を作ってください。相関図の中の各キャラクターは絵文字1つで表現してください。\n注意点:\n1.嘘を書かないでください。あなたが持っている正確な情報の範囲で文章を生成してください。情報がない場合は「わかりません」のみ出力してください。2. maemaid記法は参考例を忠実に真似しシンタックスエラーが発生しないようにしてください。3. subgraphは必ず[]を使ってラベルをつけてください\n\n以下、ドラえもんを例にした出力形式\n{"キャラクター同士の関係性": "ドラえもんは22世紀からやってきたロボット猫で、のび太の良き相談相手であり支援者です。のび太は主人公で、成績が悪く運動も苦手ですが、優しい心を持っています。しずかちゃんはのび太の同級生で、のび太が好意を持っている女の子です。ジャイアンは力が強く、時にのび太をいじめる存在ですが、友情も持っています。スネ夫はジャイアンの取り巻きで、お金持ちの家庭の息子です。ドラミはドラえもんの妹で、時々未来からやってきて皆を手助けします。","相関図": "graph TD\\nsubgraph School [学校]\\nNobita[🧒]\\nShizuka[👧]\\nGian[👦]\\nSuneo[👦]\\nend\\n\\nsubgraph Future [未来]\\nDoraemon[🐱]\\nDorami[🐱]\\nend\\n\\nNobita <--親友--> Doraemon\\nNobita --好き--> Shizuka\\nNobita --いじめられる--> Gian\\nNobita --いじめられる--> Suneo\\nGian --手下--> Suneo\\nDoraemon --妹--> Dorami\\nDoraemon --助ける--> Nobita","キャラクターと絵文字の対応表": "🧒 - のび太\\n👧 - しずかちゃん\\n👦 - ジャイアン\\n👦 - スネ夫\\n🐱 - ドラえもん\\n🐱 - ドラミ"}`;
const storyAnswer = await serverAi.getAnswerText('7M1Cgeu4c9xhSyGwfYMCux', '', storyPrompt);
const parsedAnswer = parseCharacterData(storyAnswer);
if (parsedAnswer) {
quizzes.push({
story: story,
relationships: parsedAnswer.relationships,
diagram: parsedAnswer.diagram,
diagramWithoutSubgraph: parsedAnswer.diagramWithoutSubgraph,
emojiMap: parsedAnswer.emojiMap
});
loadingSection.style.display = 'none';
const buttonHint = document.getElementById('button-hint');
const buttonAnswer = document.getElementById('button-answer');
const isShowingNextButton = nextButton.style.display === 'block';
if (!isShowingNextButton) {
showQuiz(currentQuizIndex);
}
}
});
// バックグラウンドでリクエストが続行
await Promise.allSettled(quizPromises);
isCreatingQuiz = false;
} catch (error) {
console.error("エラーが発生しました:", error.message);
alert("データの取得中にエラーが発生しました。もう一度試してください。");
}
} else {
alert('ジャンルを選んでね!');
}
});
// クイズの内容を表示
function showQuiz(index) {
if (quizzes.length > 0 && quizzes[index]) {
buttonHint.style.display = 'block';
buttonAnswer.style.display = 'block';
nextButton.style.display = 'block';
const quiz = quizzes[index];
const diagramDiv = document.getElementById('diagram');
diagramDiv.removeAttribute('data-processed');
diagramDiv.innerHTML = quiz.diagramWithoutSubgraph;
diagramDiv.classList.add("mermaid");
mermaid.init();
}
}
// 次の問題に進む
const nextButton = document.getElementById('button-next');
nextButton.addEventListener('click', () => {
document.getElementById('answer').innerText = "";
document.getElementById('emojiMap').innerText = "";
currentQuizIndex++;
if (currentQuizIndex < quizzes.length) {
showQuiz(currentQuizIndex);
} else {
nextButton.style.display = 'none';
buttonAnswer.style.display = 'none';
buttonHint.style.display = 'none';
const diagramDiv = document.getElementById('diagram');
diagramDiv.removeAttribute('data-processed');
diagramDiv.classList.remove("mermaid");
if (isCreatingQuiz) {
document.getElementById('loading-section').style.display = 'flex';
diagramDiv.innerHTML = "";
} else {
startButton.style.display = 'block';
select.value = '';
select.focus();
document.getElementById('customGenreInput').value = '';
diagramDiv.innerHTML = "全部のクイズが終わったよ👏\nジャンルを変えてまた遊んでみてね✨";
}
}
});
// ヒントを見るボタン
const buttonHint = document.getElementById('button-hint');
buttonHint.addEventListener('click', () => {
const quiz = quizzes[currentQuizIndex];
const diagramDiv = document.getElementById('diagram');
diagramDiv.removeAttribute('data-processed');
diagramDiv.innerHTML = quiz.diagram;
diagramDiv.classList.add("mermaid");
mermaid.init();
buttonHint.style.display = 'none';
});
// 答えを見るボタン
const buttonAnswer = document.getElementById('button-answer');
buttonAnswer.addEventListener('click', () => {
const quiz = quizzes[currentQuizIndex];
document.getElementById('answer').innerText = `答えは「${quiz.story}」でした!`;
document.getElementById('emojiMap').innerText = quiz.emojiMap;
buttonAnswer.style.display = 'none';
});
}
loadMermaid();
</script>
まとめ
Markdown AIを使った「相関図クイズゲーム」の制作プロセスを紹介しました!
このツールは、Markdown記法の手軽さと高度な機能を両立しており、クリエイターや開発者にとって強力な味方です。
今回のプロジェクトでは、「ロボットAI」と「Mermaid」を駆使して、クイズ生成の効率化と視覚的な楽しさを両立させました。ぜひ皆さんもMarkdown AIを使って、オリジナルコンテンツを制作してみてください!
👇 Markdown AIはこちらから試せます👇
Markdown AI公式サイト
👇相関図クイズゲームはこちらから👇
相関図クイズゲーム