TENUKIS v0.1

Macテトリスクローンが見当たらないので*1、試みに製作中です。今のところは手動落下のみ。そして難しいことは解らないので常に一番下まで落下することにしてます。

テトリスのプレイが下手くそなのはさておき。
バックアップを兼ねて現時点のソースコードを載せておきます。ライセンスは…このバージョンはパブリックドメインでいいや。片や 482 バイトでテトリスを作ってる人すらいらっしゃるというのに、私ときたら未完成時点で 351 行…。まぁ、 Pythonpygame も初心者なので仕方ない、ということで…。
そして、行き当たりばったりで作ってるからスパゲッティー

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

SCR_W = 320 # 表示ウィンドウの横幅
SCR_H = 240 # 表示ウィンドウの縦幅

class Mino:
    """ テトラミノの基底クラス """
    blocks =  None # blocksは左上を(0,0)とするブロックの配置 ( ( (x1,y1),(x2,y2)...),... ) の形式
    appearance_at = (3,0) #出現場所
    
    def __init__(self, field):
        self.field = field
        self.position = [i for i in self.appearance_at]
        self.rotation = 0 #現在の回転位置
        #self.fall()
    
    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を右回転させる。落下したらTrue(アソビがリセットされる予定) """
        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 self.fall()
        else:
            return False
    def rotate_l(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 self.fall()
        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 self.fall()
        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 self.fall()
        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
    def settle(self):
        """ ミノを field に固定する """
        blocks = [ (block[0]+self.position[0], block[1]+self.position[1]) for block in self.blocks[self.rotation] ]
        for block in blocks:
            self.field[block[1]][block[0]] = 1
        return

class MinoT(Mino):
    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))) #ト
    def rotate_r(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 self.fall()
        elif (self.rotation == 1 and self.can_alive(self.blocks[2], (self.position[0]-1, self.position[1]), self.field)):
            self.position[0] -= 1
            self.rotation = 2
            return self.fall()
        else:
            return False
    def rotate_l(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 self.fall()
        elif (self.rotation == 3 and self.can_alive(self.blocks[2], (self.position[0]+1, self.position[1]), self.field)):
            self.position[0] += 1
            self.rotation = 2
            return self.fall()
        else:
            return False

class MinoI(Mino):
    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 (self.fall() or self.rotation==1)
        else:
            return False
            
class MinoO(Mino):
    appearance_at = (4,0)
    blocks = [[(0,0),(0,1),(1,0),(1,1)]]
    
class MinoZ(Mino):
    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
            self.fall()
            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
            self.fall()
            return True
        else:
            return False
    def rotate_l(self):
        return self.rotate_r()
    
class MinoS(Mino):
    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
            self.fall()
            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
            self.fall()
            return True
        else:
            return False
    def rotate_l(self):
        return self.rotate_r()

class MinoL(Mino):
    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.can_alive(self.blocks[(self.rotation+1)%len(self.blocks)], self.position, self.field):
            self.rotation = (self.rotation+1)%len(self.blocks)
            return self.fall()
        elif (self.rotation == 1 and self.can_alive(self.blocks[2], (self.position[0]+1, self.position[1]), self.field)):
            self.position[0] += 1
            self.rotation = 2
            return self.fall()
        elif (self.rotation == 1 and self.can_alive(self.blocks[2], (self.position[0]-1, self.position[1]), self.field)):
            self.position[0] -= 1
            self.rotation = 2
            return self.fall()
        elif (self.rotation == 3 and self.can_alive(self.blocks[0], (self.position[0]+1, self.position[1]), self.field)):
            self.position[0] += 1
            self.rotation = 0
            return self.fall()
        else:
            return False
    
    def rotate_l(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 self.fall()
        elif (self.rotation == 0 and self.can_alive(self.blocks[3], (self.position[0]-1, self.position[1]), self.field)):
            self.position[0] -= 1
            self.rotation = 3
            return self.fall()
        elif (self.rotation == 1 and self.can_alive(self.blocks[0], (self.position[0]-1, self.position[1]), self.field)):
            self.position[0] -= 1
            self.rotation = 0
            return self.fall()
        elif (self.rotation == 3 and self.can_alive(self.blocks[2], (self.position[0]+1, self.position[1]), self.field)):
            self.position[0] += 1
            self.rotation = 2
            return self.fall()
        else:
            return False


class MinoJ(Mino):
    blocks = (((0,1),(1,1),(2,1),(2,2)),
                    ((2,0),(2,1),(1,2),(2,2)), # 」
                    ((0,1),(0,2),(1,2),(2,2)),
                    ((1,0),(2,0),(1,1),(1,2))) #「
    def rotate_r(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 self.fall()
        elif (self.rotation == 0 and self.can_alive(self.blocks[1], (self.position[0]+1, self.position[1]), self.field)):
            self.position[0] += 1
            self.rotation = 1
            return self.fall()
        elif (self.rotation == 1 and self.can_alive(self.blocks[2], (self.position[0]+1, self.position[1]), self.field)):
            self.position[0] += 1
            self.rotation = 2
            return self.fall()
        elif (self.rotation == 3 and self.can_alive(self.blocks[0], (self.position[0]+1, self.position[1]), self.field)):
            self.position[0] += 1
            self.rotation = 0
            return self.fall()
        else:
            return False
#    def rotate_l(self):

class MinoGenerator:
    def __init__(self):
        self.c = 0
    kinds = [MinoT, MinoI, MinoO, MinoZ, MinoS, MinoL, MinoJ]
    def next(self):
        self.c += 1
        if self.c > len(MinoGenerator.kinds)-1:
            self.c = self.c % len(MinoGenerator.kinds)
            random.shuffle(MinoGenerator.kinds)
        return MinoGenerator.kinds[self.c]

class Field:
    """ 10*20 の大きさをもつゲームフィールド """
    WIDTH = 10
    HEIGHT = 20
    def __init__(self, block_size=10):
        self._block_size = block_size
        self.surface = pygame.Surface(( block_size*Field.WIDTH, block_size*Field.HEIGHT ))
        self.field = [[ None for i in range(Field.WIDTH)] for j in range(Field.HEIGHT)] #10*20のマス。Noneが空、[0-6]が色。
        
        self.mino = False #MinoT(self.field) #TEST CODE
    
    def _render_block(self, position, color):
        """ position = (x,y) のマスを描く """
        pygame.draw.rect(self.surface, color,
                                    pygame.Rect(
                                                        (self._block_size*position[0], self._block_size*position[1]),
                                                        (self._block_size, self._block_size)
                                    ))
    
    def render(self):
        """ fieldを描画する """
        self.surface.fill( (0,0,0) )
        # 固定済みブロックの描画
        for line in xrange(len(self.field)):
            for masu in xrange(len(self.field[line])):
                if( self.field[line][masu] != None ):
                    self._render_block( (masu, line), (220,220,220) )
        # 操作中のミノの表示
        if self.mino:
            for block in self.mino.blocks[self.mino.rotation]:
                self._render_block( (self.mino.position[0]+block[0], self.mino.position[1]+block[1]), (255,255,255) )
    
def main():
    pygame.init() # pygameの初期化
    screen = pygame.display.set_mode( (SCR_W, SCR_H) ) # 画面を作る
    pygame.display.set_caption('TENUKIS') # タイトル
    
    #fieldの初期化
    field = Field()
    generator = MinoGenerator()
    field.mino = (generator.next())(field.field)
    
    c = pygame.Color(128,128,128)
    screen.fill(c)
    
    def render_field():
        field.render()
        screen.blit(field.surface, (10,20))
        pygame.display.flip()
    
    render_field()
    
    font = pygame.font.Font(None, 32) # フォントを読み込む
    text = font.render('score', True, (255,255,255))
    screen.blit(text, (260,0)) # 文字を画面に貼り付ける
    
    pygame.display.flip()
    
    #clock = pygame.time.Clock()
    
    while 1:
        #clock.tick(60) #FPS
        
        for event in pygame.event.get(): # イベントチェック
            if event.type == QUIT: # 終了が押された?
                return
            if (event.type == KEYDOWN and
                event.key  == K_ESCAPE): # ESCが押された?
                return
            
            #右回転
            if (event.type == KEYDOWN and
                event.key  == K_KP2):
                field.mino.rotate_r()
                render_field()
                next
            #左回転
            if (event.type == KEYDOWN and
                ((event.key == K_KP1 ) or (event.key == K_KP3))):
                field.mino.rotate_l()
                render_field()
                next
            
            #固定
            if(event.type == KEYDOWN and
                event.key == K_DOWN):
                # TESTING
                if not field.mino.fall():
                    #本番環境では、ここからインデント1個あげる
                    field.mino.settle()
                    #削除処理
                    def is_filled( line ):
                        for b in line:
                            if b == None:
                                return False
                        return True
                    for i in xrange(len(field.field)):
                        if is_filled(field.field[i]):
                            del field.field[i]
                            field.field.insert(0, [None for i in xrange(Field.WIDTH)])
                    
                    field.mino = (generator.next())(field.field)
                    #インデント上げここまで
                render_field()
                next
            #左移動
            if(event.type == KEYDOWN and
                event.key == K_LEFT):
                field.mino.move_l()
                render_field()
                next
            #右移動
            if(event.type == KEYDOWN and
                event.key == K_RIGHT):
                field.mino.move_r()
                render_field()
                next


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

たとえば Mino クラスで rotate_r 等を実装してるんですが、ミノの種類によっていちいち特殊な回転を設けているので*2殆どオーバーライドしちゃってて無意味な感じに。
あと、いちいち Field.field をメンバーにしてるオブジェクトが多すぎる感じも…。たぶん MVC (的な要素) の分離が上手くいってないのが悪いんですね。やっぱちゃんと設計しろ、と。

*1:探していないという説もある。

*2:そのくせ現時点では「横を入れながら回転」は実装してない。