それからのPython 1

はじめに

これは何か

これは、
「はじめてのPythonMOD」シリーズの続きです。
もうちょっとPythonの書き方について勉強して、
より思い通りに記述できるようになろう、というのが大きな目的です。

これは何でないか

これは、前シリーズよりもさらに技術的な解説、Pythonの文法の解説に偏重しています。
また、「はじめてのPythonMOD」程度のMOD制作経験を前提としています。
コピペですぐ動くコードはあまりないかもしれません。

準備

最小構成からスタートしましょう。

CivilizationIV.ini

<<<<<<<<
; Enable the logging system
; Documents\My Games\Beyond the Sword(J)\Logs
LoggingEnabled = 1

; Enable synchronization logging
SynchLog = 1

; Overwrite old network and message logs
OverwriteLogs = 1
>>>>>>>>

ログファイルが出力されるようにしておきます。

フォルダ構成

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

CvEventInterface.py

# Sid Meier's Civilization 4
# Copyright Firaxis Games 2005
#
# CvEventInterface.py
#
# These functions are App Entry Points from C++
# WARNING: These function names should not be changed
# WARNING: These functions can not be placed into a class
#
# No other modules should import this
#
import CvUtil
# import CvEventManager
import KujiraEventManager
from CvPythonExtensions import *

# normalEventManager = CvEventManager.CvEventManager()
myEventManager = KujiraEventManager.MyEventManager()

def getEventManager():
    return myEventManager

def onEvent(argsList):
    'Called when a game event happens - return 1 if the event was consumed'
    return getEventManager().handleEvent(argsList)

def applyEvent(argsList):
    context, playerID, netUserData, popupReturn = argsList
    return getEventManager().applyEvent(argsList)

def beginEvent(context, argsList=-1):
    return getEventManager().beginEvent(context, argsList)

KujiraEventManager.py

from CvPythonExtensions import *
import CvEventManager
import CvUtil

class MyEventManager(CvEventManager.CvEventManager, object):

    def onGameStart(self, argsList):
        'Called at the start of the game'
        super(self.__class__, self).onGameStart(argsList)
        ##########
        # ログファイルに出力する
        CvUtil.pyPrint("Hello, Python!")

ためす

実行してゲームを開始し(開始さえすればそのあとすぐに終了して構いません)、
PythonDbg.log(日本語パッケージ版の場合Documents\My Games\Beyond the Sword(J)\Logs にあります)に
ちゃんと出力されているか確認しましょう。

いいですね。

変数

いきなりですが、ちょっと変えてゲームを開始してみましょう。

>>>>>>>>>>
        # ログファイルに出力する
        a = "Hello, Python!"
        CvUtil.pyPrint(a)
<<<<<<<<<<

さっきと同じく、PythonDbg.logにPY:Hello, Python!と出力されています。

このように、プログラムではデータに名前を付けてとっておいて、
後からその名前で中のデータを参照できるのですが、
そのうちのa変数、その中に納まっているデータをと呼びます。
そしてa = "Hello, Python!"のように、変数名 = 値の形になっている代入と呼びます。
この場合「aという名前の変数に"Hello, Python!"という値を代入している」ということになります。

代入以外の場所に書かれている変数は中身の値を表します。
いまaの中身は代入によって"Hello, Python!"ですから、


## 型
**値**にはデータの性質によって種類があります。
たとえば...

+ 数値型 ```a = 123```
+ 文字列型 ```a = "123"```
+ リスト型 ```a = [1,2,3]```
+ ブール型 ```a = True```

............などなど、いろいろあります。

## 型の一致
**型**の話で大事なのは、***型の種類があっていないと関数は基本的に失敗する***ということです。

試してみましょう。文字列型の代わりに数値型を使って```CvUtil.pyPrint(a)```してみます。
``` python
>>>>>>>>>>
        # ログファイルに出力する
        a = 1234
        CvUtil.pyPrint(a) # 数値型で呼び出す
<<<<<<<<<<

これでゲームを開始してみてください。

PythonDbg.logをいくら眺めてみても、「1234」が出力されている様子はありません。
かわりにPythonErr.logを見てみると、”KujiraEventManager”の12行目、
すなわちCvUtil.pyPrint(a)と呼び出したところで
エラーになってしまっていることがわかります。

かわりにこうすべきです。

>>>>>>>>>>
        # ログファイルに出力する
        a = "1234"
        CvUtil.pyPrint(a) # 文字列型で呼び出す
<<<<<<<<<<

ゲームを開始して、どういう動作になるか自分で確かめてみましょう。

例外もある

先に基本的にと書いたのは、まれに融通が利く場面もあるからです。
その筆頭がprint文です。どんな型の変数を指定しても、ある程度はいい感じに出力してくれます。

>>>>>>>>>>
        # ログファイルに出力する
        var1 = 1234
        var2 = "1234"
        var3 = [1,2,3,4]
        print var1
        print var2
        print var3
<<<<<<<<

ゲームを開始して、どういう動作になるか確かめましょう。

でも基本はだめ

しかし、融通が利くことはまれです。
基本的には型があっていないとだめです。
例えば、同じようなプログラムでも、片方はOKでもう片方はエラーです。

数値型同士でないと自由に計算はできません。
どちらがエラーになるか、予想して確かめましょう。
(エラーになったことを確認するにはどうすればよかったでしょうか?)

>>>>>>>>>>
        # ログファイルに出力する
        tokugawa = 12
        saradin = 34
        answer = tokugawa * saradin
        print answer
<<<<<<<<
>>>>>>>>>>
        # ログファイルに出力する
        ragunaru = "12"
        monte = "34"
        answer = ragunaru * monte
        print answer
<<<<<<<<


あるいは、ややこしいことに、同じ書き方でも動作が違ってくるような場合もあります。
下の2つはどちらもエラーにはなりません。
両方実行してみて、出力結果を見比べ、どうなっているか考えてみましょう。

>>>>>>>>>>
        # ログファイルに出力する
        tokugawa = 12
        saradin = 34
        answer = tokugawa + saradin
        print answer
<<<<<<<<
>>>>>>>>>>
        # ログファイルに出力する
        ragunaru = "12"
        monte = "34"
        answer = ragunaru + monte
        print answer
<<<<<<<<


そして、違う型同士の値を足し算することはできません。
エラーになることを確かめてみましょう。

>>>>>>>>>>
        # ログファイルに出力する
        pakaru = "12"
        ramusesu = 34
        answer = pakaru + ramusesu # エラー!
        print answer
<<<<<<<<

文字列と、数値と。

でも、作っているMODによっては、数値型で計算した後、
計算結果を文字列に混ぜて表示したいこともあるかもしれません。
そこでこのようにしてしましたが、うまくいきません…

>>>>>>>>>>
        # ログファイルに出力する
        message1 = "ato "
        turnleft = 10 - 2  # 全部で10ターンあるうちの2ターン消化した
        message2 = " ta-n desu!"
        
        # "ato 8 ta-n desu!"という文字列の値をつくりたい!
        message = message1 + turnleft + message2 # でも型があってないからエラー。
        print message
<<<<<<<<

文字列を足し算して連結しようとしたところで、turnleftの値が数値型のため失敗してしまいます。
そこで、文字列型へと変換してあげましょう。str()という関数があり、
引数を文字列型に変換したものを返してくれます。

>>>>>>>>>>
        # ログファイルに出力する
        message1 = "ato "
        turnleft = 10 - 2  # 全部で10ターンあるうちの2ターン消化した
        message2 = " ta-n desu!"
        
        # "ato 8 ta-n desu!"という文字列の値をつくりたい!
        message = message1 + str(turnleft) + message2 # 文字列型の値に変換してから足し算。
        print message
<<<<<<<<

起動して、PythonDbg.logにato 8 ta-n desu!と書き込まれていることを確認しましょう。

ゲーム画面に表示してみる

文字列の値が用意できれば、画面上にメッセージとして出すことができるようになります。


``` python
>>>>>>>>>>
        # ログファイルに出力する
        message1 = "ato "
        turnleft = 10 - 2
        message2 = " ta-n desu!"
        
        message = message1 + str(turnleft) + message2
        CyInterface().addImmediateMessage(message, "")
<<<<<<<<

ゲームを開始して…

いいですね!

フォーマット文字列

足し算で連結する方法がうまくいきましたが、ややコードが見づらいですしわかりづらいです。
“ato (turnleftの値) ta-n desu!“みたいにかけたらいいのに…………
というわけで、フォーマット文字列という機能がそれを実現してくれます。
%d%を使ってmessage = "ato %d ta-n desu!" % turnleftのように書くと、
%dのある位置に数値型の変数、つまりturnleftの値が文字列の一部として埋め込まれます。

これを用いると、前後の文字列を一旦ためておく必要もなくなり、次のようにすっきりします。

>>>>>>>>>>
        # ログファイルに出力する
        turnleft = 10 - 2
        message = "ato %d ta-n desu!" % turnleft
        CyInterface().addImmediateMessage(message, "")
<<<<<<<<

文字列と、数値と、それから日本語と

ここまでわかると、どうせなら「あと8ターンです!」と日本語で表示したくなるのですが、
今までの方法をそのまま流用してもうまくいきません。
それもそのはず、日本語の文字列は型が違うのです。

日本語の文字列はUnicode文字列型という型になり、u"日本語"と書きます。
ただひとつ注意点があり、この書き方でソースコード中に日本語を書き込むとき、
「その.pyファイルはどの文字コードで保存されているか」を1行目に明示的に示す必要があります。
普通にしていればおそらくshift_jisという文字コードになりますから、
(もちろん、わかっている方で、euc_jpやutf-8をお使いの方は読み替えてください)
1行目に# -*- coding: shift_jis -*-と書き込みます。

そのうえで、フォーマット文字列の仕組みを使い、


ファイル全体で...

-- coding: shift_jis --

from CvPythonExtensions import *
import CvEventManager
import CvUtil

class MyEventManager(CvEventManager.CvEventManager, object):

def onGameStart(self, argsList):
    'Called at the start of the game'
    super(self.__class__, self).onGameStart(argsList)
    ##########
    # ログファイルに出力する
    turnleft = 10 - 2

    message = u"あと%dターンです!" % turnleft
    print message

    CyInterface().addImmediateMessage(message, "")

```
こうなります。
このコードではUnicode文字列に対してprint文を使っていますが、その場合もよしなにやってくれて
正しくPythonDbg.logに日本語が書き込まれるので、覗いてみてください。

さて、ゲームを開始して…

できました!