اصنع معركتك الأولى برمجياً!

تعلم كيف تستخدم لغة Python ومكتبة Matplotlib لبرمجة فيديو رسوم متحركة (GIF) يظهر مقاتلي كاراتيه في نزال ملحمي.

# إنشاء الأنيميشن
ani = animation.FuncAnimation(
    fig, update, frames=120, 
    init_func=init, blit=True
)

# حفظ الفيديو
ani.save('karate_fight.gif')
01

تثبيت المكتبات المطلوبة

قبل أن نبدأ في كتابة الكود، نحتاج إلى التأكد من توفر المكتبات اللازمة. افتح موجه الأوامر (Terminal) وقم بتثبيت `numpy` و `matplotlib`.

pip install numpy matplotlib pillow
02

استيراد المكتبات وإعداد الشاشة

في البداية، نستورد المكتبات ونقوم بإعداد مساحة الرسم (Figure) وإخفاء المحاور لكي يبدو المشهد كفيلم.

import numpy as np # استيراد مكتبة العمليات الرياضية (التي تحوي دوال الجيب)
import matplotlib.pyplot as plt # استيراد مكتبة الرسم البياني لإنشاء الأشكال
import matplotlib.animation as animation # استيراد مكتبة التحريك لإنشاء الأنيميشن

# إعداد الرسم
fig, ax = plt.subplots(figsize=(8, 5)) # إنشاء نافذة الرسم بمقاس 8x5 بوصة
ax.set_xlim(0, 10) # تحديد حدود المحور الأفقي من 0 إلى 10
ax.set_ylim(0, 10) # تحديد حدود المحور الرأسي من 0 إلى 10
ax.set_aspect('equal') # جعل نسبة الطول والعرض متساوية حتى لا تتشوه الأشكال
ax.axis('off') # إخفاء المحاور والأرقام الجانبية لتبدو كشاشة سينمائية
fig.patch.set_facecolor('white') # جعل لون خلفية النافذة أبيض
03

تجهيز المقاتلين (الأسلاك)

سنقوم برسم المقاتل الأزرق (يسار) والمقاتل الأصفر (يمين) باستخدام خطوط ودوائر بسيطة. ننشئ متغيرات فارغة سنقوم بتحديثها لاحقاً في كل إطار (frame).

# المقاتل الأزرق (يسار)
head_b, = ax.plot([], [], 'o', color='#1f77b4', markersize=25) # إنشاء الرأس كدائرة زرقاء بحجم 25
body_b, = ax.plot([], [], '-', color='#1f77b4', lw=5) # إنشاء الجسم كخط أزرق بسمك 5
arms_b, = ax.plot([], [], '-', color='#1f77b4', lw=5) # إنشاء الذراعين كخط أزرق بسمك 5
legs_b, = ax.plot([], [], '-', color='#1f77b4', lw=5) # إنشاء الساقين كخط أزرق بسمك 5

# المقاتل الأصفر (يمين)
head_y, = ax.plot([], [], 'o', color='#ff7f0e', markersize=25) # إنشاء الرأس كدائرة صفراء بحجم 25
body_y, = ax.plot([], [], '-', color='#ff7f0e', lw=5) # إنشاء الجسم كخط أصفر بسمك 5
arms_y, = ax.plot([], [], '-', color='#ff7f0e', lw=5) # إنشاء الذراعين كخط أصفر بسمك 5
legs_y, = ax.plot([], [], '-', color='#ff7f0e', lw=5) # إنشاء الساقين كخط أصفر بسمك 5
04

برمجة دالة التهيئة (Init)

هذه الدالة تقوم بتفريغ البيانات في البداية قبل أن تبدأ الحركة.

def init(): # دالة التهيئة التي تعمل في بداية الأنيميشن فقط
    head_b.set_data([], []) # تفريغ إحداثيات رأس الأزرق
    body_b.set_data([], []) # تفريغ إحداثيات جسم الأزرق
    arms_b.set_data([], []) # تفريغ إحداثيات أذرع الأزرق
    legs_b.set_data([], []) # تفريغ إحداثيات سيقان الأزرق
    
    head_y.set_data([], []) # تفريغ إحداثيات رأس الأصفر
    body_y.set_data([], []) # تفريغ إحداثيات جسم الأصفر
    arms_y.set_data([], []) # تفريغ إحداثيات أذرع الأصفر
    legs_y.set_data([], []) # تفريغ إحداثيات سيقان الأصفر
    
    # إرجاع جميع الأجزاء لكي يتم رسمها كإطار فارغ أولي
    return head_b, body_b, arms_b, legs_b, head_y, body_y, arms_y, legs_y
05

محرك الحركة (Update Function)

هذا هو القلب النابض للمشروع. تقوم هذه الدالة بتحديث الإحداثيات لكل مقاتل في كل جزء من الثانية (frame). وتستخدم الدوال الجيبية (sin/cos) لحساب المسافات والركلات لتكوين حركات ناعمة.

def update(frame): # دالة التحديث التي يتم استدعاؤها في كل إطار من الإطارات
    # حساب الإحداثي السيني (X) للاقتراب والابتعاد باستخدام دالة الجيب (sin)
    base_x_b = 2.5 + np.sin(frame * 0.1) * 1.5 # تحريك المقاتل الأزرق للأمام والخلف
    base_x_y = 7.5 - np.sin(frame * 0.1) * 1.5 # تحريك المقاتل الأصفر عكس الأزرق تماماً
    
    # التحقق من المسافة: إذا كانت أقل من 3.5، يصبح المتغير 1 (بدء القتال)، وإلا 0
    fighting = 1 if abs(base_x_y - base_x_b) < 3.5 else 0
    
    # تحديث إحداثيات الرأس (الرأس يظل ثابتاً على ارتفاع 6.5 في المحور الصادي)
    head_b.set_data([base_x_b], [6.5])
    head_y.set_data([base_x_y], [6.5])
    
    # تحديث إحداثيات الجسد (رسم خط عمودي يمتد من النقطة 6 إلى 4)
    body_b.set_data([base_x_b, base_x_b], [6, 4])
    body_y.set_data([base_x_y, base_x_y], [6, 4])
    
    # حساب قوة الركلات والضربات باستخدام الدوال المثلثية، وتفعيلها فقط وقت القتال (fighting)
    kick_b = max(0, np.sin(frame * 0.8)) * fighting # ركلة الأزرق
    kick_y = max(0, np.cos(frame * 0.8)) * fighting # ركلة الأصفر، نستخدم cos ليكون هناك تناوب
    
    # تحديث إحداثيات الذراعين مع إضافة تأثير الركل والضرب عند نقطة النهاية للذراع
    arms_b.set_data([base_x_b-0.8, base_x_b, base_x_b+0.8 + kick_b*1.5], [5, 5.5, 5.5 + kick_b*0.5])
    arms_y.set_data([base_x_y-0.8 - kick_y*1.5, base_x_y, base_x_y+0.8], [5.5 + kick_y*0.5, 5.5, 5])
    
    # تحديث حركة الساقين للأزرق
    if kick_b > 0.5: # إذا كانت قيمة الركلة عالية
        legs_b.set_data([base_x_b-0.5, base_x_b, base_x_b+1.5], [2, 4, 4.5]) # رفع الساق للركل للأمام
    else:
        legs_b.set_data([base_x_b-0.5, base_x_b, base_x_b+0.5], [2, 4, 2]) # الوقوف العادي (القدمين على الأرض)
        
    # تحديث حركة الساقين للأصفر
    if kick_y > 0.5: # إذا كانت قيمة الركلة عالية
        legs_y.set_data([base_x_y-1.5, base_x_y, base_x_y+0.5], [4.5, 4, 2]) # رفع الساق للركل للأمام
    else:
        legs_y.set_data([base_x_y-0.5, base_x_y, base_x_y+0.5], [2, 4, 2]) # الوقوف العادي (القدمين على الأرض)

    # إرجاع العناصر المحدثة لكي يقوم Matplotlib برسم الإطار الجديد
    return head_b, body_b, arms_b, legs_b, head_y, body_y, arms_y, legs_y
06

تشغيل الأنيميشن وحفظه كـ GIF

أخيراً، نقوم بدمج كل شيء باستخدام FuncAnimation ونقوم بتصدير النتيجة النهائية إلى ملف GIF متحرك يوثق المعركة.

# ربط كل شيء معاً لإنشاء الأنيميشن (المجسم، دالة التحديث، عدد الإطارات 120، دالة التهيئة، وتحديث التغييرات فقط بـ blit)
ani = animation.FuncAnimation(fig, update, frames=120, init_func=init, blit=True, interval=50)

# تحديد اسم الملف النهائي
output_file = 'karate_fight.gif'
# حفظ الأنيميشن في ملف بصيغة GIF باستخدام مكتبة pillow بسرعة 20 إطار في الثانية
ani.save(output_file, writer='pillow', fps=20)
# طباعة رسالة في الكونسول تفيد بنجاح العملية
print(f"تم حفظ الفيديو بنجاح في ملف: {output_file}")