Shoeisha Technology Media

CodeZine(コードジン)

特集ページ一覧

Java meets Python - 第4回 配列と別れる50の方法(2) オセロゲーム

よろずプログラマーのためのPython導入ガイド (6)

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2008/01/30 14:00

伝統的なスタイルを卒業して、OOPを習得するときに、越えなければならない壁の一つが配列です。2次元配列を扱った事例は多数ありますが、ともすると、配列が主役になり、その中身が脇役になる場面も少なくありません。そこで、発想を転換して主客逆転させると、新しい世界、オブジェクト指向の世界が開けてきます。

目次

はじめに

 伝統的なスタイルを卒業して、OOPを習得するときに、越えなければならない壁の一つが配列です。2次元配列を扱った事例は多数ありますが、ともすると、配列が主役になり、その中身が脇役になる場面も少なくありません。そこで、発想を転換して主客逆転させると、新しい世界、オブジェクト指向の世界が開けてきます。

 その効用は、冗長な条件判定や例外処理が不要になり、例外が発生するのを防ぐだけではありません。コードの見通しをよくし、バグの発見が容易になる、強力な処方箋となります。

 今回は、第2回に続いて、伊藤が担当します。先の連載で紹介した2つのゲームから、共通するフレームワークを抽出して、それをもとにオセロゲームを作成します。前回の復習を兼ねて、初心に還ったつもりで解説しますので、よろしくお願いします。

注意
 今回の記事は、ゲームを作成するための一般的な手法を紹介したものではありません。伝統的な手法とは違うオブジェクト指向の発想を示すのが「目的」です。ゲームを題材に選んだのは、目的(学習)を達成する手段(教材)の一つにすぎません。

対象読者

 こんな症状を抱えているなら……。

  • 配列を扱うたび、例外ArrayIndexOutOfBoundsExceptionに悩まされる
    【効能】 主客転倒させることで、パラダイムシフトが容易になるかも
    【副作用】 まだ配列を使っているんですかと言いたくなるかも
    【おまけ】 Java/C#版のコードを、Jython版と比較してみるのも一興かも

オセロゲームはコミュニケーションツール

 今回は、新しい話題を提供すると共に、先の連載の内容を再検討するために、新しい課題を紹介します。

 オセロゲームの原型は、長谷川五郎さんが、戦後間もない1945年に発案したとされています。その名前の由来は、シェークスピア作の劇中で、白人の妻を持つ黒人将軍オセロが、緑の平原で勇敢に戦う波瀾万丈の物語にちなんだものだそうです。1972年、当時(株)ツクダの社長との出会いが、本格的な普及への扉を開きます。その後、日本オセロ連盟(1973年)、世界オセロ連盟(1976年)が設立され、世界中に多くの愛好家がいます。今日では、老若男女、人種を問わない、バリアフリーのコミュニケーションツール、脳細胞の活性化(脳トレ)ツールとしても注目されています。

オセロゲームのルール:要求仕様

 ルールは簡単です。マス目の中央に白黒の石を2つずつ並べた状態から、ゲームを開始します。使用する石は、表裏が黒/白に塗り分けられています。先手(黒)後手(白)が、交互に石を置き続けます。相手の石を自分の石で挟むと、その場所が自分の領地となります。どこにも置けないときには順番をパスできますが、どこかに置ける限りパスはできません。盤面をすべて埋め尽くすか、双方がどこにも置けなくなると、ゲームは終了します。自分の領地にある石が多い方が勝者となります。典型的なマス目(8×8)の他に、六角形の形をしたものなど、いくつかの変種が存在します。

どうにも止まらない:隘路(あいろ)を切り開く

 アプリケーションを作成するときに「どこから着手すべきか」という問いに、唯一の正解はありません。さまざまな方法論の中から、状況に応じて最適解を模索するものです。今回は、モデル側から作り始めます。このとき、モデル作りがどこまで進んだら、どこで止まるかが鍵となります。

 まず、石を単なる値ではなく、オブジェクトとして表現することから始めます。

はじめに光(インターフェイス)ありき

 モデルを規定するときに限らず、メッセージ(目的 what)とメソッド(手段 how)とを明確に区別する戦略は、関心の分離(separation of concerns)の趣旨にも適います。カプセル化によって、インスタンス属性とメソッド操作とを一元管理すると共に、公開インターフェイスを明確に規定するのは、情報隠蔽の原則にも適います。

class Shape:
    def isExist(self, x, y):
        raise NotImplementedError, "def isExist(self,x,y)"
    def paint(self, g):
        raise NotImplementedError, "def paint(self,g)"
    def dim(self):
        raise NotImplementedError, "def dim(self)"

 クラスShapeは、インターフェイスの役割を担い、子孫クラスに共通するプロトコルを規定します。これらのメソッドは、子孫クラスで『必須として』再定義する必要があり、そうしないと、実行時に例外NotImplementedErrorを生成します。

その前に抽象モデルから

 先の連載で紹介したゲーム(バズルゲーム)に共通する特性を抽出して、新たに親クラスGameItemを規定します。

class GameItem(Shape):
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __str__(self):
        return repr(self)
    def __repr__(self):
        return "(%i,%i)"%(self.x, self.y)

    def isExist(self, x, y):
        return (self.x, self.y) == (x, y)
    def width(self, g):
        return g.clipBounds.width / self.dim()
    def height(self, g):
        return g.clipBounds.height / self.dim()

 メソッドisExistは、指定した座標x/yに、実体(子孫クラスのインスタンス)が存在するかどうか判定した結果をリターン値とします。先の連載では、その名前をdetectとしましたが、関心の分離の趣旨に沿って、目的と手段の使い分けを利用者(プログラマー)に示唆するために、リファクタリングを実践しています。

 メソッドwidth/heightは、パネルの大きさが変化しても、モノの大きさ(幅/高さ)を適切に保つために必要な情報を提供します。

《Tips》抽象クラスとabstractとの違い
 抽象クラスに期待されるGameItemの役割には、注意が必要です。子孫クラスに共通するフレームワークを提供しますが、具体的な特性は子孫に依存します。そのため、変数selfが参照する実体はどれも、子孫クラスのインスタンスであり、実際の処理は実行時に確定します。Javaにおけるabstract宣言されたクラスと違って「抽象クラスはインスタンスを生成できない」という制約こそありませんが、Smalltalkと同様に、抽象クラスのインスタンスを生成することに意味はありません。その判断は、コンパイラーではなく、プログラマーの英断に委ねられます。抽象クラスと、abstract宣言されたクラスとの違いについては、ブログ「ひよ子のきもち」で紹介しています。

ようやく具象モデルに

class Stone(GameItem):
    def __init__(self, x, y, state):
        GameItem.__init__(self, x, y)
        self.state = state
    def __repr__(self):
        if self.state == None:
            s = self.state
        else:
            s = ("black", "white")[not self.state]
        return "(%s,%s)"%(GameItem.__repr__(self), s)
    def dim(self):
        return OthelloPanel.dim

 クラスStoneは、インターフェイスShapeに従って、メソッドを再定義します。メソッドdimは、マス目の数OthelloPanel.dimをリターン値とします。

《Tips》抽象メソッドの意味と意義
 メソッドdimは、インターフェイスShapeで抽象メソッドとして規定してあるので、これを再定義するのは、具象クラスStoneの責務です。利用者(プログラマー)は、これを積極的に活用することで、その実現方法に依存しない、柔軟性/拡張性に優れたコードを記述できます。しかし、この問題解決の手法には、工夫の余地があります。

 配列に値を保持させるのではなく、その対象を「オブジェクト」として実現します。表/裏の状態にある石は、単なる真偽値ではありません。自分がどの状態にあるべきかは、石自身で判断します。つまり、オブジェクト自身が「思考」するというわけです。

 ゲームを開始するときに、左上に置かれた石は、x座標 3、y座標 3、白の面を上にしたインスタンスと見なせます。ここで、石の左上の隅を、その座標値とします。インスタンスに固有の情報を文字列として出力すると、次のようになります。

((3,3),True)

 この結果は、メソッド__str__で規定した文字列表現から得られます。

class Stone(GameItem):
    def paint(self, g):
        self.paintBackground(g)
        self.paintItem(g)
    def paintBackground(self, g):
        width = self.width(g)
        height = self.height(g)
        x = self.x * width
        y = self.y * height
        g.color = Color.green
        g.fillRect(x, y, width, height)
        g.color = Color.black
        g.drawRect(x, y, width, height)

 メソッドpaintは、インターフェイスShapeに従って、盤面を描画します。実際の処理は、補助関数paintBackground/paintItemに委ねます。

 補助関数paintBackgroundは、盤面の背景(緑 Color.green)と罫線(黒 Color.black)を描きます。


  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • 伊藤うさぎ(イトウ ウサギ)

    ペンネームの「由来は」と言うと。苗字の方は、セミナー研修で同じチームになった、3人の合体ユニット名 [I:石塚, T:田川, O:尾沢] から来ています。名前の方は、同じ干支(卯:1987 年生)に因んだものです。既に2人は卒業して、残る1人がその名跡を継承しています。

  • 小泉ひよ子とタマゴ倶楽部(コイズミヒヨコトタマゴクラブ)

    http://tamago-club.cocolog-nifty.com/ 「楽しくなければ仕事じゃない」が私たちのモットー。99%の苦悩の連続も、1%の成功に報われます。だからこそ、この仕事が楽しくて仕方がないのです。楽をするための努力なら惜しみません。何もせず楽をしているのと、努力をしたか...

バックナンバー

連載:よろずプログラマーのためのPython導入ガイド

もっと読む

All contents copyright © 2005-2019 Shoeisha Co., Ltd. All rights reserved. ver.1.5