「P は Python の P <実戦編>」
〜やるぞ作るぞRPG!〜
NP-2 領土分割
というわけで、前回作ったEndless Battleという簡単ゲームを、今回は改良
してみようと思います。
とはいっても、実はゲーム内容は(表示部分を除けば)全く変わりません。
何をするかというと、コードの整理を行うわけです。
ゲーム開発は使い捨てコードではありませんから、そのまま無理やり拡張し
ていけば、そのうちに何がなんだかわからなくなる恐れがあります。
ですから、まずは拡張しやすいように、モジュール分割を行おうというわけ
です。
ユーザ・インターフェイス、データの改良などをいっぺんに行うと、そのう
ちに不具合の追跡に困難をきたすことになります。
というわけで、勇者に魔法の剣を持たせたり、他のモンスターを加えたりし
たい気持ちを抑えて、まずはゲームを整理してみましょう。
#<BEGIN>---ebv2.py---
#---ENDLESS BATTLE ver.2---
from dice import *
from command import *
from display import *
from unit import *
# 初期化作業
hero = Hero()
hero_action = FormalCommand((
("ATTACK",hero.attack),
("HEAL",hero.heal)
))
# ゲーム本体
print 'WELCOME TO THE "ENDLESS BATTLE" FIELD!!'
# メインループ
try:
numberOfSlay = 0
while numberOfSlay < 10 :
print "Encounter!!"
dragon = Dragon()
while 1 :
print "HERO'S HP = ",hero.hp
print "HERO'S MP = ",hero.mp
print "DRAGON'S HP = ",dragon.hp
# 勇者の行動
dragon.hp -= hero_action()
# ドラゴンの反撃
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()
next()
except "HeroHPZero":
print "--HERO IS DEAD!--"
print "--THE GAME OVER--"
next()
else:
print "--HERO SLAY 10 DRAGONS--"
print "----CONGRATULATION!!----"
print "--------GAME END--------"
next()
#<END>---ebv2.py---
手続きやクラスの定義がすべて外部モジュールになり、メインモジュールは
実際の処理の内容だけになっています。
この内容自体はほとんど変化がありませんので、分割されたモジュールの方
を詳しく見ていってみましょう。
#<BEGIN>---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
#<END>---dice.py---
ボードゲームなどに馴染みの無い方には、「ナニやってんだコレ?」な内容
だと思われます(笑)が、実は、実際にダイス(サイコロ)を転がしてゲームを行
うことを想定しながら作っております。
というか…コンピュータを使わないRPGとゆーのは、コンピュータのソフトの
マニュアルより分厚いルールブックを読みながら行うゲームですので、実際コー
ディングしながら「こんなの、よくやってたな」と自分自身で感心してました。
4面〜100面ダイスは実在します。
私はほとんど持っていますが、ゴルフボールのように座りの悪い100面ダイス
は嫌いなので持ってません。代わりに、00-90の刻印がある10面ダイスを、普通
の10面ダイスとペアで使って代用しています。
ちなみに、30面ダイスはマイナーなので、実際のゲームではあまり使われま
せん。
このモジュールは、現在はごく一部しか使われていませんが、様々な拡張を
予想して、いろいろ便利につかえるように用意されています。
まぁ、これだけあれば、多分乱数発生は問題無いでしょう(笑)
#<BEGIN>---command.py---
# 定型式コマンドクラス
# コマンド内容と反応が決まっているコマンドを作るクラス
# システムコマンドなどに使用
# 要求されるのは、('コマンド文字列',コマンド)のペアのタプルかリスト
from sys import exit
class FormalCommand :
def __init__(self,comList):
self.comList = comList
self.num = comList.__len__()
def __call__(self):
n = 0
# コマンド内容の一覧
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
#<END>---command.py
コマンドモジュールは、現在使用されている定型式コマンドの他に、ファジー
に拡張可能な非定型式コマンドを予定していますが、現在はアイディアだけで
す(笑)
最終的には、定型、非定型を組み合わせて階層構造をつくり、ゲーム全体で
様々な行動ができるように改良する予定です。
#<BEGIN>---display.py---
# 表示停止、更新プロンプト
# Windows2000やNTのコマンドプロンプトでは、どうやらエスケープシーケンシー
# が使えないらしいので、直接コマンド入力。Win9xもLinuxもOKのはず・・・
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" # エスケープシーケンスによる画面制御
#<END>---display.py---
MS-DOS〜Win9x時代のcommand.comに代わって新たにNT以降登場したcmd.exeは、
様々な点で変更がなされていて、使いやすく高性能になった点も多いのですが、
エスケープシーケンシーを扱っていたansi.sysが組み込まれないため、エスケー
プ文字出力による画面制御ができなくなったようです。
ちなみに、以前に友人に頼んでテストしてもらった結果、DOSプロンプトでも
Linuxなどでも、同じエスケープコードが使える(同じような端末規格を使用し
ている)ようなので、それで制御してありますが、おかしな挙動を示すようなら、
エスケープ文字出力のところは、適当に書き直すか、あるいは消してください
(単に、画面の文字を消して、カーソルを左上に戻しているだけです)
#<BEGIN>---unit.py---
from dice import *
# 勇者クラスを作る
class Hero:
def __init__(self):
self.lv = 0
self.levelUp() # HP,MPについては、1レベルにアップさせて初期化する。
def levelUp(self):
self.lv += 1
self.hp = 10 + self.lv
self.mp = self.lv
def attack(self):
print "HERO'S ATTACK!"
if d20() > 4 :
print "HIT!"
return 2
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
# ドラゴンクラスを作る
class Dragon:
def __init__(self):
self.hp = dice(1,6,9) # 10-15
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
#<END>---unit.py---
将来ゲームシステムが安定した後は、このようなクラス設定を書くのがメイ
ンになります。
現在はユニット管理だけの簡単なものですが、職業・魔法・アイテム・モン
スターなどのデータも全てクラスで表現される予定です。
まぁ、このゲーム自体は感触をつかむだけの試作品なので、どこまで拡張す
るかはわかりませんが、再利用できるパーツはできるだけ作っておきたいと思
います。
では次回は、ゲームの内容自体に少し改良を加えてみたいと思います。
※ちなみに、前回は強制終了させるしか途中終了させられませんでしたが、コ
マンドモジュールを独立させるにあたって、"end"と入力すると、途中でプロ
グラムを終えるようにしてあります。
機械伯爵