参考にしたのは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 件のコメント:
コメントを投稿