2018年12月19日水曜日

WxPythonのテトリスがあったのでコードを読んでみた。№3ブロック落下


参考サイト

テトリス
http://wxpython.at-ninja.jp/thetetrisgame.html

だいぶ複雑なコードなので順に読み解いていきます。
最初はウィンドウなどの表示のみのコード
№2ブロックも表示
今回は №3ブロック落下です。

コードの流れ



    def oneLineDown(self):                                    #****************
        print "oneLineDown"
        #tryMove=False ブロックが底に行っていたり他のブロックに接触していたら
        #self.curY - 1 によって落下する
        if not self.tryMove(self.curPiece, self.curX, self.curY - 1):
.tryMove(self.curPiece, self.curX, self.curY - 1):
で落下します。
その後 tryMove のなかの self.Refresh() によって OnPainが呼ばれて
そこで drawSquar が呼ばれて 再描画されます。

簡単に言えば、再描画を受ける関数OnPaintの中に描画の drawSquare関数をいれて
タイマーを受け付ける関数OnTimer内に、描画するブロックの位置を変えるコード
例えば y+=1 を入れて RefreshもいれたらOnPaintが呼び出されてそこから
再描画されます。

コード

E:\MyBackups\goolgedrive\myprg_main\python_my_prg\wxpython\game_tetoris_draw_rakka.py

前回のブロック表示より新しく追加したコードに#*******  をつけています。



# coding: UTF-8
#http://wxpython.at-ninja.jp/thetetrisgame.html
#テトリス
#ゲームは少し簡略化してあるので理解しやすいと思います。 アプ
#リケーションを立ち上げるとすぐにゲームが開始されます。 pキー
#を押すと、ゲームを中断することができます。 スペースキーでテ
#トリミノを即座に落下させることができます。 dキーはミノを1行
#ずつ落下させます。(落下速度を少し速くしたい場合に使いましょ
#う) ゲーム速度は一定で、速くなることはありません。 スコアは
#消去した行数です。

#Shapeクラスでcoordsを決定してブロックの形状を決定します。
#それをOnPaintで実際の座標に変換して、drawSquareでブロックを
#描きます
#coordsでの形状表示が、OnPaintで実際の座標に変換される時
#y軸方向の表示が上下逆になります。

# game_tetoris_draw_kakidasi.py によってブロック表示だけを実現
# 今回、それを落下させてみる

#横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 wx
import random

#SetFocus ( ) 
#これにより、キーボード入力を受け取るウィンドウが設定されます。
#別にこの関数いらないと思う。今までウィンドウのフォーカスを
#気にしなくてもうまくいっていた。

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.board              = []#ブロックのミノの位置

        #再描画の時OnPaintを呼び出す
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_TIMER, self.OnTimer, id=Board.ID_TIMER)#********

        self.clearBoard()

        #簡略化のため元コードは関数をつかっていたが、ここでは定数化した
        #ブロックのセル一個の幅、高さ
        self.squareWidth = 17
        self.squareHeight = 15
        self.boardTop = 3


    #boardのリストを変えているのだが、何をしているのか不明
    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",
        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)

        #次の段階では現在落下中のを描画します。
        #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)

                #縦に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の隙間があるが)

    #OnTimer() メソッドでは、以前のミノが底に落下しきった後
    #に新しいミノを生成したり、落下中のミノを1行落としたりし
    #ます。
    def OnTimer(self, event):                            #***************
        print
        print "Board OnTimer"
        if event.GetId() == Board.ID_TIMER:
            #if self.isWaitingAfterLine:
            #    self.isWaitingAfterLine = False
            #    self.newPiece()
            #else:
                self.oneLineDown()
        else:
            event.Skip()


    #newpiece() メソッドはランダムに新しいミノを生成します。
    #ミノを初期位置に配置できなければ、ゲームオーバーです。
    def newPiece(self):
        print "",
        #現在のブロックのオブジェクトに次のブロックのオブジェクトを入れる
        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はブロックに対して最適な表示位置を示すパラメータと思われる

        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 によって落下する
        if not self.tryMove(self.curPiece, self.curX, self.curY - 1):
            #ブロック接触の処理
            self.pieceDropped()
            pass

    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() 
            #ブロックの形状を示す番号を返す
            #boardのリストを変えているのだが、何をしているのか不明
            self.setShapeAt(x, y, self.curPiece.shape())

        #self.removeFullLines()

        #if not self.isWaitingAfterLine:
        #    self.newPiece()
        
    #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に代入して
        #本当に移動させる
        #下一行のコード意味がない。なくてもいい
        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 "shape",
        #ブロックの形状をしめす番号を返す coordTableのインデックス
        return self.pieceShape

    def setShape(self, shape):
        print "setShape",
        #self.coords = [[0, -1], [0, 0], [1, 0], [1, 1]]
        self.coords = [[0, 1], [0, 0], [1, 0], [1, 1]]
        self.pieceShape = 2


    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):
        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

    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

#app = wx.App()
app = MyApp()
Tetris(None, -1, 'tetris.py')
app.MainLoop()

0 件のコメント:

コメントを投稿

About

参加ユーザー

連絡フォーム

名前

メール *

メッセージ *

ブログ アーカイブ

ページ

Featured Posts