tkinterを使ってブロック崩しを作りました。ell様のシューティングゲームを参考にさせていただきました。https://qiita.com/ell/items/f0f74865c07710f1eab8
だいぶお粗末なものです。壁際でたまにバグります。
当たり判定が上手くできなかった。
できたらこれにAI搭載したい。環境が固定じゃない→状態価値関数を簡単に作れないから、DQNになるのかな?やってみます。
ソースコード
ブロック崩し
import tkinter as tk
import random
from PIL import Image, ImageTk
from abc import ABC, abstractmethod
"""
クリックでスタート
カーソルで操作
"""
# ステージは縦20マス×横20マス
STAGE1 = [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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0,
0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 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, 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, 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,
]
STAGE2 = [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,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
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, 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, 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, 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,
]
STAGE3 = [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,
1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1,
1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2,
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, 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, 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, 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,
]
STAGES = [STAGE1, STAGE2, STAGE3]
WINDOW_HEIGHT = 600 # ウィンドウの高さ
WINDOW_WIDTH = 800 # ウィンドウの横幅
BAR_Y = 550 # バーの高さ
BAR_HEIGHT = 5 # バーの高さの半分,10
BAR_WIDTH = 50 # バーの横幅の半分,80
BROC_HEIGHT = 20 # ブロックの高さ
BROC_WIDTH = 40 # ブロックの横幅
BALL_RADIUS = 8 # ボールの半径
BALL_DY = 8 # ボールがSPEEDごとに進む距離
BALL_SPEED = 10 # ボールのスピード(10 ms)
COLLISION_DETECTION = 5 # 当たり判定
TEXT_CONGRATULATIONS_SIZE = 50 # congratularionsのサイズ
TEXT_GAMECLEAR_SIZE = 60 # gameclearのサイズ
TEXT_GAMEOVER_SIZE = 90 # gameoverのサイズ
class Bar: # プレイヤーが操作するバー
def __init__(self, x, y=BAR_Y):
self.x = x
self.y = y
self.draw()
self.bind()
def draw(self):
self.id = cv.create_rectangle(
self.x-BAR_WIDTH, self.y+BAR_HEIGHT, self.x+BAR_WIDTH, self.y-BAR_HEIGHT, fill="red")
def bind(self):
cv.bind("<Motion>", self.moved)
def moved(self, event):
if event.x > 20 and event.x < WINDOW_WIDTH-20:
dx = event.x - self.x
cv.move(self.id, dx, 0)
if ball.rolling == False:
cv.move(ball.id, dx, 0)
ball.x = event.x
self.x = event.x
class Ball: # ボール
def __init__(self, x, y):
self.x = x
self.y = y
self.rolling = False
self.draw()
self.bind()
def draw(self):
self.id = cv.create_oval(
self.x-BALL_RADIUS, self.y-BAR_HEIGHT, self.x+BALL_RADIUS, self.y-BALL_RADIUS*2-BAR_HEIGHT, fill="blue")
def bind(self):
cv.bind("<ButtonPress>", self.fire)
def fire(self, event): # ボールを発射
if self.rolling == False:
self.rolling = True
dx = 0
dy = -BALL_DY
self.roll(dx, dy)
def roll(self, dx, dy): # ボールが動く
dx, dy = self.collision(dx, dy)
cv.move(self.id, dx, dy)
self.x += dx
self.y += dy
root.after(BALL_SPEED, self.roll, dx, dy)
def collision(self, dx, dy):
# 壁との衝突
if self.x <= COLLISION_DETECTION or self.x >= WINDOW_WIDTH-COLLISION_DETECTION:
dx *= -1
if self.y <= 0:
dy *= -1
if self.y >= WINDOW_HEIGHT:
gameover()
# バーとの衝突
if (self.x > bar.x-BAR_WIDTH-COLLISION_DETECTION) and (self.y > bar.y-BAR_HEIGHT-COLLISION_DETECTION) and \
(self.x < bar.x+BAR_WIDTH+COLLISION_DETECTION) and (self.y < bar.y+BAR_HEIGHT+COLLISION_DETECTION):
dx = (self.x - bar.x) / 4
dy = -BALL_DY
# ブロックとの衝突
for block in blocks:
if block.exist:
if self.y < block.y+BROC_HEIGHT and self.y > block.y:
if (self.x < block.x+COLLISION_DETECTION and self.x > block.x-COLLISION_DETECTION)\
or (self.x < block.x+BROC_WIDTH+COLLISION_DETECTION and self.x > block.x+BROC_WIDTH-COLLISION_DETECTION):
block.on_hit()
dx *= -1
if self.x < block.x+BROC_WIDTH and self.x > block.x:
if (self.y < block.y+COLLISION_DETECTION and self.y > block.y-COLLISION_DETECTION)\
or (self.y < block.y+BROC_HEIGHT+COLLISION_DETECTION and self.y > block.y+BROC_HEIGHT-COLLISION_DETECTION):
block.on_hit()
dy *= -1
return dx, dy
class Block(ABC): # ブロック
def __init__(self, x, y):
self.x = x
self.y = y
self.exist = True
self.draw()
@abstractmethod
def draw(self):
...
@abstractmethod
def on_hit(self):
...
class BreakableBlock(Block): # 壊せるブロック
def draw(self):
self.id = cv.create_rectangle(
self.x, self.y, self.x+BROC_WIDTH, self.y+BROC_HEIGHT, fill="yellow")
def on_hit(self):
self.exist = False
cv.delete(self.id)
class UnbreakableBlock(Block): # 壊せないブロック
def draw(self):
self.id = cv.create_rectangle(
self.x, self.y, self.x+BROC_WIDTH, self.y+BROC_HEIGHT, fill="red")
def on_hit(self):
pass
def gameclear(): # ゲームクリア
cnt = 0
for block in breakableblocks:
if block.exist == True:
cnt += 1
if cnt == 0:
cv.create_text(WINDOW_WIDTH//2, WINDOW_HEIGHT//2-80, text="Congratulations!",
fill="lime", font=("System", TEXT_CONGRATULATIONS_SIZE))
cv.create_text(WINDOW_WIDTH//2, WINDOW_HEIGHT//2+20, text="GAME CLEAR!",
fill="lime", font=("System", TEXT_GAMECLEAR_SIZE))
root.after(1000, gameclear)
def gameover(): # ゲームオーバー
cv.create_text(WINDOW_WIDTH//2, WINDOW_HEIGHT//2, text="GAME OVER",
fill="red", font=("System", TEXT_GAMEOVER_SIZE))
if __name__ == "__main__":
# 初期描画
root = tk.Tk()
root.title("ブロック崩し")
cv = tk.Canvas(root, width=WINDOW_WIDTH, height=WINDOW_HEIGHT, bg="white")
cv.pack()
# メニューバー
menubar = tk.Menu(root)
root.configure(menu=menubar)
menubar.add_command(label="QUIT", underline=0, command=root.quit)
# インスタンス生成
bar = Bar(WINDOW_WIDTH//2, BAR_Y)
ball = Ball(WINDOW_WIDTH//2,BAR_Y)
stage = random.choice(STAGES)
blocks = []
breakableblocks = []
for i in range(400):
if stage[i] == 1:
block_i = BreakableBlock(i%20*40, (i//20)*20)
blocks.append(block_i)
breakableblocks.append(block_i)
elif stage[i] == 2:
block_i = UnbreakableBlock(i%20*40, (i//20)*20)
blocks.append(block_i)
gameclear()
root.mainloop()