ゲームに学ぶフレームワーク #3.7: クロージャー

前の記事記事一覧次の記事

ゲームに学ぶ Jython/Swing フレームワーク《Jython2.5》
クロージャ

《著》森こねこ+小粒ちゃん《監修》小泉ひよ子とタマゴ倶楽部
第1版♪2003/05/23 ● 第2版♪2009/04/03

■ 概要

アプリケーションを作成する過程を通して、Jython/Swing によるフレームワークを習得します。

典型的なオセロゲームから始めて、蜂の巣(6角形)状の盤面を作り、3人で対戦できるゲームへと進化させます。

《Note》JPython1.1.x/Jython2.1.x 用に作成したセミナー課題を、Jython2.5 で再構成しました。

事例:モジュールを起動する

モジュールを起動すると、次のようなウィンドウが現れます。

$ jython2.5.0 -i step06/othelloEx.py 


初期設定として、4つの石(黒/白)を描きます。


>>> 76 47
最初に、空いたマス目には「黒」石を置けます。ただし、挟んだはずの「白」が「黒」に変化しないのは`問題`です。


>>> 87 54
次に、空いたマス目には「黒」石を置けます。ただし、色が「黒」から「白」に変化しないのは`問題`です。


>>> 69 67
さらに、「白」石を置いたマス目にも「黒」石を置けます。ただし、ここに石を置けるのは`問題`です。

》作業中です《

事例:Java の世界を Jython から観察する

実行中の Java アプリケーションの状態を、Jython の対話モードで確認できるので便利です。

>>> view = Xview
>>> view
java.awt.Canvas[canvas0,0,0,200x128]

大域変数 Xview を介して、Canvas を参照できます。すると、キャンバスの大きさ(幅x高さ)が 200x128 になっているのが分かります。ここで、次に示すコードの断片と比較してください。すると、幅 150 のウィンドウを指定したのに対して、実際にはキャンバスの幅が狭くなっているのが分かります。

事例:コードの解説

■ モジュール:othelloModel.py
class Model(object):
    ...
    _black = True
    _white = not _black

    def paint(self, g):
        e = self.geom
        x, y, w, h = e.x, e.y, e.width, e.height
        g.color = self._fillColor
        g.fillRect(x, y, w, h)
        g.color = self._drawColor
        g.drawRect(x, y, w, h)
        if self.state != None:
            g.color = (Color.white, Color.black)[self.state]
            g.fillOval(x, y, w, h)
            g.color = Color.black
            g.drawOval(x, y, w, h)

モデルの状態に応じて、円の内部を黒 Color.white/白 Color.black で塗り潰します。

    def black(self):
        self.state = Model._black
    def white(self):
        self.state = Model._white
■ モジュール:othelloView.py
class GameBoard(Canvas):
    def __init__(self, *args, **keys):
        super(self.__class__,self).__init__(
            mouseClicked = self._mouseClicked,
            )
        e = Model._extent
        self.stones = dict([
            ((x,y), Model(Point(e*x, e*y)))
            for y in range(8)
            for x in range(8)
            ])
        for e in (3,4),(4,3):
            self.stones[e].black()
        for e in (3,3),(4,4):
            self.stones[e].white()
    def __iter__(self):
        for e in self.stones.values():
            yield e        

ゲーム盤に配置された駒(黒白)が順に得られます。

《Note》組み込み関数 iter に呼応して呼び出されるだけでなく、for..in 文など、iterable が期待される場所では、__iter__ が起動されます。

    def do(self, ex):
        for e in self:
            ex(e)

    def detect(self, ex):
        for e in self:
            if ex(e): return e

クロージャーとしての関数の特徴に着目して、内部イテレーターを用意します。

《Note》これらは、Smalltalk における、do:/detect: を模したものです。

これらのメソッド本体では、具体的な処理を含まない、抽象的な記述がされています。各要素 e を参照する具体的な方法は、self に委ねられます。その詳細を知る必要はありません。実際の具体的な処理 ex は動的(メソッド呼び出し時)に確定します。静的(コンパイル時)に問題解決を図る必要はありません。重要な意思決定を先送りにできるので、いち早く開発に着手できるだけでなく、後に要求仕様の変更にも柔軟に対処できます。⇒ 以下に続く…

    def _mouseClicked(self, e):
        print e.x, e.y
        point = Point(e.x, e.y)
        stone = self.detect(lambda stone: stone.geom.contains(point))
        if stone: stone.state = True
        self.repaint()

各モデル stone に固有の矩形領域 geom が、クリックした座標 point を含む contains なら、モデルの状態 .state を変更(黒 True)します。

    def paint(self, g):
        self.do(lambda stone: stone.paint(g))

キャンバスを再描画して、マス目に置かれた石 stone を表示 paint します。

《Note》承前:メソッド detect/do の実引数には、ラムダ式を指定しています。前者の場合には、矩形領域に依らない条件判定を採用したとしても、detect の本体を変更する必要はありません。また、detect を利用するコードが他にあっても、その影響を受けません。後者の場合には、グラフィックスコンテキストに依らない描画方式を採用したとしても、do の本体を変更する必要はありません。また、do を利用するコードが他にあっても、その影響を受けません。規定されたフレームワーク detect/do の中に、その状況に依存した個別の処理を「動的に」注入するだけです。

Tips

》作業中です《

Last updated♪09/07/02