プログラミング

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を作り込んでいけばそれだけでかなり高難度のバトルシステムを再現することも可能です。

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

 

注目の記事

具体的にフリーランスで生きていくには -独立してからの働き方を徹底解説-
フリーランス
フリーランス,独立
具体的にフリーランスで生きていくには -独立してからの働き方を徹底解説-

  安定した仕事に就きたい。。。   多くの人がそう思っていた時代はどんどんと変化して、今やフリーランスでいることの方が安定しているのではないかと言えるような時代です。 そう、もはや会社員でいる人の大半が安定ではなくなってきているのです。 フリーランスでいる人も、根本的に安定という...

投稿タイトルを自動生成する方法【WordPressを便利にカスタマイズ】
プログラミング
PHP,WordPress
投稿タイトルを自動生成する方法【WordPressを便利にカスタマイズ】

  カスタムフィールドを含む投稿の場合、わざわざタイトルを設定する必要がないものもあります。 そういったときに、カスタムフィールド値を使ってタイトルを自動生成してくれると楽ですし、一覧で見てもわかりやすい状態にすることができます。   今回はWordPressで「投稿タイトルを自動生成する方法」につ...

スキル習得で挫折しないための3ステップ
雑記
デザイン,ブログ,プログラミング
スキル習得で挫折しないための3ステップ

  せっかく高い教材を買ったのに、気づいたら積んでしまっている・・・   そんな悩み、あなたには有りませんか? 今回は、そんな方のための勉強方法をテーマに説明していきます。 正しい勉強方法を身に着けていれば、どういったメリットがあるのでしょうか? 三日坊主になりにくい いざという...

手っ取り早く情報強者になる簡単な方法
雑記
アウトプット,インプット,ニュース
手っ取り早く情報強者になる簡単な方法

  ニュースや情報番組、討論番組をみると、出演者の方々の情報量の多さに圧倒されることがあります。 また、ユニークな考え方に共感を得る人も多いでしょう。   どうやって、情報を仕入れているのか? なぜそんなことまで知っているのか?   メディアで取り上げられているような人や、活躍している人の多く...

あばれる編(あばれる・はなびらのまい)PHPポケモン52
プログラミング
PHP,PHPポケモン,ポケモン
あばれる編(あばれる・はなびらのまい)PHPポケモン52

あばれる状態とは 技を使うと、しばらくの間その技のみしか使用できなくなる技があります。その状態を「あばれる状態」と言います。 あばれる状態(ポケモンwiki) https://wiki.ポケモン.com/wiki/あばれる_(状態変化) あばれるの効果 2~3ターンの間攻撃し続け、交換したり、他の指示を出すことが出...

独立するならWordPress理解しておけばOK!プログラミングでフリーランスはこれ一つで成り立ちます
プログラミング
PHP,WordPress,フリーランス,独立
独立するならWordPress理解しておけばOK!プログラミングでフリーランスはこれ一つで成り立ちます

  プログラミングでフリーランスを目指すには、どの言語始めればいいの?   プログラミングの学習を始めたのに、それをどう活かせばよいか分からず、いざフリーランスで活動しようと思ってもイメージできずに断念してしまう人は多いです。 言語にも向き不向きがあるため、フリーランスとして活動するために向...

TwitterでYouTubeのリンク付きサムネイルを表示させる方法【超実践的Webプログラミング活用法】
プログラミング
HTML,JavaScript,PHP,Twitter,YouTube
TwitterでYouTubeのリンク付きサムネイルを表示させる方法【超実践的Webプログラミング活用法】

  Twitter(ツイッター)をブログや商品、イベントの宣伝目的で使用している人は多いです。そして、そのためのマーケティング方法や戦略は数多く練られています。 今回は、その中でもYouTubeの告知をするために特化させた内容をまとめました。   一般的な方法と、プログラミングの知識(HTMLやJavascript等)があ...

食に関する考察 自炊と節約は成立するのか?
雑記
食に関する考察 自炊と節約は成立するのか?

スキル関係の記事が多めの当ブログですが、今回は「食」をテーマに触れていきます。突き詰めていけば食も立派なスキルであり、奥が深いものです。更に、生活の基盤となるため多くの人がこの問題に直面することであり、健康とも直結するため仕事などにおけるパフォーマンスにも大きく関係します。   これから一人暮ら...

カテゴリ

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 独立 神戸 福祉 秘密鍵 翻訳 自己啓発 英語 見積書 計算機 読書 起業 迷惑メール 配列 銀の弾丸 集客 雑学力