Files
Sprout-Farm/Scene/SmallGame/Tetris.gd

334 lines
7.8 KiB
GDScript

extends Panel
# 游戏常量
const BOARD_WIDTH = 10
const BOARD_HEIGHT = 20
const CELL_SIZE = 30
# 方块类型
enum PieceType {
I, O, T, S, Z, J, L
}
# 方块形状定义
const PIECE_SHAPES = {
PieceType.I: [
[[1, 1, 1, 1]],
[[1], [1], [1], [1]]
],
PieceType.O: [
[[1, 1], [1, 1]]
],
PieceType.T: [
[[0, 1, 0], [1, 1, 1]],
[[1, 0], [1, 1], [1, 0]],
[[1, 1, 1], [0, 1, 0]],
[[0, 1], [1, 1], [0, 1]]
],
PieceType.S: [
[[0, 1, 1], [1, 1, 0]],
[[1, 0], [1, 1], [0, 1]]
],
PieceType.Z: [
[[1, 1, 0], [0, 1, 1]],
[[0, 1], [1, 1], [1, 0]]
],
PieceType.J: [
[[1, 0, 0], [1, 1, 1]],
[[1, 1], [1, 0], [1, 0]],
[[1, 1, 1], [0, 0, 1]],
[[0, 1], [0, 1], [1, 1]]
],
PieceType.L: [
[[0, 0, 1], [1, 1, 1]],
[[1, 0], [1, 0], [1, 1]],
[[1, 1, 1], [1, 0, 0]],
[[1, 1], [0, 1], [0, 1]]
]
}
# 方块颜色
const PIECE_COLORS = {
PieceType.I: Color.CYAN,
PieceType.O: Color.YELLOW,
PieceType.T: Color.MAGENTA,
PieceType.S: Color.GREEN,
PieceType.Z: Color.RED,
PieceType.J: Color.BLUE,
PieceType.L: Color.ORANGE
}
# 游戏变量
var board = []
var current_piece = null
var current_piece_pos = Vector2()
var current_piece_rotation = 0
var next_piece_type = PieceType.I
var score = 0
var level = 1
var lines_cleared = 0
var game_over = false
var drop_time = 1.0
# 节点引用
@onready var game_area = $GameArea
@onready var next_piece_area = $NextPieceArea
@onready var score_label = $ScoreLabel
@onready var level_label = $LevelLabel
@onready var lines_label = $LinesLabel
@onready var game_over_label = $GameOverLabel
@onready var drop_timer = $DropTimer
func _ready():
# 连接定时器信号
drop_timer.timeout.connect(_on_drop_timer_timeout)
# 设置游戏区域样式
game_area.modulate = Color(0.1, 0.1, 0.1, 1.0)
next_piece_area.modulate = Color(0.2, 0.2, 0.2, 1.0)
# 初始化游戏
init_game()
func init_game():
# 重置游戏状态
game_over = false
score = 0
level = 1
lines_cleared = 0
drop_time = 1.0
# 初始化游戏板
board.clear()
for y in range(BOARD_HEIGHT):
var row = []
for x in range(BOARD_WIDTH):
row.append(0)
board.append(row)
# 生成第一个方块
next_piece_type = randi() % PieceType.size()
spawn_new_piece()
# 更新UI
update_ui()
game_over_label.visible = false
# 启动定时器
drop_timer.wait_time = drop_time
drop_timer.start()
func _input(event):
if event is InputEventKey and event.pressed:
if game_over:
if event.keycode == KEY_SPACE:
init_game()
return
if not current_piece:
return
# 控制方块
match event.keycode:
KEY_A:
move_piece(-1, 0)
KEY_D:
move_piece(1, 0)
KEY_S:
move_piece(0, 1)
KEY_W:
rotate_piece()
KEY_SPACE:
drop_piece()
func spawn_new_piece():
current_piece = {
"type": next_piece_type,
"rotation": 0
}
current_piece_pos = Vector2(BOARD_WIDTH / 2 - 1, 0)
current_piece_rotation = 0
# 生成下一个方块
next_piece_type = randi() % PieceType.size()
# 检查游戏是否结束
if not can_place_piece(current_piece_pos, current_piece_rotation):
game_over = true
show_game_over()
func move_piece(dx: int, dy: int):
var new_pos = current_piece_pos + Vector2(dx, dy)
if can_place_piece(new_pos, current_piece_rotation):
current_piece_pos = new_pos
queue_redraw()
func rotate_piece():
var new_rotation = (current_piece_rotation + 1) % get_piece_rotations(current_piece.type)
if can_place_piece(current_piece_pos, new_rotation):
current_piece_rotation = new_rotation
queue_redraw()
func drop_piece():
while can_place_piece(current_piece_pos + Vector2(0, 1), current_piece_rotation):
current_piece_pos.y += 1
place_piece()
func can_place_piece(pos: Vector2, rotation: int) -> bool:
var shape = get_piece_shape(current_piece.type, rotation)
for y in range(shape.size()):
for x in range(shape[y].size()):
if shape[y][x] == 1:
var board_x = pos.x + x
var board_y = pos.y + y
# 检查边界
if board_x < 0 or board_x >= BOARD_WIDTH or board_y >= BOARD_HEIGHT:
return false
# 检查碰撞
if board_y >= 0 and board[board_y][board_x] != 0:
return false
return true
func place_piece():
var shape = get_piece_shape(current_piece.type, current_piece_rotation)
for y in range(shape.size()):
for x in range(shape[y].size()):
if shape[y][x] == 1:
var board_x = current_piece_pos.x + x
var board_y = current_piece_pos.y + y
if board_y >= 0:
board[board_y][board_x] = current_piece.type + 1
# 检查并清除完整的行
clear_lines()
# 生成新方块
spawn_new_piece()
queue_redraw()
func clear_lines():
var lines_to_clear = []
# 找到完整的行
for y in range(BOARD_HEIGHT):
var is_full = true
for x in range(BOARD_WIDTH):
if board[y][x] == 0:
is_full = false
break
if is_full:
lines_to_clear.append(y)
# 清除行并下移
for line_y in lines_to_clear:
board.erase(board[line_y])
var new_row = []
for x in range(BOARD_WIDTH):
new_row.append(0)
board.insert(0, new_row)
# 更新分数和等级
if lines_to_clear.size() > 0:
lines_cleared += lines_to_clear.size()
score += lines_to_clear.size() * 100 * level
# 每10行提升一个等级
level = lines_cleared / 10 + 1
drop_time = max(0.1, 1.0 - (level - 1) * 0.1)
drop_timer.wait_time = drop_time
update_ui()
func get_piece_shape(type: PieceType, rotation: int) -> Array:
return PIECE_SHAPES[type][rotation]
func get_piece_rotations(type: PieceType) -> int:
return PIECE_SHAPES[type].size()
func update_ui():
score_label.text = "分数: " + str(score)
level_label.text = "等级: " + str(level)
lines_label.text = "消除行数: " + str(lines_cleared)
func show_game_over():
drop_timer.stop()
game_over_label.visible = true
func _on_drop_timer_timeout():
if game_over or not current_piece:
return
if can_place_piece(current_piece_pos + Vector2(0, 1), current_piece_rotation):
current_piece_pos.y += 1
queue_redraw()
else:
place_piece()
func _draw():
if not game_area:
return
# 获取游戏区域的位置
var area_pos = game_area.position
# 绘制游戏板
for y in range(BOARD_HEIGHT):
for x in range(BOARD_WIDTH):
var cell_value = board[y][x]
if cell_value > 0:
var rect = Rect2(
area_pos.x + x * CELL_SIZE,
area_pos.y + y * CELL_SIZE,
CELL_SIZE - 1,
CELL_SIZE - 1
)
var color = PIECE_COLORS[cell_value - 1]
draw_rect(rect, color)
# 绘制当前方块
if current_piece:
var shape = get_piece_shape(current_piece.type, current_piece_rotation)
for y in range(shape.size()):
for x in range(shape[y].size()):
if shape[y][x] == 1:
var board_x = current_piece_pos.x + x
var board_y = current_piece_pos.y + y
if board_y >= 0:
var rect = Rect2(
area_pos.x + board_x * CELL_SIZE,
area_pos.y + board_y * CELL_SIZE,
CELL_SIZE - 1,
CELL_SIZE - 1
)
var color = PIECE_COLORS[current_piece.type]
draw_rect(rect, color)
# 绘制网格线
for x in range(BOARD_WIDTH + 1):
var start_pos = Vector2(area_pos.x + x * CELL_SIZE, area_pos.y)
var end_pos = Vector2(area_pos.x + x * CELL_SIZE, area_pos.y + BOARD_HEIGHT * CELL_SIZE)
draw_line(start_pos, end_pos, Color.GRAY, 1)
for y in range(BOARD_HEIGHT + 1):
var start_pos = Vector2(area_pos.x, area_pos.y + y * CELL_SIZE)
var end_pos = Vector2(area_pos.x + BOARD_WIDTH * CELL_SIZE, area_pos.y + y * CELL_SIZE)
draw_line(start_pos, end_pos, Color.GRAY, 1)
# 绘制下一个方块预览
var next_area_pos = next_piece_area.position
var next_shape = get_piece_shape(next_piece_type, 0)
for y in range(next_shape.size()):
for x in range(next_shape[y].size()):
if next_shape[y][x] == 1:
var rect = Rect2(
next_area_pos.x + 20 + x * 20,
next_area_pos.y + 50 + y * 20,
19,
19
)
var color = PIECE_COLORS[next_piece_type]
draw_rect(rect, color)