テトリス作成
blocks.png
ブロックの画像はなかったので自作しました。参考サイト
http://euphorie.sakura.ne.jp/junk/page_python_teto.htmlPython 入門
遊び方
(テトリスを作って遊び方は "スペース" でゲーム開始, "←", "→", "↓" で移動, "ENTER" で回転です.みる)移動可能なテトリミノと固定されたテトリミノのそれぞれのデータがあり, 移動可能テトリミ
ノはキーまたはタイマで移動されます. 移動可能テトリミノは落下して移動不能になれば, 固
定テトリミノにコピーされる流れです.
移動ブロックについて
移動ブロックのセルに各番号を割り当てています。下は参考サイトより引用しています。
移動テトリミノは, 図 3 のようにテトリミノ中心を 0 番となるように相対的に番号を割り当てます.
図 3 移動可能テトリミノのデータ構造
各変数について
fixed_: わかりやすく背景情報と呼ぶことにします。要素数 GAME_H * GAME_W = 21*12 の一次配列
壁(-1)、固定ブロック(ブロックのid 1~7)、
移動ブロックが移動できる領域(0) の各数字が入っています。
pos: 背景情報位置と呼ぶ事にする。0より始まります。
各関数の変数として出て来ます。例えば pos = j * GAME_W + i で j=2 i= 5なら
上から2段目の左から6番目の位値を指します。(0より始まっているので)
背景情報位置 最上段 0~11
次段 12~23
これが21段続く
self.pos_ ブロックの位値。正確にはブロックのセル番号0に位値になります。
初期位置はself.pos_ = GAME_W + GAME_W / 2 - 1 #12+12/2-1 です。
self.id_ 1 ~ 7 ブロックのIDを示します。
移動ブロックの表示の概要
pos 背景情報位置 左上が0で右端が12 下の段左端が13と続きます。このコードでは以下の順でブロック画像の表示されています。
block()で以下のように処理されます。
pos = j * GAME_W + i
for block in self.floating_:
if self.pos_ + block == pos:
return self.id_
初期位置self.pos_ = 17として。
self.floating_= [0, 1, -GAME_W, 2] (-GAME_W=-12) のブロックだとしたら
self.pos_ + block = 17, 18, 5, 19 の各値になります。
(floating_のリストの中身はブロックの各セルの番号になります。)
それがpos(0から始まっています)と一致しますと、ブロックのIDが返ります。
下に上から4段だけのブロックの表示領域を示しました。
上左端から順に0から数えていくと■の部分で、self.pos_ + block == pos
となりブロックのIDが返される事になります。
□□□□□■□□□□□□ 左右端、一番下部の□は壁となります。
□□□□□■■■□□□□ ウィンドウには表示されません。
□□□□□□□□□□□□
□□□□□□□□□□□□
次に
OnPaintで
for j in range(GAME_H - 1):
for i in range(GAME_W - 2):
b = self.game_.block(i + 1, j)
if b >= 1 and b <= len(self.blockBitmaps_):
dc.DrawBitmap(self.blockBitmaps_[b - 1],
BLOCK_BMP_SIZE * i, BLOCK_BMP_SIZE * j)
とループさせて ほぼ全てのposに対して、さらにそのブロックの一個一個の
セルの番号に対して block()にて
self.pos_ + block == pos が成り立てばブロックのID(1~7)が返されるので
if b >= 1 and b <= len(self.blockBitmaps_):
がTrueとなり、DrawBitmapでブロック画像が表示されます。
落下操作
OnTimer→FallAndTest()→fall() 経由でブロックが落下可能ならfall()で、 self.pos_ += GAME_W とされます。
self.floating_= [0, 1, -GAME_W, 2] (-GAME_W=-12) のブロックだとしたら
self.pos_ + block = 17, 18, 5, 19 の各値に、さらに+12される事となります。
これと一致するposは一段下のposとなります。
故に、block() OnPaint()で一段下にブロックが表示される事となります。
底辺にぶつかった場合
fall()でこれ以上落下出来ないと判断されたら、fix()で移動ブロックを固定させています。とはいっても、fixed_[]に移動ブロックのIDをいれているだけです。
そしてnext()で次のブロックが移動ブロックにセットされますので、現在のブロックの固定
されたブロックは、移動しません。
固定ブロックもblock()でIDを返しますので、OnPaint()でその画像が表示されます。
回転の操作
turn()がなぜ回転の動作をするのか、テストコードを使って確認しました。結果、セル番号0で右回転を90度していました。
E:\MyBackups\goolgedrive\myprg_main\python_my_prg\wxpython\game_tetoris_two1_turn.py
# -*- coding: utf-8 -*-
GAME_H = 21 # 画面の高さ (番兵を含む)
GAME_W = 12 # 画面の幅 (番兵を含む)
blocks_ = [
[0, 1, -GAME_W, -GAME_W - 1], # Z, green
[0, 1, -GAME_W, -GAME_W * 2], # L, orange
[0, 1, -GAME_W, GAME_W + 1], # S, pink
[0, 1, -GAME_W, 2], # J, blue
[0, 1, -GAME_W, -GAME_W + 1], # O, yellow
[-1, 0, 1, 2], # I, red
[-1, 0, 1, -GAME_W] # T, light blue
]
def turn(floating_):
movable = True
rot = []
for b in floating_:
#round:丸める(ほぼ四捨五入をする)
v = int(round(float(b) / float(GAME_W))) # 回転先の x 座標
w = b - v * GAME_W # 回転先の y 座標
p = w * GAME_W - v # 回転先の位置
rot.append(p)
print "b",b,
print " ",
#print "v",v
#print "w",w
print "p",p
for bks in blocks_:
turn(bks)
#出力結果
#初期位置での元ブロックbと回転ブロックpを表示する
#
#b 0 p 0 □□□□■■□□□□□□ □□□□□□■□□□□□
#b 1 p 12 □□□□□■■□□□□□ □□□□□■■□□□□□
#b -12 p 1 □□□□□□□□□□□□ □□□□□■□□□□□□
#b -13 p -11 □□□□□□□□□□□□ □□□□□□□□□□□□
#
# ■
#b 0 p 0 □□□□□■□□□□□□ □□□□□□□□□□□□
#b 1 p 12 □□□□□■■□□□□□ □□□□□■■■□□□□
#b -12 p 1 □□□□□□□□□□□□ □□□□□■□□□□□□
#b -24 p 2 □□□□□□□□□□□□ □□□□□□□□□□□□
#
#b 0 p 0 □□□□□■□□□□□□ □□□□□□□□□□□□
#b 1 p 12 □□□□□■■□□□□□ □□□□□■■□□□□□
#b -12 p 1 □□□□□□■□□□□□ □□□□■■□□□□□□
#b 13 p 11 □□□□□□□□□□□□ □□□□□□□□□□□□
#
#b 0 p 0 □□□□□■□□□□□□ □□□□□□□□□□□□
#b 1 p 12 □□□□□■■■□□□□ □□□□□■■□□□□□
#b -12 p 1 □□□□□□□□□□□□ □□□□□■□□□□□□
#b 2 p 24 □□□□□□□□□□□□ □□□□□■□□□□□□
#
#b 0 p 0 □□□□□■■□□□□□ □□□□□□□□□□□□
#b 1 p 12 □□□□□■■□□□□□ □□□□□■■□□□□□
#b -12 p 1 □□□□□□□□□□□□ □□□□□■■□□□□□
#b -11 p 13 □□□□□□□□□□□□ □□□□□□□□□□□□
#
#b -1 p -12 □□□□□□□□□□□□ □□□□□■□□□□□□
#b 0 p 0 □□□□■■■■□□□□ □□□□□■□□□□□□
#b 1 p 12 □□□□□□□□□□□□ □□□□□■□□□□□□
#b 2 p 24 □□□□□□□□□□□□ □□□□□■□□□□□□
#
#b -1 p -12 □□□□□■□□□□□□ □□□□□■□□□□□□
#b 0 p 0 □□□□■■■□□□□□ □□□□□■■□□□□□
#b 1 p 12 □□□□□□□□□□□□ □□□□□■□□□□□□
#b -12 p 1 □□□□□□□□□□□□ □□□□□□□□□□□□
リストのスライスがわかりませんでしたので、検証してみました。
def remove(self, line):# 指定列を削除
#横一要素、縦5要素のfixed_のデータとして、それが[12345]として3が横いっぱい
#となったとしてそれを削除して、一番目に0を加えると[01245]となる
#ここのやり方は正しい
del self.fixed_[line * GAME_W: (line + 1) * GAME_W]
# 1 列先頭について
top = []
for i in range(GAME_W):
if i == 0 or i == GAME_W - 1:
top.append(-1) # 左右端に(-1)壁を作成
else:
top.append(0) # それ以外は移動領域を作成
self.fixed_[0: 0] = top
#fixed_[0: 0] 空のリストを作成している
#ではなく、リストのスライスによる値の代入が行われている
#fixed_の0の位値、つまりリストのいち番目にtopの値を入れている
#>>> ls =[0,0,0,0]
#>>> ls[0:0]
#[]
#>>> ls
#[0, 0, 0, 0]
#>>> top =[1]
#>>> ls[0: 0] = top
#>>> ls
#[1, 0, 0, 0, 0]
テトリスのコード
E:\MyBackups\goolgedrive\myprg_main\python_my_prg\wxpython\game_tetoris_two1.pyimport wx
import random
BLOCK_BMP_SIZE = 20 # 1 ブロックのビットマップ サイズ
GAME_PANEL_W = BLOCK_BMP_SIZE * 10
GAME_PANEL_H = BLOCK_BMP_SIZE * 20
GAME_H = 21 # 画面の高さ (番兵を含む)
GAME_W = 12 # 画面の幅 (番兵を含む)
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 Game:
# コンストラクタ
def __init__(self):
# ブロック形状の定義
# ブロックのセルに番号を打っている
self.blocks_ = [
[0, 1, -GAME_W, -GAME_W - 1], # Z, green
[0, 1, -GAME_W, -GAME_W * 2], # L, orange
[0, 1, -GAME_W, GAME_W + 1], # S, pink
[0, 1, -GAME_W, 2], # J, blue
[0, 1, -GAME_W, -GAME_W + 1], # O, yellow
[-1, 0, 1, 2], # I, red
[-1, 0, 1, -GAME_W] # T, light blue
]
# 固定ブロックの初期化
#下端左右端を-1に、それ以外を0にする
#fixed_: 壁(-1)、固定ブロック(ブロックのid 1~7)、
#移動ブロックが移動できる領域(0)を示している背景情報
#と思えばいいか。
self.fixed_ = []
for j in range(GAME_H):
for i in range(GAME_W):
if j == GAME_H - 1:
self.fixed_.append(-1) # 下端
elif i == 0 or i == GAME_W - 1:
self.fixed_.append(-1) # 左右端
else:
self.fixed_.append(0) # それ以外
## フローティング ブロックの初期化
#ブロックの形状からどれか一つをランダムに選ぶ
#randint:1~7 len(self.blocks_) までをランダムに返す。1と7を含む
self.id_ = random.randint(1, len(self.blocks_))
#floating にブロックの形状の一つをセットする
self.floating_ = self.blocks_[self.id_ - 1]
#pos_:ブロックの位置、多分上左端を0として順に番号を打っていったものだ #ろう
#下はこれだと、上より2段目の真ん中になる。-1されているのはpos_が0より
#始まるため
self.pos_ = GAME_W + GAME_W / 2 - 1 #12+12/2-1
# クリアする
# fixed_の壁以外を0にして移動ブロックが移動できるようにする
def clear(self):
for j in range(GAME_H - 1):
for i in range(1, GAME_W - 1):
self.fixed_[j * GAME_W + i] = 0
# 左右に移動する (引数 delta, -1: 左, 1: 右, 戻り値, True: 移動完了,
# False: 移動不 可)
def move(self, delta):
# 移動可能か判定
movable = True
for b in self.floating_:
pos = self.pos_ + b + delta
if pos >= 0 and self.fixed_[pos] != 0:
movable = False
break
# 移動可能であるときは移動
if movable:
self.pos_ = self.pos_ + delta
return movable
# 回転する (戻り値, True:回転可 回転後のセル番号を返す , False: 回転不可)
# なぜ以下の計算で回転のセル番号がわかるのか解らないが
#game_tetoris_two1_turn.py でした所合っていた。
#90度右回転を一回させている
def turn(self):
movable = True
rot = []
for b in self.floating_:
#round:丸める(ほぼ四捨五入をする)
v = int(round(float(b) / float(GAME_W))) # 回転先の x 座標
w = b - v * GAME_W # 回転先の y 座標
p = w * GAME_W - v # 回転先の位置に当たるセル番号
rot.append(p)
pos = self.pos_ + p
if pos >= 0 and self.fixed_[pos] != 0:
movable = False
break
if movable:
self.floating_ = rot
return movable
# 落下移動する (戻り値, True: 移動完了, False: 移動不可)
def fall(self):
# 落下可能か判定
movable = True
for b in self.floating_:
pos = self.pos_ + b + GAME_W
#posが0より小さくなる事があるのだろうか?
#posが0以上でpos位置が壁であったら
if pos >= 0 and self.fixed_[pos] != 0:
#セル一個でも落下不可能であればブロックとして落下不能になる
movable = False
break
# 落下可能であれば落下
if movable:
self.pos_ = self.pos_ + GAME_W
return movable
# 現在のフローティング ブロックを固定する
def fix(self):
# ブロック固定
#ブロックのセル位置の背景情報をブロックid番号にする
for b in self.floating_:
self.fixed_[self.pos_ + b] = self.id_ #id 1~7
# 次のブロックを落とし始める (戻り値, True: 開始完了, False: 開始不能)
#次のブロックを落とせる時は、ブロック、初期値をセットする
def next(self):
# 次のブロック決定
nextId = random.randint(1, len(self.blocks_))
nextFloating = self.blocks_[nextId - 1]
nextPos = GAME_W + GAME_W / 2 - 1
# 次のブロックを落とし始めることができるか判定
#このファイルによくあるテクニックを使っている
#次のブロックの各セルの背景情報fixed_が0でない(壁がある)
#またはpos>=0の時、つまりセル番号-24、初期位置17ではpos=-7となり
#セルの表示ができない時、ブロックを落とせないとして
#Falseを返す
starting = True
for b in nextFloating:
pos = nextPos + b
if pos >= 0 and self.fixed_[pos] != 0:
starting = False
break
# 落とし始めることができるときは開始
if starting:
self.id_ = nextId
self.floating_ = nextFloating
self.pos_ = nextPos
return starting
# 固定ブロックが 1 列がそろっていることの判定
# (引数 line, 列番号, 戻り値: True: そろっている, False: そろっていない)
def full(self, line):
#fixed_のリストのスライス
#fixed_[a:b] インデックスaからb-1までの要素を取り出す(一列分になる)
for b in self.fixed_[line * GAME_W: (line + 1) * GAME_W]:
if b == 0: #bが0なら移動領域である
return False
return True
# 固定ブロックの 1 列削除 (引数 line, 列番号)
def remove(self, line):
# 指定列を削除
#横一要素、縦5要素のfixed_のデータとして、それが[12345]として
#3が横いっぱい
#となったとしてそれを削除して、一番目に0を加えると[01245]となる
del self.fixed_[line * GAME_W: (line + 1) * GAME_W]
# 1 列先頭について
top = []
for i in range(GAME_W):
if i == 0 or i == GAME_W - 1:
top.append(-1) # 左右端に(-1)壁を作成
else:
top.append(0) # それ以外は移動領域を作成
self.fixed_[0: 0] = top
# フローティングブロックの場合はブロックのidを
#そうでない時は、固定ブロックの情報を返す
# 指定した座標 (i, j) の値を返す
def block(self, i, j):
pos = j * GAME_W + i
# フローティング ブロックであれば、ブロックのid(1~7)を返す
#pos_:フローティングブロックの位置
#例えば、floating_= [0, 1, -GAME_W, -GAME_W - 1] となる
#floating_の要素はそのセルの位置をしめしているので
for block in self.floating_:
if self.pos_ + block == pos:
return self.id_
# フローティング ブロックでないときは固定ブロックされたブロックのid
#を返すか、壁側は-1 その内側は0を返す。
return self.fixed_[pos]
class GamePanel(wx.Panel):
def __init__(self, parent, status):
wx.Panel.__init__(self, parent, size=(GAME_PANEL_W, GAME_PANEL_H),
style=wx.ALIGN_LEFT)
# ゲームを初期化
self.game_ = Game()
self.playing_ = False
self.score_ = 0
#画像の縦横がBLOCK_BMP_SIZEのものを、横に7つ並べたものを用意する。
#その画像のビットマップをRectで始点座標、終点座標を引数にして
#切り取り、ブロックごとの画像にしている
#ブロックの形状によってどれが一つの画像をえらぶ
#
#"blocks.png"を作成しようとして
#Windowsのペイントによって縦横BLOCK_BMP_SIZEのものを、
#7つコピーす
#るとincorrect sbit chunk length のエラーを起こす
#
self.blockBitmaps_ = []
# ビットマップ データをロードする
blockBitmap = wx.Bitmap("blocks.png")
for i in range(7):
self.blockBitmaps_.append(blockBitmap.GetSubBitmap(
wx.Rect(BLOCK_BMP_SIZE * i, 0, BLOCK_BMP_SIZE, BLOCK_BMP_SIZE)))
#テトリスとは関係ないがブロック画像をを保存してみる
self.blockBitmaps_[0].SaveFile( "blocks_save.png", wx.BITMAP_TYPE_PNG )
self.backgroundBitmap_ = wx.Bitmap("back.png")
# タイマーの設定
self.timer_ = wx.Timer(self, 1)
# ステータス バーの設定
self.status_ = status
status.SetStatusText("SPACE key to start the game!")
# イベントハンドラの設定
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
self.Bind(wx.EVT_TIMER, self.OnTimer)
#スペースキーが押されたら→Start()
# ゲーム開始
def Start(self):
self.game_.clear()
self.timer_.Start(250)
self.playing_ = True
self.score_ = 0
status.SetStatusText("Score: " + str(self.score_))
# ゲームオーバー
def Over(self):
self.timer_.Stop()
#以下の行でOnTimerとOnKeyDownが無効になるので、
#上の行はいらにのでは?
self.playing_ = False
status.SetStatusText("Score: " + str(self.score_) + ".
SPACE key to restart!")
def FallAndTest(self):
#fall:落下出来ない時False 出来た時Trueを返する
#fall:落下できる時はブロックの位置の一段下げる
#Trueの場合ブロック位置self.pos_ を変更している
if not self.game_.fall():
# 落下できないとき, 移動ブロックを固定
self.game_.fix()
# そろった列を削除
#full:列が揃っていればTrue を返す
lines = 0
for line in range(GAME_H - 1):
if self.game_.full(line):
self.game_.remove(line)
lines = lines + 1
# スコア加算
if lines != 0:
#妙なスコアの計算、単に100*lineではいけないのか
self.score_ = self.score_ + 100 * pow(2, lines - 1)
status.SetStatusText("Score: " + str(self.score_))
# 次のブロックを落とし始める
# nest():落とす事ができたら、次のブロック、初期位置をセットして
#Trueを返す
if not self.game_.next():
# 落とし始めることができないときは, ゲームオーバー
self.Over()
def OnPaint(self, evt):
dc = wx.PaintDC(self)
dc = wx.BufferedDC(dc) # ダブルバッファ
#0, 0 に背景画像をセットする
dc.DrawBitmap(self.backgroundBitmap_, 0, 0)
#game_: Gameクラスのオブジェクト
for j in range(GAME_H - 1):
#なんで-2なのか? i=1 ~ GAME_W-1 ならわかるが
#一段目のposの移動領域は1~10なのでこれでいいのだ
for i in range(GAME_W - 2):
#block():
#移動ブロックの場合id(1~7)、
#固定ブロック(1~7)(fixed_[pos]の値) 壁-1 その内側0
b = self.game_.block(i + 1, j)
#bがブロックidのどれかであれば、そのidのブロックの
#画像を i jの位置に表示する。
#移動ブロック、固定ブロックとも表示
#len(saelf.blockBitmaps_)=7
if b >= 1 and b <= len(self.blockBitmaps_):
dc.DrawBitmap(self.blockBitmaps_[b - 1],
BLOCK_BMP_SIZE * i, BLOCK_BMP_SIZE * j)
#Refresh()によって、OnPaint()に移動する
def OnKeyDown(self, evt):
#スペースキーを押せばplaying_がTrueになる
if self.playing_:
if evt.KeyCode == wx.WXK_LEFT:
self.game_.move(-1)
self.Refresh(False)
elif evt.KeyCode == wx.WXK_RIGHT:
self.game_.move(1)
self.Refresh(False)
elif evt.KeyCode == wx.WXK_DOWN:
self.FallAndTest()
self.Refresh(False)
elif evt.KeyCode == wx.WXK_RETURN:
# 回転させる (戻り値, True:回転可 回転後のセル番号を返す ,
# False: 回転不可)
self.game_.turn()
self.Refresh(False)
else:
if evt.KeyCode == wx.WXK_SPACE:
self.Start()
def OnTimer(self, evt):
#スペースキーを押すことのよりplaying_がTrueになる
if self.playing_:
self.FallAndTest()
self.Refresh(False)
if __name__ == "__main__":
app = MyApp()
frame = wx.Frame(None, wx.ID_ANY, "Tetris",
style=wx.MINIMIZE_BOX | wx.CLOSE_BOX | wx.SYSTEM_MENU | wx.CAPTION)
status = frame.CreateStatusBar()
gamePanel = GamePanel(frame, status)
#SetClientSizeWH: frameのタイトルバー、ステータスバーなどを除いた領域
#を設定していると思う
#gamePanel.Size = (200,400)だが print"frame.Size", frame.Sizeを実行すると
#出力結果(210,449)となりおかしい
#frame.SetClientSizeWH(gamePanel.Size.width + 4, gamePanel.Size.height + 4)
# SUNKEN_BORDER の幅は 2 px らしい
#SUNKEN_BORDER というスタイルだとClidenSizeが少なくなると思って+4
#されているようだが
#代わりに以下の2行のどちらかのコードをいれてみたら、これでも行けそう
#frame.SetClientSize((200, 400))
frame.SetClientSizeWH(200, 400)
frame.Show()
app.MainLoop()
0 件のコメント:
コメントを投稿