ジュリスト震災関係記事の PDF を一括ダウンロードして一つの PDF にまとめる Automator Workflow

  1. 新規フォルダ
    • 名前 : _temp
    • 場所 : デスクトップ
  2. 変数の値を設定
    • 変数 : temp_dir
  3. 指定された URL を取得
  4. Web ページからリンク URL を取得
  5. URL にフィルタを適用
    • 条件 : 【URL 全体】 .pdf 【で終わる】
  6. URL をダウンロード
    • 場所 : temp_dir*1
  7. PDF ページを結合
  8. Finder 項目の名前を変更
    • 【名前の一部を変更】
    • 変更する部分 : 【名前全体】
    • 新しい名前 : ジュリスト震災関係記事.pdf
  9. Finder 項目を移動
    • 保存先 : デスクトップ
  10. PDF メタデータを設定 (※必須ではない)
    • タイトル : ジュリスト震災関係記事
    • 作成者 : 有斐閣
  11. 変数の値を取得
    • 変数 : temp_dir
  12. Finder 項目をゴミ箱に入れる

このワークフローを実行すると、最終的にデスクトップに 1098 頁からなる PDF ファイルが生成されます。
ダウンロード時点では 93 MB 弱なんですが、結合すると 180 MB 強に倍増するのは何ででしょうかね。取り回しが楽になるので良しとしますが。

*1:Automator 下部に表示されている変数 temp_dir を「場所」にドラッグアンドドロップ

メモ:テトリス算数

例えば1000ミノでゲームクリアとなるゲームシステムを考えると、この場合4000マスが画面に現れることになり、最大で400行が消されることになる。
10行消すごとに 1 レベル上がるシステムを採用するならば、レベルは40段階*1
ところで 60 fps の場合、1フレームは0.0166... 。だいたい7フレームで反応速度の限界に達する*2
問題は快適な最高速度だ。 TAP や Ti のように人間の反応速度の限界に挑戦させるのは、人を選ぶ。
仮に 1Lv 毎に 1 フレームずつアソビが少なくなるルールを採るならば、
初期アソビ = 最小アソビ + 40
実際には設置アソビ、消去アソビも存在する。

*1:0-9=Lv1, 10-19=Lv2, ... , 390-399=Lv40。400行目が消えるとしても、その瞬間にゲームクリア。

*2:テトリスの場合は次ミノが予め分かるので、純粋な反応勝負ではないのだけど

で、 TENUKIS v0.4

音を出すようにしました。回転法則を調整しました。
ちなみに効果音は自作してます。 GarageBand で適当なシンセを鳴らして、 Audacity で切り出し。効果音のファイルはサンプリングレートが 44,100 Hz なんですけど、 pygame.mixer の (初期)設定が 22,050 Hz なので、元ファイルとはちょっと違う音が出ます。で、その音が意外に却っていい感じだったので、このサンプリングレートのズレを残したままでやってます。

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

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

KEY_ROTATE_R = K_KP2
KEY_ROTATE_L1 = K_KP1
KEY_ROTATE_L2 = K_KP3

#MINO_SET = [MinoT, MinoI, MinoO, MinoZ, MinoS, MinoL, MinoJ]
MINO_COLORS = [(173, 216, 230, 255), (251, 182, 182, 255), (251, 251, 182, 255), (182, 251, 182, 255), (223, 186, 246, 255), (251, 226, 182, 255), (182, 182, 251, 255)]
MINO_SETTLED_COLORS = [(215, 236, 243, 255), (254, 204, 204, 255), (254, 254, 204, 255), (204, 254, 204, 255), (234, 207, 251, 255), (254, 237, 204, 255), (204, 204, 254, 255)] # MINO_COLORS各色をhslaのlにつき10%増やした物

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)

class MinoGenerator:
    def __init__(self, field):
        self.c = 0
        self.field = field
        random.shuffle(MinoGenerator.kinds)
    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])(self.field)

class Score():
    def __init__(self, screen):
        self.font = pygame.font.Font(None, 32) # フォントを読み込む
        self.screen = screen
        self._value = 0
        self._text = self.font.render("score", True, (255,255,255))
        self.screen.blit( self._text, (260,0))
        self.render()
    def get(self):
        return self._value
    def set(self,v):
        self._value = v
        self.render()
    def append(self,v):
        self.set( self._value + v )
    def render(self):
        v = self.font.render( str(self.get()*100), True, (255,255,255), (128,128,128))
        self.screen.blit( v, (self.screen.get_width() - v.get_width()-5, 32))

class Game():
    """ 1ゲーム """
    FPS = 60 #frame per second
    MSPF = 1000/FPS #milliseconds per frame
    MAX_ASOBI = 120 #設置後固定までのフレーム数
    MAX_DELETE_TIME = 10 #列が消える場合の次ミノ落下までのフレーム数
    MAX_WAIT_BEFORE_MINO = 30 #設置(&削除)後、次のミノの登場までのフレーム数
    MOVE_ASOBI = 6 #pressedキープで移動を始めるまで。
    WIDTH = 10
    HEIGHT = 20
    def __init__(self, screen, block_size=10):
        self.screen = screen
        self.block_size = block_size
        self.surface = pygame.Surface(( block_size*Game.WIDTH, block_size*Game.HEIGHT ))
        self.next_surface = pygame.Surface(( block_size*4, block_size*4 )) #次ミノ表示
        self.clock = pygame.time.Clock()
        self.asobi = Game.MAX_ASOBI
        self.move_asobi = Game.MOVE_ASOBI
        self.delete_time = Game.MAX_DELETE_TIME
        self.wait_before_mino = Game.MAX_WAIT_BEFORE_MINO
        self.field = [[ None for i in range(Game.WIDTH)] for j in range(Game.HEIGHT)] #10*20のマス。Noneが空、[0-6]が色。
        self.minogen = MinoGenerator( self.field )
        self.mino = self.minogen.next()
        self.next_mino = self.minogen.next()
        self.score = Score(self.screen)
        
        self.sounds = {
            "appear0": pygame.mixer.Sound("appear0.wav"),
            "appear1": pygame.mixer.Sound("appear1.wav"),
            "rotate_r": pygame.mixer.Sound("rotate_r.wav"),
            "rotate_l": pygame.mixer.Sound("rotate_l.wav"),
            "delete0": pygame.mixer.Sound("delete0.wav"),
            "delete1": pygame.mixer.Sound("delete1.wav"),
            "delete2": pygame.mixer.Sound("delete2.wav"),
            "delete3": pygame.mixer.Sound("delete3.wav"),
            "delete4": pygame.mixer.Sound("delete4.wav"),
            "fall": pygame.mixer.Sound("fall.wav")
        }
        
        self.render()
        self.render_next()
        
    def main_loop(self):
        while(True):
            if pygame.event.get(QUIT):
                return
            
            if self.mino:
                self.clock.tick( Game.FPS ) #FPS
                self.asobi -= 1
                if self.asobi < 0:
                    self.settle_and_next()
                    self.render()
                    next
                else: #回転→移動、順らしい。
                    #主に回転処理
                    for event in pygame.event.get(KEYDOWN):
                        if event.key == K_ESCAPE:
                            return
                        if event.key == KEY_ROTATE_R:
                            self.rotate_r()
                        elif event.key == KEY_ROTATE_L1 or event.key == KEY_ROTATE_L2:
                            self.rotate_l()
                        elif event.key == K_DOWN:
                            self.settle_and_next()
                            pygame.event.clear()
                            next
                    #移動処理
                    pygame.event.pump() #おまじない
                    kp = pygame.key.get_pressed()
                    if kp[K_LEFT]:
                        if self.move_asobi == Game.MOVE_ASOBI:
                            self.move_l()
                        if self.move_asobi > 0:
                            self.move_asobi -= 1
                        else:
                            self.move_l()
                    elif kp[K_RIGHT]:
                        if self.move_asobi == Game.MOVE_ASOBI:
                            self.move_r()
                        if self.move_asobi > 0:
                            self.move_asobi -= 1
                        else:
                            self.move_r()
                    else:
                        self.move_asobi = Game.MOVE_ASOBI
                    
                    if self.fall():
                        self.sounds["fall"].stop()
                        self.sounds["fall"].play()
                    self.render()
                    next
                    
    def settle_and_next(self):
        """ self.mino を self.field に固定し、新ミノを出す """
        for block in self.mino.get_block_positions():
            self.field[block[1]][block[0]] = self.mino.__class__.color_id
        self.mino = None
        self.render()
        if self.delete_line(): #delete_line()は消える列があれば消してTrueを返す
            self.render()
            pygame.time.wait( self.delete_time * Game.MSPF )
        pygame.time.wait( (self.wait_before_mino) * Game.MSPF )
        self.mino = self.next_mino
        #回転チェック
        pygame.event.pump() #おまじない
        kp = pygame.key.get_pressed()
        if kp[KEY_ROTATE_R]:
            if self.mino.rotate_r():
                self.sounds["rotate_r"].play()
            else:
                self.sounds["appear0"].play()
        elif kp[KEY_ROTATE_L1] or kp[KEY_ROTATE_L2]:
            if self.mino.rotate_l():
                self.sounds["rotate_l"].play()
            else:
                self.sounds["appear1"].play()
        else:
            if random.randint(0,1):
                self.sounds["appear0"].play()
            else:
                self.sounds["appear1"].play()
        if kp[K_LEFT] or kp[K_RIGHT]: #横溜め
            self.move_asobi = 0
        
        self.next_mino = self.minogen.next()
        self.render_next()
        if not self.mino.can_alive(self.mino.get_blocks(), self.mino.position, self.field): #将来的にはコード変えたい。
            self.render()
            raise GameOver( self.score.get() )
        self.fall()
        return
    
    def delete_line(self):
        """ 列を消す。消した列数を返す。 """
        deleted = 0
        for line in xrange(len(self.field)):
            is_filled = True
            for block in self.field[line]:
                if block == None:
                    is_filled = False
            if is_filled:
                del self.field[line]
                self.field.insert(0, [None for i in xrange(self.WIDTH)])
                deleted += 1
        self.score.append([0,1,3,7,12][deleted]) #点数
        self.sounds["delete"+str(deleted)].play()
        return deleted
    
    def move_l(self):
        self.mino.move_l()
    def move_r(self):
        self.mino.move_r()
    def rotate_l(self):
        self.mino.rotate_l()
    def rotate_r(self):
        self.mino.rotate_r()
    
    def fall(self):
        if self.mino.fall():
            self.asobi = self.MAX_ASOBI
            return True
        else:
            return False
    
    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):
        """ メイン画面を描画する """
        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), MINO_SETTLED_COLORS[self.field[line][masu]]  )#(220,220,220) )
        # 操作中のミノの表示
        if self.mino:
            for block in self.mino.get_block_positions():
                self._render_block( (block[0], block[1]), MINO_COLORS[self.mino.color_id])#(255,255,255) )
        # screen に書き付ける。
        self.screen.blit(self.surface, (10,20))
        pygame.display.flip()
    
    def render_next(self):
        """ 次ミノを描画する """
        self.next_surface.fill((0,0,0))
        for block in self.next_mino.blocks[0]:
            pygame.draw.rect(self.next_surface, MINO_COLORS[self.next_mino.color_id],
                                        pygame.Rect(
                                                            (self.block_size*block[0], self.block_size*block[1]),
                                                            (self.block_size, self.block_size)
                                        ))
        self.screen.blit(self.next_surface, (120,20))
        pygame.display.flip()

class GameOver(Exception):
    def __init__(self, score):
        self.score = score
    def __str__(self):
        return "GAME OVER -- socre:" + str(self.score)

def main():
    pygame.mixer.pre_init(channels=1, buffer=1024)
    pygame.init() # pygameの初期化
    screen = pygame.display.set_mode( (SCR_W, SCR_H) ) # 画面を作る
    pygame.display.set_caption('TENUKIS') # タイトル
    
    c = pygame.Color(128,128,128)
    screen.fill(c)
    
    pygame.display.flip()
    
    game = Game(screen)
    try:
        game.main_loop()
    except GameOver as go:
        pygame.time.wait(1000)
        print go
        return

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

動画の FPS を変更する。

TENUKIS の動画は スクリン・クロラ (現在サイト閉鎖中?) で撮影しているのですが、(私の環境では) 30fps で撮影しているのに、何故か 29.05fps くらいの動画を書き出してしまいます。
動画だけならこのまま公開しても大きな支障は無いのですが、音声と合成する場合、音がズレてしまう。
そこで、動画の FPS を変更し、然る後に音声と結合させます。
動画の結合は下記サイトの通りなので、特に書くことはないんですが。
Macで動画の再生速度を変換する [Mac OSの使い方] All About
で、これを別途録音していたオーディオファイルと、上手いこと結合させたら*1出来上がりです。
完成品はこんな感じ。

*1:私は iMovie HD を使用。

pygame での音の出し方

sound = pygame.mixer.Sound("sound.wav")
sound.play()

ただし、遅延が気になる場合には pygame.mixer の設定を調整し、バッファを減らす必要がある。 pygame.mixer は、一度 init されると事後的に変更することはできないので (どうしてもやりたい場合、pygame.mixer.quit() した後に再度 pygame.mixer.init() することになる)、 init 前に設定を済ませる必要がある。そして pygame.mixer は pygame.init() で初期化される。
そういうわけで、遅延を減らすにはこんな感じのコードが必要。

def main():
    pygame.mixer.pre_init(buffer=1024)
    pygame.init() # pygameの初期化
    ...

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)

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

TENUKIS v0.3

  • ミノの移動、コンコンとグイーンに対応しました。
  • 色がつきました。


これで回転法則をきっちり実装すれば、練習用にはなるかなっていう程度の段階には来ました。
今後の目標

  1. 回転法則をちゃんと実装する
  2. 遊べる感じにする
    1. ゲームが進むにつれ難易度が上がるようにする*1
    2. 面白みのある点数システムや、ゲームクリアの実装*2
    3. 効果音*3
  3. 公開できるようにする
    1. キーコンフィグ ( 現状はテンキー付きキーボードでのみ遊べる。 )
    2. py2app や py2exe

ソースコードはこんな感じ。色は、クラスをキーにした辞書で実装するというより理論的な実装も考えたんですが、却って手間が大きくなりそうだったので、たまたま一致してる感じの実装に。パブリックドメインで。

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

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

KEY_ROTATE_R = K_KP2
KEY_ROTATE_L1 = K_KP1
KEY_ROTATE_L2 = K_KP3

#MINO_SET = [MinoT, MinoI, MinoO, MinoZ, MinoS, MinoL, MinoJ]
MINO_COLORS = [(173, 216, 230, 255), (251, 182, 182, 255), (251, 251, 182, 255), (182, 251, 182, 255), (223, 186, 246, 255), (251, 226, 182, 255), (182, 182, 251, 255)]
 #[ pygame.Color(color) for color in ["light blue", "red", "yellow", "green", "purple", "orange", "blue"]]
MINO_SETTLED_COLORS = [(215, 236, 243, 255), (254, 204, 204, 255), (254, 254, 204, 255), (204, 254, 204, 255), (234, 207, 251, 255), (254, 237, 204, 255), (204, 204, 254, 255)]
 # MINO_COLORS各色をhslaのlにつき10%増やした物
"""
MINO_DICT = {
    'MinoT':{'color':(173, 216, 230, 255) , 'settled_color':(61, 162, 195, 255)},
    'MinoI':{'color':(255, 0, 0, 255) , 'settled_color':(163, 0, 0, 255)},
    'MinoO':{'color':(255, 255, 0, 255) , 'settled_color':(162, 163, 0, 255)},
    'MinoZ':{'color':(0, 255, 0, 255) , 'settled_color':(0, 163, 0, 255)},
    'MinoS':{'color':(160, 32, 240, 255) , 'settled_color':(103, 10, 162, 255)},
    'MinoL':{'color':(255, 165, 0, 255) , 'settled_color':(163, 105, 0, 255)},
    'MinoJ':{'color':(0, 0, 255, 255) , 'settled_color':(0, 0, 163, 255)}
}
"""

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を右回転させる。落下したら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)
        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
        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
        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))) #ト
    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)
        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
        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)
        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
        else:
            return False

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
        else:
            return False
            
class MinoO(Mino):
    color_id = 2
    appearance_at = (4,0)
    blocks = [[(0,0),(0,1),(1,0),(1,1)]]
    
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
        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
        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.can_alive(self.blocks[(self.rotation+1)%len(self.blocks)], self.position, self.field):
            self.rotation = (self.rotation+1)%len(self.blocks)
        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
        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
        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
        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)
        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
        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
        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
        else:
            return False

class MinoJ(Mino):
    color_id = 6
    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)
        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
        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
        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
        else:
            return False

class MinoGenerator:
    def __init__(self, field):
        self.c = 0
        self.field = field
        random.shuffle(MinoGenerator.kinds)
    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])(self.field)

class Score():
    def __init__(self, screen):
        self.font = pygame.font.Font(None, 32) # フォントを読み込む
        self.screen = screen
        self._value = 0
        self._text = self.font.render("score", True, (255,255,255))
        self.screen.blit( self._text, (260,0))
        self.render()
    def get(self):
        return self._value
    def set(self,v):
        self._value = v
        self.render()
    def append(self,v):
        self.set( self._value + v )
    def render(self):
        v = self.font.render( str(self.get()*100), True, (255,255,255), (128,128,128))
        self.screen.blit( v, (self.screen.get_width() - v.get_width()-5, 32))

class Game():
    """ 1ゲーム """
    FPS = 60 #frame per second
    MSPF = 1000/FPS #milliseconds per frame
    MAX_ASOBI = 120 #設置後固定までのフレーム数
    MAX_DELETE_TIME = 10 #列が消える場合の次ミノ落下までのフレーム数
    MAX_WAIT_BEFORE_MINO = 30 #設置(&削除)後、次のミノの登場までのフレーム数
    MOVE_ASOBI = 6 #pressedキープで移動を始めるまで。
    WIDTH = 10
    HEIGHT = 20
    def __init__(self, screen, block_size=10):
        self.screen = screen
        self.block_size = block_size
        self.surface = pygame.Surface(( block_size*Game.WIDTH, block_size*Game.HEIGHT ))
        self.next_surface = pygame.Surface(( block_size*4, block_size*4 )) #次ミノ表示
        self.clock = pygame.time.Clock()
        self.asobi = Game.MAX_ASOBI
        self.move_asobi = Game.MOVE_ASOBI
        self.delete_time = Game.MAX_DELETE_TIME
        self.wait_before_mino = Game.MAX_WAIT_BEFORE_MINO
        self.field = [[ None for i in range(Game.WIDTH)] for j in range(Game.HEIGHT)] #10*20のマス。Noneが空、[0-6]が色。
        self.minogen = MinoGenerator( self.field )
        self.mino = self.minogen.next()
        self.next_mino = self.minogen.next()
        self.score = Score(self.screen)
        
        self.render()
        self.render_next()
        
    def main_loop(self):
        while(True):
            if pygame.event.get(QUIT):
                return
            
            if self.mino:
                self.clock.tick( Game.FPS ) #FPS
                self.asobi -= 1
                if self.asobi < 0:
                    self.settle_and_next()
                    self.render()
                    next
                else:
                    pygame.event.pump() #おまじない
                    kp = pygame.key.get_pressed()
                    if kp[K_LEFT]:
                        if self.move_asobi == Game.MOVE_ASOBI:
                            self.move_l()
                        if self.move_asobi > 0:
                            self.move_asobi -= 1
                        else:
                            self.move_l()
                    elif kp[K_RIGHT]:
                        if self.move_asobi == Game.MOVE_ASOBI:
                            self.move_r()
                        if self.move_asobi > 0:
                            self.move_asobi -= 1
                        else:
                            self.move_r()
                    else:
                        self.move_asobi = Game.MOVE_ASOBI
                    
                    for event in pygame.event.get(KEYDOWN):
                        if event.key == K_ESCAPE:
                            return
                        if event.key == KEY_ROTATE_R:
                            self.rotate_r()
                        elif event.key == KEY_ROTATE_L1 or event.key == KEY_ROTATE_L2:
                            self.rotate_l()
                        elif event.key == K_DOWN:
                            self.settle_and_next()
                            pygame.event.clear()
                            next
                            
                    if self.fall():
                        self.asobi = self.MAX_ASOBI
                    self.render()
                    next
                    
    def settle_and_next(self):
        """ self.mino を self.field に固定し、新ミノを出す """
        for block in self.mino.get_block_positions():
            self.field[block[1]][block[0]] = self.mino.__class__.color_id
        self.mino = None
        self.render()
        if self.delete_line(): #delete_line()は消える列があれば消してTrueを返す
            self.render()
            pygame.time.wait( self.delete_time * Game.MSPF )
        pygame.time.wait( (self.wait_before_mino) * Game.MSPF )
        self.mino = self.next_mino
        #回転チェック
        pygame.event.pump() #おまじない
        kp = pygame.key.get_pressed()
        if kp[KEY_ROTATE_R]:
            self.mino.rotate_r()
        elif kp[KEY_ROTATE_L1] or kp[KEY_ROTATE_L2]:
            self.mino.rotate_l()
        
        self.next_mino = self.minogen.next()
        self.render_next()
        if not self.mino.can_alive(self.mino.get_blocks(), self.mino.position, self.field): #将来的にはコード変えたい。
            self.render()
            raise GameOver( self.score.get() )
        self.fall()
        return
    
    def delete_line(self):
        """ 列を消す。消した列数を返す。 """
        deleted = 0
        for line in xrange(len(self.field)):
            is_filled = True
            for block in self.field[line]:
                if block == None:
                    is_filled = False
            if is_filled:
                del self.field[line]
                self.field.insert(0, [None for i in xrange(self.WIDTH)])
                deleted += 1
        self.score.append([0,1,3,7,12][deleted]) #点数
        return deleted
    
    def move_l(self):
        self.mino.move_l()
        self.fall()
    def move_r(self):
        self.mino.move_r()
        self.fall()
    def rotate_l(self):
        self.mino.rotate_l()
        self.fall()
    def rotate_r(self):
        self.mino.rotate_r()
        self.fall()
    
    def fall(self):
        if self.mino.fall():
            self.asobi = self.MAX_ASOBI
            return True
        else:
            return False
    
    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):
        """ メイン画面を描画する """
        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), MINO_SETTLED_COLORS[self.field[line][masu]]  )#(220,220,220) )
        # 操作中のミノの表示
        if self.mino:
            for block in self.mino.get_block_positions():
                self._render_block( (block[0], block[1]), MINO_COLORS[self.mino.color_id])#(255,255,255) )
        # screen に書き付ける。
        self.screen.blit(self.surface, (10,20))
        pygame.display.flip()
    
    def render_next(self):
        """ 次ミノを描画する """
        self.next_surface.fill((0,0,0))
        for block in self.next_mino.blocks[0]:
            pygame.draw.rect(self.next_surface, MINO_COLORS[self.next_mino.color_id],
                                        pygame.Rect(
                                                            (self.block_size*block[0], self.block_size*block[1]),
                                                            (self.block_size, self.block_size)
                                        ))
        self.screen.blit(self.next_surface, (120,20))
        pygame.display.flip()

class GameOver(Exception):
    def __init__(self, score):
        self.score = score
    def __str__(self):
        return "GAME OVER -- socre:" + str(self.score)

def main():
    pygame.init() # pygameの初期化
    screen = pygame.display.set_mode( (SCR_W, SCR_H) ) # 画面を作る
    pygame.display.set_caption('TENUKIS') # タイトル
    
    c = pygame.Color(128,128,128)
    screen.fill(c)
    
    pygame.display.flip()
    
    game = Game(screen)
    try:
        game.main_loop()
    except GameOver as go:
        pygame.time.wait(1000)
        print go
        return

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

*1:概ね 7 フレーム (≒0.117秒)が最高難度になるように。

*2:ハイスコア機能はめんどくさそうなのでパスだが、結果をつぶやく機能はあっても良いかもしれない。

*3:音楽はいらないと思う。バックグラウンドで iTunes 動かせば良い。