TENUKIS の回転チェック用スクリプト

回転法則を作り込もうと思ったら、ゲームの画面でやるのはちょい不便。
そこで、より簡易のチェックに便利なようにスクリプトを書きました。
チェックの手段としてはユニットテストを使うのも手なのですが、「この時こうなるはず」は抽象的にソースコードで定義するよりも、実際に回転させて見た方が判りやすいと思ったので、人力でチェックすることにしました。
ソースコードはこんな感じ ( public domain ) 。

# -*- coding:utf-8 -*-
import pygame
from pygame.locals import *
from tenukis import *

MINO_SET = [MinoT, MinoI, MinoO, MinoZ, MinoS, MinoL, MinoJ]

def main():
    pygame.init() # pygameの初期化
    screen = pygame.display.set_mode( (150, 150) ) # 画面を作る
    pygame.display.set_caption('ROTATION CHECKER') # タイトル
    
    c = pygame.Color(128,128,128)
    screen.fill(c)
    
    field = [[None for i in range(7)] for j in range(6)]
    
    mino_of = 0
    mino = (MINO_SET[mino_of])(field)
    mino.position = [0,0]
    while True:
        for event in pygame.event.get():
            if (event.type == pygame.QUIT or
                (event.type == KEYDOWN and event.key == K_ESCAPE)):
                return
            elif event.type == KEYDOWN:
                if event.key == K_RIGHT:
                    mino.move_r()
                elif event.key == K_LEFT:
                    mino.move_l()
                elif event.key == K_UP:
                    mino.position[1] -= 1 
                elif event.key == K_DOWN:
                    mino.position[1] += 1
                elif event.key == K_KP1:
                    mino.rotate_l()
                elif event.key == K_KP2:
                    mino.rotate_r()
                elif event.key == K_SPACE:
                    mino_of = (mino_of +1) % len(MINO_SET)
                    mino = (MINO_SET[mino_of])(field)
            elif event.type == MOUSEBUTTONDOWN:
                mp = [i/20 for i in event.pos] #event.pos から field座標に変換
                field[mp[1]][mp[0]] = None if field[mp[1]][mp[0]] else 1
        
        
        for line in xrange(len(field)):
            for masu in xrange(len(field[line])):
                if( field[line][masu] != None ):
                    pygame.draw.rect(screen, (200,200,200),
                                                pygame.Rect( (20*masu, 20*line), (20, 20)
                                                ))
                else:
                    pygame.draw.rect(screen, (0,0,0),
                                                pygame.Rect( (20*masu, 20*line), (20, 20)
                                                ))
        if mino:
            for block in mino.get_block_positions():
                pygame.draw.rect(screen, (255,255,255), pygame.Rect( (20*block[0], 20*block[1]), (20,20)))

        pygame.display.flip()


if __name__ == '__main__': main()
# end of file

で、その結果、とりあえず回転ルールはこんな感じに。基底クラスを活かすようにしたので、割と書き換わってます。

class Mino:
    """ テトラミノの基底クラス """
    blocks =  None # blocksは左上を(0,0)とするブロックの配置 ( ( (x1,y1),(x2,y2)...),... ) の形式
    appearance_at = (3,0) #出現場所
    color_id = 1
    
    def __init__(self, field):
        self.field = field
        self.position = [i for i in self.appearance_at]
        self.rotation = 0 #現在の回転位置
    
    def get_blocks(self):
        """ 現在の回転位置に基づくblock """
        return self.blocks[self.rotation]
    def get_block_positions(self):
        """ 現在の各blockのfield上の座標 """
        return [(self.position[0]+block[0], self.position[1]+block[1]) for block in self.get_blocks()]
    
    def can_alive(self, blocks, position, field):
        """ position,blocks で定義されたブロック群が field で存在しうるか """
        for block in blocks:
            p = (block[0]+position[0], block[1]+position[1])
            if p[0]<0:
                return False
            try:
                b = field[p[1]][p[0]]
                if b != None:
                    return False
            except IndexError:
                return False
        return True
    
    def rotate_r(self):
        """ selfを右回転させる。 """
        if self.can_alive(self.blocks[(self.rotation+1)%len(self.blocks)], self.position, self.field):
            self.rotation = (self.rotation+1)%len(self.blocks)
            return True
        elif self.can_alive(self.blocks[(self.rotation+1)%len(self.blocks)], (self.position[0]+1, self.position[1]), self.field):
            self.position[0] += 1
            self.rotation = (self.rotation+1)%len(self.blocks)
            return True
        elif self.can_alive(self.blocks[(self.rotation+1)%len(self.blocks)], (self.position[0]-1, self.position[1]), self.field):
            self.position[0] -= 1
            self.rotation = (self.rotation+1)%len(self.blocks)
            return True
        else:
            return False
    
    def rotate_l(self):
        """ selfを左回転させる。 """
        if self.can_alive(self.blocks[(self.rotation-1)%len(self.blocks)], self.position, self.field):
            self.rotation = (self.rotation-1)%len(self.blocks)
            return True
        elif self.can_alive(self.blocks[self.rotation-1], (self.position[0]+1, self.position[1]), self.field):
            self.position[0] += 1
            self.rotation = (self.rotation-1)%len(self.blocks)
            return True
        elif self.can_alive(self.blocks[self.rotation-1], (self.position[0]-1, self.position[1]), self.field):
            self.position[0] -= 1
            self.rotation = (self.rotation-1)%len(self.blocks)
            return True
        else:
            return False
    
    def move_r(self):
        if self.can_alive(self.blocks[self.rotation], (self.position[0]+1, self.position[1]), self.field):
            self.position[0] += 1
            return True
        else:
            return False
    def move_l(self):
        if self.can_alive(self.blocks[self.rotation], (self.position[0]-1, self.position[1]), self.field):
            self.position[0] -= 1
            return True
        else:
            return False
    def fall(self):
        fell = False # 1行でも落ちたか否か
        while self.can_alive( self.blocks[self.rotation], (self.position[0], self.position[1]+1), self.field):
            self.position[1] += 1
            if not fell:
                fell = True
        return fell

class MinoT(Mino):
    color_id = 0
    blocks = (((0,1),(1,1),(2,1),(1,2)), #T
                    ((1,0),(0,1),(1,1),(1,2)), 
                    ((1,1),(0,2),(1,2),(2,2)),
                    ((1,0),(1,1),(2,1),(1,2))) #ト

class MinoI(Mino):
    color_id = 1
    blocks = (((0,1),(1,1),(2,1),(3,1)),
                    ((2,0),(2,1),(2,2),(2,3)))
    def rotate_r(self):
        if self.can_alive(self.blocks[1-self.rotation], self.position, self.field):
            self.rotation = 1-self.rotation
            return True
        else:
            return False
    def rotate_l(self):
        return self.rotate_r()
            
class MinoO(Mino):
    color_id = 2
    appearance_at = (4,0)
    blocks = [[(0,0),(0,1),(1,0),(1,1)]]
    def rotate_r(self):
        return False
    def rotate_l(self):
        return False
    
class MinoZ(Mino):
    color_id = 3
    blocks = (((0,1),(1,1),(1,2),(2,2)),
                    ((2,0),(1,1),(2,1),(1,2)))
    
    def rotate_r(self):
        if self.can_alive(self.blocks[1-self.rotation], self.position, self.field):
            self.rotation = 1-self.rotation
            return True
        elif self.can_alive(self.blocks[1-self.rotation], (self.position[0]-1,self.position[1]), self.field):
            self.position[0] -= 1
            self.rotation = 1-self.rotation
            return True
        elif self.can_alive(self.blocks[1-self.rotation], (self.position[0]+1,self.position[1]), self.field):
            self.position[0] += 1
            self.rotation = 1-self.rotation
            return True
        else:
            return False
    def rotate_l(self):
        return self.rotate_r()
    
class MinoS(Mino):
    color_id = 4
    blocks = (((1,1),(2,1),(0,2),(1,2)),
                    ((0,0),(0,1),(1,1),(1,2)))
    
    def rotate_r(self):
        if self.can_alive(self.blocks[1-self.rotation], self.position, self.field):
            self.rotation = 1-self.rotation
            return True
        elif self.can_alive(self.blocks[1-self.rotation], (self.position[0]+1,self.position[1]), self.field):
            self.position[0] += 1
            self.rotation = 1-self.rotation
            return True
        elif self.can_alive(self.blocks[1-self.rotation], (self.position[0]-1,self.position[1]), self.field):
            self.position[0] -= 1
            self.rotation = 1-self.rotation
            return True
        else:
            return False
    def rotate_l(self):
        return self.rotate_r()

class MinoL(Mino):
    color_id = 5
    blocks = (((0,1),(1,1),(2,1),(0,2)),
                    ((0,0),(1,0),(1,1),(1,2)),
                    ((2,1),(0,2),(1,2),(2,2)),
                    ((1,0),(1,1),(1,2),(2,2))) #L
    def rotate_r(self):
        if self.rotation == 0:
            if self.can_alive(self.blocks[1], self.position, self.field):
                self.rotation = 1
                return True
            elif self.can_alive(self.blocks[1], (self.position[0]-1,self.position[1]), self.field):
                self.rotation = 1
                self.position[0] -= 1
            else:
                return False
        else:
            return Mino.rotate_r(self)
    
    def rotate_l(self):
        if self.rotation == 0:
            for i in xrange(3):
                if self.can_alive(self.blocks[3], (self.position[0]-i, self.position[1]), self.field):
                    self.position[0] -= i
                    self.rotation = 3
                    return True
            return False
        else:
            return Mino.rotate_l(self)

class MinoJ(Mino):
    color_id = 6
    blocks = (((0,1),(1,1),(2,1),(2,2)),
                    ((1,0),(1,1),(0,2),(1,2)), # 」
                    ((0,1),(0,2),(1,2),(2,2)),
                    ((1,0),(2,0),(1,1),(1,2))) #「
    def rotate_r(self):
        if self.rotation == 0:
            for i in xrange(3):
                if self.can_alive(self.blocks[1], (self.position[0]+i, self.position[1]), self.field):
                    self.position[0] += i
                    self.rotation = 1
                    return True
            return False
        elif self.rotation == 3:
            if self.can_alive(self.blocks[0], self.position, self.field):
                self.rotation = 0
                return True
            elif self.can_alive(self.blocks[3], (self.position[0]-1,self.position[1]), self.field):
                self.position[0] -= 1
                self.rotation = 0
                return True
            elif (self.can_alive(self.blocks[3], (self.position[0]+1,self.position[1]), self.field) and
                    self.can_alive( [[2,2]], self.position, self.field )):
                self.position[0] += 1
                self.rotation = 0
                return True
            else:
                return False
        else:
            return Mino.rotate_r(self)
    def rotate_l(self):
        if self.rotation == 0:
            if self.can_alive(self.blocks[3], self.position, self.field):
                self.rotation = 3
                return True
            elif self.can_alive(self.blocks[3], (self.position[0]+1,self.position[1]), self.field):
                self.rotation = 3
                self.position[0] += 1
                return True
            else:
                return False
        elif self.rotation == 1:
            if self.can_alive(self.blocks[0], self.position, self.field):
                self.rotation = 0
                return True
            elif self.can_alive(self.blocks[0], (self.position[0]-1, self.position[1]), self.field):
                self.rotation = 0
                self.position[0] -= 1
                return True
            elif (self.can_alive(self.blocks[0], (self.position[0]+1, self.position[1]), self.field) and
                    self.can_alive([[2,2]], self.position, self.field)):
                self.rotation = 0
                self.position[0] += 1
                return True
            else:
                return False
        else:
            return Mino.rotate_l(self)

で、試した動画がこんな感じ。