東北電力の使用状況を表示する Google Gadget を作りました。


←に付いてると思います。「ecoco - 東北電力使用状況」です。
iGoogle なり blog パーツにするなりして使ってください。
主だったコードは Dashboard Widget 版から流用しているので解説もそちらに譲ります。


余談。
Gadget はデバッグがやりにくいのが難です。 Google Gadget Editor のプレビューでは gadget 固有の API を使えないので、外部通信 (からのパース処理) のテストができないのです。
なので、そういったロジック部分のコードを、よりテストのしやすい Dashboard Widget として開発することで、デバッグの手間を削減してみました。
本当はガジェットじゃなくて iPhone(/Android) アプリにして、 iAd で広告収入でウハウハ プッシュ配信を使い電力需給の逼迫をタイムリーに配信したいと思ってたんですけど、何か開発環境の構築が上手く行かなかったのでこういう形でお茶を濁しました。ウィジェットにせよガジェットにせよ、そんなん見るくらいなら回線切って寝た方が節電になるのでアレですけど。

東北電力の使用状況を表示する Dashboard Widget を作りました。


「東北電力使用状況」(愛称「えここ」)です。
使い方は見ての通りです。
東北電力が提供している電力使用情況CSV ファイルをパースして、(さしあたり)当日のピーク供給力と直近の使用状況データを元に、使用率を計算しています(小数点以下切り上げ)。
あと関電の天気予報に合わせて、 90%, 95% を超えると文字色が変わります。
東北電力電力使用制限令を出さなければならない程度には電気が不足しているようなので、節電を心がけましょう。
標準パーツしか使ってないんだから、もっと早く作れたはずなんですが…すみません。


余談ですけど、この「使用率」はあくまで当日のピーク時供給力を元に算出しています。どの程度供給するかは、需要の予測に基づいて電力会社が(供給能力の限度内で)決めるので、全力での供給力とは必ずしも一致しないはずです。要するに、涼しい日にはピーク時供給も少なくするので、少ない使用量でも「使用率」は高めに出るということです。
もっとも、節電に気を遣う必要があるほど使用率が高いときには全力に近い供給を行うはずなので、「使用率」が高ければ高いほど、真の供給能力比での使用率に近づくと考えられます。
つまり何が言いたいかというと、簡単に陰謀論に引っかからないでくださいって話。
だいたい無駄な電気は使わないほど良いに決まってるので*1、電力不足にならなくても節電はするべきなんですよ。大人は分からないのかもしれないですけど。

*1:少なくとも現時点では、使えば使うほど化石燃料を燃やす。

たいてぃーぬむ途中まで。

うまく動かない…。 OSX で Titanium Studio で Android しようとしたら、

Could not locate the Android SDK at the given path

というエラーメッセージが出た。
場合の解決策として、パッと見2つくらいが提示されてた。

前者を試したけど上手く行かなかったので、後者も試してみた。けど上手く行かなかったので、その途中までの作業メモ。


http://wiki.appcelerator.org/display/tis/Installing+the+ADT+Plugin#InstallingtheADTPlugin-InstalltheADTplugin
Help / Install New Software... を開き、 Work with に https://dl-ssl.google.com/android/eclipse/ を入力して Add をクリック。名前は適当に(例:Google ADT)付けて OK 。
しばらく待つと Developer Tools がリストアップされるので、 Select All して Next。
で、上掲サイトによればこれで上手くインストールされるらしい。


が、私は下記エラーが出た。

Cannot complete the install because one or more required items could not be found

これを解決するには、 http://download.eclipse.org/releases/galileo を Available Software Sites に加えると良いらしい。
Android-er: Cannot complete the install because one or more required items could not be found
Help / Install New Software... のダイアログ内に、よくみると Find more software by working with the "Available Software Sites" preferences. という文字列がある ( Add の下)。なので、これをクリックするとリストが出る(Preferences / Install/Update の中へのリンクになっている)。
で、そこに上記の http://download.eclipse.org/releases/galileo を Add すれば良い…んだと思うんだけど、私の場合はどうやらそれが元から存在するようで (Duplicate locationエラーが出る)、リストにある http://download.eclipse.org/releases/galileo のチェックを入れて Enabled にしてあげれば良いみたいだ。


以上を終えた上で、再度、上記 ADT plug-in のインストールにチャレンジ。
…すると、下記エラーが出て失敗。

Cannot complete the install because of a conflicting dependency.

というところまで。この先も頑張るかどうかはわからんです。

screen = pygame.display.set_mode(QVGA, pygame.FULLSCREEN) の話

フルスクリーンディスプレイモードが設定された時、場合によってはこちらで想定していたフルスクリーン状態にならない場合もあります。こうした状況において、pygameは要求に最も近いディスプレイモードを選び出して実行します。 作成されたsurfaceのサイズは、設定されたresolution引数と常に同じ値になります。

display - Pygameドキュメント 日本語訳

普通の PC 用ディスプレイは QVGA (320*240 pixel) の画面モードに対応していない。
なので、画面サイズをフルスクリーンとしつつ QVGA を指定すると、画面は違うサイズに設定される。 pygame がベースにしている SDL が、聴きごとの画面サイズの違いを吸収するべく、可能な画面サイズでエミュレートしてくれるためらしい。
フルスクリーンのスクリーンショットを撮ってみると解りやすい。

(※ はてダの仕様で、実際のスクショよりも縮小されてます。)
スクショの画像サイズは 640*400 だった。
だから何って話だけど、フルスクリーンにするなら、一般的なディスプレイサイズ(例えば VGA )に合わせて作るのが吉。エミュレーションで縦横比がしばしば変になるから。

TENUKIS v0.6

簡易ですが、メニュー画面的を実装しました。機能するのは "new_game" と "exit" だけなんですが。
ゲームクリアの概念を導入しました。 999 ミノ耐えたらクリアです。
エンドレスモードもそのうち実装します。
あとコードの見通しを良くするため、 Mino 及びそのサブクラスは別ファイルに分割。

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

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

KEY_ROTATE_R = K_KP2
KEY_ROTATE_L1 = K_KP1
KEY_ROTATE_L2 = K_KP3

BG_COLOR = pygame.Color(128,128,128)

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

class RenderableNumber():
    def __init__(self, screen, format, position, font_size=24, value=0):
        self.font = pygame.font.Font(None, font_size)
        self.screen = screen
        self._value = value
        self.format = format
        self.position = position
    def get(self):
        return self._value
    def set(self, v):
        self._value = v
        self.render()
    def __iadd__(self, v):
        self.set(self._value + v)
        return self
    def render(self):
        v = self.font.render(self.format % self.get(), True, (255,255,255), BG_COLOR)
        self.screen.fill( BG_COLOR, (self.position, (v.get_width()+self.font.get_height(), self.font.get_height())) )
        self.screen.blit( v, self.position)
        
class Score(RenderableNumber):
    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 render(self):
        v = self.font.render( str(self.get()), True, (255,255,255), (128,128,128))
        self.screen.fill( BG_COLOR, ((self.screen.get_width()-v.get_width()-30, 32), (v.get_width()+30, 32)) )
        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 = 60 #設置後固定までのフレーム数
    MAX_DELETE_TIME = 8 #列が消える場合の次ミノ落下までのフレーム数
    MAX_WAIT_BEFORE_MINO = 25 #設置(&削除)後、次のミノの登場までのフレーム数
    WIDTH = 10
    HEIGHT = 20
    CLEAR_LENGTH = 999 #このミノが固定したらクリア。
    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.max_asobi = Game.MAX_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+2)] #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.mino_length = RenderableNumber(self.screen, "mino : %d /"+str(Game.CLEAR_LENGTH), (200,160))
        self.deleted_length = RenderableNumber(self.screen, "line : %d", (200, 190))
        self.controller = ControllerManager()
        self.start_time = False
        
        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"),
            "ready": pygame.mixer.Sound("ready.wav")
        }
        
        self.render()
        self.render_next()
        
        self.sounds["ready"].play()
        self.wait(60)
        self.start_time = pygame.time.get_ticks() #これとの差分から経過時間を。
        
    def main_loop(self):
        while(True):
            if pygame.event.get(QUIT) or self.controller.to_quit():
                return
            
            if self.mino:
                self.tick() #FPS
                #遊びを減らす
                self.asobi -= 1
                if self.asobi < 0:
                    self.settle_and_next()
                    self.render()
                    next
                else: #回転→移動、順らしい。
                    #回転処理等
                    if self.controller.to_rotate_r():
                        self.rotate_r()
                    elif self.controller.to_rotate_l():
                        self.rotate_l()
                    if self.controller.to_settle():
                        self.settle_and_next()
                    #移動処理
                    if self.controller.to_move_l():
                        self.move_l()
                    elif self.controller.to_move_r():
                        self.move_r()
                    
                    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()
            self.wait( self.delete_time )
        if self.mino_length.get() >= Game.CLEAR_LENGTH: #ゲームクリア処理
            raise GameOver(self.score.get(),self.deleted_length.get(),self.mino_length.get(),timedelta(milliseconds=pygame.time.get_ticks()-self.start_time),clear=True )
        
        self.wait( (self.wait_before_mino) )
        self.mino = self.next_mino
        self.asobi = self.max_asobi
        #回転チェック
        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()
        
        self.next_mino = self.minogen.next()
        self.render_next()
        
        pygame.event.clear()
        self.mino_length += 1
        
        if not self.mino.can_alive(self.mino.get_blocks(), self.mino.position, self.field): #将来的にはコード変えたい。
            self.render()
            pygame.display.flip()
            raise GameOver(
                self.score.get(),
                self.deleted_length.get(),
                self.mino_length.get(),
                timedelta(milliseconds=pygame.time.get_ticks()-self.start_time)
                )
        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
        if deleted:
            self.score += [0,100,300,700,1200][deleted] #点数
            pd = self.deleted_length.get()
            self.deleted_length += deleted
            if  pd/10 < self.deleted_length.get()/10: #桁が増えたとき
                self.max_asobi -= 1
                if self.deleted_length.get()/10 % 5 == 0: #50行ごとに待ち時間を減らす
                    self.wait_before_mino -= 1
        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 tick(self):
        """ 1 フレーム """
        self.clock.tick( Game.FPS ) #FPS
        #入力状態の処理
        self.controller.update()
        #時計の描画
        position = (200,100)
        str_delta = str( timedelta(milliseconds=pygame.time.get_ticks()-self.start_time))[2:-3] if self.start_time else "0:00:00"
        v = (pygame.font.Font(None, 18)).render( str_delta, True, (255,255,255), BG_COLOR)
        self.screen.fill( BG_COLOR, (position, (v.get_width()+10, 18)) )
        self.screen.blit( v, position)
        
        pygame.display.flip()
    
    def wait(self, frame):
        for i in xrange(frame):
            self.tick()
    
    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):
        """ メイン画面を描画する。ただし実際の描画はGame.tickで。 """
        self.surface.fill( (0,0,0) )
        # 固定済みブロックの描画
        for ln in xrange(len(self.field)-2):
            line = ln+2
            for masu in xrange(len(self.field[line])):
                if( self.field[line][masu] != None ):
                    self._render_block( (masu, line-2), 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]-2), 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,line,mino,time, clear=False):
        self.score = score
        self.line = line
        self.mino = mino
        self.time = time
        self.clear = clear
    def __str__(self):
        return "GAME OVER -- socre:" + str(self.score)

class ControllerManager:
    ASOBI = 6 #移動の遊び
    def __init__(self):
        self._state = {
            "quit" : False,
            "rotate_r" : False,
            "rotate_l" : False,
            "move_r" : False,
            "move_l" : False,
            "settle" : False
        }
        self.asobi = ControllerManager.ASOBI
    
    def update(self):
        for item in self._state:
            self._state[item] = False
        
        for event in pygame.event.get(KEYDOWN):
            if event.key == K_ESCAPE:
                self._state["quit"] = True
            elif event.key == KEY_ROTATE_R:
                self._state["rotate_r"] = True
            elif (event.key == KEY_ROTATE_L1 or event.key == KEY_ROTATE_L2):
                self._state["rotate_l"] = True
            elif event.key == K_DOWN:
                self._state["settle"] = True
        pygame.event.clear(KEYDOWN)
        
        pygame.event.pump() #おまじない
        kp = pygame.key.get_pressed()
        if kp[K_LEFT]:
            if self.asobi == ControllerManager.ASOBI: #コンコン
                self._state["move_l"] = True
            if self.asobi > 0:
                self.asobi -= 1
            else:
                self._state["move_l"] = True
        elif kp[K_RIGHT]:
            if self.asobi == ControllerManager.ASOBI: #コンコン
                self._state["move_r"] = True
            if self.asobi > 0:
                self.asobi -= 1
            else:
                self._state["move_r"] = True
        else:
            self.asobi = ControllerManager.ASOBI
    
    def to_quit(self):
        return self._state["quit"]
    def to_rotate_r(self):
        return self._state["rotate_r"]
    def to_rotate_l(self):
        return self._state["rotate_l"]
    def to_move_r(self):
        return self._state["move_r"]
    def to_move_l(self):
        return self._state["move_l"]
    def to_settle(self):
        return self._state["settle"]

class Menu:
    def __init__(self, items):
        self.items = items
        self.selected = 0
        self.render()
    
    def render(self):
        surface = pygame.Surface( (200,200) )
        surface.fill( (0,0,0) )
        
        font = pygame.font.Font(None, 20)
        for i in xrange(len(self.items)):
            color = (255,255,255) if i==self.selected else (128,128,128)
            surface.blit( font.render( self.items[i], True, color ), (0, 25*i) )
        global screen
        screen.blit( surface, (20,20) )
        
        pygame.display.flip()
        return
    
    def wait_choose(self):
        print "push any key to select"
        while True:
            for event in pygame.event.get(KEYDOWN):
                if event.key == K_DOWN:
                    self.selected +=1
                    if self.selected >= len(self.items):
                        self.selected = 0
                    self.render()
                elif event.key == K_UP:
                    self.selected -=1
                    if self.selected < 0:
                        self.selected = len(self.items)-1
                    self.render()
                else:
                    return self.items[self.selected]

def new_game():
    global screen
    screen.fill( BG_COLOR )
    pygame.display.flip()
    
    game = Game(screen)
    game.main_loop()

def main():
    pygame.mixer.pre_init(channels=1, buffer=1024)
    pygame.init() # pygameの初期化
    global screen
    screen = pygame.display.set_mode( (SCR_W, SCR_H) ) # 画面を作る
    pygame.display.set_caption('TENUKIS') # タイトル
    
    screen.fill( BG_COLOR )
    
    pygame.display.flip()
    
    menu = Menu( ("new_game", "endless_game", "score", "setting", "exit") )
    
    while True:
        menu.render()
        pygame.time.wait(500)
        pygame.event.clear()
        select = menu.wait_choose()
        if select == "new_game":
            try:
                new_game()
            except GameOver as go:
                pygame.time.wait(1000)
                print "Score:%d by %d lines from %d minos in %s" % (go.score, go.line, go.mino, str(go.time)[:-3]) 
                next
        elif select == "endless_game":
            raise NotImplementedError
        elif select == "score":
            raise NotImplementedError
        elif select == "setting":
            raise NotImplementedError
        elif select == "exit":
            return

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

ただ、ゲームなんか作ってる場合か!という気もしないでもないので、少し開発停滞するかもです。

Text to Speech から音声ファイルを出力する。 (Mac OS X)

ターミナルで

say -v pipe -o hello.aiff hello

とやれば、パイプオルガンが「テスト」と発音する音声ファイルが。
say コマンドの最も簡単な用例は

say hello

で、これならシステム標準音声で、スピーカーから "Hello" と声がします。
-v オプションで声を選択できますが、声の種類は 【システム環境設定 > スピーチ > テキスト読み上げ】 内の「システムの声」の項目名を参照。名前が複合語の場合 ( Good News, Bad News, Pipe Organ ) は、最初の単語が声色になります。

余談。

今回は 22050 Hz な WAV ファイルが欲しかったところ、私は既に SOX をインストールしてあったので、上記 hello.aiff をこんな感じで処理。

sox -r 22050 hello.aiff hello.wav

TENUKIS v0.5.1


動画からは違いが分かりにくいところですが、まず目に見える違いとして、時間を計測することにしました。
時間の計測には datetime.timedelta を用いてます。便利ですね。そして、 timedelta.__str__() は長ったらしい形式だったり精度が過剰だったりするんですが、 Python は文字列の切り出しが簡単でいいですね。自分でクラスを作るときも、冗長なテキストを出力するようにすれば切り出しで合理化できそう。


で、従来は消えたとき等の待ち時間について pygame.time.wait を使っていたのですが、それだと時計が止まって見えてしまうので、自前の wait を用意しました。実際には tick を wait 回呼び出すだけなんですけど。ただ、この tick も、その度に時計を描画したりするために、独自の tick を用意しました。
で、それとも多少関係するところで、キー入力については独自のクラスに処理をさせるようにしました。毎フレームの入力状態をチェックして、メインループからは簡単なチェックで処理できるように。
実は v0.4 までは、ミノを回転させた状態で登場させる処理がしばしば入力ミスになっていて、原因不明なるも入力周りを刷新しようと思っていました。で、今回改変したところ、特に理由はわからないのですが*1、正しく処理されるようになりました。うーむ…。
ソースコードは下記。ただし動画撮影時はこれ以上に汚いコードでした。(なので動画は v0.5 , この記事は v0.5.1 なのです。)

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

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

KEY_ROTATE_R = K_KP2
KEY_ROTATE_L1 = K_KP1
KEY_ROTATE_L2 = K_KP3

BG_COLOR = pygame.Color(128,128,128)

#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,1) #出現場所
    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,2)
    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 RenderableNumber():
    def __init__(self, screen, title, position, font_size=24, value=0):
        self.font = pygame.font.Font(None, font_size)
        self.screen = screen
        self._value = value
        self.title = title
        self.position = position
    def get(self):
        return self._value
    def set(self, v):
        self._value = v
        self.render()
    def __iadd__(self, v):
        self.set(self._value + v)
        return self
    def render(self):
        v = self.font.render( self.title+" : "+str(self.get()), True, (255,255,255), BG_COLOR)
        self.screen.fill( BG_COLOR, (self.position, (v.get_width()+self.font.get_height(), self.font.get_height())) )
        self.screen.blit( v, self.position)
        
class Score(RenderableNumber):
    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 render(self):
        v = self.font.render( str(self.get()), True, (255,255,255), (128,128,128))
        self.screen.fill( BG_COLOR, ((self.screen.get_width()-v.get_width()-30, 32), (v.get_width()+30, 32)) )
        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 = 47 #設置後固定までのフレーム数
    MAX_DELETE_TIME = 8 #列が消える場合の次ミノ落下までのフレーム数
    MAX_WAIT_BEFORE_MINO = 25 #設置(&削除)後、次のミノの登場までのフレーム数
    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.max_asobi = Game.MAX_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+2)] #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.mino_length = RenderableNumber(self.screen, "mino", (200,160))
        self.deleted_length = RenderableNumber(self.screen, "line", (200, 190))
        self.controller = ControllerManager()
        self.start_time = pygame.time.get_ticks() #これとの差分から経過時間を。
        
        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) or self.controller.to_quit():
                return
            
            if self.mino:
                self.tick() #FPS
                #遊びを減らす
                self.asobi -= 1
                if self.asobi < 0:
                    self.settle_and_next()
                    self.render()
                    next
                else: #回転→移動、順らしい。
                    #回転処理等
                    if self.controller.to_rotate_r():
                        self.rotate_r()
                    elif self.controller.to_rotate_l():
                        self.rotate_l()
                    if self.controller.to_settle():
                        self.settle_and_next()
                    #移動処理
                    if self.controller.to_move_l():
                        self.move_l()
                    elif self.controller.to_move_r():
                        self.move_r()
                    
                    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()
            self.wait( self.delete_time )
        self.wait( (self.wait_before_mino) )
        self.mino = self.next_mino
        self.asobi = self.max_asobi
        #回転チェック
        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()
        
        self.next_mino = self.minogen.next()
        self.render_next()
        
        pygame.event.clear()
        self.mino_length += 1
        
        if not self.mino.can_alive(self.mino.get_blocks(), self.mino.position, self.field): #将来的にはコード変えたい。
            self.render()
            pygame.display.flip()
            raise GameOver(
                self.score.get(),
                self.deleted_length.get(),
                self.mino_length.get(),
                timedelta(milliseconds=pygame.time.get_ticks()-self.start_time)
                )
        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
        if deleted:
            self.score += [0,100,300,700,1200][deleted] #点数
            pd = self.deleted_length.get()
            self.deleted_length += deleted
            if  pd/10 < self.deleted_length.get()/10: #桁が増えたとき
                self.max_asobi -= 1
                if self.deleted_length.get()/10 % 5 == 0: #50行ごとに待ち時間を減らす
                    self.wait_before_mino -= 1
        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 tick(self):
        """ 1 フレーム """
        self.clock.tick( Game.FPS ) #FPS
        #入力状態の処理
        self.controller.update()
        #時計の描画
        position = (200,100)
        v = (pygame.font.Font(None, 18)).render( str(timedelta(milliseconds=pygame.time.get_ticks()-self.start_time))[2:-3], True, (255,255,255), BG_COLOR)
        self.screen.fill( BG_COLOR, (position, (v.get_width()+10, 18)) )
        self.screen.blit( v, position)
        
        pygame.display.flip()
    
    def wait(self, frame):
        for i in xrange(frame):
            self.tick()
    
    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]-2)),
                                                        (self.block_size*position[0], self.block_size*(position[1])),
                                                        (self.block_size, self.block_size)
                                    ))
    def render(self):
        """ メイン画面を描画する。ただし実際の描画はGame.tickで。 """
        self.surface.fill( (0,0,0) )
        # 固定済みブロックの描画
        for ln in xrange(len(self.field)-2):
            line = ln+2
            for masu in xrange(len(self.field[line])):
                if( self.field[line][masu] != None ):
                    self._render_block( (masu, line-2), 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]-2), 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,line,mino,time):
        self.score = score
        self.line = line
        self.mino = mino
        self.time = time
    def __str__(self):
        return "GAME OVER -- socre:" + str(self.score)

class ControllerManager:
    ASOBI = 6 #移動の遊び
    def __init__(self):
        self._state = {
            "quit" : False,
            "rotate_r" : False,
            "rotate_l" : False,
            "move_r" : False,
            "move_l" : False,
            "settle" : False
        }
        self.asobi = ControllerManager.ASOBI
    
    def update(self):
        for item in self._state:
            self._state[item] = False
        
        for event in pygame.event.get(KEYDOWN):
            if event.key == K_ESCAPE:
                self._state["quit"] = True
            elif event.key == KEY_ROTATE_R:
                self._state["rotate_r"] = True
            elif (event.key == KEY_ROTATE_L1 or event.key == KEY_ROTATE_L2):
                self._state["rotate_l"] = True
            elif event.key == K_DOWN:
                self._state["settle"] = True
        pygame.event.clear(KEYDOWN)
        
        pygame.event.pump() #おまじない
        kp = pygame.key.get_pressed()
        if kp[K_LEFT]:
            if self.asobi == ControllerManager.ASOBI: #コンコン
                self._state["move_l"] = True
            if self.asobi > 0:
                self.asobi -= 1
            else:
                self._state["move_l"] = True
        elif kp[K_RIGHT]:
            if self.asobi == ControllerManager.ASOBI: #コンコン
                self._state["move_r"] = True
            if self.asobi > 0:
                self.asobi -= 1
            else:
                self._state["move_r"] = True
        else:
            self.asobi = ControllerManager.ASOBI
    
    def to_quit(self):
        return self._state["quit"]
    def to_rotate_r(self):
        return self._state["rotate_r"]
    def to_rotate_l(self):
        return self._state["rotate_l"]
    def to_move_r(self):
        return self._state["move_r"]
    def to_move_l(self):
        return self._state["move_l"]
    def to_settle(self):
        return self._state["settle"]

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') # タイトル
    
    screen.fill( BG_COLOR )
    
    pygame.display.flip()
    
    game = Game(screen)
    try:
        game.main_loop()
    except GameOver as go:
        pygame.time.wait(1000)
        print "Score:%d by %d lines from %d minos in %s" % (go.score, go.line, go.mino, str(go.time)[:-3]) 
        return

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

*1:たぶん event.clear のタイミングとかだと思うんですけど。