プログラミング

PHPポケモン「バトルシステム編〜状態異常2〜」31

PHP PHPポケモン ポケモン
PHPポケモン「バトルシステム編〜状態異常2〜」31

 

前回に引き続き、状態異常チェックを実装します。

まず、前回実装した「ねむり」の処理についてですが、やはりターン数をセットして経過ターン数を引いていくという処理の方が解除率もゲーム再現になるので、まず修正をしておきます。サーセン。

 

チェック格納トレイト(/Traits/Battle/CheckTrait.php
/**
* アタック前の状態異常チェック
*
* @param object Pokemon
* @return boolean
*/
protected function checkBeforeSa($pokemon)
{
    if(empty($pokemon->getSa())){
        // 状態異常にかかっていない
        return true;
    }
    switch ($pokemon->getSa()) {
        /**
        * まひ
        */
        case 'SaParalysis':
        // 1/4の確率で行動不能
        $paralysis = new SaParalysis;
        if(random_int(1, 4) === 1){
            $this->setMessage($paralysis->getFalseMessage($pokemon->getPrefixName()));
            return false;
        }
        break;
        /**
        * こおり
        */
        case 'SaFreeze':
        // 1/5の確率でこおり解除
        $freeze = new SaFreeze;
        if(random_int(1, 5) === 1){
            // こおり解除
            $pokemon->releaseSa();
            $this->setMessage($freeze->getRecoveryMessage($pokemon->getPrefixName()));
        }else{
            // 行動不可
            $this->setMessage($freeze->getFalseMessage($pokemon->getPrefixName()));
            return false;
        }
        break;
        /**
        * ねむり
        */
        case 'SaSleep':
        // ターンカウントが残っていれば行動不能
        $sleep = new SaSleep;
        // ターンカウントを進める
        $pokemon->goSaTurn();
        if($pokemon->getSa('turn') <= 0){
            // ねむり解除
            $pokemon->releaseSa();
            $this->setMessage($sleep->getRecoveryMessage($pokemon->getPrefixName()));
        }else{
            // 行動失敗
            $this->setMessage($sleep->getFalseMessage($pokemon->getPrefixName()));
            return false;
        }
        break;
        /**
        * ひんし
        */
        case 'SaFainting':
        return false;
        break;
    }
    // 行動可
    return true;
}

 

その他若干修正をかけていますが、メインの修正は前述したように「ねむり状態」のターン数部分です。ねむり状態をセットする際に、2〜4のターン数を確定させて、チェック時に毎回減算してき0ターンになれば解除という方法です。

 

攻撃技である「ねむりごな」の追加効果を見てみましょう。

 

ねむりごな(/Classes/Move/SleepPowder.php
/**
* 追加効果
*
* @param array $args
* @return void
*/
public function effects(...$args)
{
    /**
    * @param Pokemon $atk 攻撃ポケモン
    * @param Pokemon $def 防御ポケモン
    */
    list($atk, $def) = $args;
    // 相手をねむり状態にする(2〜4ターン)
    $msg = $def->setSa('SaSleep', random_int(2, 4));
    // メッセージをセット
    $this->setMessage($msg);
}

 

おなじみrandom_intを使ってターン数をランダムでセットしています。これで、ねむるなどを使った際も同じ処理で判定することができます。

 

それでは今回のメインである状態異常チェックの後半部分に入りましょう。

 

状態異常チェック

行動前にチェックする状態異常は前回実装しましたね。今回は行動後に判定を行う状態異常です。該当するのは以下の3つです。

  1. どく
  2. もうどく
  3. やけど

 

それぞれ、ダメージを受けるといったものです。もちろん、これらのダメージによってひんしになる可能性もあるため、それぞれチェックを行なった後にダメージ計算、ひんし判定という流れも実装しなければなりません。

 

チェック格納トレイト(/Traits/Battle/CheckTrait.php
/**
* アタック後の状態異常チェック
*
* @param object Pokemon
* @return boolean (false:ひんし)
*/
protected function checkAfterSa($pokemon)
{
    if(empty($pokemon->getSa())){
        // 状態異常にかかっていない
        return true;
    }
    switch ($pokemon->getSa()) {
        /**
        * どく
        */
        case 'SaPoison':
        // 最大HPの1/8ダメージを受ける
        $poison = new SaPoison;
        // 小数点以下切り捨て
        $damage = (int)($pokemon->getStats('HP') / 8);
        if($damage){
            // 最小ダメージ数は1
            $damage = 1;
        }
        // メッセージ
        $this->setMessage($poison->getTurnMessage($pokemon->getPrefixName()));
        break;
        /**
        * もうどく
        */
        case 'SaBadPoison':
        // 最大HPの(ターン数/16)ダメージを受ける(最大15/16)
        $bad_poison = new SaBadPoison;
        // ターンカウントを進める
        $pokemon->goSaTurn();
        // 小数点以下切り捨て
        $damage = (int)($pokemon->getStats('HP') / 16) * $pokemon->getSa('turn');
        if($damage){
            // 最小ダメージ数は1
            $damage = 1;
        }
        // メッセージ
        $this->setMessage($bad_poison->getTurnMessage($pokemon->getPrefixName()));
        break;
        /**
        * やけど
        */
        case 'SaBurn':
        // 最大HPの1/16ダメージを受ける
        $burn = new SaBurn;
        // 小数点以下切り捨て
        $damage = (int)($pokemon->getStats('HP') / 16);
        if($damage){
            // 最小ダメージ数は1
            $damage = 1;
        }
        // メッセージ
        $this->setMessage($burn->getTurnMessage($pokemon->getPrefixName()));
        break;
    }
    // ダメージ計算
    $pokemon->calRemainingHp('sub', $damage ?? 0);
    // ひんしチェック(ひんしチェックとは逆の結果(boolean)を返却)
    return !$this->checkFainting($pokemon);
}

 

それでは状態異常について1つずつ見ていきましょう。

  

どく

 ターン終了ごとに、ダメージを受ける(第一世代では最大HP1/16、第二世代以降は最大HP1/8)

小数点は切り捨て。ただし、ダメージの最小は1 

 

毒のダメージは最大HPの1/8(切り捨て)、毎ターン受けることになります。

/**
* どく
*/
case 'SaPoison':
// 最大HPの1/8ダメージを受ける
$poison = new SaPoison;
// 小数点以下切り捨て
$damage = (int)($pokemon->getStats('HP') / 8);
if($damage){
    // 最小ダメージ数は1
    $damage = 1;
}
// メッセージ
$this->setMessage($poison->getTurnMessage($pokemon->getPrefixName()));
break;

 

 最小ダメージは1となるので、もし計算結果が0であれば1ダメージと判定しています。ダメージ計算からひんし判定の流れは残りの2つと共通処理になるため、switchから抜け出した後に記述します。

// ダメージ計算
$pokemon->calRemainingHp('sub', $damage ?? 0);
// ひんしチェック(ひんしチェックとは逆の結果(boolean)を返却)
return !$this->checkFainting($pokemon);

  

もうどく

 ターン終了ごとに、ダメージを受ける。受けるダメージは、ターンごとにHP1/162/16・・・と増える(最高15/16)

小数点は切り捨て。ただしダメージの最小は1

もうどく(ポケモンwiki

 

こちらも毒と同じように毎ターンダメージを受けるというものですが、経過ターンに合わせてダメージ量に変化があります。ここでターン数を使用します。

/**
* もうどく
*/
case 'SaBadPoison':
// 最大HPの(ターン数/16)ダメージを受ける(最大15/16)
$bad_poison = new SaBadPoison;
// ターンカウントを進める
$pokemon->goSaTurn();
// 小数点以下切り捨て
$damage = (int)($pokemon->getStats('HP') / 16) * $pokemon->getSa('turn');
if($damage){
    // 最小ダメージ数は1
    $damage = 1;
}
// メッセージ
$this->setMessage($bad_poison->getTurnMessage($pokemon->getPrefixName()));
break;

 

ねむり状態とは異なり、最初にターン数をデフォルト(0)でセットしておきます。毎ターンチェックを行う最初の段階でターンを進行させ、その数値を16で割った数にかけることでダメージ量とします。

 

ポケモンクラス(/Classes/Pokemon.php
/**
* ターンカウントをすすめる(状態異常)
*
* @return void
*/
public function goSaTurn()
{
    // 状態異常クラスを取得
    $sa = $this->getSa();
    switch ($sa) {
        /**
        * ねむり
        */
        case 'SaSleep':
        // 残ターン数を1マイナス
        $this->sa[$sa]--;
        break;
        /**
        * もうどく
        */
        case 'SaBadPoison':
        // 経過ターン数を1プラス(最大15)
        if($this->sa[$sa] <= 14){
            $this->sa[$sa]++;
        }else{
            $this->sa[$sa] = 15;
        }
        break;
    }
}

 

ターンカウントを進めるメソッド(goSaTurn)はポケモンクラスに作成しています。ねむりでは減算もうどくでは加算処理が必要になるため、分岐させてそれぞれに適した処理を割り当てています。もうどくの最大ダメージ量は15/16のため、ターン数の上限は15としています。

 

やけど

 ターン終了時に、HPが減少する(第一世代・第七世代以降では最大HP1/16、第二世代~第六世代は最大HP1/8)

小数点は切り捨て。ただしダメージの最小は1

第二世代まで、ダメージ発生のタイミングはターン終了時ではなく行動直後(#備考も参照)

やけど(ポケモンwiki

 

やけどのダメージは最新世代を参考に1/16のダメージとしています。こちらは毒ダメージの処理と同じで、ダメージ量だけが異なります。

/**
* やけど
*/
case 'SaBurn':
// 最大HPの1/16ダメージを受ける
$burn = new SaBurn;
// 小数点以下切り捨て
$damage = (int)($pokemon->getStats('HP') / 16);
if($damage){
    // 最小ダメージ数は1
    $damage = 1;
}
// メッセージ
$this->setMessage($burn->getTurnMessage($pokemon->getPrefixName()));
break;

 

これで毎ターンの状態異常チェック用のメソッドが完成しました。

 

行動順判定の変更

実際に状態異常のメソッドを呼び出す前に、行動順判定の算出方法を見直しておきましょう。

状態異常も行動順で呼び出す必要があり、その度に2つの順番分の処理を記述していては可視性も保守性も悪くなってしまいます。なので、ダブルバトル等をもし実装した場合でも活用できるように、行動順で配列に格納するというメソッドを作成し、foreachで順番に処理ができるようにしましょう。

 

バトルコントローラー(/Classes/Controller/BattleController.php
/**
* たたかう
*/
case 'fight':
// 自ポケモンの技をインスタンス化
$p_move = $this->getInstance($param);
// 行動順の判定
$order_array = $this->orderMove(
    [$this->pokemon, $this->enemy, $p_move],
    [$this->enemy, $this->pokemon, $e_move],
);
// 行動順にforeachでattackメソッドを実行
foreach($order_array as list($atk, $def, $move)){
    if($this->attack($atk, $def, $move)){
        // 相手をひんし状態にした
        break;
    }
}
// 行動順にforeachでcheckAfterSaとcheckAfterScを実行
foreach($order_array as list($atk, $def, $move)){
    if(!$this->checkAfterSa($atk)){
        // ひんし状態になった
        break;
    }
}

 

新しくorderMoveというメソッドを作成しました。これにポケモンの情報を配列として格納して、行動順に配列で受け取り($order_array)、foreachで処理をしています。

行動が終われば、再度foreachで順番に行動後の状態異常判定(checkAfterSa)を実行しています。

 

行動順判定用数値の算出

それでは新しく作成したorderMoveというメソッドの內部を見てみましょう。

 

バトルコントローラー(/Classes/Controller/BattleController.php
/**
* 行動順の判定
* (行動順判定用数値を生成)
* @var 100万の位:優先度
* @var 10〜10万の位:すばやさ
* @var 1の位:乱数
* @param array [攻撃ポケモン, 防御ポケモン, 攻撃ポケモンの技]
* @return array [行動順判定用数値 => [攻撃ポケモン, 防御ポケモン, 技],...]
*/
private function orderMove(...$pokemons)
{
    $results = [];
    foreach($pokemons as list($atk, $def, $move)){
        // 優先度のセット
        $speed = $move->getPriority() * 1000000;
        // 素早さ実数値の加算
        $speed += $atk->getStats('Speed', true) * 10;
        // 乱数の生成(同速判定用)
        $key = $speed + random_int(0, 9);
        // 重複回避
        while(isset($results[$key])){
            $key = $speed + random_int(0, 9);
        }
        // 配列へセット
        $results[$key] = [$atk, $def, $move];
    }
    // 降順(行動が早い順)に並び替え
    krsort($results);
    // [行動順判定用数値 => [ポケモン => 技],...] の多次元配列で返却
    return $results;
}

 

引数は可変長引数でポケモンの配列を更にまとめた配列として受け取っています。現在はダブルバトルの仕様は実装していないので不要ですが、今後柔軟に活用できるようにと作成しました。

 

今までは技の優先度が勝っていれば先行と単純に判定をしていましたが、これらをすべて数値化して判定を行います。

優先度 + 素早さ実数値 + 乱数

 

単純にこの計算をしてしまえば、正しい判定はできません。なので、優先度の高い順に上位の位を割り当てました。

 

すばやさには上限が設定されている。ランク補正・特性・もちもの・おいかぜの効果を受けてもすばやさは10000を超えることはない。8192以上になったすばやさは8192引かれ、すばやさ1809~8191のポケモンより行動順が遅くなる。

すばやさ(ポケモンwiki

 

ポケモンの素早さの最大値は10000を超えることが無いらしいので、そのまま参考にして100万の位を優先度、実数値を10から10万の位、そして乱数を1の位に割当てました。乱数は、同速だった場合の判定用として使用します。

// 優先度のセット
$speed = $move->getPriority() * 1000000;
// 素早さ実数値の加算
$speed += $atk->getStats('Speed', true) * 10;
// 乱数の生成(同速判定用)
$key = $speed + random_int(0, 9);
// 重複回避
while(isset($results[$key])){
    $key = $speed + random_int(0, 9);
}

 

素早さをそれぞれの値に割当てて計算、最後に乱数を割り当てて、それをキーにした配列を作成していきます。同速で乱数が同じとなれば上書きされてしまうので、重複回避用にwhileを使用しました。

 

あとは、キーの値で降順に並び替えをすれば、行動順に並び替えされた配列を返却することができます。

// 降順(行動が早い順)に並び替え
krsort($results);
// [行動順判定用数値 => [ポケモン => 技],...] の多次元配列で返却
return $results;

 

これで、より簡単に行動順でアクションを起こすことができるようになりました。出力結果を見てみましょう。

 

 

 

 

 

いろんな技を試さないとダメになったので、デバック用にミュウを作成しました。きっとゲームフリークスもこのために用意したんでしょう。調べていないので、その辺り気になった人は是非調べてみてください。

 

問題なく毒のダメージ判定が行われましたね。これで行動後の状態異常チェック処理は完成です。

 

まとめ

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

今回のPHPポケモンでは「行動後の状態異常の判定」についてご紹介しました。

ゲームづくりに興味がある人、現在プログラミング学習に取り組んでいる人は、ぜひ参考にしてみてくださいね。

 

注目の記事

CSSとの意外な関係「よくわかるSEO対策」スタイルシート編
SEO対策
CSS,スタイルシート,レンダリング
CSSとの意外な関係「よくわかるSEO対策」スタイルシート編

   今回の「よくわかるSEO対策」はスタイルシート(CSS)編です。   多くの人が 「SEO対策なのに、CSSの説明をするの?」 と思うかも知れませんが、これには意外な関係性があったのです。   現在CSSを学んでいる人、そしてCSSの書き方ならマスターしているといった人でも、SEOのことまでを考慮した記述ができて...

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

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

PHPポケモン「レベルアップ進化編」9
プログラミング
PHP,PHPポケモン,ポケモン
PHPポケモン「レベルアップ進化編」9

  PHPポケモン(旧:ピカチュウから学ぶオブジェクト指向)も第9回目となりました。前回GitHubよりコードを配布したので、これから学習しようと考えている人は、ぜひ参考にしてください。   最初からコードの内容について学びたい人は、ぜひ記念すべき第1回から順を追って取り組んでみてください。   ...

フォームはもう古い?サイトのCVRを高くするチャットシステムとは
マーケティング
CVR,ECショップ
フォームはもう古い?サイトのCVRを高くするチャットシステムとは

  サイトのコンバージョン率を上げたい お問い合わせフォームから連絡が来ない   ちょっとWebサイトやネットショップを立ち上げても中々思うような結果につながらない時代になりました。数多くのサイトが出回っていることはもちろん、ユーザーの閲覧頻度が増えて目が肥えているというのも大きな理由の1...

システムエンジニアとプログラマーの違いって?仕事内容や必要スキルについてわかりやすく解説します【SE・PG】
プログラミング
システムエンジニアとプログラマーの違いって?仕事内容や必要スキルについてわかりやすく解説します【SE・PG】

  システムエンジニアってどんな仕事? プログラマーとどう違うの?   プログラミング教育が必修化になり、SEやPGの職業が注目された今、この違いについてわからない、教えて欲しいという質問が後を絶ちません。 今回はそういった人たちのために、システムエンジニア(SE)とプログラマー(PG)という職業に...

レベニューシェアとは?利益報酬・共同事業に潜む罠
フリーランス
レベニューシェアとは?利益報酬・共同事業に潜む罠

  レベニューシェアという言葉を聞いたことがありますか?   ビジネスの世界にいる人なら、意味は知らなくてもやったことがある方が意外と多いはずです。特にフリーランスの人や、プログラミングやデザインといったスキルを持っている人はレベニューシェアでは重宝されるため多い傾向があります。ここ最近...

【Adobe Animate CC】モーショントゥイーンを使ったアニメーション作成
動画編集
Adobe,Animate,Illustrator,Photoshop,アニメーション
【Adobe Animate CC】モーショントゥイーンを使ったアニメーション作成

    今回はキャラクターがうちわを扇いでいるアニメーションのAdobe Animateを使った作成方法をご紹介します。 完成イメージは以下の通りです。   Photoshopなどを使ってもgifアニメーションの作成はできますが、Adobe Animateのモーショントゥイーンを使えば、変化するポイントだけを設定す...

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

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

カテゴリ

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