extends Node2D

============================================================

🎮 نبض المدينة — Pulse City

لعبة إيقاع سريعة: اضغط المسار المناسب لما النبضة توصل

============================================================

====== الإعدادات ======

const LANE_X := [200.0, 540.0, 880.0] const LANE_Y := 1400.0 const SPAWN_Y := -100.0 const HIT_WINDOW := 110.0 const GAME_DURATION := 90.0

====== الحالة ======

var score := 0 var combo := 0 var max_combo := 0 var battery := 100.0 var time_left := GAME_DURATION var active_notes: Array = [] var lane_flash := [0.0, 0.0, 0.0] var shield_active := false var double_active := false var game_over := false var note_speed := 550.0 var spawn_rate := 4.0

====== عقد الواجهة ======

var score_label: Label var combo_label: Label var battery_bar: ProgressBar var time_label: Label var city_glow: ColorRect var decision_panel: Panel var lanes_visual: Array = [] var notes_layer: Node2D var end_label: Label

============================================================

البناء

============================================================

func _ready(): _build_scene() _update_ui()

func _build_scene(): # --- خلفية --- var bg := ColorRect.new() bg.color = Color(0.03, 0.03, 0.08) bg.size = Vector2(1080, 1920) bg.z_index = -10 add_child(bg)

# --- توهج المدينة ---
city_glow = ColorRect.new()
city_glow.color = Color(0.1, 0.6, 1.0, 0.0)
city_glow.size = Vector2(1080, 1920)
city_glow.z_index = -5
add_child(city_glow)

# --- المسارات الثلاثة ---
for i in range(3):
    var circle := ColorRect.new()
    circle.size = Vector2(200, 200)
    circle.position = Vector2(LANE_X[i] - 100, LANE_Y - 100)
    circle.color = Color(0.2, 0.8, 1.0, 0.25)
    add_child(circle)
    lanes_visual.append(circle)

# --- حاوية النبضات ---
notes_layer = Node2D.new()
notes_layer.name = "Notes"
add_child(notes_layer)

# --- الواجهة ---
var ui := CanvasLayer.new()
add_child(ui)

score_label = Label.new()
score_label.position = Vector2(40, 40)
score_label.add_theme_font_size_override("font_size", 52)
score_label.add_theme_color_override("font_color", Color(0.4, 0.9, 1.0))
score_label.text = "Score: 0"
ui.add_child(score_label)

combo_label = Label.new()
combo_label.position = Vector2(40, 110)
combo_label.add_theme_font_size_override("font_size", 42)
combo_label.add_theme_color_override("font_color", Color(1, 0.8, 0.2))
combo_label.text = ""
ui.add_child(combo_label)

time_label = Label.new()
time_label.position = Vector2(880, 40)
time_label.add_theme_font_size_override("font_size", 42)
time_label.add_theme_color_override("font_color", Color(1, 1, 1))
time_label.text = "90s"
ui.add_child(time_label)

battery_bar = ProgressBar.new()
battery_bar.position = Vector2(40, 1800)
battery_bar.size = Vector2(1000, 50)
battery_bar.max_value = 100
battery_bar.value = 100
ui.add_child(battery_bar)

# --- لوحة القرار ---
decision_panel = Panel.new()
decision_panel.position = Vector2(140, 700)
decision_panel.size = Vector2(800, 500)
decision_panel.visible = false
ui.add_child(decision_panel)

var title := Label.new()
title.text = "⚡ اختر!"
title.position = Vector2(0, 5)
title.size = Vector2(800, 50)
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
title.add_theme_font_size_override("font_size", 36)
decision_panel.add_child(title)

var labels := ["⚡ سرعة (x2 نقاط)", "🛡️ حماية", "💥 انفجار"]
for i in range(3):
    var b := Button.new()
    b.text = labels[i]
    b.position = Vector2(50, 70 + i * 135)
    b.size = Vector2(700, 110)
    b.add_theme_font_size_override("font_size", 40)
    b.pressed.connect(_on_decision.bind(i))
    decision_panel.add_child(b)

# --- شاشة النهاية (مخفية) ---
end_label = Label.new()
end_label.visible = false
end_label.add_theme_font_size_override("font_size", 64)
end_label.add_theme_color_override("font_color", Color(1, 0.9, 0.3))
end_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
end_label.position = Vector2(140, 700)
end_label.size = Vector2(800, 600)
ui.add_child(end_label)

============================================================

الحلقة الرئيسية

============================================================

func _process(delta): if game_over: return

# المؤقت
time_left -= delta
time_label.text = str(int(max(0, time_left))) + "s"
if time_left <= 0:
    _end_game()
    return

# صعوبة تدريجية
var elapsed = GAME_DURATION - time_left
note_speed = 550.0 + elapsed * 6.0
spawn_rate = 4.0 + elapsed * 0.05

# إنتاج نبضات
if randf() < delta * spawn_rate:
    _spawn_note()

# تحريك النبضات
var to_remove := []
for i in range(active_notes.size()):
    var n = active_notes[i]
    n.node.position.y += note_speed * delta
    if n.node.position.y > LANE_Y + HIT_WINDOW:
        to_remove.append(i)
        _on_miss()
for i in range(to_remove.size() - 1, -1, -1):
    _remove_note(to_remove[i])

# تلاشي المسارات
for i in range(3):
    lane_flash[i] = max(0.0, lane_flash[i] - delta * 4.0)
    lanes_visual[i].color = Color(0.2, 0.8, 1.0, 0.25 + lane_flash[i] * 0.6)

# تلاشي التوهج
city_glow.color.a = max(0.0, city_glow.color.a - delta * 1.5)

_update_ui()

============================================================

النبضات

============================================================

func _spawn_note(): var lane := randi() % 3 var rect := ColorRect.new() rect.size = Vector2(160, 32) rect.position = Vector2(LANE_X[lane] - 80, SPAWN_Y) rect.color = Color(1.0, 0.3, 0.6) if lane != 1 else Color(0.3, 1.0, 0.6) notes_layer.add_child(rect) active_notes.append({"node": rect, "lane": lane})

func _remove_note(idx): if idx < 0 or idx >= active_notes.size(): return var n = active_notes[idx] if is_instance_valid(n.node): n.node.queue_free() active_notes.remove_at(idx)

============================================================

المدخلات

============================================================

func _input(event): if game_over or decision_panel.visible: return if event is InputEventKey and event.pressed and not event.echo: var lane := -1 if event.keycode == KEY_A or event.keycode == KEY_LEFT: lane = 0 elif event.keycode == KEY_S or event.keycode == KEY_DOWN: lane = 1 elif event.keycode == KEY_D or event.keycode == KEY_RIGHT: lane = 2 if lane != -1: _try_hit(lane) get_viewport().set_input_as_handled()

func _try_hit(lane): lane_flash[lane] = 1.0

var best_idx := -1
var best_dist := 99999.0
for i in range(active_notes.size()):
    var n = active_notes[i]
    if n.lane != lane:
        continue
    var dist = abs(n.node.position.y - LANE_Y)
    if dist < best_dist:
        best_dist = dist
        best_idx = i

if best_idx != -1 and best_dist <= HIT_WINDOW:
    _on_hit()
    _remove_note(best_idx)
else:
    _on_miss()

============================================================

الأحداث

============================================================

func _on_hit(): combo += 1 max_combo = max(max_combo, combo) var mult := 2 if double_active else 1 score += 10 * combo * mult city_glow.color.a = min(0.5, city_glow.color.a + 0.15)

if combo > 0 and combo % 10 == 0:
    _show_decision()

func _on_miss(): if shield_active: shield_active = false return combo = 0 battery -= 8.0 if battery <= 0: battery = 0 _end_game()

============================================================

موجات القرار

============================================================

func _show_decision(): decision_panel.visible = true set_process(false)

func _on_decision(choice): decision_panel.visible = false set_process(true)

match choice:
    0:
        double_active = true
        await get_tree().create_timer(8.0).timeout
        double_active = false
    1:
        shield_active = true
    2:
        var count = active_notes.size()
        for n in active_notes:
            if is_instance_valid(n.node):
                n.node.queue_free()
        active_notes.clear()
        for _i in range(count):
            _on_hit()

============================================================

الواجهة

============================================================

func _update_ui(): score_label.text = "Score: %d" % score combo_label.text = "Combo x%d" % combo if combo > 1 else "" battery_bar.value = battery

============================================================

نهاية اللعبة

============================================================

func _end_game(): if game_over: return game_over = true set_process(false)

for n in active_notes:
    if is_instance_valid(n.node):
        n.node.queue_free()
active_notes.clear()

end_label.text = "🎮 النهاية!\n\nScore: %d\nMax Combo: x%d\n\n[R] إعادة" % [score, max_combo]
end_label.visible = true

func _unhandled_input(event): if game_over and event is InputEventKey and event.pressed and event.keycode == KEY_R: get_tree().reload_current_scene()