春うらら:コードを巡る旅の縁(よすが)に
同じ景色を眺めても、思い入れがあるのとないのとでは、その感動も違ってきます。そこで、漠然とコードを眺めるのではなく、構造化ブログラミング(SP)とオブジェクト指向プログラミング(OOP)との違いを示しながら、その相互理解を深めます。
最初に石を配置するのは
ゲームを始める前に、ルール(要求仕様)に従って石を配置します。
class OthelloPanel(GameBoardPanel):
def locateStone(self):
points = [(x, y)
for y in range(-1, self.dim+1)
for x in range(-1, self.dim+1)]
for x, y in points:
self.items.append(NullStone(x, y, None))
self.place(Stone(3, 3, self.white))
self.place(Stone(3, 4, self.black))
self.place(Stone(4, 3, self.black))
self.place(Stone(4, 4, self.white))
def place(self, stone):
null = self.detect(stone.x, stone.y)
self.items.remove(null)
self.items.append(stone)
メソッドlocateStoneは、ゲームを開始するときに必要な石を配置します。盤面の中央には、白黒2つずつの石Stoneを配置します。他の場所には、何も存在しないことを表す、見えない石NullStoneを配置します。これで準備が整います。
メソッドplaceは、引数に指定した石stoneを盤面に配置します。見えない石NullStoneを削除して、そこに新たな石stoneを追加します。
そこに石があるなら
class OthelloPanel(GameBoardPanel):
def detectStone(self, e):
return self.detect(
self.dim*e.x/self.width,
self.dim*e.y/self.height)
メソッドdetectStoneは、盤面をクリックしたときに発生するイベントeをもとに、その位置にある石を検出detectします。このとき、ウィンドウの大きさが変化しても、それに応じて柔軟に対処できます。
そこが領地になるのは
反転できる石は、自分と同じ色で挟まれた石だけです。伝統的な手法では、これを配列の値に対する操作として実現します。そのとき、主役を演じるのは配列で、値は脇役にすぎません。OOPでは、これをオブジェクト間のメッセージ送受信としてモデル化します。そこでは、石(オブジェクト)が主役を演じます。
class OthelloPanel(GameBoardPanel):
def reverseStones(self, stone, x, y):
stones = [stone]
for i in range(1, self.dim):
e = self.detect(stone.x+x*i, stone.y+y*i)
if e.state == None:
stones = []; break
if e.state == self.mode:
break
if e.state == (not self.mode):
stones.append(e)
if not stones: return
for e in stones:
e.state = self.mode
self.reversed = True
print stones

メソッドreverseStonesは、指定した石stoneのオフセット位置x/yと同じ方向にある石を裏返えします。局所変数stonesが、裏返したい石を管理します。最初に、指定した石stoneを、裏返す対象として追加しておきます。
2つのオブジェクトが「石を裏返しませんか」というメッセージ(目的 what)をやり取りする様子を、メソッド(手段 how)として実現することを目指します。つまり、OthelloPanelインスタンス(盤面)からStone(石)への「石を裏返したい」という意思の伝達と考えます。
事例:単一方向に単数の石

>>> ((2,3),None) [((3,3),white)]
座標(2,3)には石がないNoneので、そこに黒(自分の石)を置けます。すると、座標(3,3)にある白 whiteが黒(自分の領地)になります。

挟むべき位置に石が存在しないe.state==Noneなら、stonesが管理するすべての石を破棄します。ここでは、右上に石が存在しない(Noneが存在する)ので、反転できる石(白の領地)は存在しません。

対象となる石が同じ色e.state==self.modeなら、処理を中断します。ここでは、右下に黒(同じ色)が存在するので、反転できる石(白の領地)は存在しません。

対象となる石が違う色e.state==(not self.mode)なら、stonesに追加します。ここでは、右に白(違う色)が存在するので、反転できる石(白の領地)が存在します。そこで、stonesに石eを追加すると共に、((3,3),white)を出力します。
e.state==(not self.mode)からe.state<>self.modeに変更すると、どのようなバグ(問題)が生じるか考察してください。事例:複数方向に複数の石

>>> ((2,5),None) [((2,4),white)] [((3,4),white)]
座標(2,5)には石がないNoneので、そこに黒(自分の石)を置けます。すると、座標(2,4)および(3,4)にある白 whiteが黒(自分の領地)になります。

対象となる石が違う色 e.state==(not self.mode)なら、stonesに追加します。ここでは、上/右上に白(違う色)が存在するので、反転できる石(白の領地)が存在します。そこで、stonesに石eを追加すると共に、((2,4),white)および((3,4),white)を出力します。
事例:単一方向に複数の石

>>> ((1,4),None) [((2,4),black), ((3,4),black)]
座標(1,4)には石がないNoneので、そこに白(自分の石)を置けます。すると、座標(2,4)および(3,4)にある黒 blackが白(自分の領地)になります。

対象となる石が違う色 e.state==(not self.mode)なら、stonesに追加します。ここでは、右に黒(違う色)が存在するので、反転できる石(黒の領地)が存在します。そこで、stonesに石eを追加すると共に、((2,4),black)および((3,4),black)を出力します。さらにその先を見ると、対象となる石が同じ色e.state==self.modeなら、処理を中断します。ここでは、その先に白(同じ色)が存在するので、そこで処理を中断します。
