はじめてのPythonMOD 3

はじめに

準備

Pythonそのものの文法を確かめるのにいちいちCiv4を起動するのでは面倒なので、
小さなPythonプログラムを試せる環境を用意しましょう。
あなたのコンピュータにPythonをインストールしてもよいですし、
現代ではオンライン上で小さなプログラムを試せるサイトもいろいろあるので、
利用させてもらってもよいでしょう。
この記事ではpaiza.ioを使うことにします。
コード作成画面に入ったら、左上(緑)の言語選択ボタンからPython2を選択し、

print 1129

と入力して、「実行」ボタンを押します。
「出力」のところに1129と出ていればOKです。

もしくは

埋め込みを置いておくので、ご利用ください。

ちょっとあそぶ

小さいプログラムを書いて遊んでみましょう。

a = 5
b = 2
if a > 3:
    b = a * a - 5
print b

さて、なにが出力されるでしょうか。まず予想を立ててみてください。
予想ができたら、実行してみます。合っていたでしょうか。違っていたでしょうか。

関数

Pythonでは、ブロックが上から下に順番に実行されるのは先述の通りですが、
処理をまとめておく方法もあります。

def doOreore():
    print "oreore,"
    print "oredayo."

doOreore()

def 関数名():とその直下のブロック関数として定義されます。
あとで関数名()とすると関数を「呼び出し」ます。
関数名は(かぶらない限り)自由につけて構いませんが、
その処理のまとまりは要するに全体で何をするものなのか、
動詞っぽいものをつけておく例が多いようです。

関数の旨みのひとつは一度定義すれば何度呼び出しても構わないことにあります。

def doOreore2():
    print "oreore,"
    print "oredayo."

doOreore2()
doOreore2()
doOreore2()
doOreore2()
doOreore2()

とすると、ひたすら5回もオレオレ詐欺を仕掛けてくるプログラムができるのです。

引数

関数のさらに強力なところは、値を外から中に持ち込めるところにあります。

def doOreore3(son):
    print "oreore,"
    print "oredayo."
    print son
    print "dayo."
    print 

doOreore3("takashi")
doOreore3("yuta")
doOreore3("jiro")
doOreore3("kazuma")

(ついてきていますか?焦らず実際に実行しながら進んでくださいね)
定義の時点では変数で記述しておいて、呼び出し時に実際の値を"渡し"ています。
受け渡しを担当しているsonのことを
引数(ひきすう)あるいは仮引数(かりひきすう)と呼びます。

息子の名前だけが違ってあとはほとんど同じ内容をしゃべっていますが、
ほとんど同じな部分をいちいちコピペすることなくほかのお宅に移れるようになっています。

「処理のまとまりに名前が付けられる」という点も強力で、
実際に実行されているのはただのprint文20回分、なのですが、名前があることで
「オレオレ詐欺を4回している」ということが明確になっています。

そういえば、第1回で出てきたpCity.setPopulation(4)
今回のdoOreore3("takashi")がすこし似ていますね。

そうです。pCity.setPopulation(4)も関数呼び出しです。
引数に4を渡して呼び出しているのですね。

市民を再配置したり、制覇条件を更新したり…とめんどくさい内部処理をすべて請け負って
「要するに何をするのか」わかりやすい名前がついていることで、
関数を使う側はやりたいことを素直に記述することに専念できるのです。

戻り値

関数は、呼び出した側に値を投げ返すこともできます。

def three():
    return 3

a = three() * 4
print a

関数内でreturn 値にたどり着くと、その関数の処理を中断して
自分を呼び出したところまで戻ってきます。
その際、関数名()という呼び出し文字列全体がそので置き換わります。
この場合の表記法は数式の関数表記法にならっていますので、
数式をご存知の方は馴染みやすいかもしれません。
このとき投げ返す値のことを戻り値と呼びます。
呼び出した側は、投げ返されてきた戻り値をさらに何かに活用することができます。

引数を使って戻り値を計算することもできます。

def square(x):
    return x * x

a = square(5)
print a

(実行してみながらついてきてくださいね)
より数学の関数に近い感じになりました。

ずっとお世話になっているgc.getInfoTypeForString('BUILDING_LIBRARY')はこのタイプの関数です。
キーの文字列を引数に渡して、
それに対応するIDという数値が戻り値として投げ返されるのですね。
わたしたちはその戻り値を変数に代入したり、
直接別の関数の引数として渡したりしていました。

やっとこMOD

前回のkujira_ifMODをフォルダごとコピーしてリネーム、kujira_defというMODを作ります。

└─kujira_def
    └─Assets
        └─Python
            │─KujiraEventManager.py
            └─Entrypoints
                 └─CvEventInterface.py

Yet Another 防衛志向

前回は《都市が建設されたとき、その都市の所有者の所属する文明がマリならば、その都市に図書館を建設するMOD》を作りました。
その際、要素をこのように分割しました。

  • 都市が建設されたとき
  • その都市の所有者の所属する文明がマリならば
  • その都市に図書館を建設する

今回は少し難しくして、
《都市が建設されたとき、所有者の志向が防衛志向ならば、その都市のタイルに弓兵を3体即座に作成するMOD》
を目指します。

KujiraEventManager.py

というわけで、全体としてこのようなものを目指します…

from CvPythonExtensions import *
import CvEventManager
import CvUtil

gc = CyGlobalContext()

def createUnit(pPlayer, unit, x, y):
    pPlayer.initUnit(unit, x, y, UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_SOUTH)

class MyEventManager(CvEventManager.CvEventManager, object):

    def onCityBuilt(self, argsList):
        'Called when a player builds a city'
        super(self.__class__, self).onCityBuilt(argsList)
        ##########
        pCity = argsList[0]
        pPlayer = gc.getPlayer( pCity.getOwner() )
        if pPlayer.hasTrait( gc.getInfoTypeForString('TRAIT_PROTECTIVE') ):
            archerUnit = gc.getInfoTypeForString('UNIT_ARCHER')
            x = pCity.getX()
            y = pCity.getY()
            createUnit(pPlayer, archerUnit, x, y)
            createUnit(pPlayer, archerUnit, x, y)
            createUnit(pPlayer, archerUnit, x, y)

Playerが防衛志向を持つなら

リファレンスのCyPlayerからtraitを探すと、こうなっています。

318. BOOL hasTrait (TraitType iIndex)
     bool hasTrait(int /*TraitTypes*/ iIndex) - returns True if player is the Trait Type.

つまり、pPlayer.hasTrait(志向の種類ID)とすると、
Playerがその志向を持っているかどうか調べられそうです。

防衛志向のキー調べるとTRAIT_PROTECTIVEであることが分かりました。
↓とすると防衛志向のIDが戻るのでした。

gc.getInfoTypeForString('TRAIT_PROTECTIVE')

↓のように変数に代入しておくこともできますし、

iProtec = gc.getInfoTypeForString('TRAIT_PROTECTIVE')

↓のように直接引数として渡すこともできます。

pPlayer.hasTrait( gc.getInfoTypeForString('TRAIT_PROTECTIVE') )

↓下の2つのプログラムは同じ動作をします。

if pPlayer.hasTrait( gc.getInfoTypeForString('TRAIT_PROTECTIVE') ):
    'something'
iProtective = gc.getInfoTypeForString('TRAIT_PROTECTIVE')
if pPlayer.hasTrait(iProtective):
    'something'

弓兵を作成する

リファレンスのCyPlayerからinitUnitを探すと、こうなっています。

321. CyUnit initUnit (UnitType iIndex, INT iX, INT iY, UnitAIType eUnitAI, DirectionType eFacingDirection)
     CyUnit* initUnit(UnitTypes iIndex, plotX, plotY, UnitAITypes iIndex) - place Unit at X,Y NOTE: Always use UnitAITypes.NO_UNITAI

前半でがんばって勉強したおかげで、これは引数がいっぱいある関数だ!とわかります。
引数をひとつひとつ読んでいきましょう。

UnitType iIndex
UnitのTypeとあるのでユニットの種類IDですね。
作りたいユニットのXMLキーをgc.getInfoTypeForString()して
ここに指定すればよさそうです。
INT iX
iとあるので数値だということはわかりますが…Xというのはx座標でしょうか。
これはPlayerに対する操作なのでした。Playerは都市と違い
シド星上に特定の位置を占めているわけではありませんから、
そのユニットがどこに降り立つのか教えてあげないといけないのですね。
INT iY
同じくy座標です。今回は都市と同じタイル上に生成されてほしいので、
PCityの座標を取得する方法を探して
取得した座標をそのまま(同じ数値として)指定したらよさそうですね。
UnitAIType eUnitAI
UnitAIのTypeなのでユニットAIの種類IDですね(そのまま)。下の行の注釈に
「どんなときでもUnitAITypes.NO_UNITAIを使ってね」
とあるのでUnitAITypes.NO_UNITAIを指定しておきましょう。
DirectionType eFacingDirection
FacingなDirectionですから「向いている方向」でしょうか。
…………ユニットの向いている方向ですね(そのまま)。
DirectionTypeとあるのでこれまた数値指定ですが、
リファレンスページ上でDirectionTypeのリンクをクリックすると、
どのようなキーと数値の対応になっているかの一覧が表示されます。
今回はとくに方向を気にしないので、
DirectionTypes.NO_DIRECTIONを指定しましょう。

なお、UnitAITypes.NO_UNITAIDirectionTypes.NO_DIRECTION
関数ではなく変数です。わざわざキーから変換しなくても、
それに対応する数値があらかじめ代入されて提供されています。
ありがたく利用しましょう。

自分で関数を定義する

pPlayer.initUnit()を呼び出すだけの関数createUnit()を定義してみましょう…

def createUnit(pPlayer, unit, x, y):
    pPlayer.initUnit(unit, x, y, UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_SOUTH)

どのPlayer、どのUnit、どこのx座標、どこのy座標 を引数として受け取っています。
別の言い方をすれば、ここではそれらを決めずに、呼び出す側が決める、ということです。

呼び出し側は、例えばこうなります…

>>>>>>>>>>
archerUnit = gc.getInfoTypeForString('UNIT_ARCHER')
x = pCity.getX()
y = pCity.getY()
createUnit(pPlayer, archerUnit, x, y)
createUnit(pPlayer, archerUnit, x, y)
createUnit(pPlayer, archerUnit, x, y)
<<<<<<<<<<

さて、すこし想像力が必要かもしれません。
前半3行では弓兵のユニット種ID、都市のx座標、都市のy座標を変数に代入していますね。
それらのをいまさっき定義した関数に引数として渡しています。

そうすると、pPlayer=所有者, unit=弓兵のユニットID, x=都市のx座標, y=都市のy座標
に相当するが入った状態でcreateUnit()(を定義したところ)の
ブロックが実行されます。

createUnit()の中身に戻ってみてみると、
ブロック中ではPlayer.initUnit()を呼び出していて、
都市と同じタイル上に弓兵が生成されますね。

createUnit()を3回同じ引数で呼び出していますから、
処理も3回実行され、弓兵が3体できるはずです。

ためす



できました!

その4へ続く

余談

このようなMODを作ったときは、防衛志向に弓が来るかだけでなく、
防衛志向でない指導者には弓が来ないことをちゃんと確認したほうがよいと思います。
防衛志向のみならず金融/哲学に弓が来てしまったら、それはおそらくバグです。
『防衛志向かどうか』の判定がうまくいっていないことを疑う必要があるかもしれません。