前回ではSRSとロックダウンを実装した
今回はゲームオーバーの判定および演出を追加する。
ゲームオーバーの実装
ゲームオーバーの条件は2つ
- 新しくミノを追加したときにミノを置けない
- 設置時画面内に1つもミノのタイルを設置できなかった
上記の状態が起きたらフラグを立て、メインの処理に伝える
新規ミノの設置失敗はこんな感じ、即ゲームオーバーでもよいが画面上部で頑張れるようにする為2つ上まで設置を試みている。
class Game:
# ---中略---
gameover = False # 追加
# ---中略---
def mino_setup(self):
self.wait_count = WAIT
self.is_draw_mino = True
t = choice(MinoType.values())
self.put_mino_cnt+=1
self.mino = Mino(t,start_pos,0)
if not self.can_mino_move(self.mino,Pos(0,0),0):
for i in range(3):
if self.can_mino_move(self.mino,Pos(0,-i),0):
self.mino.move(Pos(0,-i))
break
else: # 追加
self.gameover = True
画面内に設置タイルが設置できないということはすべてのタイルの設置が画面外、
つまり設置したミノのタイルのY座標がすべてマイナスかどうかを判定する。
そこで、設置するタイミングでY座標がマイナスか確認する
- すべてマイナスだったらゲームオーバーにする
- 実装では最初にフラグを立て、設置したタイルのY座標が0以上のときにフラグを消している
def put_mino(self):
self.is_draw_mino = False
m = self.mino
flg = True # 追加
for p in m.get_shape():
self.matrix[p.y][p.x] = m.m.tile
if p.y >= 0: # 追加
flg = False
if flg: # 追加
self.gameover = True
条件を設定したので次はゲームオーバーしたら操作を無効化するのとなにかしらの演出を追加する、
演出はとりあえず設置したタイルが下から塗りつぶされていくやつをやってみた。
操作の無効化は対応する処理の箇所にフラグ変数の条件分岐追加するだけでOK。
def run(self):
clock = pygame.time.Clock()
running = True
tmx_data = tmx_util.load_pygame('field.tmx')
mino_block = tmx_util.load_pygame('mino_block.tmx')
self.mino_setup()
gameover_cnt = 0 # 追加
gameover_cnt_sub = 0 # 追加
# 番兵を設置
for i in range(-20,21):
for j in range(-2,12):
if j < 0 or j >= 10:
self.matrix[i][j] = 1
else:
self.matrix[i][j] = 0
for i in range(-2,12):
self.matrix[20][i] = 1
if self.is_debug :self.for_debug()
while running:
if not self.gameover: # 追加
self.key.process_key_input() # キー入力の処理
self.move_and_rotate_mino()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
self.screen.fill((0, 0, 0))
for layer in tmx_data.layers:
if layer.name == "mainfield":
for x, y, gid in layer.iter_data():
self.draw_tile(tmx_data, x, y, gid)
# 追加
if self.gameover:
# ゲームオーバーの演出、下から灰色になっていく
for i in range(20,19-gameover_cnt,-1):
for j in range(10):
if self.matrix[i][j] != 0:
self.matrix[i][j] = 8
if gameover_cnt_sub == 2:
gameover_cnt = min(22, gameover_cnt+1)
gameover_cnt_sub = 0
else: gameover_cnt_sub += 1
x, y = MATRIX_POS
for i in range(-1, MATRIX_SIZE[0]):
for j in range(MATRIX_SIZE[1]):
self.draw_tile(mino_block, x+j, y+i, self.matrix[i][j])
if not self.gameover: # 追加
if self.is_draw_mino:
for p in self.mino.get_shape():
self.draw_tile(mino_block, x+p.x, y+p.y, self.mino.m.tile)
pygame.display.flip()
clock.tick(60)
pygame.quit()
実は一番上はY座標-1のタイルを表示している。
その状態では画面外だと分かりづらいので全体の3分の1だけ表示させる
ワールドルールたしかそういう仕様を認めていた記憶
def run(self):
# ---中略---
while running:
# ---中略---
# 追加
self.screen.fill((0, 0, 0),
(10*TILE_SIZE,0,12*TILE_SIZE,TILE_SIZE*2//3)
)
pygame.display.flip()
clock.tick(60)
pygame.quit()
動作確認
今回もデバッグコードを動かし、すべてのタイルが同じ色で塗りつぶされていくのを確認する
def for_debug(self):
DTD = [
[0,3,3,3,3,3,3,3,3,0,],
[2,0,2,2,2,2,2,2,0,2,],
[1,1,0,1,1,1,1,0,1,1,],
[7,7,7,0,7,7,0,7,7,7,],
[6,6,6,6,0,0,6,6,6,6,],
[5,5,5,5,0,0,5,5,5,5,],
[4,4,4,0,4,4,0,4,4,4,],
[3,3,0,3,3,3,3,0,3,3,],
[2,0,2,2,2,2,2,2,0,2,],
[0,1,1,1,1,1,1,1,1,0,],
][::-1]
for i, c in enumerate(DTD,start=1):
for j, b in enumerate(c):
self.matrix[20-i][j] = b
画面外にミノをおいた瞬間にすべてのタイルが同じ色で塗りつぶされていくのが確認できる。
まとめ
今回はゲームオーバーを実装した。
新規ミノ追加時やミノの設置のタイミングでゲームオーバーか判定し
ゲームオーバーしたなら演出としてしたから同じ色で塗りつぶされていく演出を実装した
次回:テトリス風落ち物パズルを作る part07 ハードドロップの実装 #Python - Qiita
付録
今回更新したソースコードコード全体を以下に示す
main.py
from random import choice
import pygame
from pytmx import util_pygame as tmx_util
from mino import MinoType, Mino, OtherTiles
from key import Key
from pos import Pos
TILE_SIZE = 32
MATRIX_SIZE = (20,10)
MATRIX_POS = (11,1)
WAIT = 60
GRAVITY = 60
SOFT_DROP_SPEED = 20
start_pos = Pos(3,-2)
class Game:
# 番兵用に連想配列でラクする
matrix = {
i:{
j:0 for j in range(-2,12)
} for i in range(-20,21)
}
is_debug = True
key = Key()
mino : Mino
put_mino_cnt = 0
lockdown_cnt = 0
gameover = False
g_cnt = 0
wait_count = WAIT
is_draw_mino = True
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((1024, 768))
def draw_tile(self, tmx, x, y, t):
tile_image = tmx.get_tile_image_by_gid(t)
if tile_image:
self.screen.blit(tile_image, (x * TILE_SIZE, y * TILE_SIZE))
def for_debug(self):
DTD = [
[0,3,3,3,3,3,3,3,3,0,],
[2,0,2,2,2,2,2,2,0,2,],
[1,1,0,1,1,1,1,0,1,1,],
[7,7,7,0,7,7,0,7,7,7,],
[6,6,6,6,0,0,6,6,6,6,],
[5,5,5,5,0,0,5,5,5,5,],
[4,4,4,0,4,4,0,4,4,4,],
[3,3,0,3,3,3,3,0,3,3,],
[2,0,2,2,2,2,2,2,0,2,],
[0,1,1,1,1,1,1,1,1,0,],
][::-1]
for i, c in enumerate(DTD,start=1):
for j, b in enumerate(c):
self.matrix[20-i][j] = b
def mino_setup(self):
self.wait_count = WAIT
self.is_draw_mino = True
t = choice(MinoType.values())
self.put_mino_cnt+=1
self.mino = Mino(t,start_pos,0)
if not self.can_mino_move(self.mino,Pos(0,0),0):
for i in range(3):
if self.can_mino_move(self.mino,Pos(0,-i),0):
self.mino.move(Pos(0,-i))
break
else:
self.gameover = True
def put_mino(self):
self.is_draw_mino = False
m = self.mino
flg = True
for p in m.get_shape():
self.matrix[p.y][p.x] = m.m.tile
if p.y >= 0:
flg = False
if flg:
self.gameover = True
def line_check(self):
flg = False
for y in range(19, -2, -1):
n = 0
for x in range(10):
if self.matrix[y][x]!=0:
n += 1
if n != 10:
continue
else:
flg = True
for x in range(10):
self.matrix[y][x] = OtherTiles.Vanish.tile
return flg
def clear_line(self):
for y in range(19,-3,-1):
while self.matrix[y][0] == OtherTiles.Vanish.tile:
self.wait_count = WAIT // 2 - 2
for i in range(y, -20, -1):
for x in range(10):
t = self.matrix[i - 1][x]
self.matrix[i][x] = t
for x in range(10):
self.matrix[-20][x] = 0
def wait(self) -> None:
if self.wait_count == 0:
self.mino_setup()
else:
self.wait_count -=1
if self.wait_count == WAIT // 2 - 1:
self.put_mino()
self.line_check()
if self.wait_count == 1:
self.clear_line()
def lock_down(self):
if self.wait_count == WAIT:
return
if self.lockdown_cnt < 15:
self.lockdown_cnt += 1
self.g_cnt = 0
self.wait_count = WAIT
def can_mino_move(self,mino:Mino,pos:Pos,r:int):
for p in mino.get_moved_mino(pos,r):
if self.matrix[p.y][p.x] != 0:
return False
return True
def move_and_rotate_mino(self):
# 待機時間が設定の半分以下の場合は待機
if self.wait_count <= WAIT // 2:
self.wait()
return
vpos = Pos()
vr = 0
# 床に着地しているかのチェック
is_floor_landed = not self.can_mino_move(self.mino, Pos(0, 1), 0)
# 回転と移動のフラグをチェックして状態を更新
if self.key.flags["is_left_rot"]:
vr += 3
elif self.key.flags["is_right_rot"]:
vr += 1
elif self.key.flags["is_left_move"]:
vpos += Pos(-1, 0)
elif self.key.flags["is_right_move"]:
vpos += Pos(1, 0)
# ソフトドロップと重力の処理
if not is_floor_landed and self.key.flags["is_soft_drop"]:
self.g_cnt += SOFT_DROP_SPEED
elif self.g_cnt < GRAVITY:
self.g_cnt += 1
if is_floor_landed:
self.wait_count -= 1
# 重力によるミノの移動
while self.g_cnt >= GRAVITY:
if not is_floor_landed:
self.mino.p += Pos(0, 1)
self.g_cnt -= GRAVITY
self.wait_count = WAIT
else:
self.g_cnt = 0
break
# 可能であればミノを移動または回転
if vpos.x == 0 and vpos.y == 0 and vr == 0:
pass
elif self.can_mino_move(self.mino, vpos, vr):
self.mino.move(vpos, vr)
if vpos.y > 0:
self.lockdown_cnt = 0
else:
self.lock_down()
elif vr != 0:
# SRSの実行
rot1 = self.mino.r % 4
rot2 = (self.mino.r + vr) % 4
for p in self.mino.m.srs.get_rotate_offsets(rot1, rot2):
if self.can_mino_move(self.mino, vpos + p, vr):
self.mino.move(vpos + p, vr)
self.lock_down()
break
def run(self):
clock = pygame.time.Clock()
running = True
tmx_data = tmx_util.load_pygame('field.tmx')
mino_block = tmx_util.load_pygame('mino_block.tmx')
self.mino_setup()
gameover_cnt = 0
gameover_cnt_sub = 0
# 番兵を設置
for i in range(-20,21):
for j in range(-2,12):
if j < 0 or j >= 10:
self.matrix[i][j] = 1
else:
self.matrix[i][j] = 0
for i in range(-2,12):
self.matrix[20][i] = 1
if self.is_debug :self.for_debug()
while running:
if not self.gameover:
self.key.process_key_input() # キー入力の処理
self.move_and_rotate_mino()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
self.screen.fill((0, 0, 0))
for layer in tmx_data.layers:
if layer.name == "mainfield":
for x, y, gid in layer.iter_data():
self.draw_tile(tmx_data, x, y, gid)
if self.gameover:
# ゲームオーバーの演出、下から灰色になっていく
for i in range(20,19-gameover_cnt,-1):
for j in range(10):
if self.matrix[i][j] != 0:
self.matrix[i][j] = 8
if gameover_cnt_sub == 2:
gameover_cnt = min(22, gameover_cnt+1)
gameover_cnt_sub = 0
else: gameover_cnt_sub += 1
x, y = MATRIX_POS
for i in range(-1, MATRIX_SIZE[0]):
for j in range(MATRIX_SIZE[1]):
self.draw_tile(mino_block, x+j, y+i, self.matrix[i][j])
if not self.gameover:
if self.is_draw_mino:
for p in self.mino.get_shape():
self.draw_tile(mino_block, x+p.x, y+p.y, self.mino.m.tile)
# 21段目チラ見せ
self.screen.fill((0, 0, 0),
(10*TILE_SIZE,0,12*TILE_SIZE,TILE_SIZE*2//3)
)
pygame.display.flip()
clock.tick(60)
pygame.quit()
if __name__ == "__main__":
game = Game()
game.run()