プログラミング

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

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

 

注目の記事

自称デザイナーがおしゃれ名刺を作成してみた!2度見したくなる名刺とは?
デザイン
Adobe,Illustrator
自称デザイナーがおしゃれ名刺を作成してみた!2度見したくなる名刺とは?

  自称デザイナーらしく、オリジナルデザインの名刺を作成しました。 今回作った名刺はコチラです。   会社名や住所、名前の部分は仮で当てはめています。公開情報なのでそのままでも良いんですが一応です。   今回は「自称デザイナーがおしゃれ名刺を作成してみた!2度見したくなる名刺とは?」につい...

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

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

なぜ(・・? あなたはMacを使うのか?
雑記
AWS,Linux,MacBook,OS,Windows
なぜ(・・? あなたはMacを使うのか?

  あなたが使っているPCはなんですか?   たまに聞かれることがあります。 そこで「Macを使っています」と答えると「何故?」という次の問いが返ってくることがあります。   あなたがもしMacを使っているのであれば、このときになんと答えますか?   今回のテーマは、「何故Macを使っているの?」と...

ビジネス系次世代ブログ!?無料で企画書が読める「机上の空論」とは
ビジネスモデル
ビジネス系次世代ブログ!?無料で企画書が読める「机上の空論」とは

  この記事は、私の考えたビジネスモデルを紹介するコーナーです。考えるだけで辞めたものや、コストやリスクを考えて断念したもの、そこまでニーズがないと判断したものなど様々なので、読んだ方は自分なりの見解や根拠を踏まえて判断したり、各自ビジネスの参考資料としてご活用ください。   このブ...

SNSだけじゃダメ!PV数アップのためにSEO対策する理由とは
SEO対策
Twitter
SNSだけじゃダメ!PV数アップのためにSEO対策する理由とは

  「SNSで集客しているけど中々伸びなくなってきた」 「たまにPV数がアップするけど安定しない」 「本当に月何十万も稼げるぐらいPVは伸びるの?」   PV数を稼ぐためにSNSを駆使したり、中には広告を使って集客しているサイトもあるでしょう。ですが、それだけでは疲弊しながらユーザーを獲得しているに...

独立してから心がけていること【社会人時代と比較して】
フリーランス
フリーランス,独立
独立してから心がけていること【社会人時代と比較して】

  今回は自分が独立してから心がけていることの中から、ある程度上手くいっていることや、オススメできるようなことを簡単に感想付きでまとめてみました。 社会人時代と比較しながらの内容なので、これからフリーランスで生きていこうと思っている人や、社会人という生活にストレスを感じている人は是非参考に...

ネコにこばん編 PHPポケモン73
プログラミング
PHP,PHPポケモン,ポケモン
ネコにこばん編 PHPポケモン73

ネコにこばんとは 初代ポケモンでは、基本的にトレーナーとのバトルでしかお金を増やすことができず、もし手持ち金が0円の状態で金が必要なイベントが発生すると詰んでしまうという隠された落とし穴がありました。 ですが、初代でも唯一トレーナー意外からお金を得る方法があります。それが「ネコにこばん」という...

アウトプットのための3つの習慣【3対7を成立させよう】
雑記
アウトプットのための3つの習慣【3対7を成立させよう】

  インプットとアウトプットの比率は3対7がベストだと言われています。 しかし、簡単にできるインプットに比べて、アウトプットは習慣化させておくことが大切です。それができていない人の多くが、膨大に本を読んだり学習に取り組んでも身につかず、趣味のレベルで終わってしまうのです。   今回は、そんな...

カテゴリ

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