プログラミング

PHPポケモン「行動順判定+敵ポケモン攻撃編」25

PHP PHPポケモン ポケモン
PHPポケモン「行動順判定+敵ポケモン攻撃編」25

行動順の判定

ポケモンの行動順は以下の通りです。

  1. 技の優先度
  2. すばやさの実数値(補正有り)
  3. 同速の場合は50%の乱数

 

この順番で比較を行い、先行後攻を決めます。ただし、これは両者ともに攻撃を選択した場合のみです。アイテムの使用や交代は技よりも優先されますし(※一部技を除く)、にげるはそれよりも先に行動することになります。

現在はアイテムと交代システムは実装出来ていないため、技選択における行動順序の比較のみを行います。

 

相手ポケモンの行動

 先手と後手を決めるためには、相手にも技の選択をして貰う必要があります。なぜなら、まず「技の優先度」を比較しなければならないからです。そのためには、簡易ながらも相手ポケモン(CP)の行動AIが必要になります。

  

簡易行動AIの作成

 では、バトルコントローラーのアクションメソッドの最初に以下の記述を追加しましょう。

 

バトルコントローラー(/Classes/Controller/BattleController.php
/**
* アクション
*
* @param string $action
* @param mixed $param
* @return void
*/
private function action($action, $param)
{
    // 敵ポケモンの技をインスタンス化
    $e_move = $this->getInstance($this->aiSelectMove());

 

相手の技については、自分がどの行動を行なったとしても基本的には実行されます。なので、最初にAIを使って技を選択させ、インスタンスを作成しておきましょう。

新しく登場したaiSelectMoveというメソッドは新しくAI用のトレイトを作成してそこに作り込んでいきましょう。

 

AIトレイト(/Traits/Battle/EnemyAiTrait.php
<?php
// 敵ポケモンの行動AI
trait EnemyAiTrait
{
    /**
    * 技の選択
    *
    * @return string
    */
    protected function aiSelectMove()
    {
        // 技の一覧を配列形式で取得
        $move = $this->enemy
        ->getMove('array');
        // ランダムで1つ返却
        return $move[array_rand($move)];
    }
 
}

 

初代ポケモンでは、ポケモンのタイプ相性に合わせて技が選択されていましたが、そのせいで攻撃技ではなく変化技を連打してしまうなどといった現象が起こってしまい、一種の不具合とまで言われていました。PHPポケモンではそういったことを防ぐためにも、独自のAIを組み上げていきますが、非常に作り込みが重視される部分のため本格的な作り込みは後回しにします。

 

今回実装した技選択のAIは、array_randを使って技をランダムで1つ選ぶというだけのものです。getMoveではインスタンス化した技の一覧を返却していましたが、すべてをインスタンス化する必要はないのでプロパティに格納された状態(配列)のまま受け取れるように引数を設定しました。

 

Get格納トレイト
/**
* 覚えている技の一覧を取得する
* @param string (object|array)
* @return array
*/
public function getMove($param='object')
{
    switch ($param) {
        case 'object':
        // オブジェクトで返却
        // array_mapで配列内の技クラスをインスタンス化
        return array_map([$this, 'getInstance'], $this->move);
        case 'array':
        // 配列で返却(そのまま)
        return $this->move;
        default:
        // オブジェクト(デフォルト)
        return array_map([$this, 'getInstance'], $this->move);
    }
 
}

※今更ですが、残り技ポイントの設定が抜けていました。こちらを変更するとポケモンの技回りの処理の大幅変更が予想されるため、後ほど実装予定です。

 

先行の取得

 それでは行動順の判定を実装します。ダブルバトルが実装されていない関係上、先行か後攻かを判定できれば行動順を決めることができます。なので「自分が先行かどうか」を判定するためのメソッドを作成し、その結果に合わせた分岐を作成します。

 

バトルコントローラー(/Classes/Controller/BattleController.php
/**
* たたかう
*/
case 'fight':
// 自ポケモンの技をインスタンス化
$p_move = $this->getInstance($param);
// 行動順の判定
if($this->checkFirstMove($p_move, $e_move)){
    // 先行
    // 自ポケモンの攻撃
    $e_damage = $this->attack($this->pokemon, $this->enemy, $p_move);
    // 敵ポケモンの攻撃
    $p_damage = $this->attack($this->enemy, $this->pokemon, $e_move);
}else{
    // 後攻
    // 敵ポケモンの攻撃
    $p_damage = $this->attack($this->enemy, $this->pokemon, $e_move);
    // 自ポケモンの攻撃
    $e_damage = $this->attack($this->pokemon, $this->enemy, $p_move);
}
break;

※敵ポケモン同様に、自ポケモンの技のインスタンス化もコントローラー内で実行する仕様に変更しました

 

checkFirstMoveというメソッドを使い、自分が「先行かどうか」をtrueまたはfalseで判定します。true(先行)が返ってくれば自→敵という順番でattackを実行、false(後攻)であれば敵→自という順番でattackを実行するという単純な仕組みです。

attackメソッドを複数回使用することになるため、ダメージはresponseに格納せず返り値として受け取っています。

 

では、checkFirstMoveメソッドの処理をみてみましょう。

 

バトルコントローラー(/Classes/Controller/BattleController.php
/**
* 先手の判定
*
* @param object 自ポケモンの技 $p_move
* @param object 敵ポケモンの技 $e_move
* @return boolean (pokemon > enemy):true (pokemon < enemy):false
*/
private function checkFirstMove($p_move, $e_move)
{
    /**
    * 優先度の比較
    */
    // 判定
    if($p_move->getPriority() > $e_move->getPriority()){
        // 優先度が高い
        return true;
    }elseif($p_move->getPriority() < $e_move->getPriority()){
        // 優先度が低い
        return false;
    }
    /**
    * すばやさの比較
    */
    // 自ポケモンの素早さ(補正あり実数値)
    $p_speed = $this->pokemon
    ->getStats('Speed', true);
    // 敵ポケモンの素早さ(補正あり実数値)
    $e_speed = $this->enemy
    ->getStats('Speed', true);
    // 判定
    if($p_speed > $e_speed){
        // 素早さが上回っている
        return true;
    }elseif($p_speed < $e_speed){
        // 素早さが下回っている
        return false;
    }elseif($p_speed === $e_speed){
        // 同速(50%の乱数)
        if(random_int(0, 1)){
            return true;
        }else{
            return false;
        }
    }
}

 

優先度ランクの比較

 まず最初に、技の優先度をそれぞれgetPriorityで取得して比較します。

/**
* 優先度の比較
*/
// 判定
if($p_move->getPriority() > $e_move->getPriority()){
    // 優先度が高い
    return true;
}elseif($p_move->getPriority() < $e_move->getPriority()){
    // 優先度が低い
    return false;
}

 

ここで差があれば、その時点で行動順が決定します。もし優先度が同じであれば、素早さの判定をしなければならないため、elseを使用せずelseifで分岐するようにしておいてください。

 

素早さの比較

 次に素早さの比較です。

/**
* すばやさの比較
*/
// 自ポケモンの素早さ(補正あり実数値)
$p_speed = $this->pokemon
->getStats('Speed', true);
// 敵ポケモンの素早さ(補正あり実数値)
$e_speed = $this->enemy
->getStats('Speed', true);
// 判定
if($p_speed > $e_speed){
    // 素早さが上回っている
    return true;
}elseif($p_speed < $e_speed){
    // 素早さが下回っている
    return false;
}elseif($p_speed === $e_speed){
    // 同速(50%の乱数)
    if(random_int(0, 1)){
        return true;
    }else{
        return false;
    }
}

 

それぞれの素早さを補正値込みで取得し、その差を比較しています。こちらもelseifで判定しながら、同速($p_speed === $e_speed)であれば50%の乱数で判定しました。

※最後の判定はelseでも構いません。今回はわかりやすくするためにelseifを使って比較式を記述しました。

 

50%の乱数の求め方は、今までの用にmt_randを使っても構いませんが、PHP7からはrandom_intという暗号化でも使用できる疑似乱数生成方法があったのでそちらを採用しました。 

 

/2を求めるだけであれば、最小値(第1引数)を0、最大値(第2引数)を1でifに掛けるだけで判定することができます。

 

最終的な出力結果を確認するために、setResponseswitch外に移動させて返却データを増やしておきましょう。 

// 結果の返却
$this->setResponse([
    '受けたダメージ' => $p_damage ?? 0,
    'ランク' => $this->pokemon->getRank(),
], $this->pokemon->getName());
$this->setResponse([
    '受けたダメージ' => $e_damage ?? 0,
    'ランク' => $this->enemy->getRank(),
], $this->enemy->getName());

 

出力結果は以下の通りです。

 

  

敵ポケモンも素早さに合わせて攻撃を仕掛けてきましたね。ピカチュウのステータスに変化があることも確認ができます。

 

にげる失敗時の相手の行動

 それではにげる失敗時の行動についても合わせて設定しておきましょう。もし失敗した場合、相手から技を受けることになります。なので、相手の攻撃処理を追加しましょう。

 

バトルコントローラー(/Classes/Controller/BattleController.php
/**
* にげる
*/
case 'run':
// $this->run++;
if($this->checkRun()){
    unset($_SESSION['enemy']);
    unset($_SESSION['rank']);
    unset($_SESSION['run']);
    header("Location: ./home.php", true, 307);
    exit;
}
$this->setMessage('逃げられない!');
// 敵ポケモンの攻撃
$p_damage = $this->attack($this->enemy, $this->pokemon, $e_move);
break;

 

これで「にげる」に失敗すると、相手から技を受けることになります。

 

 

 

以下がバトルコントローラーの最終コードになります。

 

バトルコントローラー(/Classes/Controller/BattleController.php
<?php
require_once(__DIR__.'/../Controller.php');
require_once(__DIR__.'/../../Traits/Battle/AttackTrait.php');
require_once(__DIR__.'/../../Traits/Battle/EnemyAiTrait.php');
 
// バトル用コントローラー
class BattleController extends Controller
{
    use AttackTrait;
    use EnemyAiTrait;
 
    /**
    * 敵ポケモン格納用
    * @var object
    */
    protected $enemy;
 
    /**
    * 逃走を試みた回数
    * @var integer
    */
    public $run = 1;
 
    /**
    * @return void
    */
    public function __construct()
    {
        // 親コンストラクタの呼び出し
        parent::__construct();
        // 自ポケモンの格納
        $this->myPokemon($_SESSION['pokemon']);
        // 敵ポケモンの格納
        if(isset($_SESSION['enemy'])){
            $this->enemyPokemon($_SESSION['enemy']);
        }else{
            $this->enemyPokemon();
        }
        // ランク(バトルステータス)の引き継ぎ
        if(isset($_SESSION['rank'])){
            $this->pokemon
            ->setRank($_SESSION['rank']['pokemon']);
            $this->enemy
            ->setRank($_SESSION['rank']['enemy']);
        }
        // にげるの実行回数を引き継ぎ
        if(isset($_SESSION['run'])){
            $this->run = $_SESSION['run'];
        }
        // アクションが選択された
        if(isset($_POST['action'])){
            // アクションメソッドの実行
            $this->action(htmlspecialchars($_POST['action']), htmlspecialchars($_POST['param'] ?? null));
            return;
        }
    }
 
    /**
    * 自ポケモンの格納
    *
    * @param array $pokemon
    * @return void
    */
    private function myPokemon($pokemon)
    {
        $this->pokemon = new $pokemon['class_name']($pokemon);
    }
 
    /**
    * 敵ポケモンの格納
    *
    * @param array|null $pokemon
    * @return void
    */
    private function enemyPokemon($pokemon=null)
    {
        if(is_null($pokemon)){
            $this->enemy = new Fushigidane();
            $this->setMessage('野生の'.$this->enemy->getName().'が現れた!');
        }else{
            $this->enemy = new $pokemon['class_name']($pokemon);
        }
    }
 
    /**
    * アクション
    *
    * @param string $action
    * @param mixed $param
    * @return void
    */
    private function action($action, $param)
    {
        // 敵ポケモンの技をインスタンス化
        $e_move = $this->getInstance($this->aiSelectMove());
        switch ($action) {
            /**
            * にげる
            */
            case 'run':
            // $this->run++;
            if($this->checkRun()){
                unset($_SESSION['enemy']);
                unset($_SESSION['rank']);
                unset($_SESSION['run']);
                header("Location: ./home.php", true, 307);
                exit;
            }
            $this->setMessage('逃げられない!');
            // 敵ポケモンの攻撃
            $p_damage = $this->attack($this->enemy, $this->pokemon, $e_move);
            break;
            /**
            * たたかう
            */
            case 'fight':
            // 自ポケモンの技をインスタンス化
            $p_move = $this->getInstance($param);
            // 行動順の判定
            if($this->checkFirstMove($p_move, $e_move)){
                // 先行
                // 自ポケモンの攻撃
                $e_damage = $this->attack($this->pokemon, $this->enemy, $p_move);
                // 敵ポケモンの攻撃
                $p_damage = $this->attack($this->enemy, $this->pokemon, $e_move);
            }else{
                // 後攻
                // 敵ポケモンの攻撃
                $p_damage = $this->attack($this->enemy, $this->pokemon, $e_move);
                // 自ポケモンの攻撃
                $e_damage = $this->attack($this->pokemon, $this->enemy, $p_move);
            }
            break;
        }
        // 結果の返却
        $this->setResponse([
            '受けたダメージ' => $p_damage ?? 0,
            'ランク' => $this->pokemon->getRank(),
        ], $this->pokemon->getName());
        $this->setResponse([
            '受けたダメージ' => $e_damage ?? 0,
            'ランク' => $this->enemy->getRank(),
        ], $this->enemy->getName());
    }
 
    /**
    * 敵ポケモン情報の取得
    *
    * @return object
    */
    public function getEnemy()
    {
        return $this->enemy;
    }
 
    /**
    * にげる判定
    * F = (A × 128 / B) + 30 × C
    * Fを256で割った値 → 逃走成功率
    * @var A 味方ポケモンのすばやさ(ランク補正有り)
    * @var B 相手ポケモンのすばやさ(ランク補正無し)
    * @var C 逃走を試みた回数
    * @return boolean
    */
    private function checkRun()
    {
        // 味方の素早さを取得(ランク補正有り)
        $a = $this->pokemon
        ->getStats('Speed', true);
        // 相手の素早さを取得(ランク補正無し)
        $b = $this->enemy
        ->getStats('Speed');
        // 逃走を試みた回数
        $c = $this->run;
        // 計算式への当てはめ
        $f = ($a * 128 / $b) + 30 * $c;
        // 確率計算
        if(round($f / 256, 2) * 100 >= mt_rand(0, 100)){
            return true;    # 逃走成功
        }else{
            return false;   # 逃走失敗
        }
    }
 
    /**
    * 先手の判定
    *
    * @param object 自ポケモンの技 $p_move
    * @param object 敵ポケモンの技 $e_move
    * @return boolean (pokemon > enemy):true (pokemon < enemy):false
    */
    private function checkFirstMove($p_move, $e_move)
    {
        /**
        * 優先度の比較
        */
        // 判定
        if($p_move->getPriority() > $e_move->getPriority()){
            // 優先度が高い
            return true;
        }elseif($p_move->getPriority() < $e_move->getPriority()){
            // 優先度が低い
            return false;
        }
        /**
        * すばやさの比較
        */
        // 自ポケモンの素早さ(補正あり実数値)
        $p_speed = $this->pokemon
        ->getStats('Speed', true);
        // 敵ポケモンの素早さ(補正あり実数値)
        $e_speed = $this->enemy
        ->getStats('Speed', true);
        // 判定
        if($p_speed > $e_speed){
            // 素早さが上回っている
            return true;
        }elseif($p_speed < $e_speed){
            // 素早さが下回っている
            return false;
        }elseif($p_speed === $e_speed){
            // 同速(50%の乱数)
            if(random_int(0, 1)){
                return true;
            }else{
                return false;
            }
        }
    }
 
}

 

まとめ

いかがだったでしょうか。

今回のPHPポケモンは「行動判定」と「敵ポケモンの攻撃」についてご紹介しました。

現在はランダムの技選択のため戦略性は乏しいかも知れませんが、AIを作り込んでいけばそれだけでかなり高難度のバトルシステムを再現することも可能です。

ゲームづくりに興味がある人は、ぜひ参考にしてくださいね。

 

注目の記事

起業・独立を考えている人に向けた具体的なアドバイス3選
雑記
起業・独立を考えている人に向けた具体的なアドバイス3選

  「どのタイミングで起業すべき?」 「会社を作るためにはどういったことをすればいいの?」   あなたはこんな悩みを抱えていませんか? 今回は、これから独立・起業をしようと考えている方や、そういった野望をいだいている人に向けて、自らの経験を元に具体的なアドバイスを3つピックアップしてご紹...

【Wi-Fi6とは】スマホやPCの買い替えは必要?騙されないための基礎知識
ネットワーク
5G,IEEE 802.11ax,Wi-Fi
【Wi-Fi6とは】スマホやPCの買い替えは必要?騙されないための基礎知識

  本格的に5G普及への取り組みが始まり、Wi-Fi6といった新世代の規格が出たことによって、超情報化社会へ加速しました。 ですが、こういったナンバリングが一般化することによって、わからずに高性能な機器を斡旋されて無駄な購入をさせられたり、わからないことを理由に詐欺的営業を仕掛けてくる悪意ある人...

PHPポケモン「バトルシステム実装編〜補正値計算・乱数・急所〜」21
プログラミング
PHP,PHPポケモン,ポケモン
PHPポケモン「バトルシステム実装編〜補正値計算・乱数・急所〜」21

バトルシステムの実装  今回は「急所」と「乱数」と「タイプ一致」の判定と補正を実装していきます。 ちなみにですが、ポケモンwikiを熟読したところ、補正値の計算にも順番があり、計算後に小数点の切り捨てや五捨五超入をするなど、そこそこ複雑な計算順序がありましたが、今回はそこまで精密に再現せず、補正値(...

オウムがえし編 PHPポケモン 68
プログラミング
PHP,PHPポケモン,ポケモン
オウムがえし編 PHPポケモン 68

オウムがえしとは 今回は久々に新しい技を実装します。それが「オウムがえし」です。 オウムがえし(ポケモンwiki) https://wiki.ポケモン.com/wiki/オウムがえし   初代で登場した技であり、序盤に登場するポッポやオニスズメがレベルアップで覚える技の1つです。最新世代ではオウムがえしという技は実...

バトルステータス可視化編 PHPポケモン 70
プログラミング
PHP,PHPポケモン,ポケモン
バトルステータス可視化編 PHPポケモン 70

バトルステータスの可視化 現在のポケモンでは、バトルステータスの現状を可視化できるようになっており、どれぐらいのランク補正がかかっているか、フィールドがどういう状態になっているかがわかるようになっています。 初代、第2世代等では確認できなかった内容ではありますが、システムとしては便利な要素でも...

PHPポケモン「アクション制御編」27
プログラミング
JavaScript,jQuery,PHP,PHPポケモン,ポケモン
PHPポケモン「アクション制御編」27

  今回のPHPポケモンでは主に画面の作り込みをしていきます。 とは言っても、ガッチリCSSを書いてよりゲームらしい見た目にするわけではなく、あくまで「ゲームシステムを再現するため」だけに整えていくのが目的です。   ということで、今回はPHPよりもBootstrapさんとjQueryさんに活躍してもらいます。   ...

初心者向けのブログで稼ぐ方法【アフィリエイト編:Google AdSense】
SEO対策
Google AdSense,アフィリエイト,クリック単価,クリック数,ブログ,広告収入
初心者向けのブログで稼ぐ方法【アフィリエイト編:Google AdSense】

  ブログを始めたほとんどの人が アフィリエイトで稼ぎたい   という強い思いを持っているのではないでしょうか。しかし現実は厳しく、広告の審査さえ通らず挫折してしまうといったことは多いです。 中には審査が通っているが、全然広告収入が得られないという人もいるでしょう。 毎日ブログを書いているの...

わざマシン編 作成 PHPポケモン104
プログラミング
PHP,PHPポケモン,ポケモン
わざマシン編 作成 PHPポケモン104

わざマシンとは ポケモンはレベルアップ以外でも技を習得することができます。それが「わざマシン」というアイテムです。  わざマシン(ポケモンwiki) https://wiki.ポケモン.com/wiki/わざマシン   最新世代では「技レコード」というものが有り、使い切りとなっています。初代ではわざマシン自体も使い...

カテゴリ

SEO対策 イベント デザイン ネットワーク ビジネスモデル フリーランス プログラミング マーケティング ライティング 動画編集 雑記

タグ

5G Adobe AfterEffects AI ajax amazon Animate api artisan atom Automator AWS Bluetooth CSS CVR description EC-CUBE4 ECショップ ESLint Facebook feedly foreach function Google Google AdSense Honeycode htaccess HTML IEEE 802.11ax Illustrator Instagram IoT JavaScript jQuery jQuery UI keyword LAN Laravel Linux MacBook MAMP meta MLM MySQL NoCode note OS OSI参照モデル Paypal Photoshop PHP phpMyAdmin PHPポケモン PremierePro rss SEO SEO対策 Sequel Pro Skype SNS SSH Symfony TCP/IP title Toastr Trait Twig Twitter UCC V系 WAN WebSub Wi-Fi wiki Windows WordPress XAMPP xml Xserver YouTube YouTuber Zoom アーティスト アウトプット アクセス層 アニメーション アフィリエイト イーブイ インターネット インプット エンジニア オブジェクト指向 お金配り クリック単価 クリック数 コミュニケーション能力 コロナ コンサルティング サムネイル システムエンジニア スタートアップ スタイルシート スパム データベース ディープフェイク デザイナー デザイン テレワーク ナンパ ニュース ネットワークモデル ノマドワーク バナー ピカチュウ ビジネス フィード フリーランス ブロガー ブログ プログラマー プログラミング プログラミング学習 プログラミング教育 プロトコル ホームページ制作 ポケモン マークアップ マーケティング メール リモートワーク レンダリング 三井住友 三宮 仕事依頼 児童デイ 児童デイサービス 児童発達支援 公開鍵 初心者 助成金 勉強法 営業 広告 広告収入 必勝マニュアル 放課後等デイサービス 朝活 楽天 深層学習 無線LAN 独立 神戸 福祉 秘密鍵 翻訳 自己啓発 英語 見積書 計算機 読書 起業 迷惑メール 配列 銀の弾丸 集客 雑学力