《付録》exTetrimino3.py
# -*- coding: utf-8 -*- #=============================================================================== # Copyright (C) 2000-2008, 小泉ひよ子とタマゴ倶楽部 # # Change History: Games # 1988/05, Smalltalk # 2004/09, Java # 2005/02, C# # 2005/03, Jython # History: WPF examples # 2008/01/25, IronPython 1.1.1 (download) # 2008/08/22, IronPython 1.1.2 (download) # 2008/03/16, ver.2.0, WPF # 2008/00/00, ver.2.1, IronPython 1.1.2 #=============================================================================== from _ant import * from System.Windows import * from System.Windows.Controls import * from System.Windows.Input import * from Tetrimino3 import * ## -------------------- class TestCase: types = "COpbYZLIJS" def __iter__(self): m = self.items() for e in self.types: yield e, m[e] def items(self): return dict((e, eval("Tetrimino%s(4, 4)"%e.upper())) for e in self.types) ## -------------------- class ExWindow(Window): def __init__(self, Content=None, **args): self.InitializeComponent(Content) self.init() def InitializeComponent(self, Content): self.Content = LoadXaml(Content) def _Controls(self, target): controls = xaml_controls(self) for e in target: setattr(self, e, controls[e]) def init(self): target = "tabControl", "button", self._Controls(target) self.items = {} for e, mino in TestCase(): tray = Tray() mino.tray = tray self.items[e] = mino item = TabItem(Header=e) self.tabControl.Items.Add(item) panel = Canvas() item.Content = panel self.addTray(panel, tray) self.addMino(panel, mino) self.tabControl.SelectionChanged += self.selectionChanged self.button.Click += self.click self.KeyDown += self.keyDown def addTray(self, panel, tray): for k, tile in tray.leftEdges.items(): panel.Children.Add(tile.shape) for k, tile in tray.rightEdges.items(): panel.Children.Add(tile.shape) for k, tile in tray.tiles.items(): panel.Children.Add(tile.shape) ## -------------------- def addMino(self, panel, mino): for e in mino.shape: panel.Children.Add(e) def selectionChanged(self, sender, e): self.mino = self.items[sender.SelectedItem.Header] self.button.Content = self.state() def click(self, sender, e): self.mino.rotateClockwise() sender.Content = self.state() def keyDown(self, sender, e): KeyValues(e.Key).switch({ Key.Right: self.mino.shiftRight, Key.Left : self.mino.shiftLeft, Key.Up : self.mino.counterClockwise, Key.Down : self.mino.rotateClockwise , }) self.button.Content = self.state() def state(self): return "Type-%s: rotation = %d"%( self.mino.__class__.__name__[-1], self.mino.phase) class KeyValues: def __init__(self, value): self.value = value def switch(self, cases): for key, f in cases.items(): if key == self.value: f() ## -------------------- if __name__ == "__main__": import sys xaml = sys.argv[1] win = ExWindow( Title=__file__, Width=234, Height=230, Content=xaml, ) Application().Run(win) ## --------------------
テトリミノの盤面を構成する
class Tray: def _tiles(self): ... s = {} for x, y in [(e*m1+2, 0) for e in range(5)]: for _ in range(3): s[x, y] = Tile(x, y); y += m2 for x, y in [(e*m1+3, 2) for e in range(4)]: for _ in range(2): s[x, y] = Tile(x, y); y += m2 return s def _edges(self, x): ... s = {} for x, y in [(x-1, e*m) for e in range(5)]: for _ in range(3): s[x, y] = Edge(x, y); x += 1 return s
新たなテストケースの下地となる盤面(トレイ)を構成します。テトリミノが自由に移動できる境界内には Tile を敷き詰め、その境界外には Edge を敷き詰めます。
class Ostone(object): def __init__(self, x, y): self.x = x self.y = y self.shape = self._shape() def _shape(self): M = 2 Ox = HexStone._dx; W = HexStone._width Oy = HexStone._dy; H = HexStone._height X = Ox + W*self.x Y = Oy + H*self.y points = self.pointCollection([Point(X + dx*M, Y + dy*M) for dx, dy in HexStone(X, Y, False).vertices]) return Polygon( HorizontalAlignment=HorizontalAlignment.Center, VerticalAlignment=VerticalAlignment.Center, Stroke=self._strokeColor, Points=points, )
Tile/Edge に共通する特性(座標や6角形の形状など)を規定します。
class Tile(Ostone): def __init__(self, x, y): self._strokeColor = Brushes.Gray super(self.__class__, self).__init__(x, y) class Edge(Ostone): def __init__(self, x, y): self._strokeColor = Brushes.LightGray super(self.__class__, self).__init__(x, y)
Tile/Edge に固有の特性を規定します。このテストケースでは、輪郭の色の違いを設定するだけです。
テストケースを記述する(3)
(Jython で作成した)既存のモジュール hexagon.py を再利用しながら、新たなモジュールの動作を検証するために、テストケースを作成します。
class ExWindow(Window): def init(self): target = "tabControl", "button", self._Controls(target) self.items = {} for e, mino in TestCase(): tray = Tray() mino.tray = tray self.items[e] = mino item = TabItem(Header=e) self.tabControl.Items.Add(item) panel = Canvas() item.Content = panel self.addTray(panel, tray) self.addMino(panel, mino) self.tabControl.SelectionChanged += self.selectionChanged self.button.Click += self.click self.KeyDown += self.keyDown
ジェネレーター TestCase によって、10種類のテトリミノが得られます。さらに、各テトリミノごとに Tray を用意します。
def addTray(self, panel, tray):
for k, tile in tray.leftEdges.items():
panel.Children.Add(tile.shape)
for k, tile in tray.rightEdges.items():
panel.Children.Add(tile.shape)
for k, tile in tray.tiles.items():
panel.Children.Add(tile.shape)
Tray を構成する各要素(leftEdges/rightEdges/tiles)をパネルに追加します。
def keyDown(self, sender, e): KeyValues(e.Key).switch({ Key.Right: self.mino.shiftRight, Key.Left : self.mino.shiftLeft, Key.Up : self.mino.counterClockwise, Key.Down : self.mino.rotateClockwise, }) self.button.Content = self.state()
矢印キーを選択すると、テトリミノを回転(↓|↑)シフト(←|→)できます。テストケース2と違って、ここでは引数を必要としないメソッド群を呼び出します。
class KeyValues: def switch(self, cases): for key, f in cases.items(): if key == self.value: f()
押されたキーに対応するアクションを起動します。テストケース2と違って、ここでは引数を必要としないメソッド f を呼び出します。
テトリミノの状態を更新する
(Jython で作成した)既存のモジュール hexagon.py を再利用しながら、新たなモジュールの動作を検証するために、テストケースを作成します。
class Omino(object): def rotate(self, sign): self.phase = self._rotate1(1, sign) for e, n in zip(self.shape[self.offset:], self.mino1): e.Points = self.matrix1[self._rotate1(n)] self._update() # here goes ... (^.^) def shift(self, n=0): self.x += n self.matrix1 = self._matrix(self.x, self.y, self._mat1) W = HexStone._width for s in self.shape: s.Points = self.pointCollection([Point(e.X + n*W, e.Y) for e in s.Points]) self._update() # here goes ... (^.^)
テトリミノの回転/シフトに伴って、その状態を更新 self._update() します。
def _update(self): s = self.spots() if self.any([e in self.tray.leftEdges for e in s]): self.shiftRight() # left -> right if self.any([e in self.tray.rightEdges for e in s]): self.shiftLeft() # right -> left def all(self, s): # compensate for Python 2.5 return False not in s def any(self, s): # compensate for Python 2.5 return True in s
テトリミノの回転/シフトに伴って、その状態を更新します。領域内に収まらないときには、テトリミノを(必要なだけ)左右にシフトします。ここで注意して欲しいのは、相互参照による再帰呼び出しになっていることです。さもないと、無限ループに陥りかねません。
《Note》組み込み関数 all/any:Python 2.5/IronPython 1.1.1 の互換性の問題から、組み込み関数 all/any に準拠したメソッドを用意しました。ただし、これらのメソッドの本体は、テストケースに特化したもので、組み込み関数 all/any との完全互換性には配慮していません。IronPython の次期リリースを見込んだ、暫定的な措置と考えてください。また「プロセス指向」の立場から、特定のリリースに依存しないように、これらのメソッドを温存するという戦略も考えられます。□
テストケース3:テトリミノの動きの制約
テストケースを起動すると、ウィンドウが開きます。テトリミノを左右にシフト/回転させて、境界に達すると、内側に押し戻されます。
シフト | ||
---|---|---|
テトリミノが「左」の境界に達すると、その先には移動できません。 | テトリミノは境界内を自由に移動できます。 | テトリミノが「右」の境界に達すると、その先には移動できません。 |
回転 | ||
---|---|---|
1)テトリミノは左の境界には達していません。 | 2)しかし、そのまま回転させようとすると、境界からはみ出します。 | 3)すると、境界の内側(1つ右側)まで押し戻されます。 |
《Note》2) の図は、コードの断片をコメントにしてから、テトリミノを回転したときに(説明のためにあえて)update しないようにして作成したものです。□