《付録》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.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

〈新規〉目に見えるコマ 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 です。


Previous|5/5|Next