0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

格闘ゲームのコンボシミュレーターをPHPで作ってみた

Last updated at Posted at 2025-11-11

暇だったので、PHPでコンボシミュレーター作りました。
コピペで誰でも使えるように1ファイルで作動します。
例として、自分がやっているガンダムのゲームを土台に作成してみました。

php

<?php
// パラメーター //————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
$count = 0;
$pero_system = false; // trueにすると初段だけダメージ1.5倍
$big_flg = false;
$detail = true; // 詳しく知りたい場合trueに
$limit_350 = true; // 350以上のダメージの伸びを大幅に軽減する
$million_half = 500000;
$output = 15; // 最大コンボの列挙数
$big_count = 0;
$top_index = 3;
$large_combo = [];
$big_large_combo = [];
$simulate_combo = [];
$EX_Burst = 'N';  // ['N', 'F', 'E', 'S']; // 覚醒
$target = ['dmg' => 0, 'rev' => 100, 'down' => 0]; // 対象の初期パラメータ
// $target = ['dmg' => 75, 'rev' => 70, 'down' => 2]; // 対象の初期パラメータ(これは射撃始動)
$sample_hit = ['前2*1', '前2*2'];
//———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

$gundam = [
    // サンプル
    'Sample' => [
        'burst_param' => ['F' => 1.07, 'S' => 1.05, 'E' => 1, 'N' => 1],
        'N1' => ['join' => array_merge(['all', 'N3'], $sample_hit), 'dmg' => 70, 'rev' => 20, 'down' => 1.7, 'next_false' => [], 'green' => [], 'red' => true, 'fighting' => true],
        'N2' => ['join' => ['N1'], 'dmg' => 60, 'rev' => 15, 'down' => 0.3, 'next_false' => [], 'green' => ['前2*3'], 'red' => false, 'fighting' => true],
        'N3' => ['join' => ['N2'], 'dmg' => 80, 'rev' => 12, 'down' => 1, 'next_false' => [], 'green' => ['N2'], 'red' => false, 'fighting' => true],
        '前1' => ['join' => array_merge(['all', 'N3'], $sample_hit), 'dmg' => 70, 'rev' => 20, 'down' => 1.7, 'next_false' => [], 'green' => [], 'red' => true, 'fighting' => true],
        '前2*1' => ['join' => ['前1'], 'dmg' => 20, 'rev' => 5, 'down' => 0.1, 'next_false' => [], 'green' => [], 'red' => false, 'fighting' => true],
        '前2*2' => ['join' => ['前2*1'], 'dmg' => 20, 'rev' => 5, 'down' => 0.1, 'next_false' => [], 'green' => [], 'red' => false, 'fighting' => true],
        '前2*3' => ['join' => ['前2*2'], 'dmg' => 20, 'rev' => 5, 'down' => 0.1, 'next_false' => [], 'green' => [], 'red' => false, 'fighting' => true],
        '前3' => ['join' => ['前2*3'], 'dmg' => 80, 'rev' => 12, 'down' => 1, 'next_false' => ['all'], 'green' => [], 'red' => false, 'fighting' => true],
        '前派1' => ['join' => ['N2'], 'dmg' => 100, 'rev' => 40, 'down' => 2, 'next_false' => [], 'green' => [], 'red' => false, 'fighting' => true],
        
        'BR' => ['join' => array_merge(['all'], $sample_hit), 'dmg' => 75, 'rev' => 30, 'down' => 2, 'next_false' => ['N3'], 'green' => [], 'red' => true, 'fighting' => false],
        'CS' => ['join' => array_merge(['all'], ), 'dmg' => 120, 'rev' => 30, 'down' => 5, 'next_false' => [], 'green' => [], 'red' => true, 'fighting' => false],
    ], // ↓にキャラクターを追加
];

/**
 * 指定されたコマンドで全てのコンボパターンを検出
 * @param array $commands コンボレシピ
 * @param array $target コンボ対象の状態
 * @param string $before 前回のコンボ
 * @param string $result コンボ結果
 * @param boolean $red_lock 赤ロ判定
 */
function combo_logic($commands, $target, $before = '', $result = '', $red_lock = true)
{
    global $count, $large_combo, $big_flg, $big_large_combo, $big_count, $million_half, $top_index, $detail;

    foreach ($commands as $command => $params) {
        if ($command === 'burst_param') { continue; }
        // コンボルートが正常か
        if ((in_array('all', $params['join']) || in_array($before, $params['join']) || in_array($before, $params['green'])) && $command !== 'burst_param') {
            // allの除外パターンの場合
            if (in_array('all', $params['join']) && in_array($before, $params['join'])) {
                continue;
            }
            $next_red_lock = $red_lock;
            // 透かしコンの可能性
            if (in_array($before, $params['green'])) {
                // 透かしコン
                if (!in_array($before, $params['join'])) {
                    $next_red_lock = false;
                }
            }
            // 透かしコン解除
            if ($next_red_lock === false && $params['red']) {
                $next_red_lock = true;
            }
            // 継ぐ透かしコンがない場合
            if ($next_red_lock === false && !in_array($before, $params['green'])) {
                continue;
            }
            $next = $result . $command;
            $before_target = $target;
            foreach ($params as $param => $value) {
                if ($param === 'dmg' || $param === 'rev' || $param === 'down') {
                    $target = attack($param, $value, $target, $params['fighting']);
                } elseif ($param === 'next_false') {
                    break;
                }
            }
        } else {
            continue;
        }
        // ダウン値が5以上(>= 5にするとなぜかバグる)、または追撃不可の場合、コンボを中止し保存
        if ($target['down'] > 4.99999999999 || (in_array($before, $params['next_false']) || in_array('all', $params['next_false']))) {
            $large_combo['dmg'][$count] = $target['dmg'];
            $large_combo['combo'][$count] = $next . ' (' . $large_combo['dmg'][$count] . 'dmg)';
            $target = $before_target;
            $count++;
            // コンボパターンが膨大すぎる場合
            if ($count >= $million_half) {
                $big_flg = true;
                array_multisort($large_combo['dmg'], SORT_DESC, $large_combo['combo']);
                foreach ($large_combo['combo'] as $index => $combo) {
                    if ($index >= $top_index) { break; }
                    $big_large_combo['dmg'][$big_count] = $large_combo['dmg'][$index];
                    $big_large_combo['combo'][$big_count] = $combo;
                    $big_count++;
                    if ($detail) { echo $index . ' : ' . $combo . PHP_EOL . PHP_EOL; }
                }
                echo PHP_EOL;
                $count = 0;
                $large_combo = [];
            }
        } else {
            combo_logic($commands, $target, $command, $next, $next_red_lock);
        }
        $target = $before_target;
    }
}

/**
 * コンボ対象の状態を更新
 * @param string $param 属性の種類
 * @param integer $value 属性の値 
 * @param array $target コンボ対象の状態
 */
function attack($param, $value, $target, $fighting)
{
    global $pero_system, $gundam, $machine, $EX_Burst, $limit_350;
    // 覚醒ごとの火力恩恵計算
    if ($fighting && $EX_Burst === 'F') {
        $value *= $gundam[$machine]['burst_param'][$EX_Burst];
    } elseif ($fighting === false && $EX_Burst === 'S') {
        $value *= $gundam[$machine]['burst_param'][$EX_Burst];
    }
    switch ($param) {
        case 'dmg':
            // ペロシステム
            $BASE_DMG = $pero_system && $target['dmg'] === 0 ? 1.5 : 1;
            if ($limit_350) {
                // 350補正
                if ($target['dmg'] < 350 && ($target['dmg'] + ceil($value * ($target['rev'] / 100) * $BASE_DMG)) >= 350) {
                    $target['dmg'] = 350;
                }
                if ($target['dmg'] >= 350) {
                    if ($value >= 300) {
                        $target['dmg'] += 4;
                    } elseif ($value >= 200) {
                        $target['dmg'] += 3;
                    } elseif ($value >= 100) {
                        $target['dmg'] += 2;
                    } elseif ($value >= 50) {
                        $target['dmg'] += 1;
                    }
                    break;
                }
            }
            // 同時ヒットの例外処理
            if ($value < 0) {
                $target['dmg'] += ceil(abs($value) / 2 * ($target['rev'] / 100) * $BASE_DMG) * 2;
            } else {
                $target['dmg'] += ceil($value * ($target['rev'] / 100) * $BASE_DMG);
            }
            break;
        case 'rev':
            $target['rev'] -= $value;
            // 補正率は10%が最小
            if ($target['rev'] <= 10) {
                $target['rev'] = 10;
            }
            break;
        case 'down':
            $burst_cut = $EX_Burst !== 'N' ? ($EX_Burst === 'F' && $fighting ? 0.7 : 0.9) : 1;
            $target['down'] += $value * $burst_cut;
            // 350以上の強制ダウン値にボーナスダメージ(1)
            if ($target['down'] > 4.99999999999 && $target['dmg'] >= 350 && $limit_350) {
                $target['dmg']++;
            }
            break;
    }
    return $target;
}

/**
 * コンボシミュレート
 * @param array $commands コンボレシピ
 * @param array $combo シミュレートしたいコンボ
 * @param array $target コンボ対象の状態
 */
function combo_simulate($commands, $combo, $target, $detail = false)
{
    global $simulate_combo;
    $simulate_combo['combo'] = $combo;
    foreach ($combo as $command) {
        foreach ($commands[$command] as $param => $value) {
            if ($param === 'dmg' || $param === 'rev' || $param === 'down') {
                $target = attack($param, $value, $target, $commands[$command]['fighting']);
            }
        }
        if ($detail) {
            echo $command . ' : ' . $target['dmg'] . 'dmg ' . $target['rev'] . 'rev ' . $target['down'] . 'down' . PHP_EOL;
        }
    }
    $simulate_combo['target'] = $target;
}

/**
 * @param string $type プログラムの実行コマンド
 * pattern  : パターン網羅
 * simulate : コンボシミュレート
 */
$type = 'pattern';
$machine = 'Sample';

switch ($type) {
    // パターン網羅
    case 'pattern':
        combo_logic($gundam[$machine], $target);  // 実行コマンド
        echo 'Test machine : [' . $machine . ']  EX_Burst : [' . $EX_Burst . ']' .  PHP_EOL;
        // コンボパターンが膨大すぎる場合
        if ($big_flg) {
            echo $million_half * ($big_count / $top_index) + $count . '通りのコンボパターンが検出されました。' . PHP_EOL . PHP_EOL;
            array_multisort($large_combo['dmg'], SORT_DESC, $large_combo['combo']);
            foreach ($large_combo['combo'] as $index => $combo) {
                if ($index >= $top_index) {break;}
                $big_large_combo['dmg'][$big_count] = $large_combo['dmg'][$index];
                $big_large_combo['combo'][$big_count] = $combo;
                $big_count++;
            }
            array_multisort($big_large_combo['dmg'], SORT_DESC, $big_large_combo['combo']);
            foreach ($big_large_combo['combo'] as $index => $combo) {
                echo $index . ' : ' . $combo . PHP_EOL . PHP_EOL;
            }
        } else {
            echo $count . '通りのコンボパターンが検出されました。' . PHP_EOL . PHP_EOL;
            array_multisort($large_combo['dmg'], SORT_DESC, $large_combo['combo']);
            foreach ($large_combo['combo'] as $index => $combo) {
                if ($index >= $output) {break;}
                echo $index . ' : ' . $combo . PHP_EOL . PHP_EOL;
            }
        }
        break;
    // コンボシミュレート
    case 'simulate':
        echo 'Simulate machine : [' . $machine . ']  EX_Burst : [' . $EX_Burst . ']' . PHP_EOL .  PHP_EOL;
        $combo = array_merge(['N1', 'N2', 'N3', 'CS'], );  // 実行コマンド
        combo_simulate($gundam[$machine], $combo, $target, $detail);
        echo PHP_EOL;
        foreach ($simulate_combo['combo'] as $index => $combo) {
            echo $combo;
        }
        echo ' (' . $simulate_combo['target']['dmg'] . 'dmg)' . PHP_EOL;
        if ($detail) {
            echo  PHP_EOL . 'コピペ用$targetパラメータ' . PHP_EOL;
            echo '$target' . " = ['dmg' => " . $simulate_combo['target']['dmg'] . ", 'rev' => " . $simulate_combo['target']['rev'] . ", 'down' => " . $simulate_combo['target']['down'] . "];";
        }
        break;
}

こんな感じです。

キャラクターのパラメータの説明は以下の通りです。

  • burst_param[]:覚醒によるダメージ上昇倍率。Nは非覚醒。
  • join:どのコマンドから追撃可能か。allだと全て、all + コマンドaだと、コマンドa以外の全てから追撃可能。(多段ヒットからの追撃を考慮するとパターン数が多くなるので$sample_hitなども加えて省略可能)
  • dmg:コマンドの威力。威力をマイナスにすることで同時ヒットの計算ができる。
  • rev:補正値。
  • down:ダウン値。今回は5がMAXだが、覚醒(EX_Burst)するとダウン値が下がる。
  • next_false:入力したコマンドから入った場合追撃不可。
  • green:透かしコン。redがtrueな攻撃が入らない限り、追撃はjoinではなくgreenが基になる
  • red:格闘初段などの赤ロック限定のコマンド
  • fighting:格闘か射撃か。覚醒によってダメージ値が変動する。

実際にコンボパターンを網羅してみよう。
下の方の変数に、以下の値をセットして実行します。

$type = 'pattern';
$machine = 'Sample';
結果
Test machine : [Sample]  EX_Burst : [N]
200通りのコンボパターンが検出されました。

0 : 前1前2*1前2*2前2*3N2N3CS (240dmg)

1 : N1N1N2N3CS (238dmg)

2 : 前1N1N2N3CS (238dmg)

3 : N1N2N3CS (234dmg)

4 : N1N2N1N2CS (227dmg)

※実際には$output分だけ結果が出力される。

こんな感じです。
実際にはもっとコンボレシピが多いので、1億通りを超えるキャラクターもいます。

次はコンボシミュレートをしてみましょう。
下の方の変数に、以下の値をセットして実行します。

$type = 'simulate';
$machine = 'Sample';

// 下の方のswitch文の「case:'simulate'」に以下を記入
$combo = array_merge(['BR', 'N1', 'N2', 'N3', '前3', 'CS'], );  // 実行コマンド
結果
Simulate machine : [Sample]  EX_Burst : [N]

BR : 75dmg 70rev 2down
N1 : 124dmg 50rev 3.7down
N2 : 154dmg 35rev 4down
N3 : 182dmg 23rev 5down
前3 : 201dmg 11rev 6down
CS : 215dmg 10rev 11down

BRN1N2N3前3CS (215dmg)

コピペ用$targetパラメータ
$target = ['dmg' => 215, 'rev' => 10, 'down' => 11];

こんな感じです。
このシミュレーションでは何でもありなので、ダウン値が超過しようが追撃不可の攻撃だろうが何でも入ります。夢コンボを作ってみてください。

まとめ
ちなみに、このプログラムはグローバル変数が乱用されてたりクソみたいな三項演算子が使われてたりするので可読性はゴミカスです。
でもやっぱり1ファイルで動くプログラムは魅力的だよネ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?