プログラミング

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

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

 

注目の記事

プログラミングとは?現役システムエンジニアが教える裏事情
プログラミング
システムエンジニア,プログラマー
プログラミングとは?現役システムエンジニアが教える裏事情

  プログラミングってよくわからない? 実際に何をやっているの?   最近注目されているSE(システムエンジニア)やPG(プログラマー)という職業ですが、「そもそもプログラミングとは?」といった疑問を抱えている方も多いですね。 今回は、そういった方へ向けて「プログラミングの基礎知識と裏事情」...

HTMLの基本設計「よくわかるSEO対策」構造化(マークアップ)編
SEO対策
HTML,SEO,マークアップ
HTMLの基本設計「よくわかるSEO対策」構造化(マークアップ)編

  HTML5がリリースされて随分経ちました。 それにより多くのタグが増え、それにより今までデザインを目的と使用されていたものが「構造化」という本来の役割を持たされるようになりました。    構造化は、目に見えにくい部分です   それぞれの役割を理解していなければ、せっかくきれいなサ...

【無料】早起きをして神戸へ行こう!「為になる雑談朝活」
イベント
三宮,朝活,神戸
【無料】早起きをして神戸へ行こう!「為になる雑談朝活」

  朝活を実施することになりましたので、その目的な概要をまとめました。 神戸三宮での開催を予定しておりますので、もしお近くにお住まいの方で日時が会いましたらご参加ください。 土日祝辺りで週1日程度の不定期開催を予定しています。学びにつながる、けど参加しやすい雑談形式ということを主としています...

個人の時代で成功するための起業への3ステップ 〜新時代を生き抜くために〜
フリーランス
ビジネス,独立,起業
個人の時代で成功するための起業への3ステップ 〜新時代を生き抜くために〜

  起業ってどのタイミングですればいいかわからない・・・   令和時代での起業の考え方は、昭和や平成とは大きく変わっています。個人の時代と呼ばれる現代では、今までのようにリスクを背負って起業することは失敗の確率はより高く、オススメできません。   今回は、起業を志している人たちへ向けて私...

なぜ、お金を配るの?悪質メール・SNSを利用した詐欺的手法への対処法とは
ネットワーク
amazon,SNS,Twitter,お金配り,楽天,迷惑メール
なぜ、お金を配るの?悪質メール・SNSを利用した詐欺的手法への対処法とは

定期的に書きたくなるコラムのコーナー! 今回は、迷惑メールやSNSのDMを活用した悪質な勧誘、巷で流行っているお金配りを隠れ蓑のした巧妙な詐欺的手法などをいくつかご紹介、その対策・リクス回避の方法をまとめました。   まず結論からお伝えすると 「怪しいと思うなら手を出すな」 です。その詳細を知りたい...

保守っていくらで何をする?フリーランスWebエンジニア対象!保守業務について解説します
フリーランス
システムエンジニア,フリーランス,プログラマー
保守っていくらで何をする?フリーランスWebエンジニア対象!保守業務について解説します

  サイトの保守を頼まれたけど、具体的に何をやればいいの?   フリーランスの、特に独学やスクール上がりでなった人にとっては保守は何をすれば良いかわからないという悩みを抱えている人は多いのではないでしょうか。 システム開発会社や制作会社にいた人でも、どのぐらいの金額で請け負うのが良いか検...

ビジュアル作り込み編(序) PHPポケモン66
プログラミング
PHP,PHPポケモン,ポケモン
ビジュアル作り込み編(序) PHPポケモン66

ビジュアルの作り込み 最近は内部の作り込みが多かったので、今回のPHPポケモンでは見た目を少しだけ作り込んでいきます。とはいっても、機能自体がそこまで揃っていないため、あくまで仮の見た目となります。 ある程度見た目が整っていると、プレイする側のモチベーションや楽しみも増えると思ったので、こちらは並...

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

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

カテゴリ

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

タグ

5G Adobe AfterEffects AI ajax amazon Animate api artisan atom Automator AWS Bluetooth CSS CVR description EC-CUBE4 ECショップ ESLint Facebook feedly foreach fortify function Google Google AdSense Honeycode htaccess HTML IEEE 802.11ax Illustrator Instagram IoT JavaScript jetstream jQuery jQuery UI keyword LAN Laravel Linux MacBook MAMP meta MLM MySQL NoCode note OS OSI参照モデル Paypal Photoshop PHP phpMyAdmin PHPポケモン PremierePro rss 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 独立 神戸 福祉 秘密鍵 翻訳 自己啓発 英語 見積書 計算機 認証 読書 起業 迷惑メール 配列 銀の弾丸 集客 雑学力