إنشاء تطبيق رمي النرد باستخدام بايثون

في هذا الدرس، ستتعلم كيفية إنشاء مُحاكي رمي النرد باستخدام بايثون. يرشدك الدرس خلال بناء تطبيق واجهة مستخدم نصية (TUI) يُحاكي رمي النرد باستخدام وحدة random. ستتعلم جمع مُدخلات المستخدم والتحقق من صحتها، واستخدام دالة ()random.randint لرمي النرد، وعرض النتائج باستخدام فن ASCII.

سيساعدك بناء مشاريع صغيرة، مثل تطبيق رمي النرد بواجهة مستخدم نصية (TUI)، على تطوير مهاراتك في برمجة بايثون. ستتعلم كيفية جمع مُدخلات المستخدم والتحقق من صحتها، واستيراد الشيفرة البرمجية من الوحدات والحزم، وكتابة الدوال، واستخدام حلقات for والشروط، وعرض المخرجات بدقة باستخدام السلاسل النصية ودالة ()print.

العرض التوضيحي

في هذا المشروع المُفصّل، ستُنشئ تطبيقًا يُشغّل محاكاة رمي النرد. سيتمكن التطبيق من رمي ما يصل إلى ستة نرد، ولكل نرد ستة أوجه. بعد كل رمية، سيُنشئ التطبيق مخططًا ASCII لأوجه النرد ويعرضه على الشاشة. يُوضّح الفيديو التالي كيفية عمل التطبيق:

عند تشغيل تطبيق محاكاة رمي النرد، ستظهر لك رسالة تطلب منك تحديد عدد النرد الذي ترغب في رميه. بمجرد إدخال عدد صحيح صحيح من 1 إلى 6، يُحاكي التطبيق عملية الرمي ويعرض مخططًا لأوجه النرد على الشاشة.

نظرة عامة على المشروع

سيحتوي تطبيق محاكاة رمي النرد على واجهة مستخدم نصية (TUI) بسيطة وسهلة الاستخدام، تتيح لك تحديد عدد النرد ذي الأوجه الستة الذي ترغب في رميه. ستستخدم هذه الواجهة لرمي النرد في منزلك دون الحاجة للسفر إلى لاس فيغاس.

فيما يلي وصف لكيفية عمل التطبيق داخليًا:

المهام المطلوب تنفيذهاالأدوات المستخدمةالكود للكتابة
اطلب من المستخدم اختيار عدد النرد ذي الستة جوانب الذي يريد رميه، ثم اقرأ إدخال المستخدمدالة ()input المضمنة في بايثوناستدعاء لـ ()input مع الوسائط المناسبة
تحليل مدخلات المستخدم والتحقق من صحتهاتوابع السلسلة، وعوامل المقارنة، والعبارات الشرطيةA user-defined function called parse_input()
قم بتشغيل محاكاة رمي النردوحدة random، وتحديدًا دالة ()randintدالة محددة من قبل المستخدم تسمى ()roll_dice
إنشاء مخطط ASCII باستخدام وجوه النرد الناتجةالحلقات، ()list.append، و ()str.joinدالة محددة من قبل المستخدم تسمى ()generate_dice_faces_diagram
عرض مخطط وجوه النرد على الشاشةدالة ()print المضمنة في Pythonاستدعاء لـ ()print مع الوسائط المناسبة

مع مراعاة هذه العمليات الداخلية، ستُبرمج ثلاث دوال مخصصة لتوفير الميزات والوظائف الرئيسية للتطبيق. ستُحدد هذه الدوال واجهة برمجة التطبيقات العامة الخاصة بك، والتي ستستدعيها لتشغيل التطبيق.

لتنظيم شيفرة مشروع مُحاكي رمي النرد، أنشئ ملفًا واحدًا باسم dice.py في المجلد الذي تختاره بنظام الملفات لديك. أنشئ الملف وابدأ العمل!

المتطلبات الأساسية

يجب أن تكون مرتاحًا للمفاهيم والمهارات التالية قبل البدء في بناء مشروع محاكاة رمي النرد هذا:

  • طرق تشغيل البرامج النصية في بايثون
  • آلية الاستيراد في بايثون
  • أساسيات أنواع البيانات في بايثون، وخاصة السلاسل والأعداد الصحيحة
  • هياكل البيانات الأساسية، وخاصة القوائم
  • متغيرات وثوابت بايثون
  • عوامل المقارنة في بايثون
  • القيم المنطقية والتعبيرات المنطقية
  • العبارات الشرطية
  • حلقات بايثون for
  • أساسيات الإدخال والإخراج وتنسيق السلسلة في بايثون

إذا لم تكن لديك جميع المعرفة الأساسية اللازمة لبدء رحلة البرمجة هذه، فلا بأس! قد تتعلم المزيد بالبدء! يمكنك دائمًا التوقف ومراجعة الموارد المذكورة هنا إذا واجهتك مشكلة.

الخطوة 1: برمجة واجهة المستخدم الرسومية لتطبيق رمي النرد الخاص بـ Python

في هذه الخطوة، ستكتب الكود المطلوب لطلب إدخال المستخدم لعدد النرد الذي يريد رميه في المحاكاة. ستبرمج أيضًا دالة بايثون تأخذ مدخلات المستخدم، وتتحقق منها، ثم تعيدها كعدد صحيح في حال نجاح التحقق. وإلا، ستطلب الدالة إدخال المستخدم مرة أخرى.

أخذ مدخلات المستخدم في سطر الأوامر

يمكنك البدء بكتابة الكود الذي يتفاعل مع المستخدم. سيوفر هذا الكود واجهة التطبيق النصية، وسيعتمد على دالة الإدخال (()input). تقرأ هذه الدالة المُدمجة مُدخلات المستخدم من سطر الأوامر. تتيح لك مُعاملات المطالبة (prompt) الخاصة بها تمرير وصف لنوع المُدخلات المطلوبة.

قم بتشغيل محرر النصوص أو IDE المفضل لديك واكتب الكود التالي في ملف dice.py الخاص بك:

# ~~~ App's main code block ~~~
# 1. Get and validate user's input
num_dice_input = input("How many dice do you want to roll? [1-6] ")
num_dice = parse_input(num_dice_input)

يعرض استدعاء دالة ()input في السطر الثالث رسالة تسأل عن عدد النرد الذي يرغب المستخدم في رميه. يجب أن يقع العدد ضمن النطاق من 1 إلى 6، كما هو موضح في الرسالة.

ملاحظة: من خلال إضافة التعليق على السطر 1، فإنك تفصل الكود الرئيسي للتطبيق عن بقية الكود الذي ستضيفه في الأقسام القادمة.

كذلك، يعكس التعليق في السطر الثاني المهمة المحددة التي تؤديها حاليًا. ستجد المزيد من التعليقات المشابهة في أقسام أخرى من هذا الدرس. هذه التعليقات اختيارية، لذا لا تتردد في حذفها إذا أردت.

يستدعي السطر الرابع دالة ()parse_input ويخزن قيمة الإرجاع في num_dice. في القسم التالي، ستُطبّق هذه الدالة.

تحليل وتأكيد إدخال المستخدم

وظيفة دالة ()parse_input هي أخذ مُدخلات المستخدم كسلسلة نصية، والتحقق من صحتها، ثم إعادتها ككائن int في بايثون. أضف ما يلي إلى ملف dice.py، قبل الكود الرئيسي للتطبيق مباشرةً:

def parse_input(input_string):
    """Return `input_string` as an integer between 1 and 6.

    Check if `input_string` is an integer number between 1 and 6.
    If so, return an integer with the same value. Otherwise, tell
    the user to enter a valid number and quit the program.
    """
    if input_string.strip() in {"1", "2", "3", "4", "5", "6"}:
        return int(input_string)
    else:
        print("Please enter a number from 1 to 6.")
        raise SystemExit(1)

# ~~~ App's main code block ~~~
# ...

إليك كيفية عمل هذا الكود سطرًا بسطر:

  • يعرف السطر 1 ()parse_input، الذي يأخذ سلسلة الإدخال كحجة.
  • توفر الأسطر من 2 إلى 7 سلسلة توثيق الدالة. يُعدّ تضمين سلسلة توثيق مفيدة ومنسقة جيدًا في دوالك من أفضل الممارسات في برمجة بايثون، لأنها تتيح لك توثيق الكود.
  • يتحقق السطر 8 مما إذا كان مُدخل المستخدم قيمةً صحيحةً لعدد النرد المطلوب رميه. يُزيل استدعاء دالة ()strip. أي مسافات غير مرغوب فيها حول سلسلة الإدخال. يتحقق مُعامل in مما إذا كان المُدخل يقع ضمن مجموعة أعداد النرد المسموح بها للرمي. في هذه الحالة، ستستخدم مجموعةً لأن اختبارات العضوية في بنية بيانات بايثون هذه فعّالة للغاية.
  • يقوم السطر 9 بتحويل المدخلات إلى رقم صحيح وإعادته إلى المتصل.
  • يقوم السطر 11 بطباعة رسالة على الشاشة لتنبيه المستخدم إلى الإدخال غير الصحيح، إذا كان ذلك ينطبق.
  • يخرج السطر 12 من التطبيق مع استثناء SystemExit ورمز حالة 1 للإشارة إلى حدوث خطأ ما.

باستخدام دالة ()parse_input، يمكنك معالجة مُدخلات المستخدم والتحقق منها عبر سطر الأوامر. يُعدّ التحقق من صحة أي مُدخلات تأتي مباشرةً من المستخدم أو من أي مصادر غير موثوقة أمرًا أساسيًا ليعمل تطبيقك بكفاءة وأمان.

الآن وقد أصبحت لديك واجهة مستخدم سهلة الاستخدام وآلية تحقق سليمة من صحة الإدخالات، عليك التأكد من عمل هذه الوظائف بشكل صحيح. هذا ما ستفعله في القسم التالي.

جرب واجهة المستخدم الخاصة بتطبيق Dice-Rolling

لتجربة الكود الذي كتبته حتى الآن، افتح نافذة سطر الأوامر وقم بتشغيل البرنامج النصي dice.py:

$ python dice.py
How many dice do you want to roll? [1-6] 3

$ python dice.py
How many dice do you want to roll? [1-6] 7
Please enter a number from 1 to 6.

إذا أدخلتَ عددًا صحيحًا من 1 إلى 6، فلن يعرض الكود رسالة. من ناحية أخرى، إذا لم يكن المُدخل عددًا صحيحًا صحيح أو كان خارج النطاق المُستهدف، فستتلقى رسالة تُخبرك بضرورة إدخال عدد صحيح من 1 إلى 6.

حتى هذه النقطة، نجحتَ في كتابة كود لطلب مُدخلات المستخدم وتحليلها عبر سطر الأوامر. يوفر هذا الكود واجهة المستخدم البرمجية (TUI) للتطبيق، والتي تعتمد على دالة ()input المُدمجة. كما برمجتَ دالة للتحقق من صحة مُدخلات المستخدم وإعادتها كعدد صحيح. الآن، حان وقت المُخاطرة!

الخطوة 2: محاكاة رمي النرد ذي الستة جوانب في بايثون

يوفر تطبيق رمي النرد الآن واجهة مستخدم تفاعلية (TUI) لاستقبال مُدخلات المستخدم ومعالجتها. رائع! لمواصلة بناء الدالة الرئيسية للتطبيق، ستكتب دالة ()roll_dice، التي ستُمكّنك من محاكاة حدث رمي النرد. ستأخذ هذه الدالة عدد النرد الذي يُريد المستخدم رميه.

توفر وحدة بايثون random من المكتبة القياسية دالة ()randint، التي تُولّد أعدادًا صحيحة شبه عشوائية في فترة زمنية محددة. ستستفيد من هذه الدالة لمحاكاة رمي النرد.

هذا هو الكود الذي ينفذ ()roll_dice:

import random

# ...

def roll_dice(num_dice):
    """Return a list of integers with length `num_dice`.

    Each integer in the returned list is a random number between
    1 and 6, inclusive.
    """
    roll_results = []
    for _ in range(num_dice):
        roll = random.randint(1, 6)
        roll_results.append(roll)
    return roll_results

# ~~~ App's main code block ~~~
# ...

في هذا المقطع، يستورد السطر الأول كائنًا عشوائيًا إلى مساحة الاسم الحالية. يتيح لك هذا الاستيراد الوصول إلى دالة ()randint لاحقًا. إليك شرحًا لبقية الكود:

  • يعرف السطر 5 ()roll_dice، الذي يأخذ وسيطة تمثل عدد النرد الذي سيتم رميه في مكالمة معينة.
  • توفر الأسطر من 6 إلى 10 سلسلة توثيق الدالة.
  • يقوم السطر 11 بإنشاء قائمة فارغة، roll_results، لتخزين نتائج محاكاة رمي النرد.
  • يحدد السطر 12 حلقة for تتكرر مرة واحدة لكل نرد يريد المستخدم رميه.
  • يستدعي السطر 13 دالة ()randint لتوليد عدد صحيح شبه عشوائي من 1١ إلى 6، شاملًا الأرقام. تُولّد هذه الدالة رقمًا واحدًا في كل تكرار. يُمثل هذا الرقم نتيجة رمي حجر نرد سداسي الأوجه.
  • يقوم السطر 14 بإضافة نتيجة رمي النرد الحالية إلى roll_results.
  • يقوم السطر 15 بإرجاع قائمة نتائج محاكاة رمي النرد.

لتجربة الدالة التي تم إنشاؤها حديثًا، أضف أسطر التعليمات البرمجية التالية إلى نهاية ملف dice.py:

# 2. Roll the dice
roll_results = roll_dice(num_dice)

print(roll_results)  # Remove this line after testing the app

في هذا الكود، يستدعي السطر 2 دالة ()roll_dice مع num_dice كمُعامل. يستدعي السطر 3 دالة ()print لعرض النتيجة كقائمة أرقام على الشاشة. يُمثل كل رقم في القائمة نتيجة حجر نرد واحد. يمكنك حذف السطر 3 بعد اختبار الكود.

قم بالمضي قدمًا وشغل التطبيق من سطر الأوامر:

$ python dice.py
How many dice do you want to roll? [1-6] 5
[6, 1, 3, 6, 6]

$ python dice.py
How many dice do you want to roll? [1-6] 2
[2, 6]

ستختلف قوائم النتائج على شاشتك لأنك تُولّد أرقامًا شبه عشوائية خاصة بك. في هذا المثال، تُحاكي رمي خمسة أحجار نرد وحجرين نرد على التوالي. قيمة كل حجر نرد تتراوح بين 1 و6 لأنك تستخدم نردًا سداسي الأوجه.

بعد أن كتبتَ وجرّبتَ الكود الذي يُحاكي حدث رمي النرد، حان وقت الانتقال إلى تزويد تطبيقك بطريقة جذابة لعرض هذه النتائج. هذا ما ستفعله في القسم التالي.

الخطوة 3: إنشاء وعرض مخطط ASCII لوجوه النرد

في هذه المرحلة، يحاكي تطبيقك رمي عدة أحجار نرد ويخزن النتائج كقائمة أرقام. مع ذلك، قد لا تبدو قائمة الأرقام جذابة للمستخدم. أنت بحاجة إلى مخرجات أكثر تطورًا ليبدو تطبيقك احترافيًا.

في هذا القسم، ستكتب كود لإنشاء مخطط يُظهر وجوه ما يصل إلى ستة أحجار نرد. للقيام بذلك، ستُنشئ رسمًا بيانيًا باستخدام نظام ASCII.

إعداد مخطط وجوه النرد

يحتاج تطبيق محاكاة رمي النرد لديك إلى طريقة لعرض نتيجة رمي النرد. لهذا الغرض، ستستخدم مخططًا ASCII لأوجه النرد، والذي سيعرض نتيجة رمي العدد المطلوب من النرد ذي الأوجه الستة. على سبيل المثال، بعد رمي أربعة نرد، سيبدو المخطط كالتالي:

~~~~~~~~~~~~~~~~~~~ RESULTS ~~~~~~~~~~~~~~~~~~~
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│  ●   ●  │ │         │ │  ●      │ │  ●      │
│         │ │    ●    │ │    ●    │ │         │
│  ●   ●  │ │         │ │      ●  │ │      ●  │
└─────────┘ └─────────┘ └─────────┘ └─────────┘

يعكس كل وجه نرد في هذا المخطط القيمة الناتجة عن تكرار واحد للمحاكاة. لبدء برمجة دالة بناء هذا المخطط، عليك إنشاء بعض الرسومات بتنسيق ASCII. ارجع إلى محرر الأكواد وأضف ما يلي:

import random

DICE_ART = {
    1: (
        "┌─────────┐",
        "│         │",
        "│    ●    │",
        "│         │",
        "└─────────┘",
    ),
    2: (
        "┌─────────┐",
        "│  ●      │",
        "│         │",
        "│      ●  │",
        "└─────────┘",
    ),
    3: (
        "┌─────────┐",
        "│  ●      │",
        "│    ●    │",
        "│      ●  │",
        "└─────────┘",
    ),
    4: (
        "┌─────────┐",
        "│  ●   ●  │",
        "│         │",
        "│  ●   ●  │",
        "└─────────┘",
    ),
    5: (
        "┌─────────┐",
        "│  ●   ●  │",
        "│    ●    │",
        "│  ●   ●  │",
        "└─────────┘",
    ),
    6: (
        "┌─────────┐",
        "│  ●   ●  │",
        "│  ●   ●  │",
        "│  ●   ●  │",
        "└─────────┘",
    ),
}
DIE_HEIGHT = len(DICE_ART[1])
DIE_WIDTH = len(DICE_ART[1][0])
DIE_FACE_SEPARATOR = " "

# ...

في الأسطر من 3 إلى 46، ارسم ستة وجوه نرد باستخدام أحرف ASCII. خزّن الوجوه في DICE_ART، وهو قاموس يُطابق كل وجه مع قيمته الصحيحة المقابلة.

يُعرّف السطر 47 قيمة DIE_HEIGHT، التي تُشير إلى عدد الصفوف التي سيشغلها وجه مُحدد. في هذا المثال، يشغل كل وجه خمسة صفوف. وبالمثل، يُعرّف السطر 48 قيمة DIE_WIDTH لتشير إلى عدد الأعمدة المطلوبة لرسم وجه نرد. في هذا المثال، يبلغ العرض 11 حرفًا.

أخيرًا، يُعرّف السطر 49 دالة DIE_FACE_SEPARATOR، التي تحتوي على مسافة واحدة. ستستخدم كل هذه الثوابت لإنشاء وعرض مخطط ASCII لأوجه النرد لتطبيقك.

إنشاء مخطط وجوه النرد

في هذه المرحلة، تكون قد أنشأتَ رسم ASCII لكل وجه من وجوه النرد. ولجمع هذه القطع في رسم تخطيطي نهائي يُمثل النتائج الكاملة لمحاكاة رمي النرد، ستكتب دالة مخصصة أخرى:

# ...

def generate_dice_faces_diagram(dice_values):
    """Return an ASCII diagram of dice faces from `dice_values`.

    The string returned contains an ASCII representation of each die.
    For example, if `dice_values = [4, 1, 3, 2]` then the string
    returned looks like this:

    ~~~~~~~~~~~~~~~~~~~ RESULTS ~~~~~~~~~~~~~~~~~~~
    ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
    │  ●   ●  │ │         │ │  ●      │ │  ●      │
    │         │ │    ●    │ │    ●    │ │         │
    │  ●   ●  │ │         │ │      ●  │ │      ●  │
    └─────────┘ └─────────┘ └─────────┘ └─────────┘
    """
    # Generate a list of dice faces from DICE_ART
    dice_faces = []
    for value in dice_values:
        dice_faces.append(DICE_ART[value])

    # Generate a list containing the dice faces rows
    dice_faces_rows = []
    for row_idx in range(DIE_HEIGHT):
        row_components = []
        for die in dice_faces:
            row_components.append(die[row_idx])
        row_string = DIE_FACE_SEPARATOR.join(row_components)
        dice_faces_rows.append(row_string)

    # Generate header with the word "RESULTS" centered
    width = len(dice_faces_rows[0])
    diagram_header = " RESULTS ".center(width, "~")

    dice_faces_diagram = "\n".join([diagram_header] + dice_faces_rows)
    return dice_faces_diagram

# ~~~ App's main code block ~~~
# ...

هذه الدالة تقوم بما يلي:

  • يُعرّف السطر 3 دالة ()generate_dice_faces_diagram بمُعامل واحد يُسمى dice_values. سيحتوي هذا المُعامل على قائمة قيم الأعداد الصحيحة الناتجة عن رمي النرد عند استدعاء ()roll_dice.
  • توفر الأسطر من 4 إلى 16 سلسلة توثيق الدالة.
  • يُنشئ السطر 18 قائمة فارغة تُسمى “وجوه النرد” لتخزين dice_faces المقابلة لقائمة قيم النرد المُدخلة. ستظهر وجوه النرد هذه في مخطط ASCII النهائي.
  • يحدد السطر 19 حلقة for للتكرار على قيم النرد.
  • يسترجع السطر 20 وجه النرد المقابل لقيمة النرد الحالية من DICE_ART ويضيفه إلى dice_faces.
  • يقوم السطر 23 بإنشاء قائمة فارغة لحمل الصفوف الموجودة في الرسم التخطيطي لوجوه النرد النهائية.
  • يحدد السطر 24 حلقة تتكرر عبر مؤشرات من 0 إلى DIE_HEIGHT – 1. يمثل كل مؤشر مؤشر صف معين في مخطط وجوه النرد.
  • يقوم السطر 25 بتعريف row_components كقائمة فارغة لحمل أجزاء من وجوه النرد التي ستملأ صفًا معينًا.
  • يبدأ السطر 26 حلقة for متداخلة للتكرار على وجوه النرد.
  • يخزن السطر 27 مكونات كل صف.
  • يقوم السطر 28 بربط مكونات الصف في سلسلة صف نهائية، مع فصل المكونات الفردية بمسافات.
  • يقوم السطر 29 بإضافة كل سلسلة صف إلى القائمة التي تحتوي على الصفوف التي ستشكل الرسم التخطيطي النهائي.
  • يقوم السطر 32 بإنشاء متغير مؤقت لحمل عرض مخطط وجوه النرد الحالي.
  • يُنشئ السطر 33 رأسًا يعرض كلمة “RESULTS”. للقيام بذلك، يستخدم الدالة tr.center()str.center مع عرض المخطط ورمز التلدة (~) كوسيطتين.
  • يُولّد السطر 35 سلسلة نصية تحتوي على مخطط وجوه النرد النهائي. يعمل حرف تغذية السطر (n\) كفاصل للصفوف. الوسيطة في دالة ()join. هي قائمة من السلاسل النصية التي تربط رأس المخطط بالسلاسل (الصفوف) التي تُشكّل وجوه النرد.
  • يقوم السطر 36 بإرجاع رسم تخطيطي لوجوه النرد جاهز للطباعة إلى المتصل.

كان ذلك رائعًا! ستعود إلى هذا الكود وتُحسّنه ليصبح أسهل في التعامل معه خلال فترة وجيزة. قبل ذلك، عليك تجربة تطبيقك، لذا عليك إكمال كتابة الكود الرئيسي.

قم بإكمال الكود الرئيسي للتطبيق ثم قم برمي النرد

باستخدام دالة ()generate_dice_faces_diagram، يمكنك الآن إكمال كتابة الكود الرئيسي للتطبيق، مما يسمح لك بإنشاء وعرض مخطط وجوه النرد على شاشتك. أضف أسطر الكود التالية إلى نهاية ملف dice.py:

# 3. Generate the ASCII diagram of dice faces
dice_face_diagram = generate_dice_faces_diagram(roll_results)
# 4. Display the diagram
print(f"\n{dice_face_diagram}")

يستدعي السطر 2 دالة ()generate_dice_faces_diagram مع قيمة roll_results كمُعامل. تُنشئ هذه الدالة وتُعيد رسمًا تخطيطيًا لوجوه النرد، يُطابق نتائج رمي النرد الحالية. يستدعي السطر 4 دالة ()print لعرض الرسم التخطيطي على الشاشة.

مع هذا التحديث، يمكنك تشغيل التطبيق مرة أخرى. ارجع إلى سطر الأوامر ونفّذ الأمر التالي:

$ python dice.py
How many dice do you want to roll? [1-6] 5

~~~~~~~~~~~~~~~~~~~~~~~~~ RESULTS ~~~~~~~~~~~~~~~~~~~~~~~~~
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│  ●   ●  │ │  ●   ●  │ │  ●   ●  │ │  ●      │ │  ●   ●  │
│    ●    │ │  ●   ●  │ │  ●   ●  │ │    ●    │ │         │
│  ●   ●  │ │  ●   ●  │ │  ●   ●  │ │      ●  │ │  ●   ●  │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘

رائع! يعرض تطبيق محاكاة رمي النرد الآن مخططًا ASCII بتنسيق رائع يوضح نتيجة حدث المحاكاة. هذا رائع، أليس كذلك؟

إذا عدت إلى تنفيذ ()generate_dice_faces_diagram، فستلاحظ أنه يتضمن بعض التعليقات التي تشير إلى ما يفعله الجزء المقابل من الكود:

def generate_dice_faces_diagram(dice_values):
    # ...
    # Generate a list of dice faces from DICE_ART
    dice_faces = []
    for value in dice_values:
        dice_faces.append(DICE_ART[value])

    # Generate a list containing the dice faces rows
    dice_faces_rows = []
    for row_idx in range(DIE_HEIGHT):
        row_components = []
        for die in dice_faces:
            row_components.append(die[row_idx])
        row_string = DIE_FACE_SEPARATOR.join(row_components)
        dice_faces_rows.append(row_string)

    # Generate header with the word "RESULTS" centered
    width = len(dice_faces_rows[0])
    diagram_header = " RESULTS ".center(width, "~")

    dice_faces_diagram = "\n".join([diagram_header] + dice_faces_rows)
    return dice_faces_diagram

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

الخطوة 4: إعادة صياغة الكود الذي يُنشئ مخطط وجوه النرد

تتطلب دالة ()generate_dice_faces_diagram تعليقات توضيحية لأنها تقوم بتنفيذ عدة عمليات في وقت واحد، وهو ما ينتهك مبدأ المسؤولية الفردية.

باختصار، ينص هذا المبدأ على أن كل دالة أو فئة أو وحدة يجب أن تؤدي وظيفة واحدة فقط. بهذه الطريقة، لن تؤثر التغييرات في دالة معينة على بقية الكود. ونتيجة لذلك، ستحصل على كود أكثر متانة وقابلية للصيانة.

هناك تقنية إعادة هيكلة تُسمى “طريقة الاستخراج”، والتي تساعدك على تحسين الكود الخاص بك من خلال استخراج وظائف تعمل بشكل مستقل. على سبيل المثال، يمكنك استخراج الكود من السطر 18 إلى 20 في التنفيذ السابق لـ ()generate_dice_faces_diagram ووضعه في دالة مساعدة غير عامة تُسمى ()get_dice_faces_:

def _get_dice_faces(dice_values):
    dice_faces = []
    for value in dice_values:
        dice_faces.append(DICE_ART[value])
    return dice_faces

يمكنك استدعاء دالة ()get_dice_faces_ من دالة ()generate_dice_faces_diagram للحصول على الوظيفة المُرادة. باستخدام هذه التقنية، يمكنك إعادة تصميم دالة ()generate_dice_faces_diagram بالكامل لتلبية مبدأ المسؤولية الفردية.

فيما يلي إصدار مُعاد تصميمه من ()generate_dice_faces_diagram الذي يستفيد من ()get_dice_faces_ وينفذ دالة مساعدة أخرى تسمى ()generate_dice_faces_rows_ لاستخراج الوظيفة من السطر 23 إلى 29:

# ...

def generate_dice_faces_diagram(dice_values):
    """Return an ASCII diagram of dice faces from `dice_values`.

    The string returned contains an ASCII representation of each die.
    For example, if `dice_values = [4, 1, 3, 2]` then the string
    returned looks like this:

    ~~~~~~~~~~~~~~~~~~~ RESULTS ~~~~~~~~~~~~~~~~~~~
    ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
    │  ●   ●  │ │         │ │  ●      │ │  ●      │
    │         │ │    ●    │ │    ●    │ │         │
    │  ●   ●  │ │         │ │      ●  │ │      ●  │
    └─────────┘ └─────────┘ └─────────┘ └─────────┘
    """
    dice_faces = _get_dice_faces(dice_values)
    dice_faces_rows = _generate_dice_faces_rows(dice_faces)

    # Generate header with the word "RESULTS" centered
    width = len(dice_faces_rows[0])
    diagram_header = " RESULTS ".center(width, "~")

    dice_faces_diagram = "\n".join([diagram_header] + dice_faces_rows)
    return dice_faces_diagram

def _get_dice_faces(dice_values):
    dice_faces = []
    for value in dice_values:
        dice_faces.append(DICE_ART[value])
    return dice_faces

def _generate_dice_faces_rows(dice_faces):
    dice_faces_rows = []
    for row_idx in range(DIE_HEIGHT):
        row_components = []
        for die in dice_faces:
            row_components.append(die[row_idx])
        row_string = DIE_FACE_SEPARATOR.join(row_components)
        dice_faces_rows.append(row_string)
    return dice_faces_rows

# ~~~ App's main code block ~~~
# ...

الدوال المساعدة المضافة حديثًا تستخرج وظائف من الدالة الأصلية. الآن، لكل دالة مساعدة مسؤوليتها الخاصة. كما تتيح لك الدوال المساعدة استخدام أسماء واضحة ووصفية، مما يُغني عن التعليقات التوضيحية.

إعادة هيكلة الكود لتحسينه مهارة رائعة لمطوري بايثون.

الفكرة الأساسية وراء إعادة هيكلة الكود هي أن يعمل الكود المُعدَّل بنفس طريقة الكود الأصلي. للتحقق من هذا المبدأ، شغّل تطبيقك مرة أخرى!

بهذا، تكون قد أكملت مشروعك! لقد أنشأت تطبيق TUI متكامل الوظائف يُمكّنك من محاكاة رمي النرد. في كل مرة تُشغّل التطبيق، يُمكنك محاكاة رمي ما يصل إلى ستة أحجار نرد بستة وجوه لكل منها. ستتمكن أيضًا من رؤية وجوه النرد الناتجة في رسم تخطيطي ASCII أنيق. عمل رائع!

لقد برمجتَ مشروعًا متكاملًا يتكون من تطبيق واجهة مستخدم نصي يحاكي رمي نرد ذي ستة أوجه باستخدام بايثون. في هذا المشروع، تعلمتَ وتدربتَ على مهارات أساسية، مثل جمع مُدخلات المستخدم والتحقق من صحتها، واستيراد الشيفرة البرمجية، وكتابة الدوال، واستخدام الحلقات والشروط، وعرض المُخرجات بتنسيق جيد على الشاشة.

بالإضافة إلى ذلك، تعلمتَ كيفية هيكلة برامج ونصوص بايثون البرمجية وتنظيمها وتوثيقها وتشغيلها. بفضل هذه المعرفة، أصبحتَ أكثر استعدادًا لمواصلة رحلتك في البرمجة باستخدام بايثون.


اكتشاف المزيد من بايثون العربي

اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

Scroll to Top

اكتشاف المزيد من بايثون العربي

اشترك الآن للاستمرار في القراءة والحصول على حق الوصول إلى الأرشيف الكامل.

Continue reading