TENUKIS v0.1
Mac のテトリスクローンが見当たらないので*1、試みに製作中です。今のところは手動落下のみ。そして難しいことは解らないので常に一番下まで落下することにしてます。
テトリスのプレイが下手くそなのはさておき。
バックアップを兼ねて現時点のソースコードを載せておきます。ライセンスは…このバージョンはパブリックドメインでいいや。片や 482 バイトでテトリスを作ってる人すらいらっしゃるというのに、私ときたら未完成時点で 351 行…。まぁ、 Python も pygame も初心者なので仕方ない、ということで…。
そして、行き当たりばったりで作ってるからスパゲッティー。
# -*- coding:utf-8 -*- import pygame from pygame.locals import * import random SCR_W = 320 # 表示ウィンドウの横幅 SCR_H = 240 # 表示ウィンドウの縦幅 class Mino: """ テトラミノの基底クラス """ blocks = None # blocksは左上を(0,0)とするブロックの配置 ( ( (x1,y1),(x2,y2)...),... ) の形式 appearance_at = (3,0) #出現場所 def __init__(self, field): self.field = field self.position = [i for i in self.appearance_at] self.rotation = 0 #現在の回転位置 #self.fall() def can_alive(self, blocks, position, field): """ position,blocks で定義されたブロック群が field で存在しうるか """ for block in blocks: p = (block[0]+position[0], block[1]+position[1]) if p[0]<0: return False try: b = field[p[1]][p[0]] if b != None: return False except IndexError: return False return True def rotate_r(self): """ selfを右回転させる。落下したらTrue(アソビがリセットされる予定) """ if self.can_alive(self.blocks[(self.rotation+1)%len(self.blocks)], self.position, self.field): self.rotation = (self.rotation+1)%len(self.blocks) return self.fall() else: return False def rotate_l(self): if self.can_alive(self.blocks[(self.rotation-1)%len(self.blocks)], self.position, self.field): self.rotation = (self.rotation-1)%len(self.blocks) return self.fall() else: return False def move_r(self): if self.can_alive(self.blocks[self.rotation], (self.position[0]+1, self.position[1]), self.field): self.position[0] += 1 return self.fall() else: return False def move_l(self): if self.can_alive(self.blocks[self.rotation], (self.position[0]-1, self.position[1]), self.field): self.position[0] -= 1 return self.fall() else: return False def fall(self): fell = False # 1行でも落ちたか否か while self.can_alive( self.blocks[self.rotation], (self.position[0], self.position[1]+1), self.field): self.position[1] += 1 if not fell: fell = True return fell def settle(self): """ ミノを field に固定する """ blocks = [ (block[0]+self.position[0], block[1]+self.position[1]) for block in self.blocks[self.rotation] ] for block in blocks: self.field[block[1]][block[0]] = 1 return class MinoT(Mino): blocks = (((0,1),(1,1),(2,1),(1,2)), #T ((1,0),(0,1),(1,1),(1,2)), ((1,1),(0,2),(1,2),(2,2)), ((1,0),(1,1),(2,1),(1,2))) #ト def rotate_r(self): if self.can_alive(self.blocks[(self.rotation+1)%len(self.blocks)], self.position, self.field): self.rotation = (self.rotation+1)%len(self.blocks) return self.fall() elif (self.rotation == 1 and self.can_alive(self.blocks[2], (self.position[0]-1, self.position[1]), self.field)): self.position[0] -= 1 self.rotation = 2 return self.fall() else: return False def rotate_l(self): if self.can_alive(self.blocks[(self.rotation-1)%len(self.blocks)], self.position, self.field): self.rotation = (self.rotation-1)%len(self.blocks) return self.fall() elif (self.rotation == 3 and self.can_alive(self.blocks[2], (self.position[0]+1, self.position[1]), self.field)): self.position[0] += 1 self.rotation = 2 return self.fall() else: return False class MinoI(Mino): blocks = (((0,1),(1,1),(2,1),(3,1)), ((2,0),(2,1),(2,2),(2,3))) def rotate_r(self): if self.can_alive(self.blocks[1-self.rotation], self.position, self.field): self.rotation = 1-self.rotation return (self.fall() or self.rotation==1) else: return False class MinoO(Mino): appearance_at = (4,0) blocks = [[(0,0),(0,1),(1,0),(1,1)]] class MinoZ(Mino): blocks = (((0,1),(1,1),(1,2),(2,2)), ((2,0),(1,1),(2,1),(1,2))) def rotate_r(self): if self.can_alive(self.blocks[1-self.rotation], self.position, self.field): self.rotation = 1-self.rotation self.fall() return True elif self.can_alive(self.blocks[1-self.rotation], (self.position[0]-1,self.position[1]), self.field): self.position[0] -= 1 self.rotation = 1-self.rotation self.fall() return True else: return False def rotate_l(self): return self.rotate_r() class MinoS(Mino): blocks = (((1,1),(2,1),(0,2),(1,2)), ((0,0),(0,1),(1,1),(1,2))) def rotate_r(self): if self.can_alive(self.blocks[1-self.rotation], self.position, self.field): self.rotation = 1-self.rotation self.fall() return True elif self.can_alive(self.blocks[1-self.rotation], (self.position[0]+1,self.position[1]), self.field): self.position[0] += 1 self.rotation = 1-self.rotation self.fall() return True else: return False def rotate_l(self): return self.rotate_r() class MinoL(Mino): blocks = (((0,1),(1,1),(2,1),(0,2)), ((0,0),(1,0),(1,1),(1,2)), ((2,1),(0,2),(1,2),(2,2)), ((1,0),(1,1),(1,2),(2,2))) #L def rotate_r(self): if self.can_alive(self.blocks[(self.rotation+1)%len(self.blocks)], self.position, self.field): self.rotation = (self.rotation+1)%len(self.blocks) return self.fall() elif (self.rotation == 1 and self.can_alive(self.blocks[2], (self.position[0]+1, self.position[1]), self.field)): self.position[0] += 1 self.rotation = 2 return self.fall() elif (self.rotation == 1 and self.can_alive(self.blocks[2], (self.position[0]-1, self.position[1]), self.field)): self.position[0] -= 1 self.rotation = 2 return self.fall() elif (self.rotation == 3 and self.can_alive(self.blocks[0], (self.position[0]+1, self.position[1]), self.field)): self.position[0] += 1 self.rotation = 0 return self.fall() else: return False def rotate_l(self): if self.can_alive(self.blocks[(self.rotation-1)%len(self.blocks)], self.position, self.field): self.rotation = (self.rotation-1)%len(self.blocks) return self.fall() elif (self.rotation == 0 and self.can_alive(self.blocks[3], (self.position[0]-1, self.position[1]), self.field)): self.position[0] -= 1 self.rotation = 3 return self.fall() elif (self.rotation == 1 and self.can_alive(self.blocks[0], (self.position[0]-1, self.position[1]), self.field)): self.position[0] -= 1 self.rotation = 0 return self.fall() elif (self.rotation == 3 and self.can_alive(self.blocks[2], (self.position[0]+1, self.position[1]), self.field)): self.position[0] += 1 self.rotation = 2 return self.fall() else: return False class MinoJ(Mino): blocks = (((0,1),(1,1),(2,1),(2,2)), ((2,0),(2,1),(1,2),(2,2)), # 」 ((0,1),(0,2),(1,2),(2,2)), ((1,0),(2,0),(1,1),(1,2))) #「 def rotate_r(self): if self.can_alive(self.blocks[(self.rotation+1)%len(self.blocks)], self.position, self.field): self.rotation = (self.rotation+1)%len(self.blocks) return self.fall() elif (self.rotation == 0 and self.can_alive(self.blocks[1], (self.position[0]+1, self.position[1]), self.field)): self.position[0] += 1 self.rotation = 1 return self.fall() elif (self.rotation == 1 and self.can_alive(self.blocks[2], (self.position[0]+1, self.position[1]), self.field)): self.position[0] += 1 self.rotation = 2 return self.fall() elif (self.rotation == 3 and self.can_alive(self.blocks[0], (self.position[0]+1, self.position[1]), self.field)): self.position[0] += 1 self.rotation = 0 return self.fall() else: return False # def rotate_l(self): class MinoGenerator: def __init__(self): self.c = 0 kinds = [MinoT, MinoI, MinoO, MinoZ, MinoS, MinoL, MinoJ] def next(self): self.c += 1 if self.c > len(MinoGenerator.kinds)-1: self.c = self.c % len(MinoGenerator.kinds) random.shuffle(MinoGenerator.kinds) return MinoGenerator.kinds[self.c] class Field: """ 10*20 の大きさをもつゲームフィールド """ WIDTH = 10 HEIGHT = 20 def __init__(self, block_size=10): self._block_size = block_size self.surface = pygame.Surface(( block_size*Field.WIDTH, block_size*Field.HEIGHT )) self.field = [[ None for i in range(Field.WIDTH)] for j in range(Field.HEIGHT)] #10*20のマス。Noneが空、[0-6]が色。 self.mino = False #MinoT(self.field) #TEST CODE def _render_block(self, position, color): """ position = (x,y) のマスを描く """ pygame.draw.rect(self.surface, color, pygame.Rect( (self._block_size*position[0], self._block_size*position[1]), (self._block_size, self._block_size) )) def render(self): """ fieldを描画する """ self.surface.fill( (0,0,0) ) # 固定済みブロックの描画 for line in xrange(len(self.field)): for masu in xrange(len(self.field[line])): if( self.field[line][masu] != None ): self._render_block( (masu, line), (220,220,220) ) # 操作中のミノの表示 if self.mino: for block in self.mino.blocks[self.mino.rotation]: self._render_block( (self.mino.position[0]+block[0], self.mino.position[1]+block[1]), (255,255,255) ) def main(): pygame.init() # pygameの初期化 screen = pygame.display.set_mode( (SCR_W, SCR_H) ) # 画面を作る pygame.display.set_caption('TENUKIS') # タイトル #fieldの初期化 field = Field() generator = MinoGenerator() field.mino = (generator.next())(field.field) c = pygame.Color(128,128,128) screen.fill(c) def render_field(): field.render() screen.blit(field.surface, (10,20)) pygame.display.flip() render_field() font = pygame.font.Font(None, 32) # フォントを読み込む text = font.render('score', True, (255,255,255)) screen.blit(text, (260,0)) # 文字を画面に貼り付ける pygame.display.flip() #clock = pygame.time.Clock() while 1: #clock.tick(60) #FPS for event in pygame.event.get(): # イベントチェック if event.type == QUIT: # 終了が押された? return if (event.type == KEYDOWN and event.key == K_ESCAPE): # ESCが押された? return #右回転 if (event.type == KEYDOWN and event.key == K_KP2): field.mino.rotate_r() render_field() next #左回転 if (event.type == KEYDOWN and ((event.key == K_KP1 ) or (event.key == K_KP3))): field.mino.rotate_l() render_field() next #固定 if(event.type == KEYDOWN and event.key == K_DOWN): # TESTING if not field.mino.fall(): #本番環境では、ここからインデント1個あげる field.mino.settle() #削除処理 def is_filled( line ): for b in line: if b == None: return False return True for i in xrange(len(field.field)): if is_filled(field.field[i]): del field.field[i] field.field.insert(0, [None for i in xrange(Field.WIDTH)]) field.mino = (generator.next())(field.field) #インデント上げここまで render_field() next #左移動 if(event.type == KEYDOWN and event.key == K_LEFT): field.mino.move_l() render_field() next #右移動 if(event.type == KEYDOWN and event.key == K_RIGHT): field.mino.move_r() render_field() next if __name__ == '__main__': main() # end of file
たとえば Mino クラスで rotate_r 等を実装してるんですが、ミノの種類によっていちいち特殊な回転を設けているので*2殆どオーバーライドしちゃってて無意味な感じに。
あと、いちいち Field.field をメンバーにしてるオブジェクトが多すぎる感じも…。たぶん MVC (的な要素) の分離が上手くいってないのが悪いんですね。やっぱちゃんと設計しろ、と。