「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口調)