代码: 全选
def click_drag_release():
#because we don't control receiving mouse events
#need to break the click_drag_release cycle
"""
click_drag_release: click_a_preview_block, drag_a_block*, release_a_block
if click_a_preview_block():
while drag_a_block(): pass
release_a_block()
"""
if click_a_preview_block(): pass
if drag_a_block(): pass
if release_a_block(): pass
以下是完整的版本:
init:
代码: 全选
"""
game: game_init, game_going+
game_init: game_static_init, generate_preview_blocks
game_going: null_mouse_event
game_going: click_drag_release
click_drag_release: click_a_preview_block, drag_a_block*, release_a_block
click_a_preview_block: MOUSE_CLICK, mouse_click_effect
drag_a_block: MOUSE_MOVE, mouse_drag_effect
mouse_drag_effect: not_fit_in_board, drag_block
mouse_drag_effect: fit_in_board, show_fit_in, drag_block
release_a_block: MOUSE_RELEASE, mouse_release_effect
mouse_release_effect: not_fit_in_board, go_back_to_preview
mouse_release_effect: fit_in_board, update_board, update_preview
update_preview: update_preview_blocks, update_game_over
update_preview_blocks: no_preview_blocks, generate_preview_blocks
"""
import tkinter as tk
import random
class BlockPuzzle:
def __init__(self):
pass
def game_init():
game_static_init()
generate_preview_blocks()
def game_static_init():
game_init_layout()
game_init_canvas()
set_game_blocks()
game_init_board()
game.game_over = False
game.dragging = False
game.fit_in = False
game.board_state = [
[{"occupied": False}
for i in range(8)]
for j in range(8) ]
def game_going(event):
game.event = event
click_drag_release()
def click_drag_release():
#because we don't control receiving mouse events
#need to break the click_drag_release cycle
"""
click_drag_release: click_a_preview_block, drag_a_block*, release_a_block
if click_a_preview_block():
while drag_a_block(): pass
release_a_block()
"""
if click_a_preview_block(): pass
if drag_a_block(): pass
if release_a_block(): pass
def game_init_layout():
game.game_bg_color = "#1A2B3C"
game.cell_color = game.game_bg_color
game.grid_color = "#708090"
game.padx = 100
game.top_section_pady = 20
game.board_pady = 20
game.preview_pady = 20
game.bottom_pady = 20
game.cell_size = 50
game.preview_cell_size = 24
game.grid_border = 1
game.board_n_cell = 8
game.board_size = game.cell_size * game.board_n_cell
game.top_section_x = game.padx
game.top_section_y = game.top_section_pady
game.top_section_height = 100
game.top_section_width = game.board_size
game.board_x = game.padx
game.board_y = game.top_section_y + game.top_section_height + game.board_pady
game.preview_x = game.padx
game.preview_y = game.board_y + game.board_size + game.preview_pady
game.preview_width = game.board_size
game.preview_height = game.board_size // 2
game.canvas_width = 2 * game.padx + game.board_size
game.canvas_height = game.preview_y + game.preview_height + game.bottom_pady
game.preview_one_size = 5* game.preview_cell_size
game.preview_padx = 10
xx = game.preview_width - 3* game.preview_one_size - 2* game.preview_padx
assert(xx % 2 == 0)
game.preview_lr_padx = xx // 2
game.preview_one_x = [
game.preview_x + game.preview_lr_padx +
i* (game.preview_one_size + game.preview_padx)
for i in range(3)]
game.preview_one_y = game.preview_y
def game_init_canvas():
root.title("Block Puzzle")
root.resizable(False, False)
game.canvas = tk.Canvas(
root,
bg=game.game_bg_color,
highlightthickness=0,
width=game.canvas_width,
height=game.canvas_height,
)
game.canvas.pack()
game.canvas.bind("<Button-1>", game_going)
game.canvas.bind("<B1-Motion>", game_going)
game.canvas.bind("<ButtonRelease-1>", game_going)
def variation_blocks():
def rotate(p):
x,y = p
return -y,x
def normalize(block):
min_x = min(dx for dx,dy in block)
min_y = min(dy for dx,dy in block)
nblock = [(dx-min_x, dy-min_y) for dx,dy in block]
return nblock
def variation_block(block):
nblocks = []
nb = block
for i in range(4):
nb = sorted(nb)
nblocks.append(tuple(nb))
nb = [rotate(c) for c in nb]
nb = normalize(nb)
nblocks = list(set(nblocks))
return nblocks
game.tetris_blocks = []
for block in game.base_tetris_blocks:
nblocks = variation_block(block["shape"])
game.tetris_blocks += [{"shape": b, "color": block["color"]} for b in nblocks]
def set_game_blocks():
game.base_tetris_blocks = [
{"shape": [(0, 0), (0, 1), (0, 2), (0, 3)], "color": "#6DB3AC"}, # I (4-square)
{"shape": [(0, 0), (0, 1), (1, 0), (1, 1)], "color": "#F0F000"}, # O
{"shape": [(0, 0), (1, 0), (1, 1), (2, 0)], "color": "#BD4DF5"}, # T
{"shape": [(0, 1), (0, 2), (1, 0), (1, 1)], "color": "#00F000"}, # S
{"shape": [(0, 0), (0, 1), (1, 1), (1, 2)], "color": "#F00000"}, # Z
{"shape": [(0, 0), (1, 0), (2, 0), (2, 1)], "color": "#699EEB"}, # J
{"shape": [(0, 1), (1, 1), (2, 0), (2, 1)], "color": "#F0A000"}, # L
# Custom blocks
{"shape": [(0, 0)], "color": "#F09050"}, # 1-square
{"shape": [(0, 0), (0, 1)], "color": "#6FEA85"}, # 2-square
{"shape": [(0, 0), (0, 1), (0, 2)], "color": "#B34DB3"}, # 3-line
{"shape": [(0, 0), (0, 1), (1, 1)], "color": "#A5B350"}, # L-3
# 5-square blocks
{"shape": [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4)], "color": "#D0603C"}, # 5-line (Dark Turquoise)
{"shape": [(0, 0), (0, 1), (0, 2), (1, 2), (2, 2)], "color": "#C8BA4A"}, # Right-angle (Red)
# 9-square block
{"shape": [
(0, 0), (0, 1), (0, 2),
(1, 0), (1, 1), (1, 2),
(2, 0), (2, 1), (2, 2)
], "color": "#FFD700"} # 3x3 (Gold)
]
variation_blocks()
def game_init_board():
#board grids
for row in range(8):
for col in range(8):
x1 = col * game.cell_size + game.board_x
y1 = row * game.cell_size + game.board_y
x2 = x1+ game.cell_size -1
y2 = y1+ game.cell_size -1
game.canvas.create_rectangle(
x1,y1,x2,y2,
fill=game.cell_color,
outline=game.grid_color,
width=game.grid_border,
)
#board outer border
game.canvas.create_rectangle(
game.board_x-1,
game.board_y-1,
game.board_x+game.board_size,
game.board_y+game.board_size,
#fill=game.cell_color,
outline=game.grid_color,
width=game.grid_border,
)
代码: 全选
######################
#Mouse Click
######################
def click_a_preview_block():
if valid_mouse_click_event():
mouse_click_effect()
return True
return False
def valid_mouse_click_event():
event_type = tk.EventType(game.event.type).name
if event_type != "ButtonPress":
return False
game.preview_clicked_on = -1
for i in range(3):
x1 = game.preview_one_x[i]
x2 = x1+ game.preview_one_size
y1 = game.preview_one_y
y2 = y1+ game.preview_one_size
if x1 <= game.event.x < x2 and y1 <= game.event.y < y2:
game.preview_clicked_on = i
break
if game.preview_clicked_on == -1:
return False
if not game.preview_blocks_present[i]:
return False
if game.preview_blocks_dead[i]:
return False
print("mouse click on ", game.preview_clicked_on)
return True
def mouse_click_effect():
i = game.preview_clicked_on
#enlarge the preview block
#delete the smaller preview block
for rect in game.preview_blocks_rect[i]:
game.canvas.delete(rect)
game.dragging = True
game.fit_in = False
game.dragging_last_x = game.event.x
game.dragging_last_y = game.event.y
#enlarge the preview block
block = game.tetris_blocks[game.preview_blocks[i]]
nx = max(dx for dx,dy in block["shape"]) +1
ny = max(dy for dx,dy in block["shape"]) +1
center_x = game.preview_one_x[i]+game.preview_one_size//2
game.dragging_block_x = center_x - nx * game.cell_size //2
game.dragging_block_y = game.preview_one_y - ny* game.cell_size
#block_nx, block_ny = game.block_nxy()
game.dragging_block_nx = -1
game.dragging_block_ny = -1
game.dragging_block_rect = []
for dx,dy in block["shape"]:
x1 = dx* game.cell_size + game.dragging_block_x
y1 = dy* game.cell_size + game.dragging_block_y
x2 = x1+ game.cell_size -1
y2 = y1+ game.cell_size -1
game.dragging_block_rect.append(
game.canvas.create_rectangle(
x1,y1,x2,y2,
fill=block["color"],
outline=game.game_bg_color,
width=game.grid_border,
))
代码: 全选
######################
#Mouse Move
######################
def drag_a_block():
if valid_dragging_event():
mouse_drag_effect()
return True
return False
def valid_dragging_event():
event_type = tk.EventType(game.event.type).name
if event_type != "Motion":
return False
if not game.dragging:
return False
return True
def block_nxy():
block_x = game.dragging_block_x - game.board_x
block_y = game.dragging_block_y - game.board_y
block_nx = block_x // game.cell_size
block_ny = block_y // game.cell_size
block_rx = block_x % game.cell_size
block_ry = block_y % game.cell_size
if block_rx > game.cell_size //2:
block_nx += 1
if block_ry > game.cell_size //2:
block_ny += 1
return block_nx, block_ny
def mouse_drag_effect():
#move the dragging block
move_x = game.event.x - game.dragging_last_x
move_y = game.event.y - game.dragging_last_y
game.dragging_last_x = game.event.x
game.dragging_last_y = game.event.y
for rect in game.dragging_block_rect:
game.canvas.move(rect, move_x, move_y)
#pre-fit-in block
game.dragging_block_x += move_x
game.dragging_block_y += move_y
block_nx, block_ny = block_nxy()
#skip if no change in fit-in block
if block_nx == game.dragging_block_nx and block_ny == game.dragging_block_ny:
return
#fit-in block moved
#remove previous fit-in block
if game.fit_in:
for rect in game.fit_in_rect:
game.canvas.delete(rect)
#calculate the size of the dragging block
game.dragging_block_nx = block_nx
game.dragging_block_ny = block_ny
block = game.tetris_blocks[game.preview_blocks[game.preview_clicked_on]]
max_x = max(dx for dx,dy in block["shape"])
max_y = max(dy for dx,dy in block["shape"])
#outside of the board --> not fit-in
if (game.dragging_block_nx < 0
or game.dragging_block_nx +max_x >= 8
or game.dragging_block_ny < 0
or game.dragging_block_ny +max_y >= 8):
game.fit_in = False
return
#board occupied --> not fit-in
for dx,dy in block["shape"]:
x = dx+ game.dragging_block_nx
y = dy+ game.dragging_block_ny
if game.board_state[x][y]["occupied"]:
game.fit_in = False
return
#fit in board
game.fit_in = True
game.fit_in_rect = put_board_block(game.preview_clicked_on, "red")
#fit-in block created after the dragging block
#need bring the dragging block up
for rect in game.dragging_block_rect:
game.canvas.lift(rect)
代码: 全选
######################
#Mouse Release
######################
def release_a_block():
if valid_mouse_release_event():
mouse_release_effect()
return True
return False
def valid_mouse_release_event():
event_type = tk.EventType(game.event.type).name
if event_type != "ButtonRelease":
return False
if not game.dragging:
return False
return True
def mouse_release_effect():
#remove the dragging block
for rect in game.dragging_block_rect:
game.canvas.delete(rect)
if not game.fit_in:
go_back_to_preview()
else:
update_board()
update_preview()
game.fit_in = False
game.dragging = False
def go_back_to_preview():
put_preview_block(game.preview_clicked_on)
def update_board():
#put the block in board by removing the temp fit-in block then put the real block
for rect in game.fit_in_rect:
game.canvas.delete(rect)
block_rect = put_board_block(game.preview_clicked_on, game.grid_color)
#update board occupied state
block = game.tetris_blocks[game.preview_blocks[game.preview_clicked_on]]
for n,(dx,dy) in enumerate(block["shape"]):
cell = game.board_state[game.dragging_block_nx+dx][game.dragging_block_ny+dy]
cell["occupied"] = True
cell["rect"] = block_rect[n]
#check row and col that can be cleared
full_cols = [x for x in range(8) if all([cell["occupied"] for cell in game.board_state[x]])]
full_rows = [y for y in range(8) if all([row[y]["occupied"] for row in game.board_state])]
to_be_cleared = set([(x,y) for x in full_cols for y in range(8)]
+ [(x,y) for y in full_rows for x in range(8)])
for x,y in to_be_cleared:
game.canvas.delete(game.board_state[x][y]["rect"])
game.board_state[x][y]["occupied"] = False
def update_preview():
update_preview_blocks()
update_game_over()
def no_preview_blocks():
return not any(game.preview_blocks_present)
def update_preview_blocks():
#the particular preview block is gone
game.preview_blocks_present[game.preview_clicked_on] = False
if no_preview_blocks():
generate_preview_blocks()
for i in range(3):
if game.preview_blocks_present[i]:
block = game.tetris_blocks[game.preview_blocks[i]]["shape"]
if dead_block(block):
if not game.preview_blocks_dead[i]:
for rect in game.preview_blocks_rect[i]:
game.canvas.delete(rect)
put_preview_block(i, "dimgray")
game.preview_blocks_dead[i] = True
else:
if game.preview_blocks_dead[i]:
for rect in game.preview_blocks_rect[i]:
game.canvas.delete(rect)
put_preview_block(i)
game.preview_blocks_dead[i] = False
def dead_block(block):
for x in range(8):
for y in range(8):
if all([x+dx in range(8) and y+dy in range(8)
and not game.board_state[x+dx][y+dy]["occupied"] for dx,dy in block]):
return False
return True
def update_game_over():
preview_blocks = [i for i in range(3) if game.preview_blocks_present[i]]
game.game_over = all([game.preview_blocks_dead[i] for i in preview_blocks])
if game.game_over:
game.game_over = True
game.canvas.create_text(
game.top_section_x+ game.top_section_width //2,
game.top_section_y+ game.top_section_height //2,
text="GAME OVER",
fill="white",
font=("Arial", 16),
anchor="center",
)
代码: 全选
######################
#utility
######################
#this function is reused by fit-in temp block and dropped block
def put_board_block(i, color):
block = game.tetris_blocks[game.preview_blocks[i]]
block_rect = []
for dx,dy in block["shape"]:
x1 = dx+game.dragging_block_nx
x1 = x1* game.cell_size + game.board_x
y1 = dy+game.dragging_block_ny
y1 = y1* game.cell_size + game.board_y
x2 = x1 + game.cell_size -1
y2 = y1 + game.cell_size -1
block_rect.append(
game.canvas.create_rectangle(
x1,y1,x2,y2,
fill=block["color"],
outline=color,
width=game.grid_border
))
return block_rect
def generate_preview_blocks():
nblock = len(game.tetris_blocks)
game.preview_blocks = [random.choice(range(nblock)) for i in range(3)]
game.preview_blocks_present = [True for i in range(3)]
game.preview_blocks_dead = [False for i in range(3)]
game.preview_blocks_rect = [[] for i in range(3)]
for i in range(3):
put_preview_block(i)
def put_preview_block(i, color=None):
block = game.tetris_blocks[game.preview_blocks[i]]
if color is None:
color = block["color"]
nx = max(dx for dx,dy in block["shape"])+1
ny = max(dy for dx,dy in block["shape"])+1
xx = game.preview_one_size - nx*game.preview_cell_size
assert(xx % 2 == 0)
yy = game.preview_one_size - ny*game.preview_cell_size
assert(yy % 2 == 0)
for dx,dy in block["shape"]:
x1 = dx* game.preview_cell_size + game.preview_one_x[i] + xx//2
y1 = dy* game.preview_cell_size + game.preview_one_y + yy//2
x2 = x1+ game.preview_cell_size -1
y2 = y1+ game.preview_cell_size -1
game.preview_blocks_rect[i].append(
game.canvas.create_rectangle(
x1,y1,x2,y2,
fill=color,
outline=game.game_bg_color,
width=game.grid_border,
))
if __name__ == "__main__":
root = tk.Tk()
game = BlockPuzzle()
game_init()
root.mainloop()