🐍

生成AI 3連コンボでTRPGログを可視化!ココフォリア技能集計アプリ開発録

に公開

はじめに

Cocoforia Skill Summary サイト画面

TRPGセッション後、「誰がどの技能をどれだけ使った?」「成功した技能は?」と気になること、ありませんか?Cocoforiaのログを手作業で集計するのは大変…。そこで、生成AIの力をフル活用して自動集計・可視化Webアプリを作りました!

本記事では、ChatGPT → OpenAI Codex → Cursor → GitHub Pagesの4段階で、どのようにアイデアを形にしたかを解説します。


1. 企画~仕様策定:ChatGPTとの対話

まずはChatGPTと要件を整理。

  • 判定結果(成功/失敗/クリティカル/ファンブル)をキャラ別・技能別に集計
  • ログ貼り付け→ワンクリックで表を出力
  • PC/スマホ両対応のレスポンシブ設計

ChatGPTで「必要なUI」「出力フォーマット」「データスキーマ」などを具体化し、Codexへのプロンプトにも活用しました。


2. コーディング:OpenAI Codexで初期実装

Codexは「Cocoforiaの判定結果を正規表現で抽出して」など高レベルな指示でも即座にJavaScriptを出力。

生成された主な構成:

  • parser.ts:ログ解析ロジック
  • main.ts:DOM操作とDataTables初期化
  • style.css:ベーススタイル

パーサー実装例:

export function parseLog(src) {
    // [チャンネル] プレイヤー名 : ...【技能名】...> ... > ... > 判定結果
    const channelLine = /^\[([^\]]+)\]\s+([^:]+?)\s*:(.*)$/;
    const skillBody = /([^]+)/;
    const resultBody = /(クリティカル|ファンブル|失敗|成功)/;
    const rows = [];
    src.split('\n').forEach((l, i) => {
        const m = l.match(channelLine);
        if (!m)
            return;
        const channel = m[1].trim();
        const player = m[2].trim();
        const rest = m[3];
        const skillMatch = rest.match(skillBody);
        const resultMatch = rest.match(resultBody);
        if (!skillMatch || !resultMatch)
            return;
        const label = classify(resultMatch[1]);
        if (!label)
            return;
        rows.push({ channel, player, skill: skillMatch[1], label, line: i + 1 });
    });
    return rows;
}

3. リファクタリング:CursorでAIペアプロ

CursorはVS CodeライクなUIで「この関数をパフォーマンス重視で書き直して」などAIと対話しながらリファクタリングできます。

TypeScriptで開発し、最終的にJavaScriptへ変換してデプロイ。

npx tsc *.ts --outDir .

4. 技術ハイライト

データ集計ロジック

export function summarize(rows: Row[]): any[] {
  const box = new Map<string, Map<string, SkillStat>>();
  for (const r of rows) {
    const p = box.get(r.player) ?? new Map<string, SkillStat>();
    const stat = p.get(r.skill) ?? { skill: r.skill, s: 0, f: 0, c: 0, b: 0 };
    if (r.label === 'success') stat.s++;
    if (r.label === 'failure') stat.f++;
    if (r.label === 'critical') stat.c++;
    if (r.label === 'fumble') stat.b++;
    p.set(r.skill, stat);
    box.set(r.player, p);
  }
  const rowsOut: any[] = [];
  for (const [player, skills] of box.entries()) {
    for (const stat of skills.values()) {
      rowsOut.push([stat.skill, stat.s, stat.f, stat.c, stat.b, player]);
    }
  }
  return rowsOut;
}

UI/UXの工夫

技術 採用理由
DataTables ソート・検索・ページネーションをワンライナーで実装
レスポンシブ設計 スマホは100%、PCは最大900pxで余白と可読性を両立
マテリアルデザイン CDN経由で軽量にモダンなUIを実現

DataTables設定例:

tableDetail = $('#resultTable').DataTable({
  data: rows.map((r) => [r.skill, r.label, r.player]),
  columns: [
    { title: '技能' },
    { title: '種別' },
  ],
  rowGroup: { dataSrc: 2 }, // プレイヤー名でグループ化
  responsive: true,
  pageLength: 10,
});

キャラクター別テーブルの動的生成:

Object.entries(byPlayer).forEach(([player, data], idx) => {
  const h = document.createElement('h2');
  h.textContent = player;
  summaryPane.appendChild(h);
  const table = document.createElement('table');
  table.id = `summaryTable_${idx}`;
  table.className = 'display';
  summaryPane.appendChild(table);
  $(`#${table.id}`).DataTable({
    data,
    columns: [
      { title: '技能' },
      { title: '成功' },
      { title: '失敗' },
      { title: 'クリティカル' },
      { title: 'ファンブル' },
    ],
    responsive: true,
    searching: false,
    paging: false,
    info: false,
    ordering: false,
  });
});

5. デザイン仕上げ

CSS例:

:root {
  --md-sys-color-primary: #6750a4;
  --md-sys-color-secondary: #625b71;
  --md-sys-color-background: #fefbff;
}
body {
  background: #faf7fb;
  font-family: 'Segoe UI', 'Meiryo', sans-serif;
  max-width: 900px;
  margin: 0 auto;
}
.display {
  background: #fff;
  border-radius: 10px;
  box-shadow: 0 2px 12px #a5b4fc33;
  overflow: hidden;
}
@media (max-width: 700px) {
  h1 { font-size: 1.3em; }
  h2 { font-size: 1em; }
  .display td, .display th { padding: 4px 6px; }
}

6. デプロイ:GitHub Pages

GitHub Actionsで自動ビルド&デプロイ:

name: Deploy to GitHub Pages
on:
  push:
    branches: [ main ]
permissions:
  contents: read
  pages: write
  id-token: write
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      - run: npm install -g typescript
      - run: tsc *.ts --outDir .
      - uses: actions/upload-pages-artifact@v1
        with:
          path: .
      - uses: actions/deploy-pages@v1

7. 実際の使用感

  1. Cocoforiaのセッションログをコピー
  2. テキストエリアに貼り付け
  3. 「集計」ボタンをクリック
  4. キャラクターごと・技能ごとの統計が一瞬で表示
  • 詳細タブ:全判定の一覧をプレイヤーごとにグループ表示
  • キャラ別表示タブ:各キャラクターの技能統計を個別テーブルで表示

キャラ別表示サンプル

  • 合計値:成功・失敗・クリティカル・ファンブルの総数を自動計算

今後のアイデア

  • 📊 成功率グラフを Chart.js で可視化
  • 📁 ログ貼り付け欄を drag & drop でファイル受け付け
  • 🔄 Service Worker でオフライン解析
  • 📱 PWA化でアプリライクな体験
  • 📈 セッション間での成長度合いを可視化

まとめ

ChatGPTで仕様を固め、Codexで骨組みを生成し、Cursorで磨き上げ、GitHub Pagesに即デプロイ──生成AI × OSS × サーバーレスの組み合わせで、アイデアを"最短距離"で公開できました。

「TRPGログをもっと便利にしたい」仲間が増えることを願っています!

🎯 アプリ: https://danielvo594520.github.io/cocoforia-skill-confirmation/


参考リンク

Discussion