ジュリスト震災関係記事の PDF を一括ダウンロードして一つの PDF にまとめる Automator Workflow
- 新規フォルダ
- 名前 : _temp
- 場所 : デスクトップ
- 変数の値を設定
- 変数 : temp_dir
- 指定された URL を取得
- Web ページからリンク URL を取得
- URL にフィルタを適用
- 条件 : 【URL 全体】 .pdf 【で終わる】
- URL をダウンロード
- 場所 : temp_dir*1
- PDF ページを結合
- Finder 項目の名前を変更
- 【名前の一部を変更】
- 変更する部分 : 【名前全体】
- 新しい名前 : ジュリスト震災関係記事.pdf
- Finder 項目を移動
- 保存先 : デスクトップ
- PDF メタデータを設定 (※必須ではない)
- タイトル : ジュリスト震災関係記事
- 作成者 : 有斐閣
- 変数の値を取得
- 変数 : temp_dir
- 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
実際には設置アソビ、消去アソビも存在する。
で、 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出来上がりです。
完成品はこんな感じ。
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
- ミノの移動、コンコンとグイーンに対応しました。
- 色がつきました。
これで回転法則をきっちり実装すれば、練習用にはなるかなっていう程度の段階には来ました。
今後の目標
- 回転法則をちゃんと実装する
- 遊べる感じにする
- 公開できるようにする
- キーコンフィグ ( 現状はテンキー付きキーボードでのみ遊べる。 )
- 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