参考にしたのはhttp://www13.plala.or.jp/kymats/study/game_other/SPACE_TETRIS/st1.html
実際には,ピースのデータ構造のみ参考にして,盤面はtkinterのCanvas上にrectangleを並べると,これがobjectとして後から色を変更できるようになるので,これを使って実装 しました.
画面はこんな感じです.
点数処理も書いたのですが,これだけで結構な分量になったので,最後に点数処理の部分を除いたソースを以下に掲載します.
これを応用して六角形にしたものを「PythonでHextris」に載せておきます.昔X68000で遊んでいたゲームを作ってみました.
#!/usr/bin/env python # -*- coding: utf-8 -*- # Python 2.7.5 on OS X 10.9.2 # by Mitsuharu Arimura 2015/3/14 import Tkinter as Tk import datetime as dt import time, random import sys, os, csv # ゲームフィールドのサイズ FIELD_WIDTH = 18 FIELD_HEIGHT = 30 # ブロックのサイズ PIECE_WIDTH = 4 PIECE_HEIGHT = 4 # 1個のセルのピクセル数 CELL_WIDTH = 20 CELL_HEIGHT = 20 # ゲーム本体の画面 View class Board( Tk.Canvas ): # フィールドのピクセル数 BOARD_WIDTH = FIELD_WIDTH * CELL_WIDTH BOARD_HEIGHT = FIELD_HEIGHT * CELL_HEIGHT margin = 2 def __init__( self, master ): self.width = self.BOARD_WIDTH self.height = self.BOARD_HEIGHT Tk.Canvas.__init__( self, master, relief=Tk.RAISED, bd=self.margin, bg='white', width=self.BOARD_WIDTH, height=self.BOARD_HEIGHT ) # Canvas上の rectangle の2次元配列 self.Field = [[0 for y in range(FIELD_HEIGHT)] for x in range(FIELD_WIDTH)] offset = self.margin * 2 + 3 for x in range( FIELD_WIDTH ): for y in range( FIELD_HEIGHT ): x0 = x * CELL_WIDTH + offset y0 = y * CELL_HEIGHT + offset r = self.create_rectangle( x0, y0, x0 + CELL_WIDTH, y0 + CELL_HEIGHT, fill='white', width=0) self.Field[x][y]=r # PAUSEの文字 TEXT_font = ('Helvetica','60','bold') TEXT_color = 'red' self.pause_text = self.create_text( self.BOARD_WIDTH/2, 100, text='PAUSE', font=TEXT_font, fill=TEXT_color, justify=Tk.CENTER) self.HidePauseText() def SetColor( self, x, y, value ): #print "SetColor:" if x < 0: return if x >= FIELD_WIDTH: return if y < 0: return if y >= FIELD_HEIGHT: return #print "(%d, %d)=>%d" % (x, y, value) if value == 1: self.itemconfigure( self.Field[x][y], fill='black') elif value == 2: self.itemconfigure( self.Field[x][y], fill='gray') elif value == 3: self.itemconfigure( self.Field[x][y], fill='slate gray') else: self.itemconfigure( self.Field[x][y], fill='white') # PAUSE中にPAUSEの文字を表示する def ShowPauseText( self ): self.itemconfigure( self.pause_text, state=Tk.NORMAL ) # PAUSEが終わるときにPAUSEの文字を非表示にする def HidePauseText( self ): self.itemconfigure( self.pause_text, state=Tk.HIDDEN ) # 次のピースを表示する画面 View class NextPieceBoard( Tk.Canvas ): # 次のピースを表示する場所のサイズ NEXT_BOARD_WIDTH = PIECE_WIDTH * CELL_WIDTH NEXT_BOARD_HEIGHT = PIECE_HEIGHT * CELL_HEIGHT margin = 2 def __init__( self, master ): Tk.Canvas.__init__( self, master, relief=Tk.RAISED, bd=self.margin, bg='white', width=self.NEXT_BOARD_WIDTH, height=self.NEXT_BOARD_HEIGHT ) self.RectArray = [[0 for y in range(PIECE_HEIGHT)] for x in range(PIECE_WIDTH)] #print self.RectArray offset = self.margin * 2 + 3 for x in range( PIECE_WIDTH ): for y in range( PIECE_HEIGHT ): x0 = x * CELL_WIDTH + offset y0 = y * CELL_HEIGHT + offset r = self.create_rectangle( x0, y0, x0 + CELL_WIDTH, y0 + CELL_HEIGHT, fill='white', width=0) self.RectArray[x][y]=r def DisplayPiecePattern( self, pattern ): p = pattern for x in range( PIECE_WIDTH ): for y in range( PIECE_HEIGHT ): # print "p[%d][%d] = %d" % (x, y, p[x][y]) if p[x][y] == 1: # print "p[%d][%d] = red" % (x, y) self.itemconfigure( self.RectArray[x][y], fill='black' ) else: # print "p[%d][%d] = blue" % (x, y) self.itemconfigure( self.RectArray[x][y], fill='white' ) # ゲーム全体 Controller class Frame( Tk.Frame ): def __init__( self, master=None ): Tk.Frame.__init__( self, master ) self.master.title( 'Tetris' ) ######################################### # board frame self.bframe = Tk.Frame( self, bd=1, relief=Tk.RIDGE ) self.bframe.pack( side=Tk.LEFT, padx=10, pady=10 ) self.board = Board( self.bframe ) self.board.pack( side=Tk.LEFT, padx=0, pady=0 ) ######################################### # frame start frame = Tk.Frame( self, bd=1, relief=Tk.RIDGE ) # exit button self.btExit = Tk.Button( frame, text='Exit', command=self.exitGame ) self.btExit.pack( anchor=Tk.E, padx=10, pady=10 ) # replay (not start) button #self.btReplay = Tk.Button( frame, text='Replay', # command=self.replayGame ) #self.btReplay.pack( anchor=Tk.E, padx=10, pady=0 ) # start button self.btStart = Tk.Button( frame, text='Start', command=self.startGame ) self.btStart.pack( anchor=Tk.E, padx=10, pady=0 ) # next piece display self.next_piece = NextPieceBoard( frame ) self.next_piece.pack( anchor = Tk.E, padx=10, pady=10 ) TEXT_font = ('Helvetica','12','italic') NUM_font = ('Helvetica','24','bold') # time text self.time_text = Tk.Label( frame, text='PLAY TIME', font=TEXT_font ) self.time_text.pack( anchor = Tk.E, padx=10, pady=0 ) # timer self.time_box = Tk.Label( frame, text='00\'00\"', font=NUM_font, bd=1, relief=Tk.RIDGE ) self.time_box.pack( anchor = Tk.E, padx=10, pady=0 ) frame.pack( side=Tk.RIGHT, padx=10, pady=10 ) # key bind self.bind( "", self.keyPressed ) self.bind( " ", self.keyLeft ) self.bind( " ", self.keyRight ) self.bind( " ", self.keyUp ) self.bind( " ", self.keyDown ) self.focus_set() self.tetris = Tetris( self ) self.processing_down = False self.timerEventCount = 0 self.timerCount = 0 self.tetris.PrepareStartGame() # exit button def exitGame( self ): self.tetris.playing = False sys.exit() # start button def startGame( self ): self.tetris.playing = True # ボタンは押せなくする self.DisableStartButton() #self.DisableReplayButton() # カウンタをリセットしてタイマーをスタートする self.timerEventCount = 0 self.timerCount = 0 # これ以降,timerEventが0.5秒に一回呼び出される self.after( 500, self.timerEvent ) # replay def replayGame( self ): self.EnableStartButton() self.tetris.PrepareStartGame() self.timerEventCount = 0 self.timerCount = 0 self.DisplayTime( 0, 0 ) # 0.5秒ごとに呼び出される def timerEvent( self ): if self.tetris.playing == False: return if self.tetris.pausing == True: return self.timerEventCount = self.timerEventCount + 1 # 0.5秒に1回呼び出されるので2回→1秒に1回の点数アップ if self.timerEventCount % 2 == 0: self.timerCount = self.timerCount + 1 self.DisplayTime( self.timerCount / 60, self.timerCount % 60 ) # print "count %d" % self.timerEventCount # print "timer %d" % self.timerCount # ブロックを1段落とす self.keyDown( None ) # 0.5秒後に自分を呼び出す self.after( 500, self.timerEvent ) def endGame( self ): self.focus_set() self.replayGame() def DisableExitButton( self ): self.btExit.configure( state=Tk.DISABLED ) def EnableExitButton( self ): self.btExit.configure( state=Tk.NORMAL ) def DisableReplayButton( self ): #self.btReplay.configure( state=Tk.DISABLED ) pass def EnableReplayButton( self ): #self.btReplay.configure( state=Tk.NORMAL ) pass def DisableStartButton( self ): self.btStart.configure( state=Tk.DISABLED ) def EnableStartButton( self ): self.btStart.configure( state=Tk.NORMAL ) def DisplayTime( self, min=0, sec=0 ): self.time_box.configure( text="%02d\'%02d\"" % (min, sec) ) # 以下はキー入力処理 def keyPressed( self, event ): c = event.char # space: pause if self.tetris.playing == False: return if c == ' ': if self.tetris.pausing == False: print "PAUSE!!!" self.tetris.pausing = True # PAUSEを表示 self.board.ShowPauseText() else: print "PAUSE END!!!" self.tetris.pausing = False # PAUSEを隠す self.board.HidePauseText() # 再度タイマーを開始 self.after( 500, self.timerEvent ) def keyLeft( self, event ): if self.tetris.playing == False: return if self.tetris.pausing == True: return # print "press Left" if self.tetris.CanMovePiece( move='left' ) == 0: self.tetris.MovePiece( move='left' ) self.tetris.RedrawAllField() def keyRight( self, event ): if self.tetris.playing == False: return if self.tetris.pausing == True: return # print "press Right" if self.tetris.CanMovePiece( move='right' ) == 0: self.tetris.MovePiece( move='right' ) self.tetris.RedrawAllField() # キーで呼ばれるのとタイマーで呼ばれるのが同時に重なる場合が # あるので再入防止している def keyDown( self, event ): if self.tetris.playing == False: return if self.tetris.pausing == True: return # print "press Down" if self.processing_down == True: return self.processing_down = True res = self.tetris.CanMovePiece( move='down' ) if res == 0: self.tetris.MovePiece( move='down' ) self.tetris.RedrawAllField() elif res == 2: self.tetris.FixPieceAndGetNextPiece() self.processing_down = False # 回転 def keyUp( self, event ): if self.tetris.playing == False: return if self.tetris.pausing == True: return # print "press Up" if self.tetris.CanTurnPiece() == True: self.tetris.TurnPiece() self.tetris.RedrawAllField() # テトリスゲームの Data class Tetris: # ゲームフィールド field = [[0 for y in range(FIELD_HEIGHT)] for x in range(FIELD_WIDTH)] # 現在移動中のブロック piece = [[0 for y in range(PIECE_HEIGHT)] for x in range(PIECE_WIDTH)] # ブロックの左上端の座標(フィールドは左上が(x,y) = (0,0)でyが下向き) location = [0, 0] # 次のブロック nextPiece = [[0 for y in range(PIECE_HEIGHT)] for x in range(PIECE_WIDTH)] # ブロックの全てのパターン piecePattern = [ [[0,0,0,0],[0,1,1,0],[0,1,1,0],[0,0,0,0]], # 0 [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]], # 1 [[0,0,0,0],[0,1,1,1],[0,0,1,0],[0,0,0,0]], # 2 [[0,0,0,0],[0,1,1,1],[0,1,0,0],[0,0,0,0]], # 3 [[0,0,0,0],[0,1,0,0],[0,1,1,1],[0,0,0,0]], # 4 [[0,0,0,0],[0,0,1,1],[0,1,1,0],[0,0,0,0]], # 5 [[0,0,0,0],[0,1,1,0],[0,0,1,1],[0,0,0,0]]] # 6 def __init__( self, parent ): self.main_frame = parent self.main_board = parent.board self.next_piece = parent.next_piece # ゲームの開始準備 def PrepareStartGame( self ): self.playing = False self.pausing = False self.game_end = False # 盤面の初期化 # 全部を白で塗る for y in range( FIELD_HEIGHT ): for x in range( FIELD_WIDTH ): self.field[x][y] = 0 # 周囲1列を3で塗る for y in range( FIELD_HEIGHT ): self.field[0][y] = 3 self.field[ FIELD_WIDTH - 1 ][y] = 3 for x in range( FIELD_WIDTH ): self.field[x][ FIELD_HEIGHT - 1 ] = 3 # 障害物を置いておく self.field[1][22] = 2 self.field[2][22] = 2 self.field[3][21] = 2 self.field[4][21] = 2 self.field[5][20] = 2 self.field[6][20] = 2 # 1個目のピースを準備する self.GetNextPiece( True ) self.RedrawAllField() # 1行だけ消す処理 def DeleteOneLine( self, line ): # 上から落とす for y in range(line, 1, -1): for x in range(1, FIELD_WIDTH-1): self.field[x][y] = self.field[x][y-1] self.RedrawAllField() # 埋まっている行を全て消す処理 def DeleteFilledLines( self ): #print "Start Delete Line!!!" y = FIELD_HEIGHT - 2 # 一番下の行を除く deleted_line = 0 # 削除できた行の数 all_clear = 0 # 一番上の行まで確認 count_sum = 0 while y >= 0: # 1行に埋まってるセルの個数を数える #print "count line %d:" % y, count = 0 for x in range(1, FIELD_WIDTH-1): # 両端を除く if self.field[x][y] == 2: count = count + 1 #print "count %d" % count # 全部が埋まっていたら行の削除処理 if count == FIELD_WIDTH - 2: # 両端を除いた数 # 削除できた場合にはyは増えない print "clear line %d" % y self.DeleteOneLine(y) deleted_line = deleted_line + 1 else: count_sum = count_sum + count # 全部が埋まっていなかったら次の行へ行く y = y - 1 print "end Delete Line!!! (%d lines)" % deleted_line print "%d cells left" % count_sum # count_sumが0だったらボーナス if count_sum == 0: all_clear == 1 print '########################################' print '######## ALL CELLS CLEARED #############' print '########################################' # 現在のピースが落ち切ったので,ここに固定する # 行が埋まったら消す # 次のピースを開始する def FixPieceAndGetNextPiece( self ): # 現在のピースを現在の位置に固定する for x in range(PIECE_WIDTH): for y in range(PIECE_HEIGHT): if self.piece[x][y] == 1: self.field[self.location[0]+x][self.location[1]+y] = 2 # 埋まっている行を消す self.DeleteFilledLines() # 次のピース self.GetNextPiece( False ) self.RedrawAllField() # next pieceの領域に次のピースを作成する def CreatePiece( self ): i = random.randint(0, len(self.piecePattern)-1) # print "[%d]" % i, self.nextPiece = self.piecePattern[i] # print "-->", self.nextPiece self.next_piece.DisplayPiecePattern( self.nextPiece ) # next pieceの領域からピースを持ってきて一番上に置く # 既にここまでピースが積んであったらゲームオーバー判定 def GetNextPiece( self, is_first ): if is_first: self.CreatePiece() #print self.nextPiece self.location[0] = 5 self.location[1] = 0 for y in range( PIECE_HEIGHT ): for x in range( PIECE_WIDTH ): # 次のピースを持ってくる self.piece[x][y] = self.nextPiece[x][y] # フィールドに置く self.main_board.SetColor( self.location[0]+x, self.location[1]+y, self.piece[x][y]) # ゲーム終了判定 for y in range( PIECE_HEIGHT ): # 内側のxのloopから抜けて来ていたら,ここでも抜ける if self.game_end == True: break for x in range( PIECE_WIDTH ): # フィールドに置いた瞬間,既にその場所にブロックがあったら if ( self.piece[x][y] == 1 and self.field[self.location[0]+x][self.location[1]+y] == 2): # 終わり self.game_end = True # タイマーでのloopを止める self.playing = False # 1個重なっていれば十分なので,ここで抜ける break if self.game_end == False: # 終わっていなかったら続ける self.CreatePiece() else: # ゲームオーバー処理の開始 print "GAME END !!!" self.main_frame.endGame() def MoveOrientation( self, move ): xmove = ymove = 0 if move == 'down': ymove = 1 elif move == 'left': xmove = -1 elif move == 'right': xmove = 1 return ( xmove, ymove ) def MovePiece( self, move ): ( xmove, ymove ) = self.MoveOrientation( move ) self.location[0] = self.location[0] + xmove self.location[1] = self.location[1] + ymove def TurnPiece( self ): turnedPiece = [[0 for y in range(PIECE_HEIGHT)] for x in range(PIECE_WIDTH)] for x in range(PIECE_WIDTH): for y in range(PIECE_HEIGHT): turnedPiece[y][PIECE_HEIGHT-1-x] = self.piece[x][y] self.piece = turnedPiece # 移動した先を計算して,fieldとかぶらないか,はみ出ないかを見る # return 1: 左右の壁に当たって移動できない # return 2: 積んであるフィールド上のセルと重なるか, # 下に当たっているので,これで固定する def CanMovePiece( self, move='down' ): ( xmove, ymove ) = self.MoveOrientation( move ) ( x0, y0 ) = ( self.location[0], self.location[1] ) # フィールド上のセルと重なっている場合 flag = 0 for x in range(PIECE_WIDTH): for y in range(PIECE_HEIGHT): if ( self.piece[x][y] == 1 and self.field[x0+x+xmove][y0+y+ymove] >= 1 ): flag = 1 #print "move NG (4) (%d,%d)->(%d,%d)" % ( # self.location[0]+x, self.location[1]+y, # self.location[0]+x+xmove, self.location[1]+y+ymove ) break if flag == 1: if ymove == 1: return 2 # 下に動けない場合はこれで固定 else: return 1 # 左右ぶつかっている場合は,動けないだけ # 以上のチェックに引っかからない場合は動ける return 0 # 回転した先を計算して,fieldとかぶらないかを見る # True: 回転できる def CanTurnPiece( self ): turnedPiece = [[0 for y in range(PIECE_HEIGHT)] for x in range(PIECE_WIDTH)] for x in range(PIECE_WIDTH): for y in range(PIECE_HEIGHT): turnedPiece[y][PIECE_HEIGHT-1-x] = self.piece[x][y] # 確認 flag = 0 for x in range(PIECE_WIDTH): for y in range(PIECE_HEIGHT): # 添字が範囲を越えているかどうかの判定 xx = self.location[0] + x yy = self.location[1] + y if ( xx < 0 or xx >= FIELD_WIDTH ): flag = 1 print "turn NG (1)" break if ( yy < 0 or yy >= FIELD_HEIGHT ): flag = 1 print "turn NG (2)" break # 重なっている場合 if ( turnedPiece[x][y] == 1 and self.field[xx][yy] >= 1 ): flag = 1 print "turn NG (3)" break if flag == 1: return False else: return True # 再描画 def RedrawAllField( self ): # ゲームの終了後は実行しない # 開始前は表示する if self.game_end == True: return #print "RedrawAllField" # まず固定セル for x in range( FIELD_WIDTH ): for y in range( FIELD_HEIGHT ): self.main_board.SetColor( x, y, self.field[x][y] ) # 次に移動中のpiece for x in range( PIECE_WIDTH ): for y in range( PIECE_HEIGHT ): xx = x + self.location[0] yy = y + self.location[1] if xx < 0: pass if xx >= FIELD_WIDTH: pass if yy < 0: pass if yy >= FIELD_HEIGHT: pass # 描かれていない場所まで塗らない if self.piece[x][y] == 1: self.main_board.SetColor( xx, yy, self.piece[x][y] ) ##------ if __name__ == '__main__': f = Frame() f.pack() f.mainloop() # EOF
0 件のコメント:
コメントを投稿