オブジェクト指向で戦闘シミュレーション?(2)

前回書いたジャンケンプログラムを、RPGの戦闘のように書き換えたいと思います。
ジャンケンだと、一回で勝ち負け引き分けが決まります。
それに対してRPG風戦闘では、攻撃側と防御側に別れて1回の攻撃でのダメージが算出されます。

また、プレーヤのステータスによって攻撃回避したりなど、攻撃判定の仕組みがあると面白いかもしれません。

その2 RPG風の戦闘プログラム

まずは単純に、1回の攻撃のみ行うプログラムを作ってみます。
1回の攻撃だけならば、ジャンケンと似ています。攻撃ターンなど複雑なことはここではあえて考えないようにしています。

攻撃は次のような仕組みにしたいと思います。

・攻撃側の命中値と、防御側の回避値を取得して比較し、命中が大きければ攻撃が当たったことにします。
・攻撃が当たった場合は、攻撃側の攻撃力から防御側の防御力を引いて、ダメージを算出します。0以下ならば、攻撃に失敗したことにします。

このルールでプログラムを書いてみましょう。

import random

class Player():
   def __init__(self):
       self.HP= 100
       
   def GetOffense(self): #攻撃力
       return int(random.random() *20)
       
   def GetDefense(self): #防御力
       return int(random.random() *20)
       
   def GetHit(self):      #命中
       return random.random()

   def GetAvoidance(self): #回避
       return random.random()
   
   def HpDecrease(self, damage): # HP減算
       self.HP -=damage
       if self.HP<0:
           self.HP= 0

class Attack():
   def Action(self, Player1, Player2):
       HitJadg= Player1.GetHit() -Player2.GetAvoidance()
       if HitJadg>0:
           Damage= Player1.GetOffense() -Player2.GetDefense()
           if Damage>0:
               Player2.HpDecrease(Damage)
               print(f"{Damage}のダメージ!")
           else:
               print("ダメージを与えられない!")
       else:
           print("ひらりと身をかわした!\n")
       
#メインルーチンはここから
P1= Player()
P2= Player()
AttackObject= Attack()
AttackObject.Action(P1, P2)

攻撃判定オブジェクトとして、「class Attack」を作りました。
値を保持する必要はないので、「def __init__」(コンストラクタ)は省略しました。

Playerオブジェクトもガッツリ改造しまして、攻撃力・防御力・命中・回避を返すメソッドを持たせています。(返り値は、ここではとりあえずランダム値ですが)
また、PlayerのHPを減算する「HpDecrease」というアクセサ(※後述)を用意しました。

実行すると、1回の攻撃結果が表示されます。

5のダメージ!

ここまでできれば、攻守を入れ替えてAttackを実行させる進行役…ゲームマスターを作れば、攻撃と防御の1ターンが完成しますね!

1ターンの戦闘プログラム

ゲームマスターには、Player1とPlayer2を「攻・守」と「守・攻」の戦闘をさせます。
さらに、戦闘前後のHPも表示させてみました。

import random

class Player():
   def __init__(self):
       self.HP= 100
       
   def GetOffense(self): #攻撃力
       return int(random.random() *20)
       
   def GetDefense(self): #防御力
       return int(random.random() *20)
       
   def GetHit(self):      #命中
       return random.random()

   def GetAvoidance(self): #回避
       return random.random()
   
   def HpDecrease(self, damage): # HP減算
       self.HP -=damage
       if self.HP<0:
           self.HP= 0

class Attack():
   def Action(self, Player1, Player2):
       HitJadg= Player1.GetHit() -Player2.GetAvoidance()
       if HitJadg>0:
           Damage= Player1.GetOffense() -Player2.GetDefense()
           if Damage>0:
               Player2.HpDecrease(Damage)
               print(f"{Damage}のダメージ!")
           else:
               print("ダメージを与えられない!")
       else:
           print("ひらりと身をかわした!")
       
class Battle():
   def Progress(self, Player1, Player2):
       AttackObject= Attack()
       print("Player1の攻撃!")
       AttackObject.Action(P1, P2)
       print("Player2の攻撃!")
       AttackObject.Action(P2, P1)
       
#メインルーチンはここから      
P1= Player()
P2= Player()
BattleObject= Battle()
print(f"Player1:{P1.HP} Player2:{P2.HP}")
BattleObject.Progress(P1, P2)
print(f"Player1:{P1.HP} Player2:{P2.HP}")

ゲームマスターは「class Battle」に実装しています。
中身は単純に、Attackオブジェクトを2回呼び出しているだけです。
だいぶ、RPG風な戦闘になったのではないでしょうか?

Player1:100 Player2:100
Player1の攻撃!
10のダメージ!
Player2の攻撃!
ひらりと身をかわした!
Player1:100 Player2:90

プログラム説明

・オブジェクトがたくさん出てきました。ややこしく見えますね。
 でも、単純に「オブジェクトを使った」と考えると、プログラムの全体像が漠然と見えてきそうです。

 たとえば、「メインルーチンでBattleオブジェクトを使った」「BattleでAttackオブジェクトを使った」など。このとき、プログラムの中身までは考えずに、関係性だけを気にしています。

アクセサ

オブジェクト内に持つ数値にアクセスするために用意するメソッドのこと。

オブジェクト指向の重要な概念の一つに「カプセル化」があります。
中身をカプセルに閉じ込め隠して、オブジェクトの中での複雑な計算などは考えさせないようにします。
だから、オブジェクトの持つ数値(今回のプログラムで言うところのHP)は、オブジェクトの外から直接扱ってはいけない、というのがオブジェクト指向では大切なマナーです。
もしHPをPlayer以外で計算することは、行儀が悪いことになります。

この作法を守るため、オブジェクト内の変数を外からアクセスできないようローカル化して、変数に値を入れたり値を得たりするには、そのためだけのメソッドを用意することになります。このメソッドを「アクセサ」と言います。
値を設定する側はSetter、読み出す側はGetterともいいます。
(あまり多用しないほうが良いという話もありますが、ここではさておき)

さて、Pythonでの話ですが、Pythonは変数をクラス内部のローカルにする(変数の先頭にアンダーバー2個付ける方法)ことはできますが、積極的に隠すようにはあまりしないようです。ローカル変数と明示するために、変数の先頭にアンダーバー1個付けて読む人にわかるようにします。

では実際にどうアクセスすれば良いのかというと…、そこまでは調べきれませんでした(汗)。逆に、正しいやり方は決まっていないのかもしれません。

私の少ない経験上ですが、値を直接読み出す方はあまり害はないようです。内部の値を計算して、意味のある数値を出力する場合は、意味のある名前を付けたアクセサを用意すると見通しが良くなります。
ただ、値を直接書き込むことをオブジェクト外でしてしまうと、どこで値を書き換えたのか分からなくなることが多く、スパゲティ・プログラムが出来上がってしまうことが多いです。書き込みについてはアクセサを用意することをオススメします。(あくまで個人的見解ですよ^^;)

この記事が参加している募集

最近の学び

この記事が気に入ったらサポートをしてみませんか?