ソシャゲのガチャの仕組みを組み合わせたポーカーゲームを作ってみた【AI生成ゲーム実験 Vol.4】 誰でも作れる遊べるプロンプトとコード公開中

2025年7月11日金曜日

オリジナルゲーム ゲーム開発 プロンプト 無料ゲーム

t f B! P L

こんにちは、AM0500です。

今回は、お風呂でアイデアを思いつき形にしてみたらどんな感じになるのかを試してみた時のものです。こういうアイデアを施策するのに便利だと感じました。

ソシャゲのガチャのようにジェムを使って10連ガチャを引き、10枚のトランプでポーカーの役を作るゲームです。レアリティが高いカードを組み込むほど役の倍率が上がります。

🔗 実際にプレイしてみる

▼ ブラウザでそのまま遊べます:
👉 ソシャゲのガチャの仕組みでポーカー作ったら面白いんじゃないかと思って試してみたゲーム (外部ページに飛びます。)

👉 作成に使用したAI Claude(claude 4 sonnet)はこちら
  ※英語でとっつきにくく見えますが、やり取りは日本語でできます。

🎮 ゲーム画面(スクリーンショット)


画像
画像

📝 一言コメント

ソシャゲのガチャのように10枚排出してポーカーをやったら面白いんじゃないか?レアリティでスコアを上げたら良いかも。なんて雑な考えから試してみた物になります。
結果としては役が簡単に作れすぎるという点と、レアリティボーナスが強すぎた、10ラウンドは長すぎたという印象でした。一度遊んでみてください。

Bloggerに作品を公開しているのですが、仕様により一部記号が文字化けするため、一部の記号を修正したバージョンで公開しています。
オリジナルのソースはこちらのNoteの内容を参照ください。




🧠 使用したClaudeへのプロンプト

今回はプロンプトをかなり細かく指示しました。

# 指示

あなたは熟練のゲーム開発者であり、JavaScript、HTML、CSSを駆使するフロントエンドエンジニアです。
これから、ソーシャルゲームの「ガチャ」と「ポーカー」を組み合わせた、新しいカードゲームを開発してください。
以下の仕様を厳密に守り、ユーザーがスマートフォンで快適に遊べるゲームのソースコードを生成してください。

---

<project_overview>
## プロジェクト概要
- **ゲームタイトル案:** ガチャポーカー・ロワイヤル
- **コンセプト:** 10連ガチャで引いたカードから手札を構築してポーカーの役を作り、ハイスコアを目指すゲーム。カードのレアリティがスコアに大きく影響する。
- **ターゲット:** スマートフォンで手軽に遊べるゲームを求めるユーザー。
</project_overview>

---

<specifications>
## ゲーム仕様詳細

### 1. ゲームの基本フロー
1.  ゲーム開始時に、プレイヤーは初期ジェムとして`30,000`ジェムを所持している。
2.10連ガチャを引く」ボタンが表示される。1回のガチャで`3,000`ジェムを消費する。
3.  ガチャを引くと、ジョーカー1枚を含む54枚のトランプデッキからランダムに10枚のカードが排出される。
4.  排出された10枚のカードそれぞれに、以下の確率でレアリティがランダムに付与される。
    - **R (レア)**: 67%
    - **SR (スーパーレア)**: 30%
    - **SSR (ダブルスーパーレア)**: 3%
5.  プレイヤーは、表示された10枚のカードの中から、ポーカーの役を作るために最も有利だと思う5枚を選択する。
6.  5枚を選択して「決定」ボタンを押すと、役の判定が行われる。
7.  役に応じた基本スコアと、手札に含まれるカードのレアリティに応じた倍率ボーナスを計算し、獲得ジェムとしてプレイヤーに付与する。
8.  1ラウンドが終了。現在のラウンド数と総ジェム数が表示される。
9.  プレイヤーは次のラウンドに進むかを選択する。ジェムが足りない場合はゲームオーバー。
10.10ラウンドが終了した時点でゲームクリアとなり、最終的な総ジェム数がリザルトとして表示される。

### 2. カードとデッキ
- **デッキ:** 標準的なトランプ52枚 + **ジョーカー1枚**の合計54枚で構成される。
- **ジョーカーの扱い:** オールマイティなワイルドカードとして機能し、どんな数字・スートの代わりにもなれる最強のカードとする。
- **カードの表示:** カードの数字とスート、そしてカッコ書きでレアリティ(R, SR, SSR)を表示する。例: `ハートのA (SSR)`

### 3. スコア(獲得ジェム)計算ルール

#### (A) ポーカーの役と基本スコア
役が成立しなかった場合(ノーペア)は`0`ジェム。成立した役に応じて、以下の基本スコアを獲得する。

| 役                    | 基本スコア(ジェム) | 説明                                       |
| --------------------- | ---------------- | ------------------------------------------ |
| **ワンペア** | 3,000            | 同じ数字2枚                                |
| **ツーペア** | 6,000            | ワンペアが2組                              |
| **スリーカード** | 12,000           | 同じ数字3枚                                |
| **ストレート** | 25,000           | 5枚の数字が連続(A-K-Q-J-10が最強)        |
| **フラッシュ** | 35,000           | 5枚のスートがすべて同じ                    |
| **フルハウス** | 60,000           | スリーカードとワンペアの組み合わせ         |
| **フォーカード** | 150,000          | 同じ数字4枚                                |
| **ストレートフラッシュ** | 500,000          | ストレートとフラッシュの組み合わせ         |
| **ロイヤルストレートフラッシュ** | 1,500,000        | A-K-Q-J-10かつスートが同じ(最強役) |
| **ファイブカード** | 3,000,000        | フォーカード + ジョーカー(最強役を超える) |

#### (B) レアリティボーナス
上記(A)で決定した基本スコアに対し、プレイヤーが選択した5枚の手札に含まれるSRとSSRカードによって、以下の倍率が**乗算**される。

- **SRカード1枚につき:** スコアを **2倍**
- **SSRカード1枚につき:** スコアを **30倍**

**計算例:**
- **基本スコア:** ワンペアで`3,000`ジェム
- **手札:** SRが2枚、SSRが1枚含まれている
- **計算式:** `3000 (基本) * 2 (SR) * 2 (SR) * 30 (SSR)`
- **最終獲得ジェム:** `360,000`ジェム

### 4. ゲームオーバー
- 10連ガチャを引くためのジェム(3,000ジェム)がなくなった時点でゲームオーバーとする。
- ゲームオーバー画面では、最終的なスコアとリトライボタンを表示する。

</specifications>

---

<ui_ux_guidelines>
## UI/UX(見た目と操作性)に関する要件
- **レイアウト:** スマートフォンの縦画面で操作しやすいレスポンシブデザインとすること。
- **カードデザイン:**
    - 数字とスートが明確にわかるデザイン。
    - レアリティに応じてカードの枠の色やエフェクトを変えること。(例: R=ブロンズ、SR=シルバー、SSR=ゴールドでキラキラ光る)
- **操作性:**
    - ボタンは大きく、タップしやすいように配置する。
    - カード選択は、タップすることで選択/選択解除できるようにする。選択中のカードは枠を光らせるなど、視覚的に分かりやすくすること。
- **演出:**
    - 10連ガチャを引いた際には、カードが1枚ずつめくれるようなアニメーションを入れるとリッチになる。「スキップ」ボタンも用意すること。
    - 役が成立して高得点を獲得した際には、祝福するような派手なエフェクト(例: ジェムが飛び散る)を表示すること。
</ui_ux_guidelines>

---

<deliverables>
## 成果物
- **形式:** 単一の`index.html`ファイルに、HTML、CSS、JavaScriptのすべてを記述してください。
- **構成:**
    1.  **HTML:** ゲームの画面構造を定義する。
    2.  **CSS:** ゲームの見た目、レイアウト、アニメーションを定義する。`<style>`タグ内に記述すること。
    3.  **JavaScript:** ゲームの全ロジック(デッキ生成、ガチャ、役判定、スコア計算、状態管理など)を記述する。`<script>`タグ内に記述すること。
- **コメント:** コードの主要な部分には、処理内容を説明するコメントを日本語で追記してください。
</deliverables>

---

<instructions_for_claude>
## 実行手順
上記のすべての要件を理解した上で、以下の手順で開発を進めてください。

1.  まず、ゲーム全体の設計思想と、実装する機能の概要を簡潔に説明してください。
2.  次に、指定された成果物形式に従い、`index.html`の完全なソースコードを生成してください。

</instructions_for_claude>

ここまで細かいプロンプトは難しいのでは?と思いますが、プロンプトもChatGPTやGeminiなどAIを組み合わせて作成しています。やりたい事を箇条書きにして、AIにプロンプトにまとめてもらうと作りやすいです。

🕹️ Claudeが返してくれた説明文

実装された主要機能

🎰 ガチャシステム
10連ガチャ(3,000ジェム消費)
54枚デッキ(ジョーカー含む)
レアリティ抽選(R: 67%, SR: 30%, SSR: 3%)
カード1枚ずつ開封するアニメーション(スキップ機能付き)

🃏 ポーカー判定
9つの基本役 + ファイブカード(ジョーカー込み)
ジョーカーをワイルドカードとして完全対応
複雑な役判定ロジック(ストレート、フラッシュ、フルハウス等)

💎 スコアシステム
基本スコア × レアリティ倍率
SR: ×2倍、SSR: ×30倍の複合計算
最大3,000,000ジェム獲得可能(ファイブカード)

📱 UI/UX
レスポンシブデザイン: スマホ縦画面に最適化
視覚的フィードバック: カード選択時の光る枠線
レアリティ表示: R(ブロンズ)、SR(シルバー)、SSR(ゴールド・キラキラ)
アニメーション: ジェム獲得エフェクト、カードホバー効果

🎮 ゲーム進行
10ラウンド制
リアルタイムジェム管理
ゲームオーバー・クリア判定
リスタート機能

🎨 デザイン特徴
グラデーション背景
ガラスモーフィズム効果
SSRカードのスパークルアニメーション
直感的なタップ操作

すべての仕様要件を満たした完全動作版です。ブラウザで開くとすぐにプレイできます!

💡 ソースコード(HTML)

下記がゲーム本体のhtmlの中身になります。ご自由にご利用ください。もちろん改変などもOKです。リンクや紹介していただけたら嬉しいです。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ガチャポーカー・ロワイヤル</title>
    <style>
        /* 基本スタイル */
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Arial', sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            color: white;
            overflow-x: hidden;
        }

        .container {
            max-width: 400px;
            margin: 0 auto;
            padding: 20px;
        }

        /* ヘッダー */
        .header {
            text-align: center;
            margin-bottom: 20px;
        }

        .title {
            font-size: 24px;
            font-weight: bold;
            text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
            margin-bottom: 10px;
        }

        .stats {
            display: flex;
            justify-content: space-between;
            background: rgba(255,255,255,0.1);
            padding: 15px;
            border-radius: 10px;
            backdrop-filter: blur(10px);
        }

        .stat {
            text-align: center;
        }

        .stat-label {
            font-size: 12px;
            opacity: 0.8;
        }

        .stat-value {
            font-size: 18px;
            font-weight: bold;
            color: #ffd700;
        }

        /* ボタン */
        .btn {
            width: 100%;
            padding: 15px;
            font-size: 18px;
            font-weight: bold;
            border: none;
            border-radius: 10px;
            cursor: pointer;
            transition: all 0.3s ease;
            margin-bottom: 10px;
            box-shadow: 0 4px 8px rgba(0,0,0,0.2);
        }

        .btn-primary {
            background: linear-gradient(45deg, #ff6b6b, #ee5a52);
            color: white;
        }

        .btn-primary:hover {
            transform: translateY(-2px);
            box-shadow: 0 6px 12px rgba(0,0,0,0.3);
        }

        .btn-secondary {
            background: linear-gradient(45deg, #4ecdc4, #44a08d);
            color: white;
        }

        .btn-secondary:hover {
            transform: translateY(-2px);
            box-shadow: 0 6px 12px rgba(0,0,0,0.3);
        }

        .btn:disabled {
            opacity: 0.5;
            cursor: not-allowed;
            transform: none;
        }

        /* カードスタイル */
        .cards-container {
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 10px;
            margin-bottom: 20px;
        }

        .card {
            background: white;
            color: black;
            border-radius: 8px;
            padding: 15px;
            text-align: center;
            cursor: pointer;
            transition: all 0.3s ease;
            position: relative;
            overflow: hidden;
            min-height: 80px;
            display: flex;
            flex-direction: column;
            justify-content: center;
        }

        .card:hover {
            transform: translateY(-2px);
            box-shadow: 0 6px 12px rgba(0,0,0,0.2);
        }

        .card.selected {
            border: 3px solid #ffd700;
            box-shadow: 0 0 20px rgba(255,215,0,0.5);
        }

        .card.rarity-R {
            border: 2px solid #cd7f32;
        }

        .card.rarity-SR {
            border: 2px solid #c0c0c0;
            background: linear-gradient(45deg, #f5f5f5, #e0e0e0);
        }

        .card.rarity-SSR {
            border: 2px solid #ffd700;
            background: linear-gradient(45deg, #fff8dc, #ffd700);
            animation: sparkle 2s infinite;
        }

        @keyframes sparkle {
            0%, 100% { box-shadow: 0 0 5px rgba(255,215,0,0.5); }
            50% { box-shadow: 0 0 20px rgba(255,215,0,0.8); }
        }

        .card-content {
            font-size: 14px;
            font-weight: bold;
        }

        .card-rarity {
            font-size: 12px;
            margin-top: 5px;
            padding: 2px 6px;
            border-radius: 4px;
            display: inline-block;
        }

        .rarity-R { background: #cd7f32; color: white; }
        .rarity-SR { background: #c0c0c0; color: black; }
        .rarity-SSR { background: #ffd700; color: black; }

        /* スーツの色 */
        .suit-hearts, .suit-diamonds { color: #ff0000; }
        .suit-clubs, .suit-spades { color: #000000; }
        .suit-joker { color: #ff4500; }

        /* ガチャアニメーション */
        .gacha-animation {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.8);
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 1000;
        }

        .gacha-card {
            width: 120px;
            height: 160px;
            background: linear-gradient(45deg, #4158d0, #c850c0);
            border-radius: 10px;
            display: flex;
            justify-content: center;
            align-items: center;
            font-size: 18px;
            font-weight: bold;
            color: white;
            cursor: pointer;
            transition: transform 0.3s ease;
        }

        .gacha-card:hover {
            transform: scale(1.05);
        }

        /* 結果表示 */
        .result {
            background: rgba(255,255,255,0.1);
            padding: 20px;
            border-radius: 10px;
            margin-bottom: 20px;
            text-align: center;
            backdrop-filter: blur(10px);
        }

        .hand-name {
            font-size: 24px;
            font-weight: bold;
            color: #ffd700;
            margin-bottom: 10px;
        }

        .score-display {
            font-size: 18px;
            margin-bottom: 10px;
        }

        .bonus-display {
            font-size: 14px;
            opacity: 0.8;
        }

        /* エフェクト */
        .gem-effect {
            position: fixed;
            pointer-events: none;
            z-index: 999;
            font-size: 20px;
            font-weight: bold;
            color: #ffd700;
            animation: gemFly 2s ease-out forwards;
        }

        @keyframes gemFly {
            0% {
                transform: translateY(0) scale(1);
                opacity: 1;
            }
            100% {
                transform: translateY(-100px) scale(0.5);
                opacity: 0;
            }
        }

        /* ゲームオーバー・クリア画面 */
        .game-end {
            background: rgba(0,0,0,0.9);
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 1000;
        }

        .game-end-content {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            padding: 40px;
            border-radius: 20px;
            text-align: center;
            max-width: 300px;
            width: 90%;
        }

        .game-end-title {
            font-size: 28px;
            font-weight: bold;
            margin-bottom: 20px;
        }

        .final-score {
            font-size: 24px;
            color: #ffd700;
            margin-bottom: 20px;
        }

        /* スキップボタン */
        .skip-btn {
            position: fixed;
            bottom: 20px;
            right: 20px;
            padding: 10px 20px;
            background: rgba(255,255,255,0.2);
            border: none;
            border-radius: 20px;
            color: white;
            cursor: pointer;
            z-index: 1001;
        }

        /* レスポンシブ対応 */
        @media (max-width: 480px) {
            .container {
                padding: 10px;
            }
            
            .title {
                font-size: 20px;
            }
            
            .cards-container {
                grid-template-columns: 1fr;
            }
            
            .card {
                padding: 10px;
            }
        }

        /* 隠れた要素 */
        .hidden {
            display: none;
        }
    </style>
</head>
<body>
    <div class="container">
        <!-- ヘッダー -->
        <div class="header">
            <div class="title">ガチャポーカー・ロワイヤル</div>
            <div class="stats">
                <div class="stat">
                    <div class="stat-label">ラウンド</div>
                    <div class="stat-value" id="round">1/10</div>
                </div>
                <div class="stat">
                    <div class="stat-label">ジェム</div>
                    <div class="stat-value" id="gems">30,000</div>
                </div>
            </div>
        </div>

        <!-- メインコンテンツ -->
        <div id="main-content">
            <!-- ガチャボタン -->
            <div id="gacha-phase">
                <button class="btn btn-primary" id="gacha-btn">
                    10連ガチャを引く (3,000ジェム)
                </button>
            </div>

            <!-- カード選択フェーズ -->
            <div id="selection-phase" class="hidden">
                <div id="instruction">
                    <p style="text-align: center; margin-bottom: 20px;">
                        10枚の中から5枚を選んでポーカーの役を作ろう!
                    </p>
                </div>
                
                <div class="cards-container" id="cards-container">
                    <!-- カードが動的に追加される -->
                </div>
                
                <div id="selection-info" style="text-align: center; margin-bottom: 20px;">
                    選択中: <span id="selected-count">0</span>/5
                </div>
                
                <button class="btn btn-secondary" id="confirm-btn" disabled>
                    この手札で勝負!
                </button>
            </div>

            <!-- 結果表示 -->
            <div id="result-phase" class="hidden">
                <div class="result">
                    <div class="hand-name" id="hand-name">役名</div>
                    <div class="score-display" id="score-display">獲得ジェム: 0</div>
                    <div class="bonus-display" id="bonus-display">レアリティボーナス詳細</div>
                </div>
                
                <button class="btn btn-primary" id="next-round-btn">
                    次のラウンドへ
                </button>
            </div>
        </div>
    </div>

    <!-- ガチャアニメーション -->
    <div id="gacha-animation" class="gacha-animation hidden">
        <div class="gacha-card" id="gacha-card">
            タップしてカードを開く
        </div>
        <button class="skip-btn" id="skip-btn">スキップ</button>
    </div>

    <!-- ゲーム終了画面 -->
    <div id="game-end" class="game-end hidden">
        <div class="game-end-content">
            <div class="game-end-title" id="game-end-title">ゲームオーバー</div>
            <div class="final-score" id="final-score">最終スコア: 0 ジェム</div>
            <button class="btn btn-primary" id="restart-btn">もう一度プレイ</button>
        </div>
    </div>

    <script>
        // ゲーム状態管理
        let gameState = {
            currentRound: 1,
            gems: 30000,
            currentCards: [],
            selectedCards: [],
            isAnimating: false
        };

        // カードデータ
        const suits = ['hearts', 'diamonds', 'clubs', 'spades'];
        const suitSymbols = {
            hearts: '♥',
            diamonds: '♦',
            clubs: '♣',
            spades: '♠'
        };
        const ranks = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'];
        const rarities = ['R', 'SR', 'SSR'];
        const rarityRates = [67, 30, 3]; // R: 67%, SR: 30%, SSR: 3%

        // ポーカー役の定義
        const pokerHands = {
            'ロイヤルストレートフラッシュ': 1500000,
            'ストレートフラッシュ': 500000,
            'ファイブカード': 3000000,
            'フォーカード': 150000,
            'フルハウス': 60000,
            'フラッシュ': 35000,
            'ストレート': 25000,
            'スリーカード': 12000,
            'ツーペア': 6000,
            'ワンペア': 3000,
            'ノーペア': 0
        };

        // デッキ生成
        function createDeck() {
            const deck = [];
            
            // 通常カード52枚
            for (let suit of suits) {
                for (let rank of ranks) {
                    deck.push({ suit, rank });
                }
            }
            
            // ジョーカー1枚
            deck.push({ suit: 'joker', rank: 'Joker' });
            
            return deck;
        }

        // レアリティ抽選
        function getRandomRarity() {
            const random = Math.random() * 100;
            if (random < rarityRates[2]) return 'SSR';
            if (random < rarityRates[1] + rarityRates[2]) return 'SR';
            return 'R';
        }

        // カードをシャッフル
        function shuffleArray(array) {
            const shuffled = [...array];
            for (let i = shuffled.length - 1; i > 0; i--) {
                const j = Math.floor(Math.random() * (i + 1));
                [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
            }
            return shuffled;
        }

        // 10連ガチャ実行
        function drawGacha() {
            const deck = createDeck();
            const shuffledDeck = shuffleArray(deck);
            const drawnCards = shuffledDeck.slice(0, 10);
            
            // 各カードにレアリティを付与
            return drawnCards.map(card => ({
                ...card,
                rarity: getRandomRarity()
            }));
        }

        // カード表示文字列生成
        function getCardDisplayText(card) {
            if (card.suit === 'joker') {
                return 'Joker';
            }
            const suitSymbol = suitSymbols[card.suit];
            return `${suitSymbol}${card.rank}`;
        }

        // カード表示
        function displayCards(cards) {
            const container = document.getElementById('cards-container');
            container.innerHTML = '';
            
            cards.forEach((card, index) => {
                const cardElement = document.createElement('div');
                cardElement.className = `card rarity-${card.rarity} suit-${card.suit}`;
                cardElement.dataset.index = index;
                
                const cardContent = document.createElement('div');
                cardContent.className = 'card-content';
                cardContent.textContent = getCardDisplayText(card);
                
                const rarityElement = document.createElement('div');
                rarityElement.className = `card-rarity rarity-${card.rarity}`;
                rarityElement.textContent = card.rarity;
                
                cardElement.appendChild(cardContent);
                cardElement.appendChild(rarityElement);
                
                cardElement.addEventListener('click', () => selectCard(index));
                container.appendChild(cardElement);
            });
        }

        // カード選択
        function selectCard(index) {
            const card = gameState.currentCards[index];
            const cardElement = document.querySelector(`[data-index="${index}"]`);
            
            if (gameState.selectedCards.includes(index)) {
                // 選択解除
                gameState.selectedCards = gameState.selectedCards.filter(i => i !== index);
                cardElement.classList.remove('selected');
            } else {
                // 選択
                if (gameState.selectedCards.length < 5) {
                    gameState.selectedCards.push(index);
                    cardElement.classList.add('selected');
                }
            }
            
            updateSelectionUI();
        }

        // 選択UI更新
        function updateSelectionUI() {
            const selectedCount = gameState.selectedCards.length;
            document.getElementById('selected-count').textContent = selectedCount;
            document.getElementById('confirm-btn').disabled = selectedCount !== 5;
        }

        // ポーカー役判定
        function evaluateHand(cards) {
            // カードを数値に変換(ジョーカーは特別扱い)
            const jokers = cards.filter(card => card.suit === 'joker').length;
            const normalCards = cards.filter(card => card.suit !== 'joker');
            
            // 役判定のロジック
            if (jokers >= 4) {
                return 'ファイブカード';
            }
            
            // 通常の役判定(ジョーカーを考慮)
            const hand = analyzeHand(normalCards, jokers);
            return hand;
        }

        // 役の詳細分析
        function analyzeHand(cards, jokers) {
            const ranks = cards.map(card => {
                if (card.rank === 'A') return 1;
                if (card.rank === 'J') return 11;
                if (card.rank === 'Q') return 12;
                if (card.rank === 'K') return 13;
                return parseInt(card.rank);
            });
            
            const suits = cards.map(card => card.suit);
            
            // 各数字の出現回数をカウント
            const rankCounts = {};
            ranks.forEach(rank => {
                rankCounts[rank] = (rankCounts[rank] || 0) + 1;
            });
            
            // スートの統一チェック
            const suitCounts = {};
            suits.forEach(suit => {
                suitCounts[suit] = (suitCounts[suit] || 0) + 1;
            });
            
            const maxSuitCount = Math.max(...Object.values(suitCounts));
            const isFlush = maxSuitCount + jokers >= 5;
            
            // ストレートチェック
            const sortedRanks = [...new Set(ranks)].sort((a, b) => a - b);
            const isStrait = checkStraight(sortedRanks, jokers);
            
            // ロイヤルストレートフラッシュチェック
            const isRoyalStraight = checkRoyalStraight(sortedRanks, jokers);
            
            if (isRoyalStraight && isFlush) {
                return 'ロイヤルストレートフラッシュ';
            }
            
            if (isStrait && isFlush) {
                return 'ストレートフラッシュ';
            }
            
            // 各種ペアの判定
            const countValues = Object.values(rankCounts).sort((a, b) => b - a);
            const maxCount = Math.max(...countValues);
            
            if (maxCount + jokers >= 4) {
                return 'フォーカード';
            }
            
            if (isFlush) {
                return 'フラッシュ';
            }
            
            if (isStrait) {
                return 'ストレート';
            }
            
            // フルハウス判定
            if (countValues.length >= 2) {
                const first = countValues[0];
                const second = countValues[1];
                if ((first === 3 && second === 2) || 
                    (first === 3 && jokers >= 2) ||
                    (first === 2 && second === 2 && jokers >= 1)) {
                    return 'フルハウス';
                }
            }
            
            if (maxCount + jokers >= 3) {
                return 'スリーカード';
            }
            
            // ツーペア判定
            if (countValues.filter(c => c === 2).length >= 2) {
                return 'ツーペア';
            }
            
            if (countValues.filter(c => c === 2).length === 1 && jokers >= 2) {
                return 'ツーペア';
            }
            
            if (maxCount + jokers >= 2) {
                return 'ワンペア';
            }
            
            return 'ノーペア';
        }

        // ストレート判定
        function checkStraight(ranks, jokers) {
            if (ranks.length === 0) return jokers >= 5;
            
            let neededJokers = 0;
            let consecutive = 1;
            
            for (let i = 1; i < ranks.length; i++) {
                const gap = ranks[i] - ranks[i-1];
                if (gap === 1) {
                    consecutive++;
                } else {
                    neededJokers += gap - 1;
                    consecutive += gap;
                }
            }
            
            return consecutive >= 5 && neededJokers <= jokers;
        }

        // ロイヤルストレート判定
        function checkRoyalStraight(ranks, jokers) {
            const royal = [1, 10, 11, 12, 13]; // A, 10, J, Q, K
            let matched = 0;
            
            for (let rank of royal) {
                if (ranks.includes(rank)) {
                    matched++;
                }
            }
            
            return matched + jokers >= 5;
        }

        // スコア計算
        function calculateScore(hand, selectedCards) {
            const baseScore = pokerHands[hand];
            let multiplier = 1;
            let bonusDetails = [];
            
            // レアリティボーナス計算
            selectedCards.forEach(index => {
                const card = gameState.currentCards[index];
                if (card.rarity === 'SR') {
                    multiplier *= 2;
                    bonusDetails.push('SR×2');
                } else if (card.rarity === 'SSR') {
                    multiplier *= 30;
                    bonusDetails.push('SSR×30');
                }
            });
            
            const finalScore = baseScore * multiplier;
            
            return {
                baseScore,
                multiplier,
                finalScore,
                bonusDetails
            };
        }

        // ジェムエフェクト
        function createGemEffect(amount) {
            const effect = document.createElement('div');
            effect.className = 'gem-effect';
            effect.textContent = `+${amount.toLocaleString()}`;
            effect.style.left = Math.random() * window.innerWidth + 'px';
            effect.style.top = Math.random() * window.innerHeight + 'px';
            document.body.appendChild(effect);
            
            setTimeout(() => {
                effect.remove();
            }, 2000);
        }

        // ガチャアニメーション
        function showGachaAnimation(cards) {
            return new Promise((resolve) => {
                const animation = document.getElementById('gacha-animation');
                const gachaCard = document.getElementById('gacha-card');
                const skipBtn = document.getElementById('skip-btn');
                
                let currentIndex = 0;
                let isSkipped = false;
                
                animation.classList.remove('hidden');
                
                const showNextCard = () => {
                    if (currentIndex >= cards.length || isSkipped) {
                        animation.classList.add('hidden');
                        resolve();
                        return;
                    }
                    
                    const card = cards[currentIndex];
                    gachaCard.innerHTML = `
                        <div>
                            <div style="font-size: 16px;">${getCardDisplayText(card)}</div>
                            <div style="font-size: 12px; margin-top: 5px;" class="rarity-${card.rarity}">
                                ${card.rarity}
                            </div>
                        </div>
                    `;
                    
                    currentIndex++;
                    
                    setTimeout(() => {
                        showNextCard();
                    }, 800);
                };
                
                gachaCard.addEventListener('click', showNextCard);
                skipBtn.addEventListener('click', () => {
                    isSkipped = true;
                    showNextCard();
                });
                
                showNextCard();
            });
        }

        // UI更新
        function updateUI() {
            document.getElementById('round').textContent = `${gameState.currentRound}/10`;
            document.getElementById('gems').textContent = gameState.gems.toLocaleString();
            document.getElementById('gacha-btn').disabled = gameState.gems < 3000;
        }

        // ゲームオーバー・クリア判定
        function checkGameEnd() {
            if (gameState.currentRound > 10) {
                showGameEnd('ゲームクリア!', `おめでとうございます!\n全10ラウンドを完走しました!`);
                return true;
            }
            
            if (gameState.gems < 3000) {
                showGameEnd('ゲームオーバー', 'ジェムが足りません...');
                return true;
            }
            
            return false;
        }

        // ゲーム終了画面表示
        function showGameEnd(title, message) {
            const gameEnd = document.getElementById('game-end');
            const gameEndTitle = document.getElementById('game-end-title');
            const finalScore = document.getElementById('final-score');
            
            gameEndTitle.textContent = title;
            finalScore.textContent = `最終スコア: ${gameState.gems.toLocaleString()} ジェム`;
            
            gameEnd.classList.remove('hidden');
        }

        // ゲームリセット
        function resetGame() {
            gameState = {
                currentRound: 1,
                gems: 30000,
                currentCards: [],
                selectedCards: [],
                isAnimating: false
            };
            
            document.getElementById('game-end').classList.add('hidden');
            document.getElementById('gacha-phase').classList.remove('hidden');
            document.getElementById('selection-phase').classList.add('hidden');
            document.getElementById('result-phase').classList.add('hidden');
            
            updateUI();
        }

        // イベントリスナー設定
        document.getElementById('gacha-btn').addEventListener('click', async () => {
            if (gameState.gems < 3000) return;
            
            gameState.gems -= 3000;
            updateUI();
            
            // ガチャ実行
            const cards = drawGacha();
            gameState.currentCards = cards;
            gameState.selectedCards = [];
            
            // アニメーション表示
            await showGachaAnimation(cards);
            
            // カード選択フェーズに移行
            document.getElementById('gacha-phase').classList.add('hidden');
            document.getElementById('selection-phase').classList.remove('hidden');
            
            displayCards(cards);
            updateSelectionUI();
        });

        document.getElementById('confirm-btn').addEventListener('click', () => {
            if (gameState.selectedCards.length !== 5) return;
            
            // 選択されたカードを取得
            const selectedCards = gameState.selectedCards.map(index => 
                gameState.currentCards[index]
            );
            
            // 役判定
            const hand = evaluateHand(selectedCards);
            
            // スコア計算
            const scoreResult = calculateScore(hand, gameState.selectedCards);
            
            // ジェム追加
            gameState.gems += scoreResult.finalScore;
            
            // 結果表示
            document.getElementById('hand-name').textContent = hand;
            document.getElementById('score-display').textContent = 
                `獲得ジェム: ${scoreResult.finalScore.toLocaleString()}`;
            
            let bonusText = `基本スコア: ${scoreResult.baseScore.toLocaleString()}`;
            if (scoreResult.bonusDetails.length > 0) {
                bonusText += `\nレアリティボーナス: ${scoreResult.bonusDetails.join(', ')}`;
                bonusText += `\n倍率: ×${scoreResult.multiplier}`;
            }
            document.getElementById('bonus-display').textContent = bonusText;
            
            // エフェクト
            if (scoreResult.finalScore > 0) {
                createGemEffect(scoreResult.finalScore);
            }
            
            // フェーズ切り替え
            document.getElementById('selection-phase').classList.add('hidden');
            document.getElementById('result-phase').classList.remove('hidden');
            
            updateUI();
        });

        document.getElementById('next-round-btn').addEventListener('click', () => {
            gameState.currentRound++;
            
            // ゲーム終了判定
            if (checkGameEnd()) {
                return;
            }
            
            // 次のラウンドに進む
            document.getElementById('result-phase').classList.add('hidden');
            document.getElementById('gacha-phase').classList.remove('hidden');
            
            updateUI();
        });

        document.getElementById('restart-btn').addEventListener('click', () => {
            resetGame();
        });

        // 初期化
        updateUI();
    </script>
</body>
</html>

🔔 補足・注意事項

このコードや説明は、Claudeのバージョンや環境によって出力内容が異なる可能性があります。
同じ命令をしても、まったく違うコードが返ってくることもあるので、再現性は参考程度にご覧ください。


🔔 リンク

👉 AM0500 作品一覧
👉 AM0500 GAME LAB

このブログを検索

アクションゲームメーカーでタイトル画面からゲーム本編へ切り替える基本手順(画面遷移のやり方)

 こんにちは、AM0500です。 今回は、アクションゲームメーカー(AGM)で タイトル画面から実際のゲームシーンへ移動させる手順 をまとめます。 AGMでは、シーンの移動を個別のプログラムで書くのではなく、 「SceneTransition」という専用の画面 で一括管理します。...

最近の投稿

記事まとめ

自己紹介

自分の写真
飼い猫のモイちゃんに朝5時に起こされる生活が続いたのでAM0500と名付けました。モイちゃんへの愛でゲーム制作にも挑戦しはじめて、素人ながらも、モイちゃんを主役にしたゲームを作り上げた時の喜びは忘れられません。そこから趣味でゲーム開発を始めました。勉強中の身ですがよろしくお願いします。

QooQ