作者: 機械伯爵
日時: 2002/5/5(18:36)
「P は Python の P <実戦編>」
 〜やるぞ作るぞRPG!〜


NP-3 夢のトレジャー

 ゲームつくり、3回目でございます。

 今回から、ちゃんとスクリプトカッター対応にしますので、コードを読まず
とも、暇つぶしにでも遊んでみてください。

 今回の実行モジュールはebv3.pyです。

 私自身のメイン環境がWinなので、UNIXのパスは書いてありませんが、
        python ebv3.py
で、起動しますので、それでお願いします。

 さて、今回からドラゴンくんは貢物を差し出します。

 モンスターからマジックアイテムをカツアゲする・・・う〜ん、勇者の醍醐
味ですねぇ(をい)

 というわけで、このドラゴンくんは剣しか出しません。

 普通の剣もありますが、そこそこ魔法の剣も混じっています。

 ・・・呪いの剣も・・・ふふふふふ・・・

 呪いの剣を持つと、以後、ゲームが終わるまで剣を変えることはできず、不
利なままで戦わなければなりません。

 しかし、通常より強い武器が出る可能性のほうが圧倒的に大きく、呪いの剣
の出る確率は非常に低いです。

 以下に、トレジャーとして出てくる武器とデータを列挙します。

LONGSWORD       1〜8ポイントのダメージを与えます(初期装備に同じ)
SWORD+1         2〜9ポイントのダメージを与えます
SWORD+2         3〜10ポイントのダメージを与えます
SWORD+3         4〜11ポイントのダメージを与えます(最強)
SWORD-1         0〜7ポイントのダメージを与えます(呪われている)

 勇者の与えるダメージが(初期装備で)平均4.5ポイントと、ver.2までの2
倍以上になったことにともなって、ドラゴンと勇者のヒットポイントも若干上
昇させてあります。

 全体的にはやや複雑になってきましたが、モジュール分割により、かなり見
やすくしたつもりです(・・・まぁ、私の力量の限界程度ですが・・・)

 それでは、コードをみていきましょう。

--^ ebv3.py
#---ENDLESS BATTLE ver.3---
from dice    import *
from command import *
from display import *
from unit    import *

# 初期化作業
hero = Hero()

# コマンド登録
hero_battle = FormalCommand((
  ("ATTACK",hero.attack),
  ("HEAL",hero.heal)
))

# お宝を装備するか捨てるかの選択コマンド(NEW!)
hero_weaponChange = FormalCommand((
  ("CHANGE",hero.changeWeapon),
  ("DROP",hero.dropTrophy)
))

# ゲーム本体
next()
print 'WELCOME TO THE "ENDLESS BATTLE" FIELD!!'
next()
# メインループ
try:
  numberOfSlay = 0
  while numberOfSlay < 10 :
    print "Encounter!!"
    next()
    dragon = Dragon()
    while 1 :
      print "HERO'S WEAPON : ",hero.weapon.name
      print "HERO'S HP     : ",hero.hp
      print "HERO'S MP     : ",hero.mp
      print "DRAGON'S HP   : ",dragon.hp
      print "NO.OF SLAY    : ",numberOfSlay
      # 勇者の行動
      dragon.hp -= hero_battle()
      # ドラゴンの反撃
      print ""
      hero.hp -= dragon.attack()
      next()
      if hero.hp <= 0 : raise "HeroHPZero"
      elif dragon.hp <= 0 : break
    print "YOU WIN!"
    numberOfSlay += 1
    print "LEVEL UP!!"
    hero.levelUp()
    # ここから、お宝に関する処理
    print "YOU FIND A NEW WEAPON\nCHANGE?"
    hero.getTrophy(dragon.treasure())
    hero_weaponChange()
    next()
except "HeroHPZero":
  print "--HERO IS DEAD!--"
  print "--THE GAME OVER--"
  next()
else:
  print "--HERO SLAY 10 DRAGONS--"
  print "----CONGRATULATION!!----"
  print "--------GAME END--------"
  next()
--$

 前回、部分分割しておいたので、メイン(実行)モジュールでの変化はほと
んどありません。

 ここでは、極力大まかな流れだけが把握できるようにしています。

--^ dice.py
# ダイス処理をまとめたモジュール
from whrandom import randint as _rnd
# 順にダイス数、ダイス面数、数値修正、出目修正
def dice(num=1,side=6,mod=0,adj=0):
  n = 0
  while num:
    n   += _rnd(1,side) + adj
    num -= 1
  return n + mod
def   d4(num=1) : return dice(num,4)
def   d6(num=1) : return dice(num,6)
def   d8(num=1) : return dice(num,8)
def  d10(num=1) : return dice(num,10)
def  d12(num=1) : return dice(num,12)
def  d20(num=1) : return dice(num,20)
def  d30(num=1) : return dice(num,30)
def d100(num=1) : return dice(num,100)
# 新しいダイスを作るための関数
def newDice(num=1,side=6,mod=0,adj=0):
  nd = lambda : dice(num,side,mod,adj)
  return nd
--$

--^ display.py
# 表示停止、更新プロンプト
import os as _os
def next():
  raw_input("\n<<HIT ENTER KEY>>\n")
  if _os.name=='nt':
    _os.system("cls")
  elif (_os.name == 'dos') or (_os.name=='posix'):
    print "\033[2J\033[0;0H" # エスケープシーケンスによる画面制御
--$

 サイコロ処理と表示制御に関しては、今回変更ありません。

 サイコロに関しては、多分今後も無いと思います。

--^ command.py
# 定型式コマンドクラス
# コマンド内容と反応が決まっているコマンドを作るクラス
# システムコマンドなどに使用
# 要求されるのは、('コマンド文字列',コマンド)のペアのタプルかリスト
from sys import exit
class FormalCommand :
  def __init__(self,comList):
    self.comList = comList
    self.num = comList.__len__()
  def __call__(self):
    n = 0
    # コマンド内容の一覧
    print "\n---COMMAND---"
    for x in self.comList :
      print n , ': ' , x[0]
      n += 1
    print "INPUT COMMAND NUMBER,AND HIT ENTER KEY"
    while 1:
      c = raw_input('COMMAND >> ')
      if c == "end":exit()
      elif c.isdigit() :
        if 0 <= int(c) < self.num : break
        else : print "OVER RANGE\nPREASE ONCE MORE..."
      else : print "BAD COMMAND\nPREASE ONECE MORE..."
    c = int(c)
    return self.comList[c][1]()

# 非定型式コマンドクラス(現在未設定)
# コマンド内容が、ファジーに変化するクラス
# 魔法やアイテム管理などに使用予定
# class InformalCommand
--$

 若干手直しはしたものの、大まかな点では変更ありません。

 非定型式コマンドクラスについては、現在どいう形がベストなのか考えてい
ます。

--^ item.py
# アイテムデータを管理する新モジュール

# Sword Class
from dice import *

class Sword:
  def __init__(self,name="SWORD",adj=0):
    self.un     = "SWORD"
    self.name   = name
    self.dice   = d8
    self.adj    = adj
    if self.adj < 0:
      self.cursed = 1
    else :
      self.cursed = 0
  def __call__(self):
    return (self.dice() + self.adj)

sword    = Sword("LONGSWORD")
sword_p1 = Sword("SWORD+1", 1)
sword_p2 = Sword("SWORD+2", 2)
sword_p3 = Sword("SWORD+3", 3)
sword_m1 = Sword("SWORD-1",-1)

# 武器交換
_TreasureTable  = [sword_m1] * 2 # 罰ゲーム
_TreasureTable += [sword]    * 6 # ハズレ
_TreasureTable += [sword_p1] * 5 # 当たり
_TreasureTable += [sword_p2] * 4 # 大当たり
_TreasureTable += [sword_p3] * 3 # グアム旅行!

def TreasureTable():
  return _TreasureTable[d20()-1]
--$

 というわけで、今回の目玉「アイテムモジュール」です。

 やっぱ、RPGといえばマジックアイテム。

 MURAMASA BLADE!を求めて、WERDNAの迷宮の地下10階を何百回歩いたことか
(私の友人の標準だと、少ない方)

 というわけで、ささやかではありますが、マジックアイテムを得たときの喜
びを表現したく、アイテムモジュールを作りました。

 まぁ、内容はみたまんまですので、説明は要らないと思いますが、プライベー
トオブジェクトの
        _TreasureTable
は、リストを一気に書かずに、見やすいようにばらしてみました。

※フリーフォーマットでないPythonだと、行を演算子で区切るとエラーになり
  ます。
  改行が自由に許されるのは、タプル・リスト・辞書の列挙だけなので、この
  辺の書き方については、注意が必要です。

 最終的には、TrasureTable()の中ではd100を振りたいですねぇ・・・

--^ unit.py
# unit.py ver2
from dice import *
from item import *

# 勇者クラス
class Hero:
  def __init__(self):
    self.lv = 0
    self.weapon = sword
    self.levelUp()   # HP,MPについては、1レベルにアップさせて初期化する。
  def levelUp(self):
    self.lv += 1
    self.hp = 20 + (2 * self.lv)
    self.mp = self.lv
  def attack(self):
    print "HERO'S ATTACK!"
    if d20() > 4 :
      hit = self.weapon()
      print "HIT! ",hit,"DAMAGE!"
      return hit
    else :
      print "MISS!"
      return 0
  def heal(self):
    print "USE MAGIC!"
    if self.mp > 0:
      self.hp = 10 + self.lv
      self.mp -= 1
    else:
      print "MP IS CONSUMED!"
    return 0
  def changeWeapon(self):
    if self.weapon.cursed :
      print "YOU CURSED!!"
      return 0
    else :
      self.weapon = self.trophy
      self.trophy = 0
      print "YOU EQUIPED NEW WEAPON"
      print "NEW WEAPON IS " , self.weapon.name
      if self.weapon.cursed :
        print "OOPS! THIS WEAPON IS CURSED!"
      return 1
  def getTrophy(self,trophy):
     self.trophy = trophy
  def dropTrophy(self):
     self.trophy = 0

# ドラゴンクラスを作る

class Dragon:
  def __init__(self):
    self.hp = dice(2,6,18) # 20-30
  def attack(self):
    n = d20()
    if n > 10 : # ドラゴンブレス
      print "DRAGON BREATH!!!"
      d = self.hp / 2
      self.hp -= 1
      return d
    else :    # 通常攻撃
      print "CLAW AND BITE!!"
      if d20() > 10:
        print "HIT!"
        return 1
      else :
        print "MISS!"
        return 0
  def treasure(self):
    return TreasureTable()
--$

 アイテムモジュールを使用できるように、ユニットモジュールにも若干手が
加えられています。

 実は、コーディングの過程ではaction.pyというモジュールが存在したのです
が、オブジェクトを外からいじったり、グローバル関数が必要になったりと、
なんだか雲行きがアヤシクなってきましたので、急遽分解してユニットモジュー
ルその他に分配しました。

※当たり前ですが、既に出来上がったゲームのコードが存在しているわけでは
  無いので、試行錯誤しながらコーディングしてます。

 このモジュールはかなり無理が来ているので、もう少し柔軟に対応できるよ
うに、そろそろ1から書き直す必要がでてくるかもしれません。

 モジュール単位で書き直しても、モジュール同士の結合をできるだけ緩やか
にする、という、構造化プログラミングの基本さえちゃんとしていれば、手間
は最小限度に抑えられます。

※RPGはやや大きめのプログラムになるので、ソフトウェア開発の手法が少なか
  らず役に立ちます。

 こうやってRPGをコーディングしてて特に思うのは、コンピュータでRPGを作
って見たい人は、是非コンピュータを使わない旧式のもの(日本流ではテーブ
ルトークなどと呼ばれます)をプレイしてみてください。

 自分がシステムを作る際に、絶対に参考になるはずです。

 んでわ、今回はこの辺で(次は何しよーかしらん)

   機械伯爵

P.S. 本講座では、皆様のご意見、ご感想をお待ちしております(NHK口調)