《付録》hexothello.py
# -*- coding: utf-8 -*- #=============================================================================== # Copyright (C) 2000-2008, 小泉ひよ子とタマゴ倶楽部 # # History: Othello Game # 1988/05, Smalltalk # 2004/09, Java # 2005/02, C# # 2005/03, Jython # 2008/01, Jython #=============================================================================== #=============================================================================== # 配列と別れる50の方法 #=============================================================================== from game import * #=============================================================================== class HexOthelloFrame(DefaultFrame): def initialize(self): self.panel = HexOthelloPanel() def initializeComponent(self): self.layout = BorderLayout() self.add(self.panel, BorderLayout.CENTER) #=============================================================================== class HexOthelloPanel(GameBoardPanel): dim = 8 black = False white = not black _bounds = (-1, -1), (1, -1), (2, 0) _bounds = list(_bounds)+[(-x, -y) for x, y in _bounds] def __init__ (self): GameBoardPanel.__init__(self) self.mode = self.black def locateItems(self): for x, y in self._items(): self.items.append(NullStone(x, y, None)) for x, y, color in ( (6 , 3, self.black), (9 , 4, self.black), (6 , 5, self.black), (10, 3, self.white), (7 , 4, self.white), (10, 5, self.white)): self.place(HexStone(x, y, color)) def _items(self): s = for y, points in enumerate(( range(3, 14, 2), range(2, 15, 2), range(1, 16, 2), range(0, 17, 2), range(1, 16, 2), range(0, 17, 2), range(1, 16, 2), range(2, 15, 2), range(3, 14, 2))): s += [(x, y) for x in points] return s def prepare(self): self.__class__.itemExtent = ( self.width/self.dim, self.height/self.dim) def this_mouseClicked(self, e): stone = self.detectStone(e) print ">>> this_mouseClicked: %s"%stone if stone: return if stone is nullStone: print "::: click again ..." return self.reversed = False self.reverse(stone) if not self.reversed: return self.place(HexStone(stone.x, stone.y, self.mode)) self.mode = not self.mode self.repaint() def detectStone(self, e): return self.detectPoint(e.x, e.y) def nullObject(self): return nullStone def reverse(self, stone): for x, y in self._bounds: e = self.detect(stone.x+x, stone.y+y) if e.state == (not self.mode): self.reverseStones(e, x, y) def detectPoint(self, x, y): obj = self.nullObject() for e in self.items: if e.isExistPoint(x, y): obj = e; break return obj 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 def place(self, stone): px, py = stone._points() null = self.detectPoint(px, py) self.items.remove(null) self.items.append(stone) print "place: %s"%stone, "%s,%s"%(px, py) #=============================================================================== class HexStone(GameItem): _dx, _dy = 7, 4 # 7*7+4*4=65 <=> 8*8=64 _R2 = (_dx*2)**2 _width, _height = _dx*2, _dy*4 _dw, _dh = _dx*2, _dy*3 deltaX, deltaY = _dw+1, (_dh+1)*2 vertices = [(_dx*x, _dy*y) for x, y in (1, 0), (2, 1), (2, 3), (1, 4), (0, 3), (0, 1)] _nPoints = len(vertices) 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 __nonzero__(self): return True def dim(self): return OthelloPanel.dim def paintBackground(self, g): width = self.width(g) height = self.height(g) x = self.x * width y = self.y * height xpoints = [x+dx*2 for dx, dy in self.vertices] ypoints = [y+dy*2 for dx, dy in self.vertices] N = self._nPoints g.color = Color.green g.fillPolygon(xpoints, ypoints, N) g.color = Color.black g.drawPolygon(xpoints, ypoints, N) def paintItem(self, g): width = self.width(g) height = self.height(g) x = self.x * width y = self.y * height color = (Color.black, Color.white)[self.state] xpoints = [x+dx*2 for dx, dy in self.vertices] ypoints = [y+dy*2 for dx, dy in self.vertices] N = self._nPoints g.color = color g.fillPolygon(xpoints, ypoints, N) g.color = Color.black g.drawPolygon(xpoints, ypoints, N) #=============================================================================== def width (self, g): return self.deltaX def height(self, g): return self.deltaY def isExistPoint(self, x, y): px, py = self._points() X, Y = x-px, y-py return X*X+Y*Y <= self._R2 def _points(self): x = self.x*self.deltaX+self._width y = self.y*self.deltaY+self._height return x, y class NullStone(HexStone): def __nonzero__(self): return False def paintItem(self, g): pass N = HexOthelloPanel.dim + 1 nullStone = NullStone(None, None, None) #=============================================================================== def example(): HexOthelloFrame("Othello: hexagon", (270, 262)) #=============================================================================== from _jython2_ import * signature("-", __file__, '1.0.1') #===============================================================================
新たなゲームを作成する:モデル
新たなゲームを構成するために「モデル」について再考します。
蜂の巣 comb
新たなゲーム用のモデル HexStone を規定します。
# --------------------------------------------------- after -----
class HexStone(GameItem):
_dx, _dy = 7, 4 # 7*7+4*4=65 <=> 8*8=64
_R2 = (_dx*2)**2
_width, _height = _dx*2, _dy*4
_dw, _dh = _dx*2, _dy*3
deltaX, deltaY = _dw+1, (_dh+1)*2
vertices = [(_dx*x, _dy*y) for x, y in
(1, 0), (2, 1), (2, 3), (1, 4), (0, 3), (0, 1)]
_nPoints = len(vertices)
クラス属性 _dx/_dy は、六角格子の最小単位を規定します。盤面は、幅 _dx=7 高さ _dy=4 を単位格子として、蜂の巣のような形状を構成します。
クラス属性 _width/_height は、六角格子を囲む矩形領域の幅/高さを表わします。
クラス属性 deltaX/deltaY は、六角格子を配置する間隔を表わします。横軸方向に1画素、縦軸方向に2画素の間隔を設けると、隣接するコマが形成する領地の美観を損ねる(窮屈な感じがする)のを防ぐ効果があります。
クラス属性 vertices は、六角格子の頂点のオフセット座標を列挙したリストです。
コマの描画
盤面に配置されるコマは、目に見えないコマ(盤面の緑が透けて見える)と、目に見えるコマ(黒:先手または白:後手)とに分かれます。
# --------------------------------------------------- after -----
def paintBackground(self, g):
width = self.width(g)
height = self.height(g)
x = self.x * width
y = self.y * height
xpoints = [x+dx*2 for dx, dy in self.vertices]
ypoints = [y+dy*2 for dx, dy in self.vertices]
N = self._nPoints
g.color = Color.green
g.fillPolygon(xpoints, ypoints, N)
g.color = Color.black
g.drawPolygon(xpoints, ypoints, N)
〈改訂〉メソッド fillPolygon/drawPolygon を利用して、多角形の内部/輪郭を描きます。このとき、縦横方向に2倍の大きさで描くと、六角格子が見やすくなります。そのため、六角格子の幅/高さは、実際の画素数にすると 28/32 になっています。
# --------------------------------------------------- after -----
def paintItem(self, g):
width = self.width(g)
height = self.height(g)
x = self.x * width
y = self.y * height
color = (Color.black, Color.white)[self.state]
xpoints = [x+dx*2 for dx, dy in self.vertices]
ypoints = [y+dy*2 for dx, dy in self.vertices]
N = self._nPoints
g.color = color
g.fillPolygon(xpoints, ypoints, N)
g.color = Color.black
g.drawPolygon(xpoints, ypoints, N)
〈改訂〉メソッド fillPolygon/drawPolygon を利用して、多角形の内部/輪郭を描きます。その状態 self.state に応じて、多角形の内部を、白 Color.white/黒 Color.black で描きます。
さまざまな補助関数
いくつかの補助関数(ユーティリティー)を規定しています。
# --------------------------------------------------- after -----
class HexStone(GameItem):
def width (self, g): return self.deltaX
def height(self, g): return self.deltaYdef isExistPoint(self, x, y):
px, py = self._points()
X, Y = x-px, y-py
return X*X+Y*Y <= self._R2
def _points(self):
x = self.x*self.deltaX+self._width
y = self.y*self.deltaY+self._height
return x, y
〈新規〉目に見えるコマ HexStone は、後述する目に見えないコマ NullStone を包括する機能を提供します。
# --------------------------------------------------- after -----
class NullStone(HexStone):
def __nonzero__(self): return False
def paintItem(self, g): pass
N = HexOthelloPanel.dim + 1
nullStone = NullStone(None, None, None)
〈新規〉目に見えないコマ NullStone は、特殊なコマ HexStone と見なせます。これは、大域変数 nullStone を介して参照される Singleton です。つまり、広く盤面に配置されているように見えても、その実体は sole-instance です。