前回はキー入力と落下処理を行った
今回はミノの設置とライン消去を行う
ミノの設置
落下処理が失敗した→地面に着いたなので
地面についた状態から少し動かす余裕を持たせるために
カウントダウンしていく
# 床に着地しているかのチェック
is_floor_landed = not self.can_mino_move(self.mino, Pos(0, 1), 0)
if is_floor_landed:
self.wait_count -= 1
移動処理のタイミングでカウントが基準値を下回ったら設置する。
def move_and_rotate_mino(self):
if self.wait_count <= WAIT // 2:
self.wait()
return
def wait(self) -> None:
if self.wait_count == 0:
self.mino_setup()
else:
self.wait_count -=1
if self.wait_count == WAIT // 2 - 1:
# print(self.mino.p)
self.put_mino()
self.line_check()
if self.wait_count == 1:
self.clear_line()
def put_mino(self):
self.is_draw_mino = False
m = self.mino
for p in m.get_shape():
self.matrix[p.y][p.x] = m.m.tile
マップの編集
ライン消去用に消えるラインを別の色のブロックにしたいが
どうやらpytmxはマップ上にタイルを設置しないと表示できないみたいなので
mino_data.tmxを編集する。マップの幅を1増やして一番右下に灰色のブロックを追加した。
mino_block.tmx
mino_block.tmx
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.2" orientation="orthogonal" renderorder="right-down" width="29" height="16" tilewidth="32" tileheight="32" infinite="0" nextlayerid="2" nextobjectid="1">
<tileset firstgid="1" source="mino_block.tsx"/>
<layer id="1" name="mino_block" width="29" height="16">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,2,2,0,0,0,3,3,0,4,0,0,0,0,0,5,0,0,6,0,0,7,7,0,8,8,8,8,0,
0,0,2,2,0,3,3,0,0,4,4,4,0,5,5,5,0,6,6,6,0,7,7,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,
0,0,0,2,0,0,3,0,0,0,4,4,0,0,5,0,0,0,6,0,0,7,7,0,0,0,8,0,0,
0,0,2,2,0,0,3,3,0,0,4,0,0,0,5,0,0,0,6,6,0,7,7,0,0,0,8,0,0,
0,0,2,0,0,0,0,3,0,0,4,0,0,0,5,5,0,0,6,0,0,0,0,0,0,0,8,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,
0,2,2,0,0,0,3,3,0,4,4,4,0,5,5,5,0,6,6,6,0,7,7,0,8,8,8,8,0,
0,0,2,2,0,3,3,0,0,0,0,4,0,5,0,0,0,0,6,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,
0,0,2,0,0,3,0,0,0,0,4,0,0,5,5,0,0,0,6,0,0,7,7,0,0,8,0,0,0,
0,2,2,0,0,3,3,0,0,0,4,0,0,0,5,0,0,6,6,0,0,7,7,0,0,8,0,0,0,
0,2,0,0,0,0,3,0,0,4,4,0,0,0,5,0,0,0,6,0,0,0,0,0,0,8,0,0,17
</data>
</layer>
</map>
追加したタイルを使えるようにmino.py
にクラスを追加する
class OtherTiles(Enum):
Vanish = "Vanish", -1, Pos(28,15)
def __init__(self, name, value, p):
self._name = name
self._value_ = value
self.tile = tmx_data.get_tile_gid(p.x,p.y,0)
@classmethod
def values(cls):
return list(cls.__members__.values())
ライン消去
ライン消去の処理を追加する
def line_check(self):
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:
for x in range(10):
self.matrix[y][x] = OtherTiles.Vanish.tile
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
プログラム全体
更新したmain.py
とmino.py
の内容全体を示す。
main.py
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(4,-2)
class Game:
# 番兵用に連想配列でラクする
matrix = {
i:{
j:0 for j in range(-2,12)
} for i in range(-20,21)
}
key = Key()
mino : Mino
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 mino_setup(self):
self.wait_count = WAIT
self.is_draw_mino = True
t = choice(MinoType.values())
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
for p in m.get_shape():
self.matrix[p.y][p.x] = m.m.tile
def line_check(self):
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:
for x in range(10):
self.matrix[y][x] = OtherTiles.Vanish.tile
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 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
# 回転と移動のフラグをチェックして状態を更新
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 self.key.flags["is_soft_drop"]:
self.g_cnt += SOFT_DROP_SPEED
elif self.g_cnt < GRAVITY:
self.g_cnt += 1
# 床に着地しているかのチェック
is_floor_landed = not self.can_mino_move(self.mino, Pos(0, 1), 0)
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)
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()
# 番兵を設置
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
while running:
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)
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 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()
if __name__ == "__main__":
game = Game()
game.run()
mino.py
mino.py
from dataclasses import dataclass
from enum import Enum, auto
import pytmx
from pos import Pos
# tmx ファイルからミノを構成するブロックの配置を定義する
tmx_data = pytmx.TiledMap('mino_block.tmx')
def get_mino_data(t, size=4):
shapes = [[] for _ in range(4)]
tile = 0
for r in range(4):
for y in range(size):
for x in range(size):
gid = tmx_data.get_tile_gid(size*t+x, size*(r%4)+y, 0)
if gid != 0:
tile = gid
shapes[r].append((x,y))
return tile, shapes
class OtherTiles(Enum):
Vanish = "Vanish", -1, Pos(28,15)
def __init__(self, name, value, p):
self._name = name
self._value_ = value
self.tile = tmx_data.get_tile_gid(p.x,p.y,0)
@classmethod
def values(cls):
# Order dictionary -> list
return list(cls.__members__.values())
# ミノの形を定義する列挙型
class MinoType(Enum):
Z = 'Z', auto(), *get_mino_data(0)
S = 'S', auto(), *get_mino_data(1)
J = 'J', auto(), *get_mino_data(2)
L = 'L', auto(), *get_mino_data(3)
T = 'T', auto(), *get_mino_data(4)
O = 'O', auto(), *get_mino_data(5)
I = 'I', auto(), *get_mino_data(6)
def __init__(self, name, value, tile, shape):
self._name = name
self._value_ = value
self.tile = tile
self.shape = tuple(tuple(s) for s in shape)
self.shape_pos = tuple(tuple(Pos(*p) for p in s) for s in shape)
@classmethod
def values(cls):
# Order dictionary -> list
return list(cls.__members__.values())
@dataclass
class Mino:
m : MinoType
p : Pos
r : int
def move(self,pos:Pos,r:int=0):
self.r = (r+self.r) % 4
self.p += pos
def get_shape(self):
for p in self.m.shape_pos[self.r%4]:
yield p + self.p
def get_moved_mino(self,pos:Pos,r:int=0):
for p in self.m.shape_pos[(100+r+self.r)%4]:
yield p + self.p + pos
def main():
for mino in MinoType.values():
print(mino.name, mino.value, mino.tile)
for mino in OtherTiles.values():
print(mino.name, mino.value, mino.tile)
if __name__ == '__main__':
main()
動作確認
実行して操作した様子の録画。
1ライン消し、複数ライン消しともに実行できているのが確認できる。
まとめ
ミノ設置とライン消去を実装した
これで最低限遊べる状態になったけど遊びづらいので改良していく
次回: テトリス風落ち物パズルを作る part05 SRSとロックダウン(ワールドルールの適応) #Python - Qiita