参考サイト
テトリスhttp://wxpython.at-ninja.jp/thetetrisgame.html
だいぶ複雑なコードなので順に読み解いていきます。
最初はウィンドウなどの表示のみのコード
№2ブロックも表示
№3ブロック落下
№4ブロック回転移動
№5底にブロックが接触したら、あたらしいブロックを発生し落下させる
#********* のところは前回から付け加えた部分。
コードの流れ
タイマー
→ OnTimer
→ oneLineDownでブロックの落下処理(curY - 1)。
→ tryMoveによってブロックが底、横壁、他のブロックに接触しているかの判定
→ 接触していたら
→ pieceDropped で現在のブロックのブロック座標に対応する
boardリストの要素に、ブロックの形状を示すインデックス番号をいれる。
→ newPiece 現在のブロックに次のブロックを入れる
タイマー → OnTimer → oneLineDownでブロックの落下処理(curY - 1)。
→ tryMoveによってブロックが底、横壁、他のブロックに接触しているかの判定
→ どこにも接触していない
→ tryMove内のRefresh
→ OnPaintの
#落下済みのブロックを表示する
for i in range(Board.BoardHeight):
で落下済みのブロックの表示
コード
E:\MyBackups\goolgedrive\myprg_main\python_my_prg\wxpython\game_tetoris_idou_nexbrock.p
# coding: UTF-8
#参考サイト
#http://wxpython.at-ninja.jp/thetetrisgame.html
#テトリス
#底辺にブロックがついたら、次のブロックを発生させる
#横一列にブロックのセルが隙間なく並んだら、その列を消滅させる
#WxPython カーソルキーイベントを検知しないようなので
#別のキーイベントを検知するコードを入れる
#Shapeクラスでcoordsを決定してブロックの形状を決定します。
#それをOnPaintで実際の座標に変換して、drawSquareでブロックを
#描きます
#coordsでの形状表示が、OnPaintで実際の座標に変換される時
#y軸方向の表示が上下逆になります。
#横10 ブロックの座標で x = 0~9
#縦22 ブロックの座標で y = 0~21
#で表現されている
#curX curY はブロックの基準となるブロック座標
#curX の初期値は6 ブロック形状座標0のセルがここにくる
#curY の初期値は 下の計算式とブロック形状によって
#yが21となるよう curYが決められる。
#これによりどのようなブロック形状座標であろうと、ブロックの
#初期描画で描画表示ウィンドウの最上部と隙間なく、描画される
#curPiece はShapeのcoordsで与えられる、0を基準とした
#ブロックの形状を表すブロックのセルの位置座標
#(ブロック形状座標と呼ぼう)を示す
#x(i) y(i) はおのおの それの x y成分をしめす。
#x = self.curX + self.curPiece.x(i)
#y = self.curY - self.curPiece.y(i)
#x y はブロックのセル各々のブロック座標となる
import threading
import ctypes
import time
import wx
import random
#キーイベント取得のための定数
LEFT = 0x25
RIGHT = 0x27
DOWN = 0x28
UP = 0x26
#下のようにかいたら思うような結果ならない
#直接16進数で書く必要がある
#UP = hex(38)
#他のキーコードを調べるにはGetAsyncKeyStateで検索するといいかも
keycode = 0
class MyApp(wx.App):
def __init__(self):
#エラーが出たらファイルに書き出す
wx.App.__init__(self, redirect=True, filename="game_tetoris_log.txt")
#wx.App.__init__(self, redirect=True )
class Tetris(wx.Frame):
def __init__(self, parent, id, title):
print "Tetris __init__",
wx.Frame.__init__(self, parent, id, title, size=(180, 380))
#ステータスバー作成
self.statusbar = self.CreateStatusBar()
self.statusbar.SetStatusText('0')
#boardはBoardクラスにも使われているからややこしい
self.board = Board(self)
self.board.SetFocus()
#初期値設定 タイマースタート
self.board.start()
self.Centre()
self.Show(True)
class Board(wx.Panel):
BoardWidth = 10
BoardHeight = 22
Speed = 300
ID_TIMER = 1
def __init__(self, parent):
print "",
print "Board __init__",
wx.Panel.__init__(self, parent)
#イベントをidで区別しているようだ
# evet_id.py 参照
self.timer = wx.Timer(self, Board.ID_TIMER)
self.curPiece = Shape()#現在のブロックの初期設定
self.nextPiece = Shape()#次のブロックの初期設定
#現在落下中のブロックを描画するときは、ミノを self.curX
#と self.curY の位置に描画します。 そして、座標テーブ
#ルを参照し、4つの正方形全てを描画します。
self.curX = 0
self.curY = 0
self.numLinesRemoved = 0
self.board = []#ブロックのミノの位置
self.isStarted = False
#self.isPaused = False
#再描画の時OnPaintを呼び出す
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_TIMER, self.OnTimer, id=Board.ID_TIMER)
self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
self.clearBoard()
#簡略化のため元コードは関数をつかっていたが、ここでは定数化した
#ブロックのセル一個の幅、高さ
self.squareWidth = 17
self.squareHeight = 15
self.boardTop = 3
#ブロック座標にブロックのセルがはいっているかを調べる
#shape = Tetrominoes.NoShape ならなにかのセルが存在する
#例えばx,y=0ならboard[0] となる
#x=0,y=1 なら board[10] となってうまくブロック座標のセルの
#状態を示す事ができる
def shapeAt(self, x, y):
print "shapeAt"
return self.board[(y * Board.BoardWidth) + x]
#ブロック座標にブロックのセルのインデックス番号を入れる
def setShapeAt(self, x, y, shape):
print "setShapeAt"
#boardは要素数22*10 初期値全て0
#以下の一行 BoardHeight ならわかるのだが
self.board[(y * Board.BoardWidth) + x] = shape
print "setShapeAt board", self.board
def start(self):
print "start",
#if self.isPaused:#isPaused:なんかのフラグ 初期値False
# return
self.isStarted = True #isPausedがTrueならisStartedをTrueに
self.isWaitingAfterLine = False #初期値False
#self.numLinesRemoved = 0
#__init__の中にもあるし二度手間では?
self.clearBoard()
self.newPiece()
#300ミリ秒ごとにOnTimer()が実行される
self.timer.Start(Board.Speed)
def clearBoard(self):
print
print "clearBoard"
for i in range(Board.BoardHeight * Board.BoardWidth):
self.board.append(Tetrominoes.NoShape)# ()のなかは0
#boardがの要素が全部0になる
def OnPaint(self, event):
print "OnPaint",
dc = wx.PaintDC(self)
#落下済みのブロックを表示する
for i in range(Board.BoardHeight): #*********
for j in range(Board.BoardWidth):
#i=0~21で、BoardHeight-i-1 = 21~0
shape = self.shapeAt(j, Board.BoardHeight - i - 1)
#ブロック座標にセルがあるかないかを示すboardリストで
#shape=Tetrominoes.NoShape(0) であればセルがない事を示す
#もしセルが存在するならば、そのセルを表示する
if shape != Tetrominoes.NoShape:
self.drawSquare(dc,
0 + j * self.squareWidth,
self.boardTop + i * self.squareHeight, shape)
#現在落下中のを描画します。
#curPiece.shapeは初期値で0
#curPieceは現在のブロックのオブジェクト
#Shape()によってpieceShapeが0(Tetrominoes.NoShape)となる
#しかしsetRandomShape()によって、pieceShapeがランダムに1~7のどれかになる
if self.curPiece.shape() != Tetrominoes.NoShape:
#ブロックは4つのセルでできているからrange(4)
for i in range(4):
#x(i) coords[index][0]を返す
#curXの初期値は 6
#xの衝突について考える
x = self.curX + self.curPiece.x(i)
y = self.curY - self.curPiece.y(i)
#def drawSquare(self, dc, x, y, shape):
self.drawSquare(dc, 0 + x * self.squareWidth,
self.boardTop + (Board.BoardHeight - y - 1) * self.squareHeight,
self.curPiece.shape())
#縦に22個のブロックのセルが表示できる
#どんな形のブロックだろうと最初はy=21
#どれかのセルがy=0となればそのセルは底辺についた事になる
#coordsで表現されたブロックは上のコードにより上下逆に表示される
#例えば coords = [[0, -1], [0, 0], [1, 0], [1, 1]] とすると
# ■ [1, 1]
# ■■ [0, 0] [1, 0]
# ■ [0, -1]
#coords[x,-1]の時newPiece()の処理→上の処理後以下となる
#つまりちょうどyの最低値(-1)で (Board.BoardHeight - y - 1)が
#うまく 0 となるよう作られている
#self.curY 20
#self.curPiece.y(i) y -1
#y 21
#Board.BoardHeight - y - 1 0
#例えばcoords = [[0, 1], [0, 0], [1, 0], [1, 1]] とすると
# ■■ [0, 1] [1, 1]
# ■■ [0, 0] [1, 0]
#coords[x,0]の時newPiece()の処理→上の処理後以下となる
#つまりちょうどyの最低値(0)で うまく0となるよう作られている
#self.curY 21
#self.curPiece.y(i) y 0
#y 21
#Board.BoardHeight - y - 1 0
#つまりどのような形状であれ、最初のブロックの表示時ウィンドウ
#の最上部にピッタリと隙間なく表示される(boardTopの隙間があるが)
def OnKeyDown(self, event):
print "OnKeyDown"
#startでisStarted=TrueになるしcurPiece.shape()=0という状態は
#初期状態だし ifが成り立つのはありえないのでは?
if not self.isStarted or self.curPiece.shape() == Tetrominoes.NoShape:
event.Skip()
return
keycode = event.GetKeyCode()
#ord アスキーコードを取得する
if keycode == ord('P') or keycode == ord('p'):
self.pause()
return
#ゲーム進行一時停止
if self.isPaused:
return
else:
event.Skip()
def getkey(self, key):
return(bool(ctypes.windll.user32.GetAsyncKeyState(key)&0x8000))
#私の環境ではカーソルイベントをOnKeyDownで拾えないので、OnTimerから
#ここへジャンプさせてここで metaGetkeyでカーソルイベントを拾う
def metaGetkey(self):
print "metaGetkey"
global keycode
keycode = 0
if self.getkey(LEFT):
keycode = wx.WXK_LEFT
self.OnKeyDown_cursor()
print "LEFT"
if self.getkey(RIGHT):
keycode = wx.WXK_RIGHT
self.OnKeyDown_cursor()
print "RIGHT"
if self.getkey(DOWN):
keycode = wx.WXK_UP
self.OnKeyDown_cursor()
print "DOWN"
if self.getkey(UP):
keycode = wx.WXK_DOWN
self.OnKeyDown_cursor()
print "UP"
else:
return
def OnKeyDown_cursor(self):
print "OnKeyDown_cursor"
global keycode
print "keycode",keycode
#OnKeyDown() メソッドで、押下されたキーを調べます。
#左矢印キーを押すと、ミノを左に 動かそうと します。
#なぜ「動かそうとする」なのかというと、ミノは移動でき
#ないかもしれないからです。
if keycode == wx.WXK_LEFT:
print "elif keycode == wx.WXK_LEFT"
self.tryMove(self.curPiece, self.curX - 1, self.curY)
elif keycode == wx.WXK_RIGHT:
self.tryMove(self.curPiece, self.curX + 1, self.curY)
elif keycode == wx.WXK_DOWN:
self.tryMove(self.curPiece.rotatedRight(), self.curX, self.curY)
elif keycode == wx.WXK_UP:
self.tryMove(self.curPiece.rotatedLeft(), self.curX, self.curY)
elif keycode == wx.WXK_SPACE:
self.dropDown()
elif keycode == ord('D') or keycode == ord('d'):
self.oneLineDown()
else:
pass
#OnTimer() メソッドでは、以前のミノが底に落下しきった後
#に新しいミノを生成したり、落下中のミノを1行落としたりし
#ます。
#oneLineDown→tryMove(Refresh)→OnPaint→drawSquareで再描画されます
#OnTimerの先にRefreshがあるため、タイマーのタイミングで再描画されます
def OnTimer(self, event):
print
print "Board OnTimer"
self.metaGetkey()
if event.GetId() == Board.ID_TIMER:
#if self.isWaitingAfterLine:
# self.isWaitingAfterLine = False
# self.newPiece()
#else:
# self.oneLineDown()
self.oneLineDown()
else:
event.Skip()
#newpiece() メソッドはランダムに新しいミノを生成します。
#ミノを初期位置に配置できなければ、ゲームオーバーです。
def newPiece(self):
print "newPiece"
#現在のブロックのオブジェクトに次のブロックのオブジェクトを入れる
self.curPiece = self.nextPiece
#ブロック位置の初期化?
#minYの動作は以下のとおり
#self.coords [[0, -1], [0, 0], [1, 0], [1, 1]]とすると
#minYは、coordsの中で一番ちいさなyを返す
#例えばminYが0とすると、curYは21となる
#実際の表示は
# coordsとは上下が反転して表示される
# ■ curY=20 [0, -1]
# ■■ curY=21 [0, 0] [1, 0]
# ■ curY=22 [1, 1]
#self.curY = Board.BoardHeight - 1 、ブロックの上の部分が
#切れて見えなくなる事がある
# _
# ■■ [0, 0][1, 0]
# ■
#
#self.curY = Board.BoardHeight とするとさらに切れてしまう
# __ [0, 0][1, 0]
# ■ [0, 1]
#以上から、curYはブロックに対して最適な表示位置を示すパラメータと思われる
#Shape()によってpieceShapeが0(Tetrominoes.NoShape)となる
#しかしsetRandomShape()によって、pieceShapeがランダムに1~7のどれかになる
self.nextPiece.setRandomShape()
self.curX = Board.BoardWidth / 2 + 1 #6となる
self.curY = Board.BoardHeight - 1 + self.curPiece.minY()
def oneLineDown(self):
print "oneLineDown"
#tryMove=False ブロックが底に行っていたり他のブロックに接触していたら
#self.curY - 1 によって落下する
#tryMoveによってブロックが底、横壁、他のブロックに接触していたら
#pieceDroppedへ行く
if not self.tryMove(self.curPiece, self.curX, self.curY - 1):
#ブロック接触の処理
#ブロックのどれかのセルが底辺に接触したら
self.pieceDropped()
#ブロックのセルが底辺または
def pieceDropped(self):
print "pieceDropped"
for i in range(4):
x = self.curX + self.curPiece.x(i)
y = self.curY - self.curPiece.y(i)
#curPiece.shape()
#ブロックの形状を示す番号を返す
#ブロックのセルの座標x,yに対応するboardのリストの要素を
#今のブロックの形状を示す数値に変更する
self.setShapeAt(x, y, self.curPiece.shape())
#self.removeFullLines()
if not self.isWaitingAfterLine:
#self.newPiece()
pass
self.newPiece()
#ミノが底に当たると、 removeFullLines() メソッドを呼びま
#す。 揃った行を見つけて、その行を削除します。 現在揃って
#いる行を取り除き、その上の行全てを1行下げることでこれを
#実現します。 削除される行の順番を逆にしていることに注意
#してください。 そうでもしなければ、正しく動かないでしょ
#うからね。 今回は単純な重力を使用しています。 つまり、ミ
#ノは空の隙間の上に浮かぶこともありうるということです。
def removeFullLines(self):
print "Board removeFullLines"
numFullLines = 0
statusbar = self.GetParent().statusbar
rowsToRemove = []
#BoardWidth = 10 BoardHeight = 22
for i in range(Board.BoardHeight):
n = 0
for j in range(Board.BoardWidth):
#ブロック座標にどんな形状のブロックのセルがはいっているか
#調べる
if not self.shapeAt(j, i) == Tetrominoes.NoShape:
#なにかしらのブロックのセルがあれば n+1
n = n + 1
#ある一列のブロック座標にブロックのセルが全部詰まっていれば
#そのブロック座標のy座標を記録する
if n == 10:
rowsToRemove.append(i)
#リストの順番を逆にして並べ替え、つまり底辺のほうからになる
rowsToRemove.reverse()
for m in rowsToRemove:
for k in range(m, Board.BoardHeight):
for l in range(Board.BoardWidth):
#m(k)のyブロック座標が一列セルで詰まっていれば
#その列のセルのブロック形状を示す数値を
#上のものに置き換える。つまり一列が消える?
self.setShapeAt(l, k, self.shapeAt(l, k+1))
numFullLines += len(rowsToRemove)
if numFullLines > 0:
self.numLinesRemoved += numFullLines
statusbar.SetStatusText(str(self.numLinesRemoved))
self.isWaitingAfterLine = True
self.curPiece.setShape(Tetrominoes.NoShape)
self.Refresh()
#tryMove() メソッドで、ミノを動かそうとします。 もしミノ
#がゲーム盤の端だったり、他のミノに隣接していたりすると
#False を返します。 そうでなければ、現在落下中のミノを新
#しい位置に置き、 True を返します
def tryMove(self, newPiece, newX, newY):
print "Board tryMove"
for i in range(4):
x = newX + newPiece.x(i)
y = newY - newPiece.y(i)
# ■■■■■■ 横10 xは0~9
# ■■■■■■ 縦22 yは上が21~下が0までとなる
# ■■■■■■
# ■■■■■■
#
#どれかのセルがy=0となればそのセルは底辺についた事になる
#どんな形のブロックだろうと最初はy=21 BoardHeightは22
#ブロックのセルが横壁か底に当たっていないか調べる
if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight:
return False
#ブロックのセルがある所にすでに別のセルがありはしないか
if self.shapeAt(x, y) != Tetrominoes.NoShape: #********
return False
#新しいnewX, newYをいれてぶつかっていないようであれば、curX curYに代入して
#本当に移動させる
#下一行のコード意味がない。なくてもいい
#上間違い。単に移動だけならばいらないが、回転時仮のcurPieceを
#作成していてそれが回転していても衝突しないようであれば
#仮のcurPieceを、本当のcurPieceに代入している
self.curPiece = newPiece
self.curX = newX
self.curY = newY
#RefreshによってOnPaintが呼ばれる
#OnPaintに描画へ行くコードあり 再描画される
self.Refresh()
return True
def drawSquare(self, dc, x, y, shape):
#def drawSquare(self, dc, x, y):
dc.SetBrush(wx.Brush('#000000'))
dc.DrawRectangle(x + 1, y + 1, self.squareWidth - 2,
self.squareHeight - 2)
#shape ブロックの形状を決める
class Tetrominoes(object):
NoShape = 0
ZShape = 1
SShape = 2
LineShape = 3
TShape = 4
SquareShape = 5
LShape = 6
MirroredLShape = 7
#Shape クラスはテトリミノの情報を保持しています。
class Shape(object):
coordTable = (
((0, 0), (0, 0), (0, 0), (0, 0)),
((0, -1), (0, 0), (-1, 0), (-1, 1)),
((0, -1), (0, 0), (1, 0), (1, 1)),
((0, -1), (0, 0), (0, 1), (0, 2)),
((-1, 0), (0, 0), (1, 0), (0, 1)),
((0, 0), (1, 0), (0, 1), (1, 1)),
((-1, -1), (0, -1), (0, 0), (0, 1)),
((1, -1), (0, -1), (0, 0), (0, 1)))
def __init__(self):
#ミノの作成中に、 空の座標リストを作成します。 この
#リストは、テトリミノの座標を保持します。 例えば、(0,
# -1), (0, 0), (1, 0), (1, 1) のタプルは回転し
#たS-テトリミノを表します。 以下の図表がミノを図示し
#ています。
#[[0, 0], [0, 0], [0, 0], [0, 0]]を作成
self.coords = [[0, 0] for i in range(4)]
#0をセット
self.pieceShape = Tetrominoes.NoShape
self.setShape(Tetrominoes.NoShape)
def shape(self):
print
print "Shape def shape"
#ブロックの形状をしめす番号を返す coordTableのインデックス
return self.pieceShape
#pieceShapeにインデックスshapeをセットする
#coordsにインデックスshapeのブロック座標をセットする
def setShape(self, shape):
print
print "setShape"
#shape初期値は0
#shapeにより coordTableの8つのうち一つを選ぶ
#それをcoordsにセットする
table = Shape.coordTable[shape]
for i in range(4):
for j in range(2):
self.coords[i][j] = table[i][j]
#shapeをセット
self.pieceShape = shape
print "self.pieceShape",self.pieceShape
#ランダムなブロックを一個作成
def setRandomShape(self):
print "setRandomShape "
#1~7までの乱数を発生
self.setShape(random.randint(1, 7))
def x(self, index):
return self.coords[index][0]
def y(self, index):
return self.coords[index][1]
def setX(self, index, x):
self.coords[index][0] = x
def setY(self, index, y):
self.coords[index][1] = y
#coordsの中で一番ちいさなxを返す
def minX(self):
m = self.coord[0][0]
for i in range(4):
m = min(m, self.coords[i][0])
return m
#coordsの中で一番大きいxを返す
def maxX(self):
m = self.coord[0][0]
for i in range(4):
m = max(m, self.coords[i][0])
return m
#self.coords [[0, -1], [0, 0], [1, 0], [1, 1]]とすると
#coordsの中で一番ちいさなyを返す
def minY(self):
m = self.coords[0][1]
for i in range(4):
m = min(m, self.coords[i][1])
return m
#coordsの中で一番大きいyを返す
def maxY(self):
m = self.coords[0][1]
for i in range(4):
m = max(m, self.coords[i][1])
return m
#時計まわりに回転
def rotatedLeft(self):
# SquareShape:5
# ブロックの形状が5ならなにもしない
#上の形状は座標の第一象限に四角のブロック形状なので
#回転させると、四角の左下の原点を中心にして回転してしまう
#ので振動したように見えてこれを回避しているのだろう
if self.pieceShape == Tetrominoes.SquareShape:
return self
#回転させた仮のブロックのオブジェクトを作成
#tryMoveによって回転後どこにもあたらないを判断されたら
#tryMoveの self.curPiece = newPiece によって今のブロック形状
#に代入される。
#ブロックの形状を初期化
result = Shape()
#今のブロック形状を仮のブロック形状にいれる
result.pieceShape = self.pieceShape
#ブロック形状座標のi番目の要素のx成分と、y成分を取り替え
#x成分に-をつける
#上で座標に印をつけて確認したところ、確かにブロック座標が
#時計まわりに回転する。
for i in range(4):
result.setX(i, self.y(i))
result.setY(i, -self.x(i))
return result
#反時計回りに回転
def rotatedRight(self):
if self.pieceShape == Tetrominoes.SquareShape:
return self
result = Shape()
result.pieceShape = self.pieceShape
for i in range(4):
result.setX(i, -self.y(i))
result.setY(i, self.x(i))
return result
#thread = threading.Thread(target=metaGetkey)
#thread.start()
#app = wx.App()
app = MyApp()
tetris = Tetris(None, -1, 'tetris.py')
app.MainLoop()
0 件のコメント:
コメントを投稿