Merge pull request 'Added tetris-game. /JL' (#4) from 0.0.3 into main

Reviewed-on: #4
This commit was merged in pull request #4.
This commit is contained in:
2025-12-06 16:15:05 +01:00
21 changed files with 776 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
root = true
[*]
charset = utf-8

2
tetris-game/.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

3
tetris-game/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
# Godot 4+ specific ignores
.godot/
/android/

1
tetris-game/icon.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>

After

Width:  |  Height:  |  Size: 994 B

View File

@@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dck747bvw17v0"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

44
tetris-game/project.godot Normal file
View File

@@ -0,0 +1,44 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="Tetris Game"
run/main_scene="uid://ckvfgxn4jyoi6"
config/features=PackedStringArray("4.5", "Forward Plus")
config/icon="res://icon.svg"
[input]
move_left={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194319,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
move_right={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194321,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
soft_drop={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194322,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
rotate={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194320,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}

View File

@@ -0,0 +1,7 @@
[gd_scene load_steps=2 format=3 uid="uid://d0rk850ey8e0g"]
[ext_resource type="Script" uid="uid://yc60e2spsmjh" path="res://scripts/board.gd" id="1_p0ybc"]
[node name="Board" type="Node2D"]
position = Vector2(100, 0)
script = ExtResource("1_p0ybc")

View File

@@ -0,0 +1,69 @@
[gd_scene load_steps=2 format=3 uid="uid://dkspo8vx4qcvb"]
[ext_resource type="Script" uid="uid://dq8ifo7mfpflr" path="res://scripts/game_over.gd" id="1_xeevv"]
[node name="GameOver" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
scale = Vector2(2, 2)
size_flags_horizontal = 4
size_flags_vertical = 4
script = ExtResource("1_xeevv")
[node name="CenterContainer" type="CenterContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Panel" type="Panel" parent="CenterContainer"]
layout_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer/Panel"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -67.0
offset_top = -69.5
offset_right = 67.0
offset_bottom = 69.5
grow_horizontal = 2
grow_vertical = 2
[node name="LabelTitle" type="Label" parent="CenterContainer/Panel/VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 4
text = "GAME OVER"
[node name="LabelScore" type="Label" parent="CenterContainer/Panel/VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 4
[node name="LabelHighScore" type="Label" parent="CenterContainer/Panel/VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 4
[node name="LabelLines" type="Label" parent="CenterContainer/Panel/VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 4
[node name="HBoxContainer" type="HBoxContainer" parent="CenterContainer/Panel/VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 4
[node name="ButtonAgain" type="Button" parent="CenterContainer/Panel/VBoxContainer/HBoxContainer"]
layout_mode = 2
text = "Play Again"
[node name="ButtonQuit" type="Button" parent="CenterContainer/Panel/VBoxContainer/HBoxContainer"]
layout_mode = 2
text = "Quit"

View File

@@ -0,0 +1,40 @@
[gd_scene format=3 uid="uid://ds6jmtmpkvtxp"]
[node name="HUDLeft" type="Control"]
layout_mode = 3
anchors_preset = 4
anchor_top = 0.5
anchor_bottom = 0.5
offset_top = -20.0
offset_right = 40.0
offset_bottom = 20.0
grow_vertical = 2
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 0
offset_right = 40.0
offset_bottom = 40.0
[node name="LabelScoreTitle" type="Label" parent="VBoxContainer"]
layout_mode = 2
text = "Score"
[node name="LabelScoreValue" type="Label" parent="VBoxContainer"]
layout_mode = 2
text = "0"
[node name="LabelHighScoreTitle" type="Label" parent="VBoxContainer"]
layout_mode = 2
text = "Highscore"
[node name="LabelHighScoreValue" type="Label" parent="VBoxContainer"]
layout_mode = 2
text = "0"
[node name="LabelLinesTitle" type="Label" parent="VBoxContainer"]
layout_mode = 2
text = "Lines"
[node name="LabelLinesValue" type="Label" parent="VBoxContainer"]
layout_mode = 2
text = "0"

View File

@@ -0,0 +1,22 @@
[gd_scene load_steps=2 format=3 uid="uid://dsc4ofx4h3gos"]
[ext_resource type="Script" uid="uid://d0wlixgjpcnyc" path="res://scripts/next_preview.gd" id="1_hyjpj"]
[node name="HUDRight" type="Control"]
layout_mode = 3
anchors_preset = 0
offset_left = 350.0
offset_right = 390.0
offset_bottom = 40.0
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 0
offset_right = 40.0
offset_bottom = 40.0
[node name="LabelNextTitle" type="Label" parent="VBoxContainer"]
layout_mode = 2
text = "Next"
[node name="NextPreview" type="Node2D" parent="VBoxContainer"]
script = ExtResource("1_hyjpj")

View File

@@ -0,0 +1,51 @@
[gd_scene load_steps=7 format=3 uid="uid://ckvfgxn4jyoi6"]
[ext_resource type="Script" uid="uid://ctadm2gu04xgn" path="res://scripts/main.gd" id="1_tbgi4"]
[ext_resource type="PackedScene" uid="uid://d0rk850ey8e0g" path="res://scenes/board.tscn" id="2_tefeu"]
[ext_resource type="PackedScene" uid="uid://ds6jmtmpkvtxp" path="res://scenes/hud_left.tscn" id="3_o6xl0"]
[ext_resource type="PackedScene" uid="uid://dsc4ofx4h3gos" path="res://scenes/hud_right.tscn" id="4_tipki"]
[ext_resource type="Shader" uid="uid://ct7v5mfajkvir" path="res://shaders/blur_overlay.gdshader" id="5_85g3d"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_lquwl"]
shader = ExtResource("5_85g3d")
shader_parameter/blur_radius = 8.00000038
shader_parameter/samples = 7
shader_parameter/blur_strength = 0.70000003325
shader_parameter/dim_amount = 0.150000007125
[node name="Main" type="Node2D"]
script = ExtResource("1_tbgi4")
[node name="GameRoot" type="Node2D" parent="."]
[node name="Board" parent="GameRoot" instance=ExtResource("2_tefeu")]
[node name="HUDLeft" parent="GameRoot" instance=ExtResource("3_o6xl0")]
offset_top = 150.0
offset_bottom = 190.0
[node name="HUDRight" parent="GameRoot" instance=ExtResource("4_tipki")]
offset_top = 24.0
offset_bottom = 64.0
[node name="UILayer" type="CanvasLayer" parent="GameRoot"]
[node name="UIRoot" type="Control" parent="GameRoot/UILayer"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
metadata/_edit_use_anchors_ = true
[node name="BlurOverlay" type="ColorRect" parent="GameRoot/UILayer/UIRoot"]
visible = false
material = SubResource("ShaderMaterial_lquwl")
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2

View File

@@ -0,0 +1,276 @@
extends Node2D
const BOARD_WIDTH := 10
const BOARD_HEIGHT := 20
const CELL_SIZE := 24 # pixels
# Types of pieces
# Each shape is a list of Vector2i, relative to a pivot (0,0)
const SHAPES := {
"I": [Vector2i(-1,0), Vector2i(0,0), Vector2i(1,0), Vector2i(2,0)],
"O": [Vector2i(0,0), Vector2i(1,0), Vector2i(0,1), Vector2i(1,1)],
"T": [Vector2i(-1,0), Vector2i(0,0), Vector2i(1,0), Vector2i(0,1)],
"L": [Vector2i(-1,0), Vector2i(0,0), Vector2i(1,0), Vector2i(1,1)],
"J": [Vector2i(-1,1), Vector2i(-1,0), Vector2i(0,0), Vector2i(1,0)],
"S": [Vector2i(0,0), Vector2i(1,0), Vector2i(-1,1), Vector2i(0,1)]
}
# Link shape keys to colors
const COLORS := {
"I": Color(0.0, 1.0, 1.0),
"O": Color(1.0, 1.0, 0.0),
"T": Color(0.6, 0.0, 0.8),
"L": Color(1.0, 0.5, 0.0),
"J": Color(0.0, 0.0, 1.0),
"S": Color(0.0, 0.5, 0.0)
}
# 2D grid: 0 = empty, else a String shape key
var grid: Array = []
# Current falling piece
var current_shape_key: String
var current_blocks: Array[Vector2i] = []
var current_pos: Vector2i
var current_color: Color
# Next piece
var next_shape_key: String = ""
var rng := RandomNumberGenerator.new()
# Drop timing
var drop_interval := 0.7
var drop_timer := 0.0
var is_game_over := false
# Signals to update HUD and preview
signal lines_cleared(total_lines: int, score: int, highscore: int)
signal next_piece_changed(shape_key: String, shapes: Dictionary, colors: Dictionary)
signal game_over(score: int, highscore: int, total_lines: int)
var score := 0
var highscore := 0
var total_lines_cleared := 0
func reset_game() -> void:
score = 0
total_lines_cleared = 0
drop_timer = 0.0
is_game_over = false
_init_grid()
_spawn_initial_pieces()
# Update HUD to show reset stats
emit_signal("lines_cleared", total_lines_cleared, score, highscore)
queue_redraw()
func _ready() -> void:
rng.randomize()
_load_highscore()
reset_game()
func _process(delta: float) -> void:
if is_game_over:
return
drop_timer += delta
if drop_timer >= drop_interval:
drop_timer = 0.0
_try_move(Vector2i(0, 1), true)
_handle_input()
queue_redraw()
func _init_grid() -> void:
grid.resize(BOARD_HEIGHT)
for y in range(BOARD_HEIGHT):
grid[y] = []
grid[y].resize(BOARD_WIDTH)
for x in range(BOARD_WIDTH):
grid[y][x] = "" # empty string = no blocks
func _spawn_initial_pieces() -> void:
next_shape_key = _random_shape()
_spawn_new_piece()
func _random_shape() -> String:
var keys := SHAPES.keys()
return keys[rng.randi() % keys.size()]
func _spawn_new_piece() -> void:
# Move "next" to "current"
current_shape_key = next_shape_key
# Get the raw shape array (untyped)
var shape_array: Array = SHAPES[current_shape_key]
# Fill our typed Array[Vector2i]
current_blocks.clear()
for p in shape_array:
current_blocks.append(p as Vector2i)
# Color for this piece
current_color = Color.WHITE
if COLORS.has(current_shape_key):
current_color = COLORS[current_shape_key] as Color
# Spawn near top center
current_pos = Vector2i(BOARD_WIDTH / 2, 1)
# Choose the next upcoming piece
next_shape_key = _random_shape()
emit_signal("next_piece_changed", next_shape_key, SHAPES, COLORS)
# Check for immediate collision → game over
if _is_colliding(current_pos, current_blocks):
_game_over()
func _handle_input() -> void:
if is_game_over:
return
if Input.is_action_just_pressed("move_left"):
_try_move(Vector2i(-1, 0))
if Input.is_action_just_pressed("move_right"):
_try_move(Vector2i(1, 0))
if Input.is_action_pressed("soft_drop"):
_try_move(Vector2i(0, 1))
if Input.is_action_just_pressed("rotate"):
_try_rotate()
func _try_move(offset: Vector2i, lock_on_fail: bool = false) -> void:
var new_pos := current_pos + offset
if not _is_colliding(new_pos, current_blocks):
current_pos = new_pos
elif lock_on_fail and offset.y > 0:
_lock_piece()
func _try_rotate() -> void:
var rotated: Array[Vector2i] = []
for b in current_blocks:
# (x, y) → (-y, x)
rotated.append(Vector2i(-b.y, b.x))
if not _is_colliding(current_pos, rotated):
current_blocks = rotated
func _is_colliding(pos: Vector2i, blocks: Array[Vector2i]) -> bool:
for b in blocks:
var x := pos.x + b.x
var y := pos.y + b.y
if x < 0 or x >= BOARD_WIDTH or y < 0 or y >= BOARD_HEIGHT:
return true
if grid[y][x] != "":
return true
return false
func _lock_piece() -> void:
# Transfer current piece into grid
var points: int
for b in current_blocks:
var x := current_pos.x + b.x
var y := current_pos.y + b.y
if y >= 0 and y < BOARD_HEIGHT and x >= 0 and x < BOARD_WIDTH:
grid[y][x] = current_shape_key
var cleared := _clear_full_lines()
# scoring (simple rules)
if cleared > 0:
total_lines_cleared += cleared
match cleared:
1:
points = 100
2:
points = 300
3:
points = 500
4:
points = 800
_:
points = cleared * 200
score += points
if score > highscore:
highscore = score
_save_highscore()
emit_signal("lines_cleared", total_lines_cleared, score, highscore)
_spawn_new_piece()
func _clear_full_lines() -> int:
var cleared_lines := 0
for y in range(BOARD_HEIGHT - 1, -1, -1):
var is_full := true
for x in range(BOARD_WIDTH):
if grid[y][x] == "":
is_full = false
break
if is_full:
_drop_lines_above(y)
cleared_lines += 1
y += 1 # re-check same row index (now replaced)
return cleared_lines
func _drop_lines_above(row: int) -> void:
for y in range(row, 0, -1):
for x in range(BOARD_WIDTH):
grid[y][x] = grid[y - 1][x]
# top row becomes empty
for x in range(BOARD_WIDTH):
grid[0][x] = ""
func _draw() -> void:
# Draw background grid (optional)
for y in range(BOARD_HEIGHT):
for x in range(BOARD_WIDTH):
var rect := Rect2(
Vector2(x * CELL_SIZE, y * CELL_SIZE),
Vector2(CELL_SIZE, CELL_SIZE)
)
draw_rect(rect, Color(0.05, 0.05, 0.05), false, 1.0)
# Draw placed blocks
for y in range(BOARD_HEIGHT):
for x in range(BOARD_WIDTH):
var key: String = grid[y][x]
if key != "":
var color: Color = Color.WHITE
if COLORS.has(key):
color = COLORS[key] as Color
_draw_cell(x, y, color)
# Draw current falling piece
for b in current_blocks:
var x := current_pos.x + b.x
var y := current_pos.y + b.y
_draw_cell(x, y, current_color)
func _draw_cell(x: int, y: int, color: Color) -> void:
var pos := Vector2(x * CELL_SIZE, y * CELL_SIZE)
var rect := Rect2(pos, Vector2(CELL_SIZE, CELL_SIZE))
draw_rect(rect.grow(-1), color, true) # small border
func _game_over() -> void:
is_game_over = true
# Make sure HUD shows final values
emit_signal("lines_cleared", total_lines_cleared, score, highscore)
# Inform main scene that the game ended
emit_signal("game_over", score, highscore, total_lines_cleared)
func _save_highscore() -> void:
var cfg := ConfigFile.new()
cfg.set_value("stats", "highscore", highscore)
cfg.save("user://tetris.cfg")
func _load_highscore() -> void:
var cfg := ConfigFile.new()
var err := cfg.load("user://tetris.cfg")
if err == OK:
highscore = int(cfg.get_value("stats", "highscore", 0))
else:
highscore = 0

View File

@@ -0,0 +1 @@
uid://yc60e2spsmjh

View File

@@ -0,0 +1,34 @@
extends Control
@export var final_score: int = 0
@export var final_highscore: int = 0
@export var total_lines: int = 0
@onready var score_label: Label = $CenterContainer/Panel/VBoxContainer/LabelScore
@onready var highscore_label: Label = $CenterContainer/Panel/VBoxContainer/LabelHighScore
@onready var lines_label: Label = $CenterContainer/Panel/VBoxContainer/LabelLines
@onready var retry_button: Button = $CenterContainer/Panel/VBoxContainer/HBoxContainer/ButtonAgain
@onready var quit_button: Button = $CenterContainer/Panel/VBoxContainer/HBoxContainer/ButtonQuit
func _ready() -> void:
_update_labels()
retry_button.pressed.connect(_on_retry_pressed)
quit_button.pressed.connect(_on_quit_pressed)
func _update_labels() -> void:
if score_label:
score_label.text = "Score: %d" % final_score
if highscore_label:
highscore_label.text = "Highscore: %d" % final_highscore
if lines_label:
lines_label.text = "Lines: %d" % total_lines
func _on_retry_pressed() -> void:
var main := get_tree().current_scene
if main and main.has_method("reset_game"):
main.reset_game()
queue_free() # remove overlay
func _on_quit_pressed() -> void:
get_tree().quit()

View File

@@ -0,0 +1 @@
uid://dq8ifo7mfpflr

View File

@@ -0,0 +1,83 @@
extends Node2D
const GAME_OVER_SCENE := preload("res://scenes/game_over.tscn")
@onready var game_root: Node2D = $GameRoot
@onready var board: Node2D = $GameRoot/Board
@onready var hud_left: Control = $GameRoot/HUDLeft
@onready var hud_right: Control = $GameRoot/HUDRight
@onready var ui_root: Control = $GameRoot/UILayer/UIRoot
@onready var blur_overlay: ColorRect = $GameRoot/UILayer/UIRoot/BlurOverlay # New node
@onready var label_score_value: Label = $GameRoot/HUDLeft/VBoxContainer/LabelScoreValue
@onready var label_highscore_value: Label = $GameRoot/HUDLeft/VBoxContainer/LabelHighScoreValue
@onready var label_lines_value: Label = $GameRoot/HUDLeft/VBoxContainer/LabelLinesValue
@onready var next_preview: Node2D = $GameRoot/HUDRight/VBoxContainer/NextPreview
func _ready() -> void:
board.connect("lines_cleared", Callable(self, "_on_lines_cleared"))
board.connect("next_piece_changed", Callable(self, "_on_next_piece_changed"))
board.connect("game_over", Callable(self, "_on_game_over"))
# Center once and whenever the window size changes
get_viewport().size_changed.connect(_on_viewport_size_changed)
_on_viewport_size_changed()
func _on_viewport_size_changed() -> void:
var window_size: Vector2 = get_viewport_rect().size
var layout_bounds := _get_layout_bounds()
var layout_center := layout_bounds.position + layout_bounds.size * 0.5
var target_center := window_size * 0.5
# Move the whole game so the *layout* center matches the window center
game_root.position = target_center - layout_center
func _on_lines_cleared(total_lines: int, score: int, highscore: int) -> void:
label_score_value.text = str(score)
label_highscore_value.text = str(highscore)
label_lines_value.text = str(total_lines)
func _get_layout_bounds() -> Rect2:
# Board rectangle (we know its size from constants)
var board_size := Vector2(
board.BOARD_WIDTH * board.CELL_SIZE,
board.BOARD_HEIGHT * board.CELL_SIZE
)
var bounds := Rect2(board.position, board_size)
# Merge HUDLeft and HUDRight rectangles into the bounds
if hud_left:
bounds = bounds.merge(hud_left.get_rect())
if hud_right:
bounds = bounds.merge(hud_right.get_rect())
return bounds
func _on_next_piece_changed(shape_key: String, shapes: Dictionary, colors: Dictionary) -> void:
next_preview.update_preview(shape_key, shapes, colors)
func _on_game_over(score: int, highscore: int, total_lines: int) -> void:
# 1) Turn on blur behind the overlay
if blur_overlay:
blur_overlay.visible = true
# 2) Show the GameOver UI on top
var game_over_layer := GAME_OVER_SCENE.instantiate()
game_over_layer.set_anchors_preset(Control.PRESET_CENTER) # New
game_over_layer.scale = Vector2(2, 2) # New
game_over_layer.final_score = score
game_over_layer.final_highscore = highscore
game_over_layer.total_lines = total_lines
ui_root.add_child(game_over_layer)
game_over_layer.set_anchors_preset(Control.PRESET_FULL_RECT)
func reset_game() -> void:
# Hide blur again
if blur_overlay:
blur_overlay.visible = false
if board.has_method("reset_game"):
board.reset_game()

View File

@@ -0,0 +1 @@
uid://ctadm2gu04xgn

View File

@@ -0,0 +1,59 @@
extends Node2D
const CELL_SIZE := 16
var shape_key: String = ""
var shapes: Dictionary = {}
var colors: Dictionary = {}
func update_preview(new_shape_key: String, new_shapes: Dictionary, new_colors: Dictionary) -> void:
shape_key = new_shape_key
shapes = new_shapes
colors = new_colors
queue_redraw()
func _draw() -> void:
# Debug frame so we can see that the node exists
var debug_rect := Rect2(Vector2.ZERO, Vector2(80, 80))
draw_rect(debug_rect, Color(0.2, 0.2, 0.2, 1.0), false, 2.0)
if shape_key == "" or not shapes.has(shape_key):
return
var blocks: Array = shapes[shape_key]
# Find bounding box of the shape
var min_x := 999
var max_x := -999
var min_y := 999
var max_y := -999
for b in blocks:
var v: Vector2i = b
if v.x < min_x:
min_x = v.x
if v.x > max_x:
max_x = v.x
if v.y < min_y:
min_y = v.y
if v.y > max_y:
max_y = v.y
var width := (max_x - min_x + 1) * CELL_SIZE
var height := (max_y - min_y + 1) * CELL_SIZE
# Center the shape in our local 80x80 area
var total_size := Vector2(width, height)
var preview_center := Vector2(40, 40) # middle of debug_rect
var offset := preview_center - total_size * 0.5
var color: Color = Color.WHITE
if colors.has(shape_key):
color = colors[shape_key] as Color
for b in blocks:
var v: Vector2i = b
var x := (v.x - min_x) * CELL_SIZE
var y := (v.y - min_y) * CELL_SIZE
var rect := Rect2(offset + Vector2(x, y), Vector2(CELL_SIZE, CELL_SIZE))
draw_rect(rect.grow(-1), color, true)

View File

@@ -0,0 +1 @@
uid://d0wlixgjpcnyc

View File

@@ -0,0 +1,39 @@
shader_type canvas_item;
uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, filter_linear_mipmap;
// Radius in pixels
uniform float blur_radius : hint_range(0.0, 32.0) = 8.0;
// Number of samples in each direction
uniform int samples : hint_range(1, 20) = 6;
// 0 = no blur, 1 = fully blurred
uniform float blur_strength : hint_range(0.0, 1.0) = 0.6;
// Optional slight dimming of background
uniform float dim_amount : hint_range(0.0, 0.5) = 0.1;
void fragment() {
vec4 original = textureLod(SCREEN_TEXTURE, SCREEN_UV, 0.0);
vec2 pixel = SCREEN_PIXEL_SIZE * blur_radius;
vec4 blurred = vec4(0.0);
int count = 0;
for (int x = -samples; x <= samples; x++) {
for (int y = -samples; y <= samples; y++) {
vec2 offset = vec2(float(x), float(y)) * pixel;
blurred += textureLod(SCREEN_TEXTURE, SCREEN_UV + offset, 0.0);
count++;
}
}
blurred /= float(count);
// Blend between original and blurred
vec4 color = mix(original, blurred, blur_strength);
// Slight dimming, if wanted
color.rgb *= (1.0 - dim_amount);
COLOR = color;
}

View File

@@ -0,0 +1 @@
uid://ct7v5mfajkvir