<?xml version="1.0"?>
<rss version="2.0"><channel><title>&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629;</title><link>https://academy.hsoub.com/programming/</link><description/><language>ar</language><item><title>&#x62A;&#x62F;&#x631;&#x64A;&#x628; &#x646;&#x645;&#x648;&#x630;&#x62C; &#x630;&#x643;&#x627;&#x621; &#x627;&#x635;&#x637;&#x646;&#x627;&#x639;&#x64A; &#x644;&#x643;&#x62A;&#x627;&#x628;&#x629; &#x627;&#x644;&#x634;&#x639;&#x631; &#x627;&#x644;&#x639;&#x631;&#x628;&#x64A;</title><link>https://academy.hsoub.com/programming/artificial-intelligence/%D8%AA%D8%AF%D8%B1%D9%8A%D8%A8-%D9%86%D9%85%D9%88%D8%B0%D8%AC-%D8%B0%D9%83%D8%A7%D8%A1-%D8%A7%D8%B5%D8%B7%D9%86%D8%A7%D8%B9%D9%8A-%D9%84%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%A7%D9%84%D8%B4%D8%B9%D8%B1-%D8%A7%D9%84%D8%B9%D8%B1%D8%A8%D9%8A-r2610/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2026_05/1803101039_.png.0794b9d06276c926960e04e04ddda64c.png" /></p>
<p>
	أدى التطور السريع لنماذج الذكاء الاصطناعي مفتوحة المصدر إلى إمكانية توظيفها في إنتاج نصوص إبداعية بلغات متعددة، ومن بينها اللغة العربية. ومع ذلك، يظل توليد الشعر العربي تحديًا خاصًا نظرًا لخصوصيته الفنية، من أوزان عروضية دقيقة وقوافٍ تتطلب دقة عالية في التوليد التلقائي.
</p>

<p>
	في هذا المقال، سنستعرض كيفية صقل نموذج Gemma، وهو نموذج لغوي مفتوح المصدر من Google، ليكون قادرًا على توليد أبيات شعرية باللغة العربية. سنوضح خطوات تشغيل النموذج وتحضير بيانات التدريب، وأساليب الصقل المعتمدة، ثم نعرض النتائج التي توصلنا إليها، مع اقتراح تحسينات تقرّب النموذج من روح الشعر العربي التقليدي من حيث الوزن والقافية والأسلوب البلاغي.
</p>

<h2 id="-gemma-">
	ما هو نموذج Gemma؟
</h2>

<p>
	Gemma هو عائلة من النماذج اللغوية الخفيفة والمفتوحة المصدر، طُوِّرت بواسطة Google DeepMind، وتعتمد على نفس التقنيات البحثية التي بُنِيَت عليها نماذج Gemini. يتميز بقدرات متقدمة في معالجة اللغة الطبيعية NLP، مما يجعله خيارًا قويًا لمجموعة واسعة من المهام اللغوية. يتوفر Gemma بإصدارين رئيسيين، مما يمنح المستخدمين مرونة في توظيفه وفقًا لمتطلبات المهام المختلفة:
</p>

<p>
	<strong>الإصدار المضبوط على التعليمات Instruction Tuned - IT:</strong> دُرب على تفاعلات لغوية بشرية، مما يجعله مثاليًا للمحادثات والتطبيقات التفاعلية مثل الشات بوت Chatbot والمساعدات الذكية. يتميز هذا الإصدار بقدرته على فهم الأسئلة والاستجابة لها بأسلوب طبيعي وقريب من طريقة تواصل البشر
</p>

<p>
	<strong>الإصدار المسبق التدريب Pretrained - PT:</strong> لم يخصص لمهام محددة، حيث يعتمد فقط على البيانات الأساسية التي دُرب عليها. وبالتالي، فهو يحتاج إلى ضبط إضافي Fine-Tuning ليتمكن من أداء مهام متخصصة مثل توليد الشعر أو تحليل النصوص. لذا، فإن استخدامه مباشرة دون ضبط قد لا يكون فعالًا في التطبيقات التي تتطلب دقة وتخصصًا أكبر
</p>

<h3 id="-gemma-">
	لماذا اخترنا Gemma؟
</h3>

<p>
	اخترنا نموذج Gemma لعدة أسباب رئيسية:
</p>

<ul>
	<li>
		مفتوح المصدر وقابل للتخصيص: يتيح إمكانية تعديله وضبطه بسهولة، مما يسمح بتكييفه مع الأوزان والقوافي الشعرية في اللغة العربية
	</li>
	<li>
		خفيف مقارنة بالنماذج العملاقة، مما يجعله مناسبًا للتشغيل محليًا أو عبر موارد سحابية محدودة
	</li>
	<li>
		مرونة في التشغيل، حيث يمكن استخدامه مع Keras, PyTorch, Transformers وغيرها من الأدوات
	</li>
	<li>
		دعم لغوي واسع في النسخة الثالثة من النموذج Gemma 3، حيث يدعم أكثر من 140 لغة، بما في ذلك اللغة العربية
	</li>
</ul>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="182450" href="https://academy.hsoub.com/uploads/monthly_2026_05/001__.png.e7952fc1b797a88fbeedbb328108a4c9.png" rel=""><img alt="001_ترتيب_النموذج.png" class="ipsImage ipsImage_thumbnailed" data-fileid="182450" data-ratio="63.50" data-unique="opnfq8ted" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_05/001__.thumb.png.f7c9287441cd5a5288f2f50e055cb468.png"></a>
</p>

<h2 id="-fine-tuning">
	عملية الصقل Fine-Tuning
</h2>

<p>
	الصقل Fine-Tuning هو عملية إعادة تدريب نموذج ذكاء اصطناعي مُدرَّب مسبقًا باستخدام مجموعة بيانات متخصصة بهدف تحسين أدائه في مهمة معينة، مثل توليد الشعر العربي. بدلاً من تدريب النموذج من البداية،
</p>

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

<p>
	خلال عملية الصقل، يمكن الاستفادة من البيانات المشروحة، حيث يتم إضافة معلومات إضافية مثل نوع الشعر أو اسم الشاعر، مما يساعد النموذج على إنتاج أبيات شعرية دقيقة وملائمة للسياق. على سبيل المثال، قد تكون بيانات التدريب المشروحة كالتالي:
</p>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			<code>{<span class="hljs-attr">"input"</span>: <span class="hljs-string">"اكتب بيتًا من الشعر العمودي في مدح العلم"</span>, <span class="hljs-attr">"output"</span>: <span class="hljs-string">"العلمُ يبني بيوتًا لا عمادَ لها  والجهلُ يهدمُ بيتَ العزّ والشّرفِ"</span>}</code>
		</p>
	</div>
</blockquote>

<p>
	يساعد الصقل Fine-Tuning النموذج على فهم الأوزان الشعرية وتكرار الأنماط اللغوية، مما يمكننا من توجيه النموذج لإنشاء شعر بأسلوب معين، مثل الشعر الجاهلي أو الحديث.
</p>

<p>
	هناك نوعان رئيسيان من تقنيات الصقل Fine-Tuning:
</p>

<ul>
	<li>
		صقل النموذج الكامل Full Fine-Tuning: تُعدل جميع أوزان النموذج بالكامل. يتطلب هذا النوع موارد حسابية كبيرة وقد يستغرق وقتًا طويلًا
	</li>
	<li>
		صقل النموذج منخفض الموارد Parameter Efficient Fine-Tuning - PEFT: يُعدل عدد محدود من الأوزان فقط، مما يقلل من استهلاك الذاكرة ويحسن الكفاءة. من الأساليب المشهورة في هذا النوع هي LoRA و QLoRA
	</li>
</ul>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="182452" href="https://academy.hsoub.com/uploads/monthly_2026_05/finetune.png.83b65d56105b6024086c699391736f7b.png" rel=""><img alt="finetune.png" class="ipsImage ipsImage_thumbnailed" data-fileid="182452" data-ratio="56.33" data-unique="97bb5ngwb" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_05/finetune.thumb.png.925f3324ec8c03f068506330db908b65.png"></a>
</p>

<h2 id="-">
	تجهيز بيانات التدريب
</h2>

<p>
	لتحقيق نتائج مثالية عند صقل نموذج Gemma لتوليد الشعر العربي، من الضروري إعداد مجموعة بيانات غنية ومتنوعة تُلبي الهدف المطلوب. تعتمد نجاح عملية الصقل Fine-Tuning على جودة البيانات وتنظيمها وتنوعها، لذا يجب أولًا تحديد الموضوعات الأساسية للقصائد، مثل وصف الطبيعة أو الغزل أو الفخر أوالرثاء أوالهجاء أو القضايا الاجتماعية والسياسية.
</p>

<p>
	بعد ذلك، تُختار المفردات بعناية ، فقد تكون فصيحة وجزلة كما في الشعر الكلاسيكي، أو حديثة وبسيطة كما في الشعر المعاصر، أو حتى دارجة إذا كان النموذج موجهًا للشعر الشعبي. كما يجب تحديد الأسلوب الشعري، سواء كان الشعر العمودي الموزون وفق بحور الخليل بن أحمد الفراهيدي، أو الشعر الحر الذي يحافظ على الوزن دون التزام بالقافية، أو النثر الشعري الذي يتميز بأسلوب أدبي دون التقيد بالأوزان التقليدية.
</p>

<p>
	لتحسين قدرة النموذج على توليد الشعر بدقة أعلى، يمكن تجهيزه ببيانات تدريب توضّح له النمط الشعري المطلوب. على سبيل المثال، قد نطلب من النموذج كتابة شعر كلاسيكي قريب من أسلوب أحمد شوقي، أو شعر حر قريب من محمود درويش، أو شعر حكمة وفخر قريب من المتنبي، أو شعر سياسي ناقد قريب من أحمد مطر.
</p>

<p>
	بعد تحديد النمط المطلوب، تُنظَّم البيانات على شكل أزواج من المدخلات والمخرجات. يكون المدخل هو الموجّه Prompt، وفيه نكتب التعليمات التي تحدد الموضوع، والأسلوب، ونوع اللغة، والقافية أو الوزن إن وُجدا. أما المخرج فهو poem، أي النص الشعري الذي نريد أن يتعلم النموذج إنتاج ما يشبهه.
</p>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			 
		</p>

		<p>
			<code>{ <span class="hljs-attr">"prompt"</span>: <span class="hljs-string">"اكتب لي أبياتًا من الشعر العربي الفصيح عن الفخر، باستخدام مفردات جزلة وقافية منتظمة، بأسلوب الشاعر أبو فراس الحمداني."</span>,<br>
			<span class="hljs-attr">"poem"</span>: <span class="hljs-string">"سَيَذكُرُني قَومي إِذا جَدَّ جِدُّهُمُ\nوَفي اللَيلَةِ الظَلماءِ يُفتَقَدُ البَدرُ\nفَإِن عِشتُ فَالطَعنُ الَّذي يَعرِفونَهُ\nوَتِلكَ القَنا وَالبيضُ وَالضُمَّرُ الشُقرُ"</span> }</code>
		</p>

		<pre><code> { <span class="hljs-attr">"prompt"</span>: <span class="hljs-string">"اكتب لي أبياتًا من الشعر العربي عن القضايا السياسية والاجتماعية، بأسلوب شعر حر، مع استخدام مفردات بسيطة ومباشرة، بأسلوب يشابه أسلوب الشاعر أحمد مطر."</span>,
 <span class="hljs-attr">"poem"</span>: <span class="hljs-string">"ما تهمتي؟\nتهمتك العروبة\nقلت لكم ما تهمتي؟\nقلنا لك العروبة\nيا ناس قولوا غيرها\nأسألكم عن تهمتي ..\nليس عن العقوبة"</span> }</code>
</pre>
	</div>
</blockquote>

<pre><code>
</code></pre>

<h2 id="-gemma">
	تشغيل وصقل نموذج Gemma
</h2>

<p>
	لتنفيذ عملية الصقل Fine-Tuning لنموذج Gemma، سنطبق الخطوات التالية من خلال منصة <a href="https://www.kaggle.com/" rel="external nofollow">Kaggle</a> لتسريع عملية التدريب باستخدام GPU أو TPU. سنعمل على تشغيل النموذج باستخدام مكتبة Keras نظرًا لسهولة استخدامها.
</p>

<p>
	نبدأ أولًا بتثبيت المكتبات الرئيسية اللازمة لتشغيل النموذج، مثل keras-nlp وkeras:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1184_10" style=""><span class="pln">!pip install -q -U keras-nlp
!pip install -q -U keras</span></pre>

<p>
	بعد ذلك، نستورد المكتبات التي سنحتاج إليها أثناء عملية التدريب:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1184_12" style=""><span class="pln">import keras
import keras_nlp
import os</span></pre>

<ul>
	<li>
		<code>Keras</code>: مكتبة أساسية لبناء نماذج التعلم العميق
	</li>
	<li>
		<code>keras-nlp</code>: مكتبة توفر أدوات ونماذج جاهزة لعمليات الـ NLP
	</li>
	<li>
		<code>os</code>: تُستخدم للتعامل مع البيئة وتشغيل أوامر النظام
	</li>
</ul>

<p>
	سوف نختار JAX كخلفية لتشغيل النموذج:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1184_24" style=""><span class="pln">os</span><span class="pun">.</span><span class="pln">environ</span><span class="pun">[</span><span class="str">"KERAS_BACKEND"</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">"jax"</span><span class="pln">  </span><span class="com"># Or "torch" or "tensorflow".</span><span class="pln">
os</span><span class="pun">.</span><span class="pln">environ</span><span class="pun">[</span><span class="str">"XLA_PYTHON_CLIENT_MEM_FRACTION"</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">"0.9"</span></pre>

<ul>
	<li>
		<code>KERAS_BACKEND</code>: تحديد البيئة التي سنستخدمها لتشغيل Keras، حيث إن Keras هو واجهة برمجة تطبيقات عالية المستوى تدعم العديد من أطر العمل للتعلم العميق مثل TensorFlow وJAX وPyTorch
	</li>
	<li>
		<code>XLA</code>: يتيح تسريع الحسابات في JAX. الكود يحدد استخدام 90% من الذاكرة المتاحة لتجنب مشاكل تجزئة الذاكرة
	</li>
</ul>

<p>
	ثم نحمل النموذج باستخدام keras_nlp:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1184_26" style=""><span class="pln">gemma_lm </span><span class="pun">=</span><span class="pln"> keras_nlp</span><span class="pun">.</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Gemma3CausalLM</span><span class="pun">.</span><span class="pln">from_preset</span><span class="pun">(</span><span class="str">"gemma3_instruct_1b"</span><span class="pun">)</span><span class="pln">
gemma_lm</span><span class="pun">.</span><span class="pln">summary</span><span class="pun">()</span></pre>

<p>
	<code>summary</code>: عرض ملخص للنموذج ليتمكن المستخدم من رؤية هيكل النموذج، مثل الطبقات وعدد المعلمات
</p>

<p>
	الخطوة التالية هي تحميل البيانات، حيث نعمل على قراءة ملف JSON الذي يحتوي على بيانات الشعر ونحضرها للنموذج. في هذه العملية، سنستخرج الموجهات prompts والاستجابات من البيانات، ثم نحولها إلى مجموعة بيانات باستخدام مكتبة TensorFlow كالتالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1184_22" style=""><span class="kwd">import</span><span class="pln"> json
</span><span class="kwd">import</span><span class="pln"> tensorflow </span><span class="kwd">as</span><span class="pln"> tf

</span><span class="kwd">def</span><span class="pln"> prepare_training_data_poetry</span><span class="pun">(</span><span class="pln">json_path</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># فتح ملف البيانات مع استخدام ترميز يدعم اللغة العربية</span><span class="pln">
    </span><span class="kwd">with</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">json_path</span><span class="pun">,</span><span class="pln"> </span><span class="str">'r'</span><span class="pun">,</span><span class="pln"> encoding</span><span class="pun">=</span><span class="str">'utf-8'</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> file</span><span class="pun">:</span><span class="pln">
        </span><span class="com"># قراءة محتوى الملف وتحويله إلى بيانات يمكن التعامل معها في بايثون</span><span class="pln">
        data </span><span class="pun">=</span><span class="pln"> json</span><span class="pun">.</span><span class="pln">load</span><span class="pun">(</span><span class="pln">file</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># قائمة لحفظ الموجهات التي تُعطى للنموذج</span><span class="pln">
    prompts </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">

    </span><span class="com"># قائمة لحفظ القصائد أو الأبيات التي تمثل الإجابات المتوقعة</span><span class="pln">
    responses </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">

    </span><span class="com"># المرور على كل مثال تدريبي داخل ملف البيانات</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> entry </span><span class="kwd">in</span><span class="pln"> data</span><span class="pun">:</span><span class="pln">
        </span><span class="com"># إضافة نص الطلب أو التعليمات إلى قائمة الموجهات</span><span class="pln">
        prompts</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">entry</span><span class="pun">[</span><span class="str">'prompt'</span><span class="pun">])</span><span class="pln">

        </span><span class="com"># إضافة النص الشعري المقابل إلى قائمة الإجابات</span><span class="pln">
        responses</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">entry</span><span class="pun">[</span><span class="str">'poem'</span><span class="pun">])</span><span class="pln">

    </span><span class="com"># إنشاء مجموعة بيانات من الموجهات والإجابات حتى يمكن استخدامها في التدريب</span><span class="pln">
    dataset </span><span class="pun">=</span><span class="pln"> tf</span><span class="pun">.</span><span class="pln">data</span><span class="pun">.</span><span class="typ">Dataset</span><span class="pun">.</span><span class="pln">from_tensor_slices</span><span class="pun">({</span><span class="pln">
        </span><span class="str">"prompts"</span><span class="pun">:</span><span class="pln"> prompts</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"responses"</span><span class="pun">:</span><span class="pln"> responses
    </span><span class="pun">})</span><span class="pln">

    </span><span class="com"># خلط البيانات، ثم تقسيمها إلى دفعات صغيرة، ثم تجهيز الدفعات التالية مسبقًا لتسريع التدريب</span><span class="pln">
    dataset </span><span class="pun">=</span><span class="pln"> dataset</span><span class="pun">.</span><span class="pln">shuffle</span><span class="pun">(</span><span class="pln">len</span><span class="pun">(</span><span class="pln">prompts</span><span class="pun">)).</span><span class="pln">batch</span><span class="pun">(</span><span class="lit">1</span><span class="pun">).</span><span class="pln">prefetch</span><span class="pun">(</span><span class="pln">tf</span><span class="pun">.</span><span class="pln">data</span><span class="pun">.</span><span class="pln">AUTOTUNE</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># إرجاع البيانات بعد تجهيزها</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> dataset

</span><span class="com"># تجهيز بيانات التدريب من ملف البيانات الموجود في المسار المحدد</span><span class="pln">
training_data </span><span class="pun">=</span><span class="pln"> prepare_training_data_poetry</span><span class="pun">(</span><span class="str">'/kaggle/input/arabic-potry/data.json'</span><span class="pun">)</span></pre>

<ul>
	<li>
		<code>json.load</code>: قراءة وتحميل البيانات من ملف بصيغة JSON
	</li>
	<li>
		<code>Dataset.from_tensor_slice</code>: تحويل القوائم إلى مجموعة بيانات قابلة للاستخدام في التدريب
	</li>
	<li>
		<code>shuffle</code>: خلط البيانات بشكل عشوائي
	</li>
</ul>

<p>
	بعد ذلك، نستخدم تقنية LoRA لتحسين التدريب عن طريق تقليل عدد المعاملات التي سوف تُعدل، مما يساعد في تقليل استهلاك الذاكرة مع الحفاظ على الأداء الجيد:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1184_28" style=""><span class="pln">gemma_lm</span><span class="pun">.</span><span class="pln">backbone</span><span class="pun">.</span><span class="pln">enable_lora</span><span class="pun">(</span><span class="pln">rank</span><span class="pun">=</span><span class="lit">64</span><span class="pun">)</span><span class="pln">
gemma_lm</span><span class="pun">.</span><span class="pln">summary</span><span class="pun">()</span></pre>

<p>
	<code>rank=64</code>: يحدد حجم المصفوفات التي سوف تُعدل. قيمة صغيرة تعني تعديلات أبسط وأقل تكلفة، بينما قيمة كبيرة تعني تعديلات أعقد وأكثر تكلفة
</p>

<p>
	ثم، نحدد المعاملات الخاصة بالتدريب مثل تحديد طول السلسلة Sequence length إلى 512 لتقليل استهلاك الذاكرة، واختيار مُحسِّن Optimizer مثل AdamW:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1184_30" style=""><span class="com"># تحديد الحد الأقصى لطول النص الذي سيدخله النموذج أثناء التدريب</span><span class="pln">
</span><span class="com"># أي أن النموذج سيعالج تسلسلًا لا يتجاوز 512 رمزًا</span><span class="pln">
gemma_lm</span><span class="pun">.</span><span class="pln">preprocessor</span><span class="pun">.</span><span class="pln">sequence_length </span><span class="pun">=</span><span class="pln"> </span><span class="lit">512</span><span class="pln">

</span><span class="com"># إنشاء المحسّن المسؤول عن تحديث أوزان النموذج أثناء التدريب</span><span class="pln">
optimizer </span><span class="pun">=</span><span class="pln"> keras</span><span class="pun">.</span><span class="pln">optimizers</span><span class="pun">.</span><span class="typ">AdamW</span><span class="pun">(</span><span class="pln">
    </span><span class="com"># تحديد سرعة تعلّم النموذج</span><span class="pln">
    </span><span class="com"># كلما كانت القيمة صغيرة كان التحديث أبطأ وأكثر استقرارًا</span><span class="pln">
    learning_rate</span><span class="pun">=</span><span class="lit">5e-5</span><span class="pun">,</span><span class="pln">

    </span><span class="com"># تقليل احتمالية حفظ النموذج للبيانات بدل تعلّم الأنماط العامة</span><span class="pln">
    </span><span class="com"># وذلك عبر إضافة عقوبة بسيطة على الأوزان الكبيرة</span><span class="pln">
    weight_decay</span><span class="pun">=</span><span class="lit">0.01</span><span class="pun">,</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

</span><span class="com"># استثناء بعض المتغيرات من عقوبة تقليل الأوزان</span><span class="pln">
optimizer</span><span class="pun">.</span><span class="pln">exclude_from_weight_decay</span><span class="pun">(</span><span class="pln">var_names</span><span class="pun">=[</span><span class="str">"bias"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"scale"</span><span class="pun">])</span><span class="pln">

</span><span class="com"># تجهيز النموذج لمرحلة التدريب من خلال تحديد دالة الخطأ والمُحسّن ومقاييس التقييم</span><span class="pln">
gemma_lm</span><span class="pun">.</span><span class="pln">compile</span><span class="pun">(</span><span class="pln">
    </span><span class="com"># دالة الخطأ المستخدمة في مهام التنبؤ بالكلمة أو الرمز التالي</span><span class="pln">
    loss</span><span class="pun">=</span><span class="pln">keras</span><span class="pun">.</span><span class="pln">losses</span><span class="pun">.</span><span class="typ">SparseCategoricalCrossentropy</span><span class="pun">(</span><span class="pln">from_logits</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">),</span><span class="pln">

    </span><span class="com"># استخدام المحسّن الذي أنشأناه لتحديث أوزان النموذج</span><span class="pln">
    optimizer</span><span class="pun">=</span><span class="pln">optimizer</span><span class="pun">,</span><span class="pln">

    </span><span class="com"># مقياس لحساب دقة النموذج في اختيار الرمز الصحيح أثناء التدريب</span><span class="pln">
    weighted_metrics</span><span class="pun">=[</span><span class="pln">keras</span><span class="pun">.</span><span class="pln">metrics</span><span class="pun">.</span><span class="typ">SparseCategoricalAccuracy</span><span class="pun">()],</span><span class="pln">
</span><span class="pun">)</span></pre>

<ul>
	<li>
		<code>sequence_length</code>: تحديد الحد الأقصى لطول التسلسل المدخل
	</li>
	<li>
		<code>learning_rate=5e-5</code>: معدل التعلم الذي يتحكم في سرعة التحديثات
	</li>
	<li>
		<code>weight_decay=0.01</code>: تقليل التأثير الزائد للوزن
	</li>
</ul>

<p>
	وأخيرًا، نبدأ عملية صقل النموذج باستخدام البيانات التي جهزناها:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1184_32" style=""><span class="pln">gemma_lm</span><span class="pun">.</span><span class="pln">fit</span><span class="pun">(</span><span class="pln">training_data</span><span class="pun">,</span><span class="pln"> epochs</span><span class="pun">=</span><span class="lit">1</span><span class="pun">)</span></pre>

<p>
	بعد صقل النموذج، يمكننا حفظ الأوزان التي عُدلت أثناء عملية الصقل Fine-Tuning لتحميلها لاحقًا دون الحاجة لإعادة التدريب، وذلك باستخدام الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1184_34" style=""><span class="pln">gemma_lm.backbone.save_lora_weights("name.lora.h5")</span></pre>

<h2 id="-">
	تجربة أداء النموذج بعد التدريب
</h2>

<p>
	بعد الانتهاء من صقل النموذج على الشعر العربي، أصبح بإمكاننا الآن تجربته عمليًا، وذلك من خلال تقديم تعليمات واضحة تتضمن الوظيفة المطلوبة من النموذج. على سبيل المثال، يمكن استخدام المُوجه التالي prompt:
</p>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1184_38" style=""><span class="pln">prompt </span><span class="pun">=</span><span class="pln"> </span><span class="str">"اكتب لي قصيدة عربية عن حب الوطن بأسلوب الشعر الحر، مع استخدام مفردات بسيطة وجميلة، وبأسلوب قريب من الشاعر محمود درويش."</span><span class="pln">

output </span><span class="pun">=</span><span class="pln"> gemma_lm</span><span class="pun">.</span><span class="pln">generate</span><span class="pun">(</span><span class="pln">
    f</span><span class="str">"Instruction:\n{prompt}\n\nResponse:\n"</span><span class="pun">,</span><span class="pln">
    max_length</span><span class="pun">=</span><span class="lit">512</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">output</span><span class="pun">)</span></pre>

		<p>
			 
		</p>
	</div>
</blockquote>

<p>
	سيحاول النموذج توليد قصيدة تتوافق مع الخصائص المطلوبة، مثل الموضوع والأسلوب.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="182453" href="https://academy.hsoub.com/uploads/monthly_2026_05/003___.png.2eb9bb9081beb99cf459b43d7e883b43.png" rel=""><img alt="003_نتيجة_الكود_الأول.png" class="ipsImage ipsImage_thumbnailed" data-fileid="182453" data-ratio="46.89" data-unique="q9y8by244" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_05/003___.thumb.png.77cda3fa14d79f9bf76d07f0903f67e6.png"></a>
</p>

<p>
	يمكن أيضًا تحسين جودة المخرجات عبر استخدام تقنية Few-shot Prompting، حيث يتم إرسال التعليمات إلى النموذج مرفقةً بأمثلة توضح المطلوب بدقة. هذا يساعد النموذج على التقاط النمط والأسلوب المستهدف. المثال التالي يوضح ذلك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1184_41" style=""><span class="pln">prompt </span><span class="pun">=</span><span class="pln"> </span><span class="str">"""

أنت نموذج ذكاء اصطناعي متخصص في كتابة الشعر العربي بأسلوب المتنبي.  
اكتب أبياتًا شعرية تعبر عن الفخر، الطموح، القوة، والحكمة بأسلوب المتنبي.  

### مثال :  
**المدخل:** "اكتب لي أبياتًا عن الطموح والسعي وراء المجد بأسلوب المتنبي."  
**المخرج:**  
"إِذا غامَرتَ في شَرَفٍ مَرومِ  
فَلا تَقنَع بِما دونَ النُجومِ  
فَطَعمُ المَوتِ في أَمرٍ صَغيرٍ  
كَطَعمِ المَوتِ في أَمرٍ عَظيمِ"  


الآن، أكمل وفقًا لنفس النمط:  

**المدخل:** "اكتب لي أبياتًا من الشعر العمودي عن الحكمة بأسلوب المتنبي."  
**المخرج:**  

"""</span><span class="pln">
result </span><span class="pun">=</span><span class="pln"> gemma_lm</span><span class="pun">.</span><span class="pln">generate</span><span class="pun">(</span><span class="pln">f</span><span class="str">"Instruction:\n {prompt} \n\nResponse:\n"</span><span class="pun">,</span><span class="pln"> max_length</span><span class="pun">=</span><span class="lit">512</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">result</span><span class="pun">.</span><span class="pln">split</span><span class="pun">(</span><span class="str">"Response:"</span><span class="pun">)[</span><span class="lit">1</span><span class="pun">])</span></pre>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="182454" href="https://academy.hsoub.com/uploads/monthly_2026_05/004___.png.c1b636286d3c5046e74899e189535c9d.png" rel=""><img alt="004_نتيجة_الكود_الثاني.png" class="ipsImage ipsImage_thumbnailed" data-fileid="182454" data-ratio="44.33" data-unique="sr7si74mp" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_05/004___.thumb.png.18e6a5268a6693bb764ebd26d751ec05.png"></a>
</p>

<p>
	من خلال تحليل النتائج، يمكن ملاحظة أن جودة الأبيات التي يولدها النموذج لا ترقى في كثير من الأحيان إلى المستوى المتوقع من حيث المعنى و الوزن و الأسلوب والبلاغة. وهذا يشير إلى أن نماذج الذكاء الاصطناعي لا تزال تعاني من جوانب قصور واضحة في توليد الشعر العربي، لا سيما عند محاولة محاكاة الأساليب الكلاسيكية مثل أسلوب المتنبي. في الفقرة التالية، سنتناول مجموعة من الحلول والاقتراحات التي قد تسهم في تحسين أداء هذه النماذج، وتمكينها من توليد نصوص شعرية أكثر جودة واحترافية.
</p>

<h2 id="-">
	نصائح لتحسين الأداء
</h2>

<p>
	لتحقيق نتائج أفضل في توليد الشعر العربي بأساليب مختلفة، يمكننا اتباع عدة استراتيجيات:
</p>

<h3 id="-">
	تحسين وتنويع بيانات التدريب
</h3>

<ul>
	<li>
		زيادة حجم بيانات التدريب: جمع المزيد من القصائد بأساليب شعراء مختلفين يساعد في تغطية نطاق أوسع من الأنماط الشعرية
	</li>
	<li>
		تنويع البيانات: من المهم تضمين أنواع مختلفة من الشعر، مثل الشعر العمودي والشعر الحر، لضمان قدرة النموذج على توليد أشكال متعددة من الشعر
	</li>
	<li>
		تحسين جودة البيانات: مراجعة البيانات يدويًا أو استخدام تقنيات التنظيف والتصفية يمكن أن يساعد في إزالة الأخطاء أو العبارات غير الطبيعية
	</li>
</ul>

<h3 id="-">
	استخدام نموذج أكبر
</h3>

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

<p>
	كما يمكن تجربة نماذج لغوية أخرى متقدمة، مثل Qwen، وإعادة صقلها على مجموعة مختارة من الدواوين العربية الكلاسيكية والحديثة. من المتوقع أن يساعد ذلك النموذج على التقاط الخصائص الأسلوبية والإيقاعية للشعر العربي بشكل أفضل، وتحسين قدرته على توليد نصوص شعرية أقرب إلى طبيعة الشعر العربي من حيث اللغة والصورة والمعنى.
</p>

<h3 id="-">
	تحسين المعاملات الخاصة بالتدريب
</h3>

<ul>
	<li>
		زيادة قيمة LoRA Rank: إذا كان النموذج الحالي يستخدم <code>rank=64</code>، يمكن زيادة هذه القيمة إلى <code>rank=128</code> أو أكثر للحصول على ضبط أدق وأداء أفضل
	</li>
	<li>
		زيادة قيمة Rank قد تساعد في تحسين قدرة النموذج على تخصيص الأوزان بشكل أفضل أثناء عملية الصقل Fine-Tuning
	</li>
	<li>
		تدريب النموذج على عدد أكبر من Epochs: يمكن تجربة قيم أعلى مثل <code>epochs=5</code> للمساعدة في تثبيت الأنماط الشعرية داخل النموذج بشكل أكثر فعالية
	</li>
</ul>

<h2 id="-">
	الخاتمة
</h2>

<p>
	في هذا المقال، عملنا على صقل نموذج Gemma لإنتاج الشعر العربي. استعرضنا كيفية تحضير البيانات وطرق الصقل المختلفة، كما ناقشنا عدة استراتيجيات لتعزيز دقة النموذج، مثل زيادة حجم البيانات واستخدام نماذج أكبر وتحسين معلمات التدريب لتحسين الأداء وتحقيق نتائج أفضل في توليد الشعر العربي.
</p>

<p>
	<a class="ipsAttachLink" data-fileext="rar" data-fileid="182455" href="https://academy.hsoub.com/applications/core/interface/file/attachment.php?id=182455&amp;key=36a34cfeffa29125a48061566387dcbd" rel="">code.rar</a>
</p>

<h2 id="-">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/artificial-intelligence/%D8%B7%D8%B1%D9%8A%D9%82%D8%A9-%D8%A7%D9%84%D8%B5%D9%82%D9%84-fine-tune-%D9%84%D9%86%D9%85%D9%88%D8%B0%D8%AC-%D8%B0%D9%83%D8%A7%D8%A1-%D8%A7%D8%B5%D8%B7%D9%86%D8%A7%D8%B9%D9%8A-%D9%85%D9%8F%D8%AF%D8%B1%D9%91%D9%8E%D8%A8-%D9%85%D9%8F%D8%B3%D8%A8%D9%82%D9%8B%D8%A7-r2367/" rel="">طريقة الصقل Fine-Tune لنموذج ذكاء اصطناعي مُدرَّب مُسبقًا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/artificial-intelligence/%D8%A7%D9%84%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%A7%D9%84%D9%85%D9%8F%D8%B3%D8%A8%D9%82%D8%A9-%D9%84%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%82%D8%A8%D9%84-%D8%AA%D9%85%D8%B1%D9%8A%D8%B1%D9%87%D8%A7-%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D8%B0%D9%83%D8%A7%D8%A1-%D8%A7%D9%84%D8%A7%D8%B5%D8%B7%D9%86%D8%A7%D8%B9%D9%8A-r2365/" rel="">المعالجة المُسبقة للبيانات قبل تمريرها لنماذج الذكاء الاصطناعي</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/artificial-intelligence/%D9%86%D8%B8%D8%B1%D8%A9-%D8%B9%D8%A7%D9%85%D8%A9-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%B5%D9%86%D9%81-trainer-%D9%81%D9%8A-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%A7%D9%84%D9%85%D8%AD%D9%88%D9%84%D8%A7%D8%AA-transformers-r2492/" rel="">نظرة عامة على الصنف Trainer في مكتبة المحولات Transformers</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/artificial-intelligence/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%AF%D8%B1%D9%8A%D8%A8-%D8%A7%D9%84%D9%85%D9%88%D8%B2%D8%B9-%D9%88%D9%85%D9%83%D8%AA%D8%A8%D8%A9-accelerate-%D9%84%D8%AA%D8%B3%D8%B1%D9%8A%D8%B9-%D8%AA%D8%AF%D8%B1%D9%8A%D8%A8-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D8%B0%D9%83%D8%A7%D8%A1-%D8%A7%D9%84%D8%A7%D8%B5%D8%B7%D9%86%D8%A7%D8%B9%D9%8A-r2376/" rel="">مشاركة نموذج ذكاء اصطناعي على منصة Hugging Face</a>
	</li>
</ul>

<h2 id="-">
	المراجع
</h2>

<ul>
	<li>
		<a href="https://ai.google.dev/gemma/docs/core" rel="external nofollow">Gemma 3 model overview</a>
	</li>
	<li>
		<a href="https://www.kaggle.com/models/google/gemma-3" rel="external nofollow">Gemma 3</a>
	</li>
	<li>
		<a href="https://www.aldiwan.net/" rel="external nofollow">aldiwan</a>
	</li>
	<li>
		<a href="https://www.linkedin.com/pulse/what-supervised-fine-tuning-tagx-yq8if/" rel="external nofollow">What is Supervised Fine-Tuning?</a>
	</li>
</ul>

<p>
	 
</p>
]]></description><guid isPermaLink="false">2610</guid><pubDate>Tue, 12 May 2026 09:38:00 +0000</pubDate></item><item><title>&#x62A;&#x639;&#x631;&#x641; &#x639;&#x644;&#x649; &#x645;&#x641;&#x647;&#x648;&#x645;  Vibe Coding &#x648;&#x627;&#x633;&#x62A;&#x641;&#x62F; &#x645;&#x646;&#x647; &#x641;&#x64A; &#x628;&#x646;&#x627;&#x621; &#x627;&#x644;&#x62A;&#x637;&#x628;&#x64A;&#x642;&#x627;&#x62A;</title><link>https://academy.hsoub.com/programming/artificial-intelligence/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%85%D9%81%D9%87%D9%88%D9%85-vibe-coding-%D9%88%D8%A7%D8%B3%D8%AA%D9%81%D8%AF-%D9%85%D9%86%D9%87-%D9%81%D9%8A-%D8%A8%D9%86%D8%A7%D8%A1-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-r2609/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2026_04/vibe.png.478a523e6d0b76b5e2c68b0278a5eedd.png" /></p>
<p>
	يبدو أننا نشهد عصرًا جديد من البرمجة ستمكننا جميعًا من إطلاق العنان لإبداعنا وصناعة ما يحلو لنا من مواقع وتطبيقات دون أن نحتاج لامتلاك معرفة تقنية واسعة ونضطر لكتابة الكثير من الأكواد البرمجية لنتفاهم مع الحواسيب ونطلب منها تنفيذ ما نريده. فالمعادلة بدأت تتغير بوتيرة متسارعة مع انتشار تقنيات الذكاء الاصطناعي المساعدة في البرمجة ومن أحدثها مفهوم فايب كودينغ Vibe Coding الذي أتاح لنا التعبير عن أفكارنا بلغة طبيعية وتحويلها لتطبيقات تعمل بمنتهى السهولة.
</p>

<p>
	فهل هذه الثورة التقنية في مجال الذكاء الاصطناعي ستساعد المبرمجين بالفعل أم ستستبدلهم بالكامل، وهل تصلح منهجية Vibe Coding لتطوير كافة أنواع التطبيقات أم أنها تصلح لأنواع دون أخرى؟ دعونا نناقش معًا هذا المفهوم الرائج ونتعرف على أهم أدواته ومنصاته ونطبقه عمليًا لتطوير تطبيق ويب متكامل يتيح لنا تقييم المعالم الأثرية في الوطن العربي لنكتشف من خلال التجربة إمكانياته ومحدودياته، وكيف يمكن للمبرمجين والمستخدمين العاديين الذين لا يملكون خبرات برمجية كبيرة الاستفادة منه بالشكل الأمثل.
</p>

<h2 id="-vibe-coding-">
	ما هو Vibe Coding؟
</h2>

<p>
	إن مصطلح فايب كودينج Vibe Coding هو اتجاه حديث في تطوير البرمجيات طرحه لأول مرة عالم الحاسوب <a href="https://en.wikipedia.org/wiki/Andrej_Karpathy" rel="external nofollow">أندريه كارباثي Andrej Karpathy</a>، أحد المؤسسين المشاركين في شركة OpenAI والمدير السابق لفريق الذكاء الاصطناعي في تسلا يشير إلى أسلوب برمجة يعتمد على النماذج اللغوية الكبيرة LLMs، حيث يصف المطوّر التطبيق المطلوب بلغة طبيعية، ويتولى الذكاء الاصطناعي توليد الكود البرمجي لهذا التطبيق.
</p>

<p>
	يساهم هذا الأسلوب في تسهيل <a href="https://academy.hsoub.com/programming/general/%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA/" rel="">برمجة التطبيقات</a> حيث يوفر منصات أو أطر عمل متكاملة تعتمد على وكلاء الذكاء الاصطناعي وتتيح لنا تنفيذ كافة المهام البرمجية المطلوبة بداية من تخطيط بنية التطبيق ثم كتابة أكواده البرمجية وصولًا إلى نشره وتصحيح أخطائه وتحسين مخرجاته، باستمرار كل ذلك من خلال التحاور التفاعلي مع الوكيل الذكي.
</p>

<p>
	لا يقتصر أسلوب Vibe Coding على إرسال موجه واحد prompt وانتظار النتيجة، بل يوفر إطارًا تفاعليًا متكاملًا يسمح لنا بالتواصل المستمر مع وكيل الذكاء الاصطناعي، ومراجعة المخرجات التي نحصل عليها، وتحليلها وتقديم ملاحظات دقيقة لتصحيح أخطائها، وتزويد الوكيل بالمعلومات اللازمة لفهم متطلباتنا بدقة، مما يضمن تطوير تطبيقات تعمل بشكل صحيح وآمن.
</p>

<h2 id="-vibe-coding">
	أهم أدوات Vibe Coding
</h2>

<p>
	تتوفر الكثير من الأدوات والمنصات التي تتيح لنا بناء التطبيقات باستخدام أسلوب Vibe Coding، ومن ضمنها:
</p>

<h3 id="replit">
	Replit
</h3>

<p>
	توفر <a href="https://replit.com" rel="external nofollow">منصة Replit</a> بيئة تطوير سحابية متكاملة تسمح للمستخدمين بإنشاء التطبيقات ونشرها دون الحاجة إلى إعداد بيئة تطوير محلية. تتميز المنصة بدعمها للغة الطبيعية من خلال وكيل ذكي يُعرف باسم Replit Agent يعمل على تثبيت التبعيات، وإنشاء قاعدة البيانات وتوليد الكود تلقائيًا مع إمكانية النشر الفوري للتطبيقات، كما أنها تعرض كامل الكود البرمجي الناتج وتتيح لنا تحميله، ويمكنها استيراد المستودعات من جيت هب بسهولة، ما يجعلها خيارًا مثاليًا للمبرمجين المبتدئين والراغبين في إنشاء نماذج أولية لمشاريعهم بسرعة.
</p>

<p>
	بالنسبة <a href="https://replit.com/pricing" rel="external nofollow">للسعر</a>، توفر المنصة خطط مدفوعة تبدأ من 20 دولار مجانًا، كما توفر خطة مجانية تسمح بإنشاء 3 تطبيقات عامة.
</p>

<h3 id="lovable">
	Lovable
</h3>

<p>
	تعد منصة <a href="https://lovable.dev/" rel="external nofollow">Lovable</a> بيئة تطوير سحابية مدعومة بالذكاء الاصطناعي، وهي مثالية لتجربة أي أفكار جديدة تخطر على البال، فهي تتيح تحويل الأفكار إلى تطبيقات متكاملة دون الحاجة إلى كتابة لسطر برمجي واحد من خلال توفير واجهة محادثة تعتمد على اللغة الطبيعية، كل ما علينا هو وصف ما نريده بوضوح، وستتولى المنصة إنشاء الواجهة الأمامية والخلفية للتطبيق، وإعداد قواعد البيانات وصولًا إلى نشر التطبيق عبر الإنترنت وتوليد وثائقه تلقائيًا، كما تتيح مزامنة الكود مع GitHub، لكنها لا تتيح التعديل المباشر عليه داخل واجهتها وتسمح فقط بإجراء التعديلات على الكود البرمجي للتطبيق عبر جيت هب.
</p>

<p>
	بالنسبة <a href="https://lovable.dev/pricing" rel="external nofollow">للسعر</a>، توفر المنصة خطط تسعير متنوعة، تبدأ بخطة مجانية، ثم تتوفر خطط احترافية مدفوعة تكلف 25 دولار إلى 30 دولار حسب عدد الطلبات المرسلة شهريًا.
</p>

<h3 id="cursor">
	Cursor
</h3>

<p>
	منصة <a href="https://www.cursor.com" rel="external nofollow">Cursor</a> هي بيئة تطوير متقدمة طورتها شركة أنيسفير Anysphere Inc مبنية على محرر الأكواد الشهير فيجوال استوديو كود تدمج الذكاء الاصطناعي لتحسين تجربة البرمجة وتمكننا من توليد وتحريره وتحسينه وإعادة هيكلته واكتشاف أخطائه وطرح أسئلة عنه كل ذلك باستخدام اللغة الطبيعية، لكنها تتطلب إعداد بيئة تطوير محلية وتلائم أكثر المطورين الذين لديهم معرفة جيدة بالبرمجة ويحتاجون لأدوات ذكية تساعدهم في التعامل مع الكود.
</p>

<p>
	توفر المنصة خطة مجانية باسم <a href="https://www.cursor.com/pricing" rel="external nofollow">Hobby</a> تمكننا من تنفيذ 50 طلب، كما توفر عدة خطط مدفوعة، وتتيح لنا تجربة مجانية لمدة أسبوعين للخطة الاحترافية المدفوعة Pro التي تبلغ تكلفتها 20 دولار في الشهر.
</p>

<h3 id="github-copilot-agent-mode">
	GitHub Copilot Agent Mode
</h3>

<p>
	يوفر <a href="https://github.com/features/copilot" rel="external nofollow">GitHub Copilot</a> يسمى وضع الوكيل Agent Mode يحوله إلى من مجرد مساعد برمجي ذكي لمنصة تطويرية ذكية حيث يصبح بإمكانه تنفيذ مهام تطويرية متعددة، كإنشاء تطبيقات متكاملة وإعادة هيكلة أكوادها، واختبارها وتصحيح أخطائها، كما يمكنه تحديث التطبيقات القديمة وترحيلها إلى أطر عمل حديثة، وهو يتكامل مع محرر الأكواد فيجوال ستوديو كود Visual Studio Code ويدعم اختيار نماذج ذكاء اصطناعي مختلفة مثل GPT-4o و Claude 3.5 وGemini 2.0 Flash، كما يتكامل بشكل كبير معGitHub، ويدعم عدة لغات برمجة، لكن ميزة Agent Mode لا تزال حديثة نسبيًا، وقد تكون بعض الاقتراحات غير دقيقة أو غير مناسبة للسياق وتحتاج لمراجعة من قبل المبرمج.
</p>

<p>
	فيما يخص <a href="https://docs.github.com/en/copilot/about-github-copilot/plans-for-github-copilot" rel="external nofollow">التسعير</a> يتيح GitHub Copilot خطة المجانية محدودة بـ50 طلبًا شهريًا، كما يوفر خطط مدفوعة تبدأ بتكلفة 10 دولار شهريًا أو 100 دولار سنويًا، ويمكن أيضًا الاشتراك في الخطط المدفوعة مجانًا لبعض الفئات مثل الطلاب والمعلمين والمساهمين في المشاريع مفتوحة المصدر.
</p>

<h2 id="openhands">
	OpenHands
</h2>

<p>
	أداة <a href="https://github.com/All-Hands-AI/OpenHands" rel="external nofollow">OpenHands</a> هي منصة مفتوحة المصدر لتطوير التطبيقات مدعومة بالذكاء الاصطناعي تتيح لنا تنفيذ مهام تطوير مختلفة مثل إنشاء كود التطبيقات ضمن بيئة آمنة وسهلة التخصيص ومعزولة تعمل ضمن حاويات Docker. لكنها قد لا تناسب المبتدئين لكونها تتطلب إعداد لبيئة العمل يدويًا وهي مثالية للمشاريع التي الجديدة التي نبدأها من الصفر ومن الصعب دمجها في مشاريع قائمة نظرًا لطبيعة عملها المعزولة.
</p>

<p>
	هناك العديد من الأدوات الأخرى التي تدعم أسلوب Vibe Coding ويعتمد اختيار الأداة المناسبة على احتياجاتكم وأهدافكم، ننصح بتجربة بعض الأدوات مع خططها المجانية أو الإصدارات التجريبية منها لمعرفة الأداة الأفضل من بينها. بعد أن تعرفنا على أبرز الأدوات التي تستخدم أسلوب Vibe Coding، دعونا الآن نجرب تطوير تطبيق متكامل باستخدامه.
</p>

<h2 id="-vibe-coding">
	تطوير تطبيق متكامل باستخدام Vibe Coding
</h2>

<p>
	لنتعرف على خطوات إنشاء تطبيق متكامل لتقييم المواقع الأثرية في الوطن العربي وننشئه خطوة خطوة بداية من النموذج الأولي البسيط، وحتى النشر النهائي.
</p>

<h3 id="-vibe-coding">
	الخطوة الأولى: اختيار منصة Vibe Coding
</h3>

<p>
	أول خطوة علينا اتباعها هو اختيار الأداة أو المنصة المناسبة للبرمجة بأسلوب Vibe Coding، سنختار في هذه المقالة منصة <a href="https://replit.com/" rel="external nofollow"><strong>Replit</strong></a> التي توفر -كما وضحنا سابقًا- بيئة تطوير متكاملة تغنينا عن الحاجة لإعداد البيئة البرمجية يدويًا وتتيح لنا التركيز على مهام البرمجة و<a href="https://academy.hsoub.com/programming/general/%D8%AD%D9%84-%D8%A7%D9%84%D9%85%D8%B4%D9%83%D9%84%D8%A7%D8%AA-%D9%88%D8%A3%D9%87%D9%85%D9%8A%D8%AA%D9%87%D8%A7-%D9%81%D9%8A-%D8%A7%D8%AD%D8%AA%D8%B1%D8%A7%D9%81-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-r760/" rel="">حل المشكلات</a>.
</p>

<p>
	يمكن بسهولة إنشاء حساب مجاني في المنصة بالضغط على زر Sign up وإدخال بيانات الدخول الخاصة بنا.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="182251" href="https://academy.hsoub.com/uploads/monthly_2026_04/001Replit.png.68367fe98aa17f8579bb609ebdea636a.png" rel=""><img alt="001 إنشاء حساب في منصة Replit.png" class="ipsImage ipsImage_thumbnailed" data-fileid="182251" data-ratio="48.17" data-unique="8yopfrsu0" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_04/001Replit.thumb.png.3a4309759af08d8842b30cc811363b1b.png"></a>
</p>

<p>
	توفر هذه المنصة ثلاث خطط اشتراك يمكن الاختيار فيما بينها، حاليًا سنكتفي بتجربة الخطة المجانية Starter فهي مناسبة لغرض التعلم وتطوير مشاريع بسيطة حيث تتيح لنا تطوير ثلاث تطبيقات مع ميزات ذكاء اصطناعي محدودة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="JPG" data-fileid="182252" href="https://academy.hsoub.com/uploads/monthly_2026_04/002replit.JPG.69f821caf813868d7ec8c942d05732b2.JPG" rel=""><img alt="002 خطط replit.JPG" class="ipsImage ipsImage_thumbnailed" data-fileid="182252" data-ratio="60.67" data-unique="fva06k4xr" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_04/002replit.thumb.JPG.890c6b36d9b84f6e93039cfff3e79199.JPG"></a>
</p>

<h3 id="-">
	الخطوة الثانية: كتابة الموجهات
</h3>

<p>
	بعد استكمال التسجيل في المنصة ستظهر لنا مساحة العمل Workspace التي تعرض بيئة تطوير متكاملة لإنشاء وتعديل وتشغيل التطبيقات مباشرة دون الحاجة إلى تثبيت أي برامج إضافية. من هنا سنبدأ بكتابة الموجهات اللازمة لعمل التطبيق، لكن قبل البدء بهذه الخطوة من الضروري أن نفكر في التطبيق الذي نرغب بإنشائه ونجهز وصف واضح له لنتمكن من كتابة الموجهات المطلوبة لإنشائه بشكل واضح وتسلسل مناسب.
</p>

<p>
	بالنسبة لهذا المقال، سنجرب تنفيذ فكرة تطبيق الويب بإطار العمل فلاسك Flask يعرض مجموعة من المواقع الأثرية بحيث يختار معلمين مقابل بعضهما كل مرة للتصويت على أحدهما، بعدها تصنف المعالم بناء على تقييمات المستخدمين لها. سنعتمد في التقييم <a href="https://ar.wikipedia.org/wiki/%D9%86%D8%B8%D8%A7%D9%85_%D8%AA%D8%B5%D9%86%D9%8A%D9%81_%D8%A5%D9%8A%D9%84%D9%88" rel="external nofollow">نظام إيلو Elo</a> المستخدم في الألعاب التنافسية مثل الشطرنج والرياضات الإلكترونية ونشاهد كيف يقوم الذكاء الاصطناعي بإنشاء كود التطبيق وجعله يعمل.
</p>

<p>
	لننتبه هنا أنه ليس علينا أن نعطي الموجه الكامل لتفاصيل التطبيق بل يكفي أن نبدأ بموجه بسيط وستتخذ أفكارنا شكلها النهائي من خلال الحوار مع الذكاء الاصطناعي.
</p>

<p>
	<strong>ملاحظة</strong>: سنكتب الموجهات prompts باللغتين العربية والإنجليزية ونعتمد على الانجليزية لأن أغلب بيانات تدريب النماذج اللغوية الضخمة LLMs باللغة الإنجليزية وخاصة تلك التي تتضمن أكواد ومصطلحات برمجية وهي تعطي نتائج أدق، ولكن لازال بالإمكان توجيه الأوامر والطلبات باللغة العربية من هذه الأدوات.
</p>

<p>
	سنبدأ العمل بكتابة موجه بسيط من وكيل الذكاء الاصطناعي لإنشاء نموذج أولي على النحو التالي:
</p>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			<strong>الموجه باللغة الإنجليزية:</strong>
		</p>

		<p>
			Help me build an interactive Flask app for voting and ranking the most famous archaeological landmarks in the Arab world. The app should allow users to vote on landmarks head-to-head, then calculate a ranking for the landmarks based on the chess Elo system. The app should prominently display the matchup along with overall rankings and recent votes.
		</p>

		<p>
			<strong>الموجه باللغة العربية:</strong>
		</p>

		<p>
			ساعدني في بناء تطبيق تفاعلي باستخدام Flask للتصويت وتصنيف أشهر المعالم الأثرية في الوطن العربي. يجب أن يتيح التطبيق للمستخدمين التصويت والاختيار ما بين معلمين أثريين مقابلين لبعضهما، ثم حساب تصنيف هذه المعالم باستخدام نظام Elo المستمد من لعبة الشطرنج. ينبغي أن يعرض التطبيق نتيجة المقارنة الحالية بشكل واضح، إلى جانب التصنيفات الإجمالية وأحدث نتائج التصويت.
		</p>
	</div>
</blockquote>

<p>
	لو رغبنا في جعل التطبيق يعرض المعالم الأثرية ضمن التطبيق من مصدر مرجعي معين يمكن أن نوفره له ضمن على النحو التالي:
</p>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			<strong>الموجه باللغة الإنجليزية:</strong>
		</p>

		<p>
			I want you to rely on the historical landmarks displayed on this page as a source of information. <a href="https://ar.wikipedia.org/wiki/%D9%82%D8%A7%D8%A6%D9%85%D8%A9_%D9%85%D9%88%D8%A7%D9%82%D8%B9_%D8%A7%D9%84%D8%AA%D8%B1%D8%A7%D8%AB_%D8%A7%D9%84%D8%B9%D8%A7%D9%84%D9%85%D9%8A_%D9%81%D9%8A_%D8%A7%D9%84%D8%AF%D9%88%D9%84_%D8%A7%D9%84%D8%B9%D8%B1%D8%A8%D9%8A%D8%A9" rel="external nofollow">https://ar.wikipedia.org/wiki/قائمة_مواقع_التراث_العالمي_في_الدول_العربية</a> 
		</p>

		<p>
			<strong>الموجه باللغة العربية:</strong>
		</p>

		<p>
			أريدك أن تعتمد على المعالم الأثرية المعروضة في هذه الصفحة كمصدر للمعلومات <a href="https://ar.wikipedia.org/wiki/%D9%82%D8%A7%D8%A6%D9%85%D8%A9_%D9%85%D9%88%D8%A7%D9%82%D8%B9_%D8%A7%D9%84%D8%AA%D8%B1%D8%A7%D8%AB_%D8%A7%D9%84%D8%B9%D8%A7%D9%84%D9%85%D9%8A_%D9%81%D9%8A_%D8%A7%D9%84%D8%AF%D9%88%D9%84_%D8%A7%D9%84%D8%B9%D8%B1%D8%A8%D9%8A%D8%A9" rel="external nofollow">https://ar.wikipedia.org/wiki/قائمة_مواقع_التراث_العالمي_في_الدول_العربية</a>
		</p>
	</div>
</blockquote>

<p>
	في هذه الحالة سنلاحظ ظهور خيارين للتعامل مع الرابط المرفق الأول هو التقاط لقطة شاشة Take screenshot من أجل التقاط صورة للمحتوى الموجود في الرابط وهو مفيد إذا كنا نريد حفظ شكل الصفحة أو مشاركتها كصورة. والثاني هو استخراج المحتوى النصي Get text content الذي يجلب المعلومات النصية الموجودة في الصفحة، بدون الصور أو التنسيقات المرئية وهو مفيد إذا كنا نريد تحليل النص واستخدمها في تطبيقنا.
</p>

<p>
	سنكتب الآن الموجه الأول كاملًا كما في الصورة التالية ونرسله بالضغط على زر Start Building:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="JPG" data-fileid="182253" href="https://academy.hsoub.com/uploads/monthly_2026_04/003.JPG.f95e67dcb761c140e15756441ad77bea.JPG" rel=""><img alt="003 كتابة الموجه الأول.JPG" class="ipsImage ipsImage_thumbnailed" data-fileid="182253" data-ratio="59.00" data-unique="xmzxdglo6" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_04/003.thumb.JPG.c4b3aa15354fe0d10c664c1e193181ae.JPG"></a>
</p>

<p>
	<strong>ملاحظة</strong>: إذا كنا غير متأكدين من كيفية وصف فكرتنا بدقة يمكن النقر على خيار Improve prompt لتحسين الموجه وإعادة صياغته قبل إرساله، حيث سيعمل وكيل الذكاء الاصطناعي في هذه الحالة على إضافة أي تفاصيل مطلوبة وتحديد التقنيات أو الأطر المناسبة للمشروع إن لزم وقد يقسم الموجه لعده خطوات قابلة للتنفيذ مما يساهم في تحسين جودة المخرجات وتقليل الأخطاء.
</p>

<p>
	بعد إرسال الموجه ستبدأ المنصة بتحليله وتعرض خطة المشروع Project Plan التي تنظم التطبيق، كما ستقترح إضافة بعض الميزات للتطبيق مثل تخزين بيانات المعالم الأثرية ونتائج التصويت في قواعد بيانات، وإضافة حسابات المستخدمين لتتبع سجل التصويت، وإضافة صفحات تفصيلية للمعالم الأثرية تتضمن معلومات وصور عن كل معلم، وخيارات للبحث حسب معايير متنوعة، كما تظهر لنا المدة التقديرية لإنشاء العرض الأولي للتطبيق والنسخة الأولية الكاملة منه.
</p>

<p>
	بداية لن تختار أيًا من المميزات الإضافية وسنبني التطبيق الأساسي فقط، ويمكننا إضافة ما نريده ميزات لاحقًا متى ما أردنا ذلك، لنضغط على زر Approve plan &amp; start لتبدأ عملية إنشاء التطبيق.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="JPG" data-fileid="182254" href="https://academy.hsoub.com/uploads/monthly_2026_04/004.JPG.6560dd110f6c48f3a5e765c220a69d5a.JPG" rel=""><img alt="004 خطة المشروع.JPG" class="ipsImage ipsImage_thumbnailed" data-fileid="182254" data-ratio="81.33" data-unique="0voeli1dw" style="width: 600px; height: auto;" width="737" src="https://academy.hsoub.com/uploads/monthly_2026_04/004.thumb.JPG.e34213311bfe49ca4dbf788fe4f937c4.JPG"></a>
</p>

<p>
	ليس علينا الآن سوى الانتظار لدقائق معدودة، وترك المنصة تقوم بعملها وتنشئ التطبيق المطلوب باسم Arab World Heritage Sites مع توفير كافة ملفات المشروع وأكواده المطلوبة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="JPG" data-fileid="182255" href="https://academy.hsoub.com/uploads/monthly_2026_04/005.JPG.0bdc041a4853d0bcb4bb457a604437bf.JPG" rel=""><img alt="005 التطبيق المطلوب والأكواد البرمجية.JPG" class="ipsImage ipsImage_thumbnailed" data-fileid="182255" data-ratio="46.50" data-unique="258x9vatl" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_04/005.thumb.JPG.289a6cac5b8474a816ae2e203e538b88.JPG"></a>
</p>

<h3 id="-">
	الخطوة الثالثة: اختبار التطبيق
</h3>

<p>
	الآن علينا تجربة الكود الناتج من خلال تشغيل التطبيق والتحقق من أدائه
</p>

<p>
	عند الانتهاء سيظهر لنا تطبيق يعرض المعالم الأثرية المختلفة ويسمح لنا بالتصويت عليها ويعرض النتائج في الأسفل كما توضح في الصورة التالية، وعلينا في هذه المرحلة تجربته والتحقق من مظهره ووظائفه.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="gif" data-fileid="182256" href="https://academy.hsoub.com/uploads/monthly_2026_04/006.gif.a487e4399bf41ab0139808aad43460b6.gif" rel=""><img alt="006 التطبيق الأولي.gif" class="ipsImage ipsImage_thumbnailed" data-fileid="182256" data-ratio="41.00" data-unique="q623o67ws" style="width: 900px; height: 369px;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_04/006.thumb.gif.7fa27598eada6095afd54873c5f59dab.gif"></a>
</p>

<p>
	نفتح التطبيق الناتج في علامة تبويب مستقلة في المتصفح بالنقر على زر New tab الموجود في شريط الأدوات العلوي في التبويب Preview. يبدو أن التطبيق يعمل بشكل صحيح وهو متجاوب مع الهاتف، لكنه يعرض لنا كافة البيانات -ما عدا اسم المعلم الأثري- باللغة الإنجليزية، لذا سنحتاج لتحسينه ليجعل المعلومات باللغة العربية، كما سنضيف له ميزات إضافية في الفقرات التالية.
</p>

<h2 id="-">
	الخطوة الرابعة: تحسين النتائج
</h2>

<p>
	سنكتب موجهات جديدة لتنفيذ مهام تحسينية ومميزات إضافية لتخصيص التطبيق وجعله يعمل وفق متطلباتنا.
</p>

<p>
	أول مهمة تحسينية هي جعل تطبيقنا يعرض المعلومات باللغة العربية بالكامل وبالاتجاه من اليمين لليسار RTL، كما أننا سنعدل تصميمه ليستخدم ألوان قاتمة تتناسب مع عرض المواقع الأثرية كتدرجات البني والألوان الترابية.
</p>

<p>
	لنكتب هذا الموجه لتنفيذ هذا التحسين.
</p>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			<strong>الموجه باللغة الإنجليزية</strong>
		</p>

		<p>
			The app is functioning properly, but it currently displays all content — except for the landmark names — in English. We need the entire application to be in Arabic, with full RTL layout support. Additionally, the current color scheme is too generic. Please update it to reflect earthy tones and shades of brown that better match the theme of historical and archaeological sites.
		</p>

		<p>
			<strong>الموجه باللغة العربية</strong>
		</p>

		<p>
			يعمل التطبيق بشكل صحيح، ولكنه يعرض جميع المحتوى — باستثناء أسماء المعالم — باللغة الإنجليزية. نحتاج إلى أن يكون التطبيق بالكامل باللغة العربية، مع دعم كامل لاتجاه الكتابة من اليمين إلى اليسار RTL. بالإضافة إلى ذلك، فإن نظام الألوان الحالي عام جدًا. الرجاء تحديثه ليعكس درجات ألوان ترابية وظلال من اللون البني، بما يتماشى بشكل أفضل مع طابع المواقع التاريخية والأثرية.
		</p>
	</div>
</blockquote>

<p>
	عند تطبيق الموجه السابق سنحصل على النتيجة التالية التي تعرض الصفحة الرئيسية للتطبيق:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="182257" href="https://academy.hsoub.com/uploads/monthly_2026_04/007.png.0173a89c6094bb29a7a2ed1cfb55efab.png" rel=""><img alt="007 التطبيق بعد التحسين الأول الصفحة الرئيسية.png" class="ipsImage ipsImage_thumbnailed" data-fileid="182257" data-ratio="100.67" data-unique="zdn5ucq27" width="596" src="https://academy.hsoub.com/uploads/monthly_2026_04/007.thumb.png.2c514080590ecb717539dcbcc1d29c23.png"></a>
</p>

<p>
	نلاحظ تحسن مظهر واجهة التطبيق ، وعرض المعلومات بشكل سليم باللغة العربية واعتماد الاتجاه من اليمين لليسار كما طلبنا، لكن تطبيقنا لا يحتفظ بنتائج التصويت التي ندخلها في كل مرة وذلك لأنه يخزن النتائج في الذاكرة عند تشغيل التطبيق فقط ولا يحفظها بشكل دائم ضمن قاعدة بيانات، وبالتالي فإن كافة نتائج التصويت ستفقد بمجرد إعادة تشغيل التطبيق من جديد.
</p>

<p>
	لو تحقننا من ملفات الكود يمكن أن نرى أن التطبيق يخزن بيانات المعالم الأثرية في القاموس <code>landmarks</code> ضمن ملف <code>models.py</code>، ويخزن نتائج التصويت الأخيرة في قائمة بالاسم <code>votes</code> تحفظ آخر 50 نتيجة وتخزنها مؤقتًا في الذاكرة. ولجعل النتائج محفوظة بشكل دائم حتى بعد إعادة تشغيل التطبيق سنحتاج إلى إنشاء قاعدة بيانات مثل SQLite أو PostgreSQL ونحول <code>landmarks</code> و <code>votes</code> إلى جداول، كما سننشئ جدول للمستخدمين للسماح لهم بالتسجيل والتصويت.
</p>

<p>
	بالطبع لن نقوم بهذه التعديلات على الكود بأنفسنا بل سنتبع أيضًا أسلوب Vibe Coding فكل ما نحتاجه هو كتابة موجه نصي بسيط على النحو التالي:
</p>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			<strong>الموجه باللغة الإنجليزية:</strong>
		</p>

		<p>
			To ensure data is persistently stored and accessible across sessions and for all users, we will store it permanently in the database. We'll implement a user authentication system with registration and login functionality, using email and securely encrypted passwords. Voting will be restricted to authenticated users only. Once a user is logged in, each vote will be linked to their account to maintain accurate user-specific data.
		</p>

		<p>
			<strong>الموجه باللغة العربية:</strong>
		</p>

		<p>
			لضمان تخزين بيانات التطبيق بشكل دائم وإمكانية الوصول إليها عبر الجلسات ولكافة المستخدمين، سنقوم بحفظها بشكل دائم في قاعدة بيانات. سننفذ نظام مصادقة للمستخدمين يتضمن التسجيل وتسجيل الدخول باستخدام البريد الإلكتروني وكلمات مرور مشفّرة بشكل آمن. سنتقيد التصويت ليكون للمستخدمين المسجلين فقط. وبمجرد تسجيل دخول المستخدم، سنربط نتائج التصويت بالمستخدمين لنتمكن من عرض نتائج التصويت لكل مستخدم بناءً على البيانات المخزنة.
		</p>
	</div>
</blockquote>

<p>
	من جديد، سنترك المنصة تقوم بعملها لنحصل بعد دقائق على التطبيق المعدل الذي يتضمن حفظ النتائج ووظيفة تسجيل مستخدمين من خلال البريد الإلكتروني وكلمة المرور.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="182258" href="https://academy.hsoub.com/uploads/monthly_2026_04/008.png.adcb16298837831185f1e25a7d605a8b.png" rel=""><img alt="008 إضافة ميزة تسجيل الدخول.png" class="ipsImage ipsImage_thumbnailed" data-fileid="182258" data-ratio="65.50" data-unique="8l2y0c7xx" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_04/008.thumb.png.f934f132b91dbd7f6274a9dce782fe5a.png"></a>
</p>

<p>
	كل ما علينا فعله هو تشغيل التطبيق والتحقق من ان كل شيء يسير على ما يرام وسنجد أننا لم نعد نستطيع التصويت إذا لم نكن مسجلين دخول للموقع، وسنتوجه لصفحة التسجيل في الموقع كي نتمكن من تقييم المواقع الأثرية.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="gif" data-fileid="182259" href="https://academy.hsoub.com/uploads/monthly_2026_04/009.gif.569262eae669979e713f55fb45afb989.gif" rel=""><img alt="009 التسجيل للتصويت.gif" class="ipsImage ipsImage_thumbnailed" data-fileid="182259" data-ratio="66.67" data-unique="uo1crxa6r" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_04/009.thumb.gif.452fb10fdf8c2ee3e1aad1f423e344bc.gif"></a>
</p>

<p>
	كما سنلاحظ أن نتائج التصويت أصبحت تحفظ عند إعادة تشغيل التطبيق.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2026_04/010.png.e5cd4fa5405ade1dc5f043f6e24b4002.png" data-fileid="182264" data-fileext="png" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="182264" data-ratio="101.69" data-unique="b1wqsgeiv" width="590" alt="010 حفظ نتائج التصويت.png" src="https://academy.hsoub.com/uploads/monthly_2026_04/010.thumb.png.589fa34b63f20d360cfd3b65eac09d98.png"></a><br>
	<span id="cke_bm_4306E" style="display: none;"> </span>
</p>

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

<p>
	ننوه لأن منصة Replit توفر لنا نقاط حفظ تلقائية أثناء تنفيذ تغييرات مهمة على التطبيق،يطلق على كل نقطة اسم checkpoint وهي بمثابة لقطة لحالة التطبيق في لحظة معينة تساعدنا على تتبع سير العمل وتتيح لنا التراجع Rollback لإلغاء التعديلات التي أجريناها في حال كانت غير مناسبة وأردنا العودة لحالة التطبيق السابقة، كما تمكننا من حفظ الكود الناتج من خلال النقر على خيارات التبويب الجانبي Files واختيار الأمر Download as zip ليحفظ لنا كافة الأكواد البرمجية التي أنشأها باسم LandmarkLegends.zip.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="182262" href="https://academy.hsoub.com/uploads/monthly_2026_04/011.png.c5a979230efa259c54e46dace23869c4.png" rel=""><img alt="011 حفظ الكود البرمجي.png" class="ipsImage ipsImage_thumbnailed" data-fileid="182262" data-ratio="48.00" data-unique="znrzpo59s" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_04/011.thumb.png.98c4dbcdb7b1013e92eac61b35ba4f39.png"></a>
</p>

<h2 id="-">
	الخطوة الخامسة: نشر التطبيق
</h2>

<p>
	بعد الانتهاء من تطوير تطبيقنا، نأتي للخطوة الأخيرة وهي نشره وإتاحته للمستخدمين، تتيح لنا منصة Replit نشر تطبيقنا بسهولة كل ما علينا هو الانتقال لمساحة العمل الخاصة بمشروعنا والنقر على زر النشر Deploy في أعلى مساحة العمل لفتح لوحة إعدادات النشر.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="182263" href="https://academy.hsoub.com/uploads/monthly_2026_04/012.png.3bbdba1ae9d69881d50bdfca2b1f890a.png" rel=""><img alt="012 لوحة النشر.png" class="ipsImage ipsImage_thumbnailed" data-fileid="182263" data-ratio="50.83" data-unique="bzoslw44h" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_04/012.thumb.png.a960ae5c37ba19d43a9353ab3c0baa3a.png"></a>
</p>

<p>
	نلاحظ من اللوحة السابقة توفر عدة خيارات للنشر:
</p>

<ul>
	<li>
		Reserved VM الذي يوفر خادم محجوز بموارد مخصصة تضمن تشغيل تطبيقنا دون انقطاع
	</li>
	<li>
		Autoscale يوسع الموارد تلقائيًا حسب استخدام التطبيق
	</li>
	<li>
		Static مناسب للمواقع الثابتة التي لا تتطلب معالجة من طرف الخادم
	</li>
	<li>
		Scheduled مناسب للتطبيقات التي تعمل في أوقات محددة نختارها
	</li>
</ul>

<p>
	بعد تحديد نوع النشر وإعداداته ستتيح لنا المنصة عنوان URL فريد خاص به يمكننا مشاركته مع المستخدمين، ويجب التأكد من أن نمط الخصوصية privacy mode لتطبيقنا هو عام public كي يتمكن الجميع من رؤيته، ويمكن مطالعة <a href="https://docs.replit.com/category/replit-deployments" rel="external nofollow">توثيق Replit</a> للحصول على معلومات إضافية حول عملية النشر.
</p>

<h2 id="-vibe-coding-">
	هل سيلغي Vibe Coding دور المبرمجين
</h2>

<p>
	إن المتابع للتطورات على الساحة التقنية سيلاحظ إثارة الكثير من التساؤلات والمخاوف حول الذكاء الاصطناعي وبالتحديد منهجية Vibe Coding وفيما إذا كانت ستلغي دور المبرمجين في المستقبل، فما الحاجة لتعلم البرمجة إن كان بإمكان أي شخص عادي ليس لدي أي خلفية برمجية إنشاء ما يحلو له من تطبيقات ونشرها من خلال أوامر بسيطة دون الحاجة لكتابة كود طويل ومعقد.
</p>

<p>
	في الواقع يجب أن نقرّ بأن استخدام الذكاء الاصطناعي لصناعة البرمجيات دون فهم جيد للكود الناتج عن هذه التطبيقات أمر محفوف بالمخاطر فضلًا عن كونه قد يؤدي إلى صعوبة في الصيانة والتطوير المستقبلي. وعلى الرغم من أن هذا أسلوب Vibe Coding يوفر تطبيقات متكاملة وتعمل بكفاءة إلا أنه مناسب فقط لتطوير النماذج الأولية السريعة والتطبيقات البسيطة محدودة الموراد والمتطلبات، أو كما قال أندريه كارباثي في <a href="https://x.com/karpathy/status/1886192184808149383?lang=en" rel="external nofollow">تغريدته على منصة X</a> إن هذه المنهجية في البرمجة مناسبة أكثر لمشاريع عطلة نهاية الأسبوع وليس للمشاريع الجادة أو المعقدة.
</p>

<p>
	فلن نستغني عن دور المبرمجين في تطوير تطبيقات معقدة تتطلب خصوصية وأمانًا عاليًا كأنظمة الدفع الإلكتروني أو التطبيقات التي تتعامل مع بيانات حساسة، أو صناعة تطبيقات عالية الموارد تحتاج لبنية تحتية قوية وكود محسن بعناية يضمن تجربة استخدام سلسة كمنصات التجارة الإلكترونية أو تطبيقات الواقع المعزز فهذه التطبيقات ستحتاج بالتأكيد لخبراء ومختصين في البرمجة والتطوير لتنفيذها بكفاءة.
</p>

<p>
	وحتى التطبيقات البسيطة المولدة بتقنيات الذكاء الاصطناعي لن تلغي دور المبرمجين ولن تغنينا عن الحاجة إلى <a href="https://academy.hsoub.com/programming/general/%D8%AA%D8%B9%D9%84%D9%85-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-r662/" rel="">تعلم البرمجة</a>، فمن ذا الذي سيصحح هذه التطبيقات إذا حدث فيه خطأ أو خلل أمني فيها، أو تبين في مرحلة ما من عملها أنها لا تنفذ بعض المهام بالشكل المطلوب منها، أو أنها تعاني من ثغرات أمنية خطيرةّ في هذه الحالة لن يستطع شخص غير متخصص بالبرمجة من معرفة ما يجري وراء الكواليس واتباع أفضل الممارسات لتحسين كود التطبيق وإصلاح أخطائه.
</p>

<p>
	فما سيحدث فقط هو تحول دور المبرمجين الأساسي من كتابة الكود إلى التوجيه والتحسين وتصحيح المشكلات وسيظل <a href="https://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9/" rel="">الفهم الأساسي لمبادئ البرمجة</a> أمرًا لا غنى عنه في تقديم أوامر أكثر دقة للذكاء الاصطناعي وتحليل الكود الناتج وتعديله إذا لزم الأم والتعامل مع المشكلات والأخطاء التقنية التي قد تظهر بكفاءة واحترافية كما يمكن للمبرمجين أنفسهم الاستفادة منه في عملهم كما سنوضح في فقرتنا التالية.
</p>

<h2 id="-vibe-coding-">
	أهمية Vibe Coding للمبرمجين
</h2>

<p>
	يوفر أسلوب Vibe Coding عدة فوائد للمطورين والمبرمجين، لنستعرض أبرزها:
</p>

<h2 id="-">
	تغير طبيعة العمل
</h2>

<p>
	لن تصبح مهمة المطورين والمبرمجين الأساسية هي كتابة الأكواد البرمجية الطويلة وتنقيحها، وإنما ستكون مهمتهم هي التوجيه والإشراف على عمل وكلاء الذكاء الاصطناعي، وستكون أهم مهارة يحتاجونها لهذا الدور هي التواصل الفعال والقدرة على التعبير عن الأفكار بوضوح إلى جانب تقييم وتحسين ما يولده لهم الذكاء الاصطناعي.
</p>

<h3 id="-">
	بناء نماذج أولية بسرعة
</h3>

<p>
	بات بمقدورنا الانتقال من مرحلة الفكرة إلى نموذج أولي قابل للتجربة بفترة وجيزة جدًا فكي نبدأ بتنفيذ تطبيقاتنا لن نحتاج كخطوة أولى سوى لفكرة مبتكرة نعبر عنها بشكل تعليمات واضحة نوفرها لوكيل الذكاء الاصطناعي ونتركه يتولى تفاصيل التنفيذ ونحن نشرب قهوتنا، وهذا يتيح يسرع وتيرة التطوير ويسهل علينا ابتكار خدمات ونماذج أعمال جديدة باستمرار.
</p>

<h2 id="-">
	توفير أسلوب تعلم مبتكر
</h2>

<p>
	سيغير هذا النهج في البرمجة الأساليب التقليدية المملة في تعلم البرمجة التي كانت تتطلب فهمًا جيدًا للمفاهيم التقنية قبل أن نتمكن من بناء أو تطبيق لنا، وأصبحت الآن تركز على تعلم كتابة الموجهات prompts لبناء التطبيق الذي يعمل بكفاءة وتوفير كوده كاملًا وبعدها يمكن أن نطلب من الوكيل الذكي أن يشرح لنا ما قام ونسأل عن وظيفة أي جزء معين من الكود لنحصل على شرح تفصيلي شامل يعزز من فهمنا النظري والعملي بنفس الوقت وهذا بلا شك سيجعل تجربة التعلم أكثر متعة.
</p>

<h3 id="-">
	تحسين مهارات كتابة الموجهات
</h3>

<p>
	فمن خلال التخاطب المستمر مع وكلاء الذكاء الاصطناعي سنطور مهارتنا في هندسة الموجهات Prompt Engineering فهذه المهارة تتطلب فهمًا عميقًا لكيفية صياغة الأوامر والتعليمات بطريقة تضمن الحصول على استجابات دقيقة وذات صلة، وهي بلا شك مهارة ضرورية ومطلوبة بشدة في وقتنا الحالي.
</p>

<h2 id="-">
	الخاتمة
</h2>

<p>
	وصلنا لنهاية مقالنا الذي شرحنا فيه تقنية Vibe Coding وطبقناها عمليًا لتطوير تطبيق ويب متكامل، لا تزال هذه التقنية في مراحلها الأولى، ولكن يمكننا الاستفادة منها كمبرمجين لتسريع عملن في تطوير البرمجيات وتسهيل تعلم البرمجة وفهمها.
</p>

<p>
	فلو كنتم مبتدئين في البرمجة صعبة، وتحتاج للكثير من الوقت قبل التمكن من تنفيذ أفكاركم المبدعة ووضعها موضع التنفيذ فإن تقنية Vibe Coding فرصتكم لبدء رحلتكم مع البرمجة وإنشاء التطبيقات الخاصة بكم، كل ما تحتاجونه هو الحوار مع وكلاء ذكاء اصطناعي وشرح ما تريدون تنفيذه بالتفصيل بلغتكم الطبيعية الواضحة وفهم الأكواد الناتجة وتحسينها وسيغدو التعلم ماتعًا وشيقًا ومفيدًا، هل بدأتم بالفعل بتجربة هذا النمط من البرمجة أم ليس بعد؟ وهل ترون أنه سيكون معيارًا أساسيًا في مستقبل تطوير البرمجيات؟ شاركونا آراءكم وتجاربكم.
</p>

<h2 id="-">
	المصادر
</h2>

<ol>
	<li>
		<a href="https://docs.replit.com/getting-started/intro-replit" rel="external nofollow">دليل البدء مع منصة Replit لإنشاء وتشغيل المشاريع البرمجية</a>
	</li>
	<li>
		<a href="https://de.wikipedia.org/wiki/Vibe_Coding" rel="external nofollow">فايب كودينج Vibe Coding: المفهوم والتعريف العام</a>
	</li>
	<li>
		<a href="https://blankslatedigital.co.uk/blog/artificial-intelligence/what-is-vibe-coding/" rel="external nofollow">ما هو Vibe Coding؟ شرح مبسط لكيفية بناء التطبيقات باستخدام الذكاء الاصطناعي</a>
	</li>
	<li>
		<a href="https://www.designrush.com/agency/software-development/trends/vibe-coding" rel="external nofollow">Vibe Coding: اتجاه حديث في تطوير البرمجيات باستخدام الذكاء الاصطناعي</a>
	</li>
	<li>
		<a href="https://medium.com/@karan.kamat1406/vibe-coding-the-ai-era-of-development-but-with-a-catch-44f021a2ed2e" rel="external nofollow">Vibe Coding: عصر جديد من البرمجة بالذكاء الاصطناعي مع تحدياته</a>
	</li>
	<li>
		<a href="https://momen.app/blogs/what-is-vibe-coding-pros-cons/" rel="external nofollow">ما هو Vibe Coding؟ المزايا والعيوب في تطوير التطبيقات بالذكاء الاصطناعي</a>
	</li>
</ol>
]]></description><guid isPermaLink="false">2609</guid><pubDate>Sun, 19 Apr 2026 13:13:00 +0000</pubDate></item><item><title>&#x628;&#x631;&#x645;&#x62C;&#x629; &#x62A;&#x637;&#x628;&#x64A;&#x642; &#x644;&#x641;&#x644;&#x62A;&#x631;&#x629; &#x627;&#x644;&#x635;&#x648;&#x631; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x644;&#x63A;&#x629; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646;</title><link>https://academy.hsoub.com/programming/python/%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%84%D9%81%D9%84%D8%AA%D8%B1%D8%A9-%D8%A7%D9%84%D8%B5%D9%88%D8%B1-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2608/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2026_04/624090640_.png.52896abac30e7d044ca97a7851add158.png" /></p>
<p>
	هل تساءلتم مسبقًا عن كيفية عمل برامج تعديل الصور وكيف يتم تطبيق الفلاتر على الصورة لتظهر بمظهر مختلف من إزالة للخلفية وتعديل السطوع وتغيير ألوانها وإلى ما ذلك؟ سنتطرق في هذه المقالة إلى شرح هذه المفاهيم ونوضح لكم كيفية بناء برنامج لفلترة الصور باستخدام لغة البرمجة بايثون وبالاستعانة ببعض مكتبات معالجة الصور التي توفرها لنا.
</p>

<h2 id="-">
	كيف تعمل فلاتر الصور؟
</h2>

<p>
	دعونا نتعلّم كيفية عمل فلاتر الصور قبل أن نبدأ ببرمجة البرنامج، إذ سيساعدنا فهم عملها على فهم الشيفرة البرمجية التي نكتبها بشكل أكبر.
</p>

<p>
	كما نعرف فإن الصورة مكوّنة من عدد من البكسلات pixels التي تحتوي على ثلاث قيم من 0 إلى 255 لكل من القنوات اللونية الثلاث وهي الأحمر والأخضر والأزرق التي يرمز لهم بالمصطلح RGB. وبالتالي، يمكننا تمثيل الصورة بمصفوفة عددية كما يوضّح الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2026_04/001.png.808039b693c6690d5fab203d9fff1ff6.png" data-fileid="182150" data-fileext="png" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="182150" data-ratio="41.17" data-unique="t8wv3t1ji" style="width: 600px; height: auto;" width="900" alt="001 تمثيل الصورة كبكسلات.png" src="https://academy.hsoub.com/uploads/monthly_2026_04/001.thumb.png.2fbfbb01427c8335c543e9e0a09cf24f.png"></a>
</p>

<p>
	وضعنا هنا صورة صغيرة لأجل اختصار طول المصفوفة العددية، لكن لك أن تتخيل أن كل بيكسل في الصورة يقابله قيمة عددية. وقد مثلنا هذه الصورة باستخدام قناة واحدة وهي تدرجات الرمادي Grayscale، لكن الحالة تنطبق أيضًا على صورة RGB الملونة لكن الفارق في هذه الحالة أننا سنستخدم ثلاث قنوات، أي ثلاث مصفوفات ثنائية البعد كما هو موضح في الصورة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2026_04/002.png.8074391d4045df27d8d594c670022016.png" data-fileid="182151" data-fileext="png" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="182151" data-ratio="95.77" data-unique="md48i2dif" style="width: 426px; height: auto;" width="626" alt="002 بكسلات صورة ملونة.png" src="https://academy.hsoub.com/uploads/monthly_2026_04/002.thumb.png.8fda17c69e529170a74eab74b47e71f8.png"></a>
</p>

<p>
	وبالتالي عندما نطبّق فلتر معين على صورة ما، فإننا نعدّل قيمة المصفوفة العددية هذه بضربها بمصفوفة أخرى نسميها مرشّحًا، أو نجري تغييرًا على كل بكسل فيها وفق معادلة معينة، إليكَ بعض الفلاتر:
</p>

<p style="text-align: center;">
	<img class="ipsImage ipsImage_thumbnailed" data-fileid="182152" data-ratio="70.84" data-unique="xhi6voqpb" width="734" alt="003 تطبيق فلاتر على الصورة.png" src="https://academy.hsoub.com/uploads/monthly_2026_04/003.png.85aa5af852cc371468d7cc9a023109e2.png">
</p>

<p>
	لحسن الحظ، ليس علينا القلق بخصوص العمليات الحسابية ومعالجة الصورة كل بيكسل على حدة، إذ ستساعدنا مكتبات بايثون على ذلك عن طريق توابع جاهزة توفرها لتسهل علينا تطبيق الفلاتر.
</p>

<p>
	بعد أن تعرفنا على آلية عمل الفلاتر على الصورة دعونا نبدأ خطوات تطوير تطبيقنا بايثون يمكننا من رفع الصورة المطلوبة وتعديلها بفلاتر مختلفة.
</p>

<h2 id="-">
	الخطوة الأولى: تجهيز بيئة العمل
</h2>

<p>
	لنبدأ أولًا بتجهيز بيئة العمل عن طريق تجهيز المكتبات التي سنستخدمها، بالإضافة لهيكل المشروع من ملفات ومجلدات. سننشئ مجلدًا نسميه <code>image-filters</code> وهو المجلد الذي سيمثل المجلد الجذر لمشروعنا، ومن ثم ننشئ بداخله مجلدين الأول باسم <code>images</code> الذي سنضع فيه الصور التي نريد فلترتها والثاني باسم <code>output</code> وهو المجلد الذي سنخزن فيه الصور الناتجة عن تطبيق الفلتر، وأخيرًا ننشئ ملف <code>image_filters.py</code> الذي سيحتوي الشيفرة البرمجية لبرنامجنا، ويمكننا إنشاء ملف يدعى <code>README.md</code> يشرح كيفية عمل البرنامج ومزاياه.
</p>

<p>
	يصبح لدينا هيكل المشروع في النهاية كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_9" style=""><span class="pln">image</span><span class="pun">-</span><span class="pln">filters</span><span class="pun">/</span><span class="pln">
</span><span class="pun">├──</span><span class="pln"> images</span><span class="pun">/</span><span class="pln">               </span><span class="com"># مجلد يحتوي الصور التي تريد فلترتها</span><span class="pln">
</span><span class="pun">│</span><span class="pln">   </span><span class="pun">├──</span><span class="pln"> sample</span><span class="pun">.</span><span class="pln">jpg
</span><span class="pun">├──</span><span class="pln"> output</span><span class="pun">/</span><span class="pln">               </span><span class="com"># مجلد نخزن فيه الصور الناتجة عن تطبيق الفلتر</span><span class="pln">
</span><span class="pun">├──</span><span class="pln"> image_filters</span><span class="pun">.</span><span class="pln">py      </span><span class="com"># الشيفرة البرمجية</span><span class="pln">
</span><span class="pun">├──</span><span class="pln"> README</span><span class="pun">.</span><span class="pln">md             </span><span class="com"># للتوثيق ‪(اختياري)‪‬‬</span></pre>

<p>
	بعدها، نذهب إلى طرفية سطر الأوامر command line وننفّذ التعليمة التالية:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_846_11" style=""><span class="pln">pip install opencv-python numpy pillow</span></pre>

<p>
	ستتكفل هذه التعليمة بتحميل المكتبات التي سنستعين بها لتطبيق الفلاتر على الصور، وهي مكتبة <code>opencv</code> ومكتبة <code>numpy</code> ومكتبة <code>pillow</code> لذا يجب التأكد من توفر اتصال الإنترنت قبل تنفيذ الأمر.
</p>

<p>
	يمكننا التأكد من أن عملية التحميل قد تمت بنجاح بتنفيذ السطر التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_846_13" style=""><span class="pln">py -c "import cv2; import numpy; import PIL; print('Packages installed successfully!')"</span></pre>

<p>
	إن تمت العملية بنجاح سنحصل على رسالة <code>Packages installed successfully!‎</code> في الطرفية.
</p>

<h2 id="-">
	الخطوة الثالثة: كتابة الهيكل الأساسي للبرنامج
</h2>

<p>
	نبدأ باستيراد import المكاتب التي سنستخدمها في بداية الشيفرة البرمجية داخل ملف <code>image_filters.py</code> سنستخدم أيضًا مكتبة <code>os</code> المضمنة في بايثون التي ستساعدنا على إنشاء المجلدات/المسارات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_15" style=""><span class="kwd">import</span><span class="pln"> cv2
</span><span class="kwd">import</span><span class="pln"> numpy </span><span class="kwd">as</span><span class="pln"> np
</span><span class="kwd">import</span><span class="pln"> os</span></pre>

<p>
	نريد أن ننشئ واجهة مستخدم بسيطة لنرفع منها الصورة التي نريد فلترتها ومن ثم نختار الفلتر ونطبقه، لتحقيق ذلك نضمّن أيضًا مكتبة tkinter و PIL بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_17" style=""><span class="kwd">import</span><span class="pln"> tkinter </span><span class="kwd">as</span><span class="pln"> tk
</span><span class="kwd">from</span><span class="pln"> tkinter </span><span class="kwd">import</span><span class="pln"> filedialog</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Label</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Button</span><span class="pun">,</span><span class="pln"> ttk
</span><span class="kwd">from</span><span class="pln"> PIL </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">,</span><span class="pln"> </span><span class="typ">ImageTk</span></pre>

<p>
	نحفظ كل من مسار الصورة مع اسمها واسم الفلتر الذي نختاره وسنبني الفلاتر المتاحة في برنامجنا في الخطوات التالية، ومن ثم نخزّن الصورة في متغير بالاستعانة بمكتبة opencv:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_19" style=""><span class="pln">image </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">imread</span><span class="pun">(</span><span class="pln">image_path</span><span class="pun">)</span></pre>

<p>
	ننشئ <code>filters</code> نضمّن فيها جميع الفلاتر التي يدعمها برنامجنا بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_21" style=""><span class="pln">filters </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"Grayscale"</span><span class="pun">:</span><span class="pln"> apply_grayscale</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"Blur"</span><span class="pun">:</span><span class="pln"> apply_blur</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"Edge Detection"</span><span class="pun">:</span><span class="pln"> apply_edges</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"Sharpen"</span><span class="pun">:</span><span class="pln"> apply_sharpen</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"Cartoon"</span><span class="pun">:</span><span class="pln"> apply_cartoon</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"Remove Background"</span><span class="pun">:</span><span class="pln"> remove_background
</span><span class="pun">}</span></pre>

<p>
	إن أردنا تجزئة منطق البرنامج المطلوب ليسهل علينا فهمه، يمكننا النظر إليه بكونه يقدّم ثلاث مزايا أساسية:
</p>

<ol>
	<li>
		تطبيق الفلتر على صورة
	</li>
	<li>
		عرض الصورة على الواجهة البرمجية
	</li>
	<li>
		حفظ الصورة التي طُبّق عليها الفلتر على الحاسب
	</li>
</ol>

<p>
	لنشرح الشيفرة البرمجية لكل ميزة على حدى بالتفصيل فيما يلي.
</p>

<h3 id="-">
	تطبيق الفلتر على الصورة
</h3>

<p>
	نطبّق الفلتر على الصورة من خلال الدالة التالية، ونضع بعين الاعتبار الحالات التي من الممكن أن تُفشل عمل البرنامج مثل عدم وجود الفلتر الذي تم اختياره في برنامجنا أو عدم استطاعة البرنامج على قراءة الصورة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_23" style=""><span class="kwd">def</span><span class="pln"> apply_filter</span><span class="pun">():</span><span class="pln">
</span><span class="com"># جعل نطاق المتغيرات نطاق عام يمكن الوصول إليهما في أي مكان من البرنامج</span><span class="pln">
    </span><span class="kwd">global</span><span class="pln"> img</span><span class="pun">,</span><span class="pln"> img_display

</span><span class="com"># بحال لم يختر المستخدم الصورة بعد أو لم يستطع البرنامج قراءة الصورة</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> img </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">

</span><span class="com"># تخزين الفلتر الذي اختاره المستخدم من الواجهة الرسومية</span><span class="pln">
    selected_filter </span><span class="pun">=</span><span class="pln"> filter_var</span><span class="pun">.</span><span class="pln">get</span><span class="pun">()</span><span class="pln">

</span><span class="com"># في حال لم يكن الفلتر مدعومًا من قبل البرنامج</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> selected_filter </span><span class="kwd">not</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> filters</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">

</span><span class="com"># نسخ الصورة بعد قرائتها وتطبيق الفلتر على الصورة</span><span class="pln">
    image </span><span class="pun">=</span><span class="pln"> img</span><span class="pun">.</span><span class="pln">copy</span><span class="pun">()</span><span class="pln">
    image </span><span class="pun">=</span><span class="pln"> filters</span><span class="pun">[</span><span class="pln">selected_filter</span><span class="pun">](</span><span class="pln">image</span><span class="pun">)</span><span class="pln">

</span><span class="com"># تحويل الصورة إلى صورة بألوان RGB في حال كانت صورة بتدرجات رمادية</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">image</span><span class="pun">.</span><span class="pln">shape</span><span class="pun">)</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">2</span><span class="pun">:</span><span class="pln">  </span><span class="com"># Convert grayscale to RGB for display</span><span class="pln">
        image </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">cvtColor</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">COLOR_GRAY2RGB</span><span class="pun">)</span><span class="pln">

</span><span class="com"># ‫تحويل ترميز الصورة من نظام BGR إلى RGB‬</span><span class="pln">
    image </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">cvtColor</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">COLOR_BGR2RGB</span><span class="pun">)</span><span class="pln">

</span><span class="com"># تحويل مصفوفة القيم إلى صورة</span><span class="pln">
    image </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">.</span><span class="pln">fromarray</span><span class="pun">(</span><span class="pln">image</span><span class="pun">)</span><span class="pln">

</span><span class="com"># تحويل الصورة إلى ترميز يدعمه‫ Tkinter لعرضها على الواجهة الرسومية‬</span><span class="pln">
    img_display </span><span class="pun">=</span><span class="pln"> </span><span class="typ">ImageTk</span><span class="pun">.</span><span class="typ">PhotoImage</span><span class="pun">(</span><span class="pln">image</span><span class="pun">)</span><span class="pln">

</span><span class="com"># عرض الصورة على الواجهة الرسومية</span><span class="pln">
    label_img</span><span class="pun">.</span><span class="pln">config</span><span class="pun">(</span><span class="pln">image</span><span class="pun">=</span><span class="pln">img_display</span><span class="pun">)</span></pre>

<p>
	ملاحظة: الخطوات الأخيرة هي بسبب اختلاف تعامل كل من مكتبة OpenCV وTkinter -وبالنتيجة مكتبة Pillow- بعرض وتمثيل الصور، إذ تستخدم كل مكتبة ترميزًا معينًا والتحويل السابق بين الترميز ضروري لعرض الصورة على الواجهة الرسومية.
</p>

<h3 id="-">
	عرض الصورة على الواجهة الرسومية
</h3>

<p>
	نريد أيضًا أن نعرض الصورة الأولية بعد أن نختارها من مستعرض الملفات، لذا ننشئ الدالة <code>load_image</code> لتحقيق ذلك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_25" style=""><span class="kwd">def</span><span class="pln"> load_image</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">global</span><span class="pln"> img</span><span class="pun">,</span><span class="pln"> img_display

</span><span class="com"># فتح نافذة مستعرض الملفات واختيار الصورة بالامتدادت المدعومة المضمنة</span><span class="pln">
    file_path </span><span class="pun">=</span><span class="pln"> filedialog</span><span class="pun">.</span><span class="pln">askopenfilename</span><span class="pun">(</span><span class="pln">filetypes</span><span class="pun">=[(</span><span class="str">"Image Files"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"*.jpg;*.png;*.jpeg"</span><span class="pun">)])</span><span class="pln">

</span><span class="com"># في حال تعذّر وجود الصورة</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> file_path</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">

</span><span class="com"># قراء الصورة وضبط أبعادها وتحضيرها للعرض على الواجهة الرسومية</span><span class="pln">
    img </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">imread</span><span class="pun">(</span><span class="pln">file_path</span><span class="pun">)</span><span class="pln">
    img_resized </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">resize</span><span class="pun">(</span><span class="pln">img</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="lit">400</span><span class="pun">,</span><span class="pln"> </span><span class="lit">300</span><span class="pun">))</span><span class="pln">
    img_resized </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">cvtColor</span><span class="pun">(</span><span class="pln">img_resized</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">COLOR_BGR2RGB</span><span class="pun">)</span><span class="pln">
    img_resized </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">.</span><span class="pln">fromarray</span><span class="pun">(</span><span class="pln">img_resized</span><span class="pun">)</span><span class="pln">

</span><span class="com"># عرض الصورة على الواجهة</span><span class="pln">
    img_display </span><span class="pun">=</span><span class="pln"> </span><span class="typ">ImageTk</span><span class="pun">.</span><span class="typ">PhotoImage</span><span class="pun">(</span><span class="pln">img_resized</span><span class="pun">)</span><span class="pln">
    label_img</span><span class="pun">.</span><span class="pln">config</span><span class="pun">(</span><span class="pln">image</span><span class="pun">=</span><span class="pln">img_display</span><span class="pun">)</span></pre>

<h3 id="-">
	حفظ الصورة الناتجة على الحاسب
</h3>

<p>
	أخيرًا، نريد حفظ الصورة الناتجة على حاسبنا، لذا نكتب دالة نسميها <code>save_image</code> بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_27" style=""><span class="kwd">def</span><span class="pln"> save_image</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">global</span><span class="pln"> img

</span><span class="com"># في حال تعذر قراءة الصورة أو كونها فارغة</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> img </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">

</span><span class="com"># الحصول على اسم الفلتر الذي اخترناه لتضمين اسمه في الصورة المحفوظة</span><span class="pln">
    selected_filter </span><span class="pun">=</span><span class="pln"> filter_var</span><span class="pun">.</span><span class="pln">get</span><span class="pun">()</span><span class="pln">
    output_dir </span><span class="pun">=</span><span class="pln"> </span><span class="str">"output"</span><span class="pln">

</span><span class="com"># ‫ننشئ مجلد الصور الناتجة output في حال عدم وجوده‬</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">exists</span><span class="pun">(</span><span class="pln">output_dir</span><span class="pun">):</span><span class="pln">
        os</span><span class="pun">.</span><span class="pln">makedirs</span><span class="pun">(</span><span class="pln">output_dir</span><span class="pun">)</span><span class="pln">

    output_filename </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">output_dir</span><span class="pun">,</span><span class="pln"> f</span><span class="str">"filtered_{selected_filter}.jpg"</span><span class="pun">)</span><span class="pln">
    cv2</span><span class="pun">.</span><span class="pln">imwrite</span><span class="pun">(</span><span class="pln">output_filename</span><span class="pun">,</span><span class="pln"> img</span><span class="pun">)</span></pre>

<p>
	القيم الموجودة في <code>filters</code> هي جميع الفلاتر التي سندعمها في برنامجنا، وسنبرمج المنطق الخاص بكل واحد منها ولكن دعونا أولًا نبرمج الواجهة الرسومية!
</p>

<h2 id="-">
	الخطوة الخامسة: برمجة الواجهة الرسومية
</h2>

<p>
	نبني الواجهة الرسومية بشكل بسيط بحيث تحتوي على ثلاثة أزرار: زر اختيار الصورة، وزر تطبيق الفلتر وزر حفظ الصورة، بالإضافة إلى قائمة منسدلة تحتوي الفلاتر التي يدعمها البرنامج لكي يستطيع المستخدم الاختيار بينها بالإضافة لإطار يعرض الصورة التي اخترناها أو الصورة الناتجة بعد الفلترة بالشكل التالي:
</p>

<p style="text-align: center;">
	<img class="ipsImage ipsImage_thumbnailed" data-fileid="182153" data-ratio="115.94" data-unique="1yov98cni" width="502" alt="004 واجهة تطبيق بايثون.png" src="https://academy.hsoub.com/uploads/monthly_2026_04/004.png.79c72860eae1343a13f735648575128e.png">
</p>

<p>
	نكتب الشيفرة البرمجية التالية لتحقيق الشكل السابق:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_29" style=""><span class="com"># إنشاء عقدة جذر البرنامج</span><span class="pln">
root </span><span class="pun">=</span><span class="pln"> tk</span><span class="pun">.</span><span class="typ">Tk</span><span class="pun">()</span><span class="pln">

</span><span class="com"># ضبط عنوان نافذة البرنامج وأبعادها</span><span class="pln">
root</span><span class="pun">.</span><span class="pln">title</span><span class="pun">(</span><span class="str">"Image Filter App"</span><span class="pun">)</span><span class="pln">
root</span><span class="pun">.</span><span class="pln">geometry</span><span class="pun">(</span><span class="str">"500x550"</span><span class="pun">)</span><span class="pln">

</span><span class="com"># ‫إنشاء متغير img وإسناد قيمة أولية له‬</span><span class="pln">
img </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">

</span><span class="com"># إنشاء زر إضافة الصورة ونص العنوان</span><span class="pln">
</span><span class="typ">Label</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">=</span><span class="str">"Image Filter Application"</span><span class="pun">,</span><span class="pln"> font</span><span class="pun">=(</span><span class="str">"Arial"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">16</span><span class="pun">)).</span><span class="pln">pack</span><span class="pun">(</span><span class="pln">pady</span><span class="pun">=</span><span class="lit">10</span><span class="pun">)</span><span class="pln">

</span><span class="typ">Button</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">=</span><span class="str">"Load Image"</span><span class="pun">,</span><span class="pln"> command</span><span class="pun">=</span><span class="pln">load_image</span><span class="pun">).</span><span class="pln">pack</span><span class="pun">(</span><span class="pln">pady</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span><span class="pln">

label_img </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Label</span><span class="pun">(</span><span class="pln">root</span><span class="pun">)</span><span class="pln">
label_img</span><span class="pun">.</span><span class="pln">pack</span><span class="pun">(</span><span class="pln">pady</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span><span class="pln">

</span><span class="com"># القائمة المنسدلة التي تحتوي على الفلاتر</span><span class="pln">
filter_var </span><span class="pun">=</span><span class="pln"> tk</span><span class="pun">.</span><span class="typ">StringVar</span><span class="pun">()</span><span class="pln">
filter_var</span><span class="pun">.</span><span class="pln">set</span><span class="pun">(</span><span class="str">"Grayscale"</span><span class="pun">)</span><span class="pln"> </span><span class="com"># نضبط فلتر التدرج الرمادي كقيمة افتراضية</span><span class="pln">
filter_menu </span><span class="pun">=</span><span class="pln"> ttk</span><span class="pun">.</span><span class="typ">Combobox</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> textvariable</span><span class="pun">=</span><span class="pln">filter_var</span><span class="pun">,</span><span class="pln"> values</span><span class="pun">=</span><span class="pln">list</span><span class="pun">(</span><span class="pln">filters</span><span class="pun">.</span><span class="pln">keys</span><span class="pun">()),</span><span class="pln"> state</span><span class="pun">=</span><span class="str">"readonly"</span><span class="pun">)</span><span class="pln">
filter_menu</span><span class="pun">.</span><span class="pln">pack</span><span class="pun">(</span><span class="pln">pady</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span><span class="pln">

</span><span class="com"># إضافة الأزرار</span><span class="pln">
</span><span class="typ">Button</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">=</span><span class="str">"Apply Filter"</span><span class="pun">,</span><span class="pln"> command</span><span class="pun">=</span><span class="pln">apply_filter</span><span class="pun">).</span><span class="pln">pack</span><span class="pun">(</span><span class="pln">pady</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span><span class="pln">
</span><span class="typ">Button</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">=</span><span class="str">"Save Image"</span><span class="pun">,</span><span class="pln"> command</span><span class="pun">=</span><span class="pln">save_image</span><span class="pun">).</span><span class="pln">pack</span><span class="pun">(</span><span class="pln">pady</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span><span class="pln">

root</span><span class="pun">.</span><span class="pln">mainloop</span><span class="pun">()</span></pre>

<h2 id="-">
	الخطوة الرابعة: كتابة الفلاتر المتاحة
</h2>

<p>
	سنضمّن المنطق لكل فلتر من الفلاتر الستة في دالة منفصلة، تسمح لنا هذه الطريقة بمعاملة كل فلتر على حدا بحيث لا يؤثر التعديل عليه أو حذفه أو إضافة فلتر جديد على عمل البرنامج ككل.
</p>

<h3 id="-grayscale">
	فلتر التدرج الرمادي Grayscale
</h3>

<p>
	نبدأ بأبسط الفلاتر عملًا وهو فلتر تغيير ألوان الصورة إلى تدرجات اللون الرمادي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_31" style=""><span class="kwd">def</span><span class="pln"> apply_grayscale</span><span class="pun">(</span><span class="pln">image</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">cvtColor</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">COLOR_BGR2GRAY</span><span class="pun">)</span></pre>

<p>
	تعمل الدالة <code>cvtColor</code> على تغيير قيم البكسلات الموجودة في الصورة التي تحتوي على القيم الثلاث الأحمر والأخضر والأزرق حتى تحتوي على قيمة واحدة تدلّ على شدة اللون مما يمنحنا اللون الرمادي بتدرجاته.
</p>

<p style="text-align: center;">
	<img class="ipsImage ipsImage_thumbnailed" data-fileid="182154" data-ratio="115.94" data-unique="8duzm825o" width="502" alt="005 فلتر تدرج الرمادي.png" src="https://academy.hsoub.com/uploads/monthly_2026_04/005.png.46ce091afb5404b30486bf36414c065f.png">
</p>

<h3 id="-blur">
	فلتر التغبيش Blur
</h3>

<p>
	ننتقل فيما بعد إلى فلتر التغبيش blur ولدى مكتبة opencv دالة جاهزة يمكننا استخدامها ألا وهي <code>GaussianBlur</code> ونمرر لها ثلاث قيم، الصورة ومقدار التشويش والانحراف المعياري للمرشح:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_33" style=""><span class="kwd">def</span><span class="pln"> apply_blur</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> ksize </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="lit">15</span><span class="pun">,</span><span class="lit">15</span><span class="pun">)):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> cv2</span><span class="pun">.</span><span class="typ">GaussianBlur</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> ksize</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span></pre>

<p>
	يمكننا زيادة مقدار التغبيش بزيادة قيمة المعامل الثاني <code>ksize</code>.
</p>

<p style="text-align: center;">
	<img class="ipsImage ipsImage_thumbnailed" data-fileid="182155" data-ratio="115.94" data-unique="21vgd3e8t" width="502" alt="006 فلتر التغبيش.png" src="https://academy.hsoub.com/uploads/monthly_2026_04/006.png.c7c315de22d1036958b071bc59b4d416.png">
</p>

<h3 id="-edge-detection">
	فلتر الكشف عن الحواف Edge detection
</h3>

<p>
	الفلتر الثالث هو فلتر الكشف عن الحواف edge detection وهو فلتر يستخدم بكثرة في تطبيقات الرؤية الحاسوبية للكشف عن الأجسام والتعرف عليها، إذ يعتمد على التغيرات الكبيرة بين بكسل وبكسل آخر يجاوره. لدينا أيضًا تابع جاهز تقدمة مكتبة opencv وهو <code>Canny</code> نمرر له ثلاث قيم وهي الصورة والعتبة الدنيا والعتبة العليا، وهي قيمتين تتحكمان بمقدار الحساسية للكشف عن حافة ما.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_35" style=""><span class="kwd">def</span><span class="pln"> apply_edges</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> low_threshold</span><span class="pun">=</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> high_threshold</span><span class="pun">=</span><span class="lit">200</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> cv2</span><span class="pun">.</span><span class="typ">Canny</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> low_threshold</span><span class="pun">,</span><span class="pln"> high_threshold</span><span class="pun">)</span></pre>

<p>
	يمكنك تغيير قيم العتبة الدنيا والعليا للتحكم بالحساسية، وسيولد لك البرنامج نتائجًا مختلفة.
</p>

<p style="text-align: center;">
	<img class="ipsImage ipsImage_thumbnailed" data-fileid="182156" data-ratio="115.94" data-unique="42v7qrq3i" width="502" alt="007 فلتر كشف الحواف.png" src="https://academy.hsoub.com/uploads/monthly_2026_04/007.png.0ba09c8eab312d94c74ce8a7a42a30c7.png">
</p>

<h3 id="-sharpen">
	فلتر الشحذ Sharpen
</h3>

<p>
	ننتقل إلى فلتر الشحذ Sharpen الذي يبرز حواف الصورة بشكل أكبر ويزيد من التباين في الصورة، نستخدم في هذا الفلتر مصفوفة معرّفة وهو أحد المرشحات Kernel التي ذكرناها في بداية المقال بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_37" style=""><span class="kwd">def</span><span class="pln"> apply_sharpen</span><span class="pun">(</span><span class="pln">image</span><span class="pun">):</span><span class="pln">
    kernel </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">array</span><span class="pun">([[</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">],</span><span class="pln">
                       </span><span class="pun">[-</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">],</span><span class="pln">
                       </span><span class="pun">[</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">]])</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">filter2D</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> kernel</span><span class="pun">)</span></pre>

<p>
	نستطيع الاستفادة من هذا الفلتر عن طريق توضيح الصور المغبّشة غير الواضحة.
</p>

<h3 id="-cartoon">
	فلتر الرسم الكرتوني Cartoon
</h3>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_39" style=""><span class="kwd">def</span><span class="pln"> apply_cartoon</span><span class="pun">(</span><span class="pln">image</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># تحويل الصورة إلى صورة ذات تدرج رمادي ‪(بشكل مشابه للفلتر الأول)‎‬</span><span class="pln">
    gray </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">cvtColor</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">COLOR_BGR2GRAY</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># تطبيق تغبيش على الصورة</span><span class="pln">
    blurred </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">medianBlur</span><span class="pun">(</span><span class="pln">gray</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># التعرف على الحواف وتنعيمها للحصول على نتيجة تشبه صورة مرسومة</span><span class="pln">
    edges </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">adaptiveThreshold</span><span class="pun">(</span><span class="pln">blurred</span><span class="pun">,</span><span class="pln"> </span><span class="lit">255</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">ADAPTIVE_THRESH_MEAN_C</span><span class="pun">,</span><span class="pln">
                                  cv2</span><span class="pun">.</span><span class="pln">THRESH_BINARY</span><span class="pun">,</span><span class="pln"> </span><span class="lit">9</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># تغبيش الصورة مع المحافظة على الحواف مشحوذة</span><span class="pln">
    color </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">bilateralFilter</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> </span><span class="lit">9</span><span class="pun">,</span><span class="pln"> </span><span class="lit">250</span><span class="pun">,</span><span class="pln"> </span><span class="lit">250</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># دمج صورة الحواف مع صورة الألوان</span><span class="pln">
    cartoon </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">bitwise_and</span><span class="pun">(</span><span class="pln">color</span><span class="pun">,</span><span class="pln"> color</span><span class="pun">,</span><span class="pln"> mask</span><span class="pun">=</span><span class="pln">edges</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> cartoon</span></pre>

<p>
	قد يصعب تخيّل نتيجة كل عملية فقط عن طريق قراءة الشيفرة البرمجية، لذا نشجعك هنا على فصل كل تعليمة لوحدها ورؤية نتيجتها على حدى. النتيجة النهائية ستبدو كالشكل التالي:
</p>

<p style="text-align: center;">
	<img class="ipsImage ipsImage_thumbnailed" data-fileid="182157" data-ratio="115.94" data-unique="w1s398ohb" width="502" alt="008 فلتر الرسم الكرتوني.png" src="https://academy.hsoub.com/uploads/monthly_2026_04/008.png.4111f5d84978e644d6d3b0dfd4938a48.png">
</p>

<h3 id="-remove-background">
	فلتر إزالة الخلفية Remove Background
</h3>

<p>
	أخيرًا، نكتب الفلتر الأخير في هذا البرنامج ألا وهو فلتر إزالة الخلفية. هذا الفلتر مفيد في الحالات التي تريد فيها عزل كائن ما عن البيئة المحيطة به مثل صورة شخصية أو في مثالنا هذا النمر.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_41" style=""><span class="kwd">def</span><span class="pln"> remove_background</span><span class="pun">(</span><span class="pln">image</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># ننشئ نسخة بذات أبعاد الصورة لكن نملؤها بالأصفار</span><span class="pln">
    mask </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">zeros</span><span class="pun">(</span><span class="pln">image</span><span class="pun">.</span><span class="pln">shape</span><span class="pun">[:</span><span class="lit">2</span><span class="pun">],</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">uint8</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># نستخدم خوارزمية‫ GrabCut الموجودة في opencv لإنشاء نموذج للكائن في الصورة‬</span><span class="pln">
    bgd_model </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">zeros</span><span class="pun">((</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">65</span><span class="pun">),</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">float64</span><span class="pun">)</span><span class="pln">
    fgd_model </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">zeros</span><span class="pun">((</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">65</span><span class="pun">),</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">float64</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># نعرف مستطيلًا حول النموذج الذي أنشأناه</span><span class="pln">
    rect </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="lit">50</span><span class="pun">,</span><span class="pln"> </span><span class="lit">50</span><span class="pun">,</span><span class="pln"> image</span><span class="pun">.</span><span class="pln">shape</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">50</span><span class="pun">,</span><span class="pln"> image</span><span class="pun">.</span><span class="pln">shape</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">50</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># ‫ننفذ خوارزمية GrabCut ‬</span><span class="pln">
    cv2</span><span class="pun">.</span><span class="pln">grabCut</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> mask</span><span class="pun">,</span><span class="pln"> rect</span><span class="pun">,</span><span class="pln"> bgd_model</span><span class="pun">,</span><span class="pln"> fgd_model</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">GC_INIT_WITH_RECT</span><span class="pun">)</span><span class="pln">
    mask </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">where</span><span class="pun">((</span><span class="pln">mask </span><span class="pun">==</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> </span><span class="pun">(</span><span class="pln">mask </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">),</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">).</span><span class="pln">astype</span><span class="pun">(</span><span class="str">"uint8"</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># نطبق الصورة ذات القيم الصفرية على صورتنا لإزالة الخلفية</span><span class="pln">
    result </span><span class="pun">=</span><span class="pln"> image </span><span class="pun">*</span><span class="pln"> mask</span><span class="pun">[:,</span><span class="pln"> </span><span class="pun">:,</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">newaxis</span><span class="pun">]</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> result</span></pre>

<p>
	نحصل على النتيجة التالية:
</p>

<p style="text-align: center;">
	<img class="ipsImage ipsImage_thumbnailed" data-fileid="182158" data-ratio="115.94" data-unique="qm9d11m0d" width="502" alt="009 فلتر إزالة الخلفية.png" src="https://academy.hsoub.com/uploads/monthly_2026_04/009.png.cbf916a3f6254a233077052f0290c0b9.png">
</p>

<p>
	سنكتفي بهذه الفلاتر في تطبيقنا، ونترك لكم حرية التجربة وإضافة المزيد من الفلاتر على البرنامج، كل ما عليكم هو كتابة دالة جديدة تحتوي على المنطق البرمجي الذي يستخدمه هذا الفلتر وإضافتها إلى <code>filters</code> واستدعاؤها. ندعوكم لتجربة فلتر آخر وليكن عكس الألوان Invert colors وتجربة تضمينه في البرنامج لتحسينه.
</p>

<h2 id="-">
	الشيفرة البرمجية الكاملة للتطبيق
</h2>

<p>
	إليكم كامل الشيفرة الخاصة بالتطبيق لاختبارها وتجربتها لديكم:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_43" style=""><span class="kwd">import</span><span class="pln"> cv2
</span><span class="kwd">import</span><span class="pln"> numpy </span><span class="kwd">as</span><span class="pln"> np
</span><span class="kwd">import</span><span class="pln"> os
</span><span class="kwd">import</span><span class="pln"> tkinter </span><span class="kwd">as</span><span class="pln"> tk
</span><span class="kwd">from</span><span class="pln"> tkinter </span><span class="kwd">import</span><span class="pln"> filedialog</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Label</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Button</span><span class="pun">,</span><span class="pln"> ttk
</span><span class="kwd">from</span><span class="pln"> PIL </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">,</span><span class="pln"> </span><span class="typ">ImageTk</span><span class="pln">

</span><span class="com"># دالة فلتر التدرج الرمادي</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> apply_grayscale</span><span class="pun">(</span><span class="pln">image</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">cvtColor</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">COLOR_BGR2GRAY</span><span class="pun">)</span><span class="pln">

</span><span class="com"># دالة فلتر التغبيش</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> apply_blur</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> ksize</span><span class="pun">=(</span><span class="lit">25</span><span class="pun">,</span><span class="pln"> </span><span class="lit">25</span><span class="pun">)):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> cv2</span><span class="pun">.</span><span class="typ">GaussianBlur</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> ksize</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">

</span><span class="com"># دالة الكشف عن الحواف</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> apply_edges</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> low_threshold</span><span class="pun">=</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> high_threshold</span><span class="pun">=</span><span class="lit">200</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> cv2</span><span class="pun">.</span><span class="typ">Canny</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> low_threshold</span><span class="pun">,</span><span class="pln"> high_threshold</span><span class="pun">)</span><span class="pln">

</span><span class="com"># دالة فلتر الشحذ</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> apply_sharpen</span><span class="pun">(</span><span class="pln">image</span><span class="pun">):</span><span class="pln">
    kernel </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">array</span><span class="pun">([[</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">],</span><span class="pln">
                       </span><span class="pun">[-</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">],</span><span class="pln">
                       </span><span class="pun">[</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">]])</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">filter2D</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> kernel</span><span class="pun">)</span><span class="pln">

</span><span class="com"># دالة فلتر الرسوم الكرتونية</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> apply_cartoon</span><span class="pun">(</span><span class="pln">image</span><span class="pun">):</span><span class="pln">
    gray </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">cvtColor</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">COLOR_BGR2GRAY</span><span class="pun">)</span><span class="pln">
    blurred </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">medianBlur</span><span class="pun">(</span><span class="pln">gray</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">)</span><span class="pln">
    edges </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">adaptiveThreshold</span><span class="pun">(</span><span class="pln">blurred</span><span class="pun">,</span><span class="pln"> </span><span class="lit">255</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">ADAPTIVE_THRESH_MEAN_C</span><span class="pun">,</span><span class="pln">
                                  cv2</span><span class="pun">.</span><span class="pln">THRESH_BINARY</span><span class="pun">,</span><span class="pln"> </span><span class="lit">9</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">)</span><span class="pln">
    color </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">bilateralFilter</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> </span><span class="lit">9</span><span class="pun">,</span><span class="pln"> </span><span class="lit">250</span><span class="pun">,</span><span class="pln"> </span><span class="lit">250</span><span class="pun">)</span><span class="pln">
    cartoon </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">bitwise_and</span><span class="pun">(</span><span class="pln">color</span><span class="pun">,</span><span class="pln"> color</span><span class="pun">,</span><span class="pln"> mask</span><span class="pun">=</span><span class="pln">edges</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> cartoon

</span><span class="com"># ‫دالة فلتر إزالة الخلفية باستخدام خوارزمية Grab cut‬</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> remove_background</span><span class="pun">(</span><span class="pln">image</span><span class="pun">):</span><span class="pln">
    mask </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">zeros</span><span class="pun">(</span><span class="pln">image</span><span class="pun">.</span><span class="pln">shape</span><span class="pun">[:</span><span class="lit">2</span><span class="pun">],</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">uint8</span><span class="pun">)</span><span class="pln">
    bgd_model </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">zeros</span><span class="pun">((</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">65</span><span class="pun">),</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">float64</span><span class="pun">)</span><span class="pln">
    fgd_model </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">zeros</span><span class="pun">((</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">65</span><span class="pun">),</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">float64</span><span class="pun">)</span><span class="pln">

    rect </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="lit">50</span><span class="pun">,</span><span class="pln"> </span><span class="lit">50</span><span class="pun">,</span><span class="pln"> image</span><span class="pun">.</span><span class="pln">shape</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">50</span><span class="pun">,</span><span class="pln"> image</span><span class="pun">.</span><span class="pln">shape</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">50</span><span class="pun">)</span><span class="pln">
    cv2</span><span class="pun">.</span><span class="pln">grabCut</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> mask</span><span class="pun">,</span><span class="pln"> rect</span><span class="pun">,</span><span class="pln"> bgd_model</span><span class="pun">,</span><span class="pln"> fgd_model</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">GC_INIT_WITH_RECT</span><span class="pun">)</span><span class="pln">
    mask2 </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">where</span><span class="pun">((</span><span class="pln">mask </span><span class="pun">==</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> </span><span class="pun">(</span><span class="pln">mask </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">),</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">).</span><span class="pln">astype</span><span class="pun">(</span><span class="str">"uint8"</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> image </span><span class="pun">*</span><span class="pln"> mask2</span><span class="pun">[:,</span><span class="pln"> </span><span class="pun">:,</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">newaxis</span><span class="pun">]</span><span class="pln">

</span><span class="com"># الفلاتر التي يدعمها البرنامج</span><span class="pln">
filters </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"grayscale"</span><span class="pun">:</span><span class="pln"> apply_grayscale</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"blur"</span><span class="pun">:</span><span class="pln"> apply_blur</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"edges"</span><span class="pun">:</span><span class="pln"> apply_edges</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"sharpen"</span><span class="pun">:</span><span class="pln"> apply_sharpen</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"cartoon"</span><span class="pun">:</span><span class="pln"> apply_cartoon</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"remove_bg"</span><span class="pun">:</span><span class="pln"> remove_background
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> apply_filter</span><span class="pun">():</span><span class="pln">
</span><span class="com"># جعل نطاق المتغيرات نطاق عام يمكن الوصول إليهما في أي مكان من البرنامج</span><span class="pln">
    </span><span class="kwd">global</span><span class="pln"> img</span><span class="pun">,</span><span class="pln"> img_display

</span><span class="com"># بحال لم يختر المستخدم الصورة بعد أو لم يستطع البرنامج قراءة الصورة</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> img </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">

</span><span class="com"># تخزين الفلتر الذي اختاره المستخدم من الواجهة الرسومية</span><span class="pln">
    selected_filter </span><span class="pun">=</span><span class="pln"> filter_var</span><span class="pun">.</span><span class="pln">get</span><span class="pun">()</span><span class="pln">

</span><span class="com"># في حال لم يكن الفلتر مدعومًا من قبل البرنامج</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> selected_filter </span><span class="kwd">not</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> filters</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">

</span><span class="com"># نسخ الصورة بعد قرائتها وتطبيق الفلتر على الصورة</span><span class="pln">
    image </span><span class="pun">=</span><span class="pln"> img</span><span class="pun">.</span><span class="pln">copy</span><span class="pun">()</span><span class="pln">
    image </span><span class="pun">=</span><span class="pln"> filters</span><span class="pun">[</span><span class="pln">selected_filter</span><span class="pun">](</span><span class="pln">image</span><span class="pun">)</span><span class="pln">

</span><span class="com"># تحويل الصورة إلى صورة بألوان RGB في حال كانت صورة بتدرجات رمادية</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">image</span><span class="pun">.</span><span class="pln">shape</span><span class="pun">)</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">2</span><span class="pun">:</span><span class="pln">  </span><span class="com"># Convert grayscale to RGB for display</span><span class="pln">
        image </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">cvtColor</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">COLOR_GRAY2RGB</span><span class="pun">)</span><span class="pln">

</span><span class="com"># ‫تحويل ترميز الصورة من نظام BGR إلى RGB‬</span><span class="pln">
    image </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">cvtColor</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">COLOR_BGR2RGB</span><span class="pun">)</span><span class="pln">

</span><span class="com"># تحويل مصفوفة القيم إلى صورة</span><span class="pln">
    image </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">.</span><span class="pln">fromarray</span><span class="pun">(</span><span class="pln">image</span><span class="pun">)</span><span class="pln">

</span><span class="com"># تحويل الصورة إلى ترميز يدعمه‫ Tkinter لعرضها على الواجهة الرسومية‬</span><span class="pln">
    img_display </span><span class="pun">=</span><span class="pln"> </span><span class="typ">ImageTk</span><span class="pun">.</span><span class="typ">PhotoImage</span><span class="pun">(</span><span class="pln">image</span><span class="pun">)</span><span class="pln">

</span><span class="com"># عرض الصورة على الواجهة الرسومية</span><span class="pln">
    label_img</span><span class="pun">.</span><span class="pln">config</span><span class="pun">(</span><span class="pln">image</span><span class="pun">=</span><span class="pln">img_display</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> load_image</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">global</span><span class="pln"> img</span><span class="pun">,</span><span class="pln"> img_display

</span><span class="com"># فتح نافذة مستعرض الملفات واختيار الصورة بالامتدادت المدعومة المضمنة</span><span class="pln">
    file_path </span><span class="pun">=</span><span class="pln"> filedialog</span><span class="pun">.</span><span class="pln">askopenfilename</span><span class="pun">(</span><span class="pln">filetypes</span><span class="pun">=[(</span><span class="str">"Image Files"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"*.jpg;*.png;*.jpeg"</span><span class="pun">)])</span><span class="pln">

</span><span class="com"># في حال تعذّر وجود الصورة</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> file_path</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">

</span><span class="com"># قراء الصورة وضبط أبعادها وتحضيرها للعرض على الواجهة الرسومية</span><span class="pln">
    img </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">imread</span><span class="pun">(</span><span class="pln">file_path</span><span class="pun">)</span><span class="pln">
    img_resized </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">resize</span><span class="pun">(</span><span class="pln">img</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="lit">400</span><span class="pun">,</span><span class="pln"> </span><span class="lit">300</span><span class="pun">))</span><span class="pln">
    img_resized </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">cvtColor</span><span class="pun">(</span><span class="pln">img_resized</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">COLOR_BGR2RGB</span><span class="pun">)</span><span class="pln">
    img_resized </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">.</span><span class="pln">fromarray</span><span class="pun">(</span><span class="pln">img_resized</span><span class="pun">)</span><span class="pln">

</span><span class="com"># عرض الصورة على الواجهة</span><span class="pln">
    img_display </span><span class="pun">=</span><span class="pln"> </span><span class="typ">ImageTk</span><span class="pun">.</span><span class="typ">PhotoImage</span><span class="pun">(</span><span class="pln">img_resized</span><span class="pun">)</span><span class="pln">
    label_img</span><span class="pun">.</span><span class="pln">config</span><span class="pun">(</span><span class="pln">image</span><span class="pun">=</span><span class="pln">img_display</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> save_image</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">global</span><span class="pln"> img

</span><span class="com"># في حال تعذر قراءة الصورة أو كونها فارغة</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> img </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">

</span><span class="com"># الحصول على اسم الفلتر الذي اخترناه لتضمين اسمه في الصورة المحفوظة</span><span class="pln">
    selected_filter </span><span class="pun">=</span><span class="pln"> filter_var</span><span class="pun">.</span><span class="pln">get</span><span class="pun">()</span><span class="pln">
    output_dir </span><span class="pun">=</span><span class="pln"> </span><span class="str">"output"</span><span class="pln">

</span><span class="com"># ‫ننشئ مجلد الصور الناتجة output في حال عدم وجوده‬</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">exists</span><span class="pun">(</span><span class="pln">output_dir</span><span class="pun">):</span><span class="pln">
        os</span><span class="pun">.</span><span class="pln">makedirs</span><span class="pun">(</span><span class="pln">output_dir</span><span class="pun">)</span><span class="pln">

    output_filename </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">output_dir</span><span class="pun">,</span><span class="pln"> f</span><span class="str">"filtered_{selected_filter}.jpg"</span><span class="pun">)</span><span class="pln">
    cv2</span><span class="pun">.</span><span class="pln">imwrite</span><span class="pun">(</span><span class="pln">output_filename</span><span class="pun">,</span><span class="pln"> img</span><span class="pun">)</span><span class="pln">

</span><span class="com"># إنشاء عقدة جذر البرنامج</span><span class="pln">
root </span><span class="pun">=</span><span class="pln"> tk</span><span class="pun">.</span><span class="typ">Tk</span><span class="pun">()</span><span class="pln">

</span><span class="com"># ضبط عنوان نافذة البرنامج وأبعادها</span><span class="pln">
root</span><span class="pun">.</span><span class="pln">title</span><span class="pun">(</span><span class="str">"Image Filter App"</span><span class="pun">)</span><span class="pln">
root</span><span class="pun">.</span><span class="pln">geometry</span><span class="pun">(</span><span class="str">"500x550"</span><span class="pun">)</span><span class="pln">

</span><span class="com"># ‫إنشاء متغير img وإسناد قيمة أولية له‬</span><span class="pln">
img </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">

</span><span class="com"># إنشاء زر إضافة الصورة ونص العنوان</span><span class="pln">
</span><span class="typ">Label</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">=</span><span class="str">"Image Filter Application"</span><span class="pun">,</span><span class="pln"> font</span><span class="pun">=(</span><span class="str">"Arial"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">16</span><span class="pun">)).</span><span class="pln">pack</span><span class="pun">(</span><span class="pln">pady</span><span class="pun">=</span><span class="lit">10</span><span class="pun">)</span><span class="pln">

</span><span class="typ">Button</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">=</span><span class="str">"Load Image"</span><span class="pun">,</span><span class="pln"> command</span><span class="pun">=</span><span class="pln">load_image</span><span class="pun">).</span><span class="pln">pack</span><span class="pun">(</span><span class="pln">pady</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span><span class="pln">

label_img </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Label</span><span class="pun">(</span><span class="pln">root</span><span class="pun">)</span><span class="pln">
label_img</span><span class="pun">.</span><span class="pln">pack</span><span class="pun">(</span><span class="pln">pady</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span><span class="pln">

</span><span class="com"># القائمة المنسدلة التي تحتوي على الفلاتر</span><span class="pln">
filter_var </span><span class="pun">=</span><span class="pln"> tk</span><span class="pun">.</span><span class="typ">StringVar</span><span class="pun">()</span><span class="pln">
filter_var</span><span class="pun">.</span><span class="pln">set</span><span class="pun">(</span><span class="str">"Grayscale"</span><span class="pun">)</span><span class="pln"> </span><span class="com"># نضبط فلتر التدرج الرمادي كقيمة افتراضية</span><span class="pln">
filter_menu </span><span class="pun">=</span><span class="pln"> ttk</span><span class="pun">.</span><span class="typ">Combobox</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> textvariable</span><span class="pun">=</span><span class="pln">filter_var</span><span class="pun">,</span><span class="pln"> values</span><span class="pun">=</span><span class="pln">list</span><span class="pun">(</span><span class="pln">filters</span><span class="pun">.</span><span class="pln">keys</span><span class="pun">()),</span><span class="pln"> state</span><span class="pun">=</span><span class="str">"readonly"</span><span class="pun">)</span><span class="pln">
filter_menu</span><span class="pun">.</span><span class="pln">pack</span><span class="pun">(</span><span class="pln">pady</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span><span class="pln">

</span><span class="com"># إضافة الأزرار</span><span class="pln">
</span><span class="typ">Button</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">=</span><span class="str">"Apply Filter"</span><span class="pun">,</span><span class="pln"> command</span><span class="pun">=</span><span class="pln">apply_filter</span><span class="pun">).</span><span class="pln">pack</span><span class="pun">(</span><span class="pln">pady</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span><span class="pln">
</span><span class="typ">Button</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">=</span><span class="str">"Save Image"</span><span class="pun">,</span><span class="pln"> command</span><span class="pun">=</span><span class="pln">save_image</span><span class="pun">).</span><span class="pln">pack</span><span class="pun">(</span><span class="pln">pady</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span><span class="pln">

root</span><span class="pun">.</span><span class="pln">mainloop</span><span class="pun">()</span></pre>

<h2 id="-">
	الخاتمة
</h2>

<p>
	استعرضنا في هذا المقال كيفية عمل فلاتر الصور، وشرحنا خطوات تطوير تطبيق بايثون البسيط يتضمن بعض الفلاتر بالاستعانة بكل من مكتبة OpenCV وPillow وTkinter، إذ نجد أن تطبيق فلتر إلى الصورة ما هو إلا عمليات حسابية تحدث على بيكسلات الصورة، ويتفاوت تعقيد الفلتر من فلتر بسيط يمكن برمجته بسطر واحد إلى فلتر معقد يحتاج إلى خوارزميات متقدمة لتنفيذه. نرجو أن يكون هذا المقال قد وفر لكم الفهم الأساسي لطريقة عمل برنامج لمعالجة الصور وتطبيق فلاتر منوعة عليها.
</p>
]]></description><guid isPermaLink="false">2608</guid><pubDate>Sun, 12 Apr 2026 12:31:45 +0000</pubDate></item><item><title>&#x623;&#x633;&#x627;&#x633;&#x64A;&#x627;&#x62A; GitHub &#x648;&#x643;&#x64A;&#x641;&#x64A;&#x629; &#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645;&#x647;</title><link>https://academy.hsoub.com/programming/workflow/git/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-github-%D9%88%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%D9%87-r2607/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2026_04/GitHub.png.78243a4517b03752ba814fc9c3abf1ab.png" /></p>
<p>
	سنستعرض في هذا المقال أساسيات منصة GitHub، التي تُعد واجهة ويب <a href="https://academy.hsoub.com/programming/workflow/git/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D9%86%D8%B8%D8%A7%D9%85-git-%D9%84%D9%84%D8%AA%D8%AD%D9%83%D9%85-%D9%81%D9%8A-%D8%A7%D9%84%D8%A5%D8%B5%D8%AF%D8%A7%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r2606/" rel="">لنظام التحكم في الإصدارات Git</a> والمملوكة لشركة Microsoft. سنركّز في هذا المقال على المفاهيم الأساسية للمنصة وآلية العمل عليها، بدءًا من إنشاء حساب جديد، ووصولًا إلى إعداد المصادقة Authentication لتأمين الوصول إلى الحساب واستخدامه بكفاءة.
</p>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			يفترض هذا المقال معرفة أساسية بنظام Git للتحكم في الإصدارات البرمجية واستخدام الطرفية أو سطر الأوامر.
		</p>
	</div>
</blockquote>

<h2 id="-github">
	إنشاء حساب GitHub
</h2>

<p>
	اذهب إلى موقع <a href="https://github.com" rel="external nofollow">GitHub</a> واضغط على زر إنشاء حساب جديد Sign up واتبع الإرشادات التي تظهر على الشاشة لإنشاء الحساب حتى تصل إلى لوحة التحكم في حسابك بعد إتمام تسجيل الدخول بنجاح.
</p>

<h2 id="-github">
	إنشاء مستودع جديد في GitHub
</h2>

<p>
	عند إنشاء مستودع جديد في GitHub فإنه يكون موجودًا في GitHub فقط وليس على حاسوبك المحلي، صحيح أنك تملكه بما أنك أنشأته وفي حسابك، لكن إذا أردت نقله إلى المستودع المحلي فيجب أن ننسخه إلى هناك، كما سنوضح لاحقًا. لننشئ المستودع الجديد الآن:
</p>

<ul>
	<li>
		<p>
			يوجد زر New أخضر على يسار اللوحة في الغالب، كما توجد قائمة منسدلة عليها علامة <code>+</code> أعلى جهة اليمين تحتوي خيار New Repository، هاتان طريقتان لإنشاء مستودع جديد تستطيع استخدام أيهما.
		</p>
	</li>
	<li>
		<p>
			أدخل اسم المستودع في الصفحة التي تلي ذلك، اختر أي اسم تريد بشرط ألا يكون لديك مستودع سابق بنفس الاسم، لنكتب <code>test-repo</code> لغرض مثالنا هنا.
		</p>
	</li>
	<li>
		<p>
			فعِّل خيار Add a README file لإضافة ملف Readme. لاحظ أن عليك تجنب هذا الخيار في المستقبل إذا كان لديك مستودع محلي وتريد دفعه push إلى هذه المستودع الجديد، لأن هذا سيمنع عملية الدفع.
		</p>
	</li>
	<li>
		<p>
			انقر على زر Create repository في أسفل الصفحة.
		</p>
	</li>
</ul>

<p>
	وهكذا صار لدينا أول مستودع خاص بنا.
</p>

<h2 id="-">
	مصادقة الحسابات
</h2>

<p>
	ينبغي أن نشرح عملية المصادقة authentication أولًا قبل الانتقال إلى شرح الاستنساخ cloning، ذلك أنك إذا جربت مزامنة مستودع محلي على حاسوبك مع مستودع بعيد على GitHub فلن تنجح لأن دعم المصادقة بكلمة المرور قد ألغي في 2021، فأمامنا الآن عدة خيارات:
</p>

<ul>
	<li>
		استخدام أداة GitHub CLI.
	</li>
	<li>
		استخدام مفاتيح <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr>.
	</li>
	<li>
		استخدام مفاتيح المصادقة الشخصية Personal Authentication Tokens.
	</li>
</ul>

<p>
	يُعد الخيار الأول هو الأسهل، أما مفاتيح <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr> فهي خيار متقدم بالنسبة للمبتدئين.
</p>

<h3 id="-github">
	واجهة سطر أوامر GitHub
</h3>

<p>
	تمثل أداة GitHub CLI واجهة نصية لموقع GitHub وتؤدي عدة وظائف أهمها تسهيل عملية المصادقة لحسابك فيه لتتمكن من تنفيذ مهامك مع Git كدفع ملفاتك إلى مستودع بعيد مثلًا.
</p>

<p>
	لتثبيت هذه الأداة، زر <a href="https://cli.github.com/" rel="external nofollow">صفحتها</a> واتبع <a href="https://github.com/cli/cli#installation" rel="external nofollow">تعليمات التثبيت</a> الخاصة بنظام تشغيلك، ثم جرب تشغيل الأمر <code>gh --version</code> بعد التثبيت لتتأكد من نجاح تثبيتها من خلال عرض معلومات إصدارها:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4296_9" style=""><span class="pln">$ gh --version
  gh version 2.42.1 (2024-01-15)
  https://github.com/cli/cli/releases/tag/v2.42.1</span></pre>

<p>
	ثم نفذ الأمرين التاليين:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4296_12" style=""><span class="pln">$ gh auth login
$ gh auth setup-git</span></pre>

<p>
	يأخذك الأمر الأول عبر خطوات تسجيل الدخول، ولن تحتاج إلى تكرار هذا إلا إذا سجلت الخروج، ثم يسألك النظام إذا كنت تريد استخدام بروتوكول <code><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr></code> أو <code>HTTP</code>، ننصح باختيار <code><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr></code> إذا كنت أعددت مفاتيح <code><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr></code> مسبقًا، وإلا فاختر <code>HTTP</code>، الفرق بينهما أن <code>HTTP</code> يخزن بياناتك دون تشفير بينما يسمح لك <code><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr></code> بحمايتها بكلمة مرور، ومع هذا يظل خيار تسجيل الدخول من المتصفح هو الأسهل.
</p>

<p>
	أما الأمر الثاني <code>gh auth setup-git</code> فننفذه مرة واحدة فقط حيث يضيف بعض العناصر المساعدة في عملية المصادقة إلى الإعدادات العامة.
</p>

<h3 id="-ssh">
	مفاتيح <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr>
</h3>

<p>
	لدينا خيار آخر للمصادقة هو مفتاح <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr>، وذلك إن لم ترغب في استخدام أداة GitHub CLI السابقة، سنبدأ بتوليد مفتاح أولًا ثم استخدامه، تجاوز هذه الخطوة إذا كان لديك زوج من مفاتيح <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr> بالفعل -ستعرف أنها لديك إذا نفذت الأمر:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4296_14" style=""><span class="pln">ls ~/.ssh</span></pre>

<p>
	ورأيت ملفًا مثل <code>id_rsa.pub</code> أو <code>id_ed25519.pub</code>. نفذ الأمر التالي لإنشاء زوج جديد من المفاتيح:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4296_16" style=""><span class="pln">$ ssh-keygen -t ed25519 -C عنوان بريد الإلكتروني هنا</span></pre>

<p>
	يضع الخيار <code>‎-C</code> تعليقًا داخل المفتاح، قد يكون أي شيء تريده، لكن الشائع هنا هو كتابة عنوان بريدك الإلكتروني، ستظهر لك عدة أسئلة تستطيع تجاوزها بالضغط على مفتاح الإدخال Enter.
</p>

<h4 id="-">
	نصيحة أمنية
</h4>

<p>
	يفضل استخدام كلمة مرور للوصول إلى هذا المفتاح، ذلك أن وصول شخص غيرك إليه يمكّنه من انتحال شخصيتك والوصول إلى حسابك في GitHub، ويلجأ المطورون إلى استخدام Key Agent يتذكر كلمة المرور لفترة زمنية محددة.
</p>

<p>
	أما إذا لم تضع كلمة مرور لمفتاحك فإنك تتركه عرضة للسرقة معتمدًا على ظن أنه لا يمكن الحصول على نسخة من الجزء الخاص من مفتاحك المخزن على حاسوبك، لكن فكر في الأمر من المنطق التالي: هل ستكون بحاجة إلى كلمة مرور أصلًا إذا كنت تثق في أمان حاسوبك؟
</p>

<p>
	على أي حال سنترك كلمة المرور فارغة في مثالنا هذا، إذ يمكن إعادة هذه الخطوات لإنشاء مفتاح جديد بكلمة مرور إذا أردت ذلك، فاطلع على <a href="https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent" rel="external nofollow">هذا التوثيق من GitHub لإنشاء مفتاح <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr></a>.
</p>

<p>
	استمر بالنقر على مفتاح الإدخال إذا ظهرت أي أسئلة على الشاشة حتى تصل إلى شيء شبيه بما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4296_18" style=""><span class="pln">Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/user/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in id_ed25519
Your public key has been saved in id_ed25519.pub
The key fingerprint is:
SHA256:/lrT43BQBRPJpUXxpTBFInhdtZSQjQwxU4USwt5c0Lw user@locahost
The key's randomart image is:
+--[ED25519 256]--+
|        .o.X^%^=+|
|        ..oo*^.=o|
|        ..o = o..|
|         . + E   |
|        S .      |
|       .   o     |
|        . + +    |
|         o = .   |
|        ... .    |
+----[SHA256]-----+</span></pre>

<p>
	سنحتاج إلى إجراء بعض الإعدادات الإضافية على المفتاح إذا اخترنا اسم ملف غير الاسم الافتراضي لكي نتمكن من تشغيله مع GitHub بشكل صحيح.
</p>

<p>
	لاحظ هذا الرسم العشوائي الغريب في المثال أعلاه، وهو مجرد تمثيل بصري للمفتاح، وتوجد طرق لإعداد <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr> بحيث تُعرض لك هذه اللوحة في كل مرة تسجل فيها الدخول، وفكرتها أنك إذا رأيت اختلافًا في اللوحة في مرة ما فهذا يعني أن شيئًا ما قد تغير في إعدادات الأمان. اكتب الآن <code>ls ~/.<abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">ssh</abbr></abbr></code>، يجب أن تظهر نتيجة كهذه:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4296_20" style=""><span class="pln">id_ed25519    id_ed25519.pub</span></pre>

<p>
	لا تشارك الملف الأول مع أحد أبدًا لأنه مفتاحك الخاص private key، بل لا يوجد سبب يدعوك حتى لنسخه، أما الملف الثاني هو المفتاح العام public key، وهو ما تستطيع مشاركته بأمان، وسنشاركه مع GitHub بعد قليل لكي تسجل الدخول باستخدامه.
</p>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			جرب تنفيذ الأمرين أدناه إذا واجهت مشكلة بسبب تدقيق <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr> الأمني:
		</p>

		<pre style="text-align: left;"> chmod 700 ~/.ssh $
*/chmod 600 ~/.ssh $
</pre>

		<p>
			هذه عملية ستتم مرة واحدة فقط، إن لم تكن أذونات الملفات مقيدة.
		</p>
	</div>
</blockquote>

<p>
	نعطي المفتاح العام الآن إلى GitHub عبر نسخه.
</p>

<p>
	لنحصل أولًا على نسخة منه ننفذ الأمر التالي -غير اسم الملف لاسم الملف الخاص بك-:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4296_22" style=""><span class="pln">$ cat ~/.ssh/id_ed25519.pub</span></pre>

<p>
	ينبغي أن تظهر لديك نتيجة كالتالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4296_24" style=""><span class="pln">ssh-ed25519 AAAC3N[بعض المحارف هنا]V+znpoO عنوان بريدك</span></pre>

<p>
	انسخ السطر كاملًا إلى الحافظة لتستطيع لصقه لاحقًا.
</p>

<h4 id="-github">
	ربط المفتاح بـ GitHub
</h4>

<p>
	اذهب الآن إلى GitHub وانقر على صورتك الشخصية في أعلى اليمين ثم اتبع ما يلي:
</p>

<ol>
	<li>
		اختر الإعدادات Settings.
	</li>
	<li>
		اختر <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr> and GPG keys.
	</li>
	<li>
		اختر New <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr> key
		<ul>
			<li>
				أدخل عنوانًا مناسبًا مثل "my laptop key" في حقل Title.
			</li>
			<li>
				تأكد من اختيار Authentication key في حقل Key type.
			</li>
			<li>
				الصق المفتاح الذي نسخناه قبل قليل في حقل key.
			</li>
			<li>
				انقر على Add <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr> key.
			</li>
		</ul>
	</li>
</ol>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			تذكر أننا استخدمنا بروتوكول <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr> لاستنساخ روابط المستودعات لاحقًا.
		</p>
	</div>
</blockquote>

<h3 id="-">
	استخدام مفاتيح الوصول الشخصي
</h3>

<p>
	حان الوقت لننشئ كلمات مرور جديدة نستخدمها مع GitHub، وستكون هذه المرة من نوع المفتاح الشخصي personal token، يوفر GitHub توثيقًا مفصلًا حول الموضوع لكن ملخصه أننا سننشئ مفتاح token يمثل بعض الصلاحيات، مثل القراءة والكتابة في مستودعاتي، وسنستخدمه بدلًا من كلمة المرور العادية في سطر الأوامر كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4296_26" style=""><span class="pln">Username for 'https://github.com': [اسم المستخدم]
Password for 'https://beejjorgensen@github.com': [مفتاح Token]</span></pre>

<p>
	قد يحفظ حاسوبك هذا المفتاح لئلا تضطر إلى إدخاله في كل مرة، المهم أن إحدى أفضل مزاياه هي التحكم الدقيق في الصلاحيات حيث تستطيع تقييد الوصول ليكون للقراءة فقط أو اقتصار الصلاحيات على مستودعات معينة، وكذلك نستطيع استخدام مصادقة أداة GitHub CLI مع هذا المفتاح من خلال إعطاء المفتاح للأداة عبر الإدخال القياسي standard input.
</p>

<p>
	لنفرض أنك حفظت المفتاح في ملف باسم <code>mytoken.txt</code>، نستطيع حينها إثبات هويتنا عبر واجهة أوامر GitHub كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4296_28" style=""><span class="pln">$ gh auth login --with-token &lt; mytoken.txt</span></pre>

<p>
	تستطيع إبطال صلاحية مفتاح معين إذا فقدت الوصول إلى الحاسوب المحفوظ عليه بسرقة أو ضياع، تمامًا كما في حالة مفاتيح <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr>.
</p>

<h2 id="-">
	إنشاء نسخة محلية من المستودع
</h2>

<p>
	نحتاج الآن إلى معرفة رابط المستودع كي نستنسخه:
</p>

<ol>
	<li>
		انقر على أيقونة صورتك الشخصية في الزاوية العلوية اليمنى ثم اختر Your repositories، ستظهر لك صفحة تضم جميع مستودعاتك، قد لا تجد هنا سوى المستودع <code>test-repo</code> الذي أنشأناه، فانقر على اسمه.
	</li>
	<li>
		تنتقل الآن إلى صفحة المستودع حيث تتصف الملفات الخاصة به، لكن هدفنا الأساسي هو الحصول على رابط الاستنتساخ، فانقر على زر Code الأزرق الكبير.
	</li>
</ol>

<p>
	لننسخه الآن من GitHub إلى الحاسوب المحلي، سيختلف هذا إذا كنت تستخدم GitHub CLI أو مفاتيح <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr>، لكن سنشرح الطريقتين.
</p>

<h3 id="-github-cli">
	الاستنتساخ من GitHub باستخدام CLI
</h3>

<p>
	لدينا خياران هنا:
</p>

<p>
	<strong>الخيار الأول:</strong> أشرنا سابقًا إلى تذكر أننا سنستخدم بروتوكول <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr> عندما أثبتنا هويتنا باستخدام <code>gh auth login</code>، وعليه سنختار التبويب الموافق لذلك في هذه النافذة، لكن إذا اخترت HTTPS فستختار تبويبها. انسخ الرابط واذهب إلى سطر الأوامر ونفذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4296_30" style=""><span class="pln"> git clone [url]</span></pre>

<p>
	حيث تضع الرابط الذي نسخته بدلًا من الأقواس، فمثلًا سيكون هكذا في حالة HTTPS:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4296_32" style=""><span class="pln">$ git clone https://github.com/user/test-repo.git</span></pre>

<p>
	وهذه في حالة <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr>:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4296_34" style=""><span class="pln">$ git clone git@github.com:user/test-repo.git</span></pre>

<p>
	<strong>الخيار الثاني: </strong>اختر تبويب GitHub CLI من القائمة المنسدلة ثم نفذ الأمر كما هو مكتوب هناك تمامًا، سيكون شبيها بالسطر التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4296_36" style=""><span class="pln">$ gh repo clone user/test-repo</span></pre>

<h3 id="-github-ssh">
	الاستنساخ من GitHub باستخدام مفاتيح <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr>
</h3>

<p>
	نستخدم هذه الطريقة إذا كنا أعددنا مفتاح <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr> من قبل:
</p>

<ol>
	<li>
		تأكد من اختيار تبويب <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr> بعد النقر على زر Code الأخضر.
	</li>
	<li>
		انسخ هذا الرابط.
	</li>
	<li>
		انتقل إلى سطر الأوامر ونفذ الأمر <code>git clone [URL]</code> حيث تضع الرابط الذي نسخته بدلًا من الأقواس بحيث يكون كالتالي:
	</li>
</ol>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4296_38" style=""><span class="pln">$ git clone git@github.com:user/test-repo.git</span></pre>

<h2 id="-">
	إجراء التعديلات ودفع الملفات إلى المستودع البعيد
</h2>

<p>
	ننتقل الآن إلى مجلد المستودع بعد استنساخه باستخدام الأمر <span style="font-family:Courier New,Courier,monospace;">git clone</span>، ثم ندخل إليه باستخدام الأمر
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_8970_7" style=""><span class="pln">cd اسم المجلد</span></pre>

<p>
	ونعدّل أحد الملفات، وبعد ذلك نضيفه إلى منطقة التحضير stage باستخدام:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_8970_9" style=""><span class="pln">git add اسم الملف</span></pre>

<p>
	ثم ننفذ عملية إيداع باستخدام:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_8970_11" style=""><span class="pln">git commit -m "message"</span></pre>

<p>
	وأخيرًا لدفعه إلى النسخة البعيدة على GitHub مرة أخرى:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4296_44" style=""><span class="pln">git push</span></pre>

<p>
	يجب أن نرى هذه التغييرات الآن إذا عدنا إلى صفحة المستودع على GitHub وأعدنا تحميل الصفحة.
</p>

<p>
	هذا يعيدنا إلى <a href="https://academy.hsoub.com/programming/workflow/git/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D9%86%D8%B8%D8%A7%D9%85-git-%D9%84%D9%84%D8%AA%D8%AD%D9%83%D9%85-%D9%81%D9%8A-%D8%A7%D9%84%D8%A5%D8%B5%D8%AF%D8%A7%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r2606/" rel="">مسار العمل الشائع على نظام Git</a>:
</p>

<ol>
	<li>
		استنساخ مستودع بعيد
	</li>
	<li>
		إجراء بعض التعديلات على النسخة المحلية.
	</li>
	<li>
		إضافة التغييرات إلى منطقة التحضير.
	</li>
	<li>
		تسجيل هذه التغييرات وإيداعها من خلال عملية commit.
	</li>
	<li>
		دفع هذه التغييرات إلى المستودع البعيد.
	</li>
	<li>
		العودة مرة أخرى إلى الخطوة رقم 2.
	</li>
</ol>

<h2 id="-github">
	التعاون في GitHub
</h2>

<p>
	توجد طريقتان أساسيتان للتعاون في موقع GitHub هما:
</p>

<ol>
	<li>
		الاشتقاق وطلب السحب Fork/Pull Request.
	</li>
	<li>
		إضافة مساهم collaborator.
	</li>
</ol>

<p>
	الطريقة الأسهل منهما هي إضافة المساهمين مباشرة إلى مستودعك، اتبع الخطوات التالية:
</p>

<ol>
	<li>
		اختر الإعدادات Settings.
	</li>
	<li>
		اختر collaborators من القائمة الجانبية اليسرى.
	</li>
	<li>
		أكد هويتك ثم انقر على زر Add people.
	</li>
	<li>
		أدخل اسم المستخدم للشخص الذي تريد التعاون معه.
	</li>
</ol>

<p>
	يجب أن يقبل المساهم دعوتك من الرسالة التي وصلته على حسابه في GitHub ليحصل على صلاحية الوصول إلى مستودعك، لكن انتبه إلى إعطاء صلاحية الوصول إلى من تثق فيه فقط لأنه سيملك صلاحية التعديل في الملفات الموجودة في المستودع.
</p>

<h2 id="-">
	خاتمة
</h2>

<p>
	تعرفنا في هذا المقال على أساسيات موقع GitHub وكيفية إنشاء حساب فيه، وكذلك إنشاء مستودع بعيد واستنساخه إلى حاسوبك المحلي، وإجراء تعديلات فيه ثم دفعه مرة أخرى إلى موقع GitHub.
</p>

<h2 id="-">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/workflow/git/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B7%D9%84%D8%A8-%D8%B3%D8%AD%D8%A8-%D8%B9%D9%84%D9%89-github-r1581/" rel=""><strong>إنشاء طلب سحب على GitHub</strong></a>
	</li>
	<li>
		<strong><a href="https://academy.hsoub.com/programming/workflow/git/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D9%86%D8%B8%D8%A7%D9%85-git-%D9%84%D9%84%D8%AA%D8%AD%D9%83%D9%85-%D9%81%D9%8A-%D8%A7%D9%84%D8%A5%D8%B5%D8%AF%D8%A7%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r2606/" rel="">أساسيات نظام Git للتحكم في الإصدارات البرمجية</a></strong>
	</li>
</ul>

<p>
	ترجمة -بتصرف- <a href="https://beej.us/guide/bggit/html/#github-how-to-use-it" rel="external nofollow">للفصل الثاني من دليل Beej إلى Git</a> لصاحبه Brian “Beej Jorgensen” Hall
</p>
]]></description><guid isPermaLink="false">2607</guid><pubDate>Sun, 05 Apr 2026 13:04:00 +0000</pubDate></item><item><title>&#x623;&#x633;&#x627;&#x633;&#x64A;&#x627;&#x62A; &#x646;&#x638;&#x627;&#x645; Git &#x644;&#x644;&#x62A;&#x62D;&#x643;&#x645; &#x641;&#x64A; &#x627;&#x644;&#x625;&#x635;&#x62F;&#x627;&#x631;&#x627;&#x62A; &#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x64A;&#x629;</title><link>https://academy.hsoub.com/programming/workflow/git/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D9%86%D8%B8%D8%A7%D9%85-git-%D9%84%D9%84%D8%AA%D8%AD%D9%83%D9%85-%D9%81%D9%8A-%D8%A7%D9%84%D8%A5%D8%B5%D8%AF%D8%A7%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r2606/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2026_03/git.png.01e46e5bb90ee4a7c4b825c852fa0065.png" /></p>
<p>
	يرى العديد من المبتدئين في مجال البرمجة أن نظام إدارة المشاريع البرمجية Git صعب أو معقد أكثر من اللازم، ورغم أنه حقًا يبدو معقدًا إلا أننا سننتقل بك في هذا الدليل من المستوى المبتدئ إلى المتوسط من خلال استخدام النظام عمليًا أثناء شرحنا له، بدءًا بمزج بعض الأوامر البسيطة مع قليل من فكرة العمل التي بني عليها، ثم ننظر في الأمور التي نستطيع تنفيذها به، وسيكون هذا المقال عن أساسيات النظام نفسه ومبدأ عمله.
</p>

<p>
	يساعدك هذا الدليل على معرفة قواعد وأوامر Git في بيئة سطر الأوامر وبناء تصور لفهم كيفية تخزينه للبيانات، ذلك أنك تحتاج إلى معرفة ما يدور خلف الكواليس في هذا النظام من أجل إتقان استخدامه، بدلًا من الحصول السهل على الأوامر الشائعة بالبحث في الإنترنت أو استخدام أدوات الذكاء الاصطناعي.
</p>

<h2 id="-">
	الفئة المستهدفة
</h2>

<p>
	يستهدف برايان "بيج جورجينسن" هول Brian “Beej Jorgensen” Hall -مؤلف الدليل- طلاب الجامعة بشكل رئيسي لكن أسلوب الشرح مصمم بحيث يستطيع غيرهم من المبتدئين في البرمجة أن يستفيدوا منه أيضًا، وإن كنا نفترض معرفتك بكيفية التعامل مع سطر الأوامر في طرفيات POSIX مثل Bash أو Zsh وغيرهما، أي تعرف الأوامر الأساسية مثل <code>cd, ls, mkdir, cp</code> وغيرها، وكذلك تثبيت البرامج التي نتعرض لها أثناء الشرح.
</p>

<p>
	ننصح باستخدام أحد أنظمة لينكس في تعلمك لنظام Git ورحلتك البرمجية عمومًا لأنها تُعد أنظمة شبيهة بيونكس UNIX LIKE من حيث موافقتها لمعايير POSIX لنظم التشغيل من ناحية، وجاهزيتها للأدوات والمكتبات البرمجية من ناحية أخرى، أو استخدام أنظمة أخرى شبيهة بيونكس مثل FreeBSD أو ماك، لكن إن كنت تستخدم ويندوز فإن نظام Git يأتي مع طرفية تسمى Git Bash، كما تستطيع تثبيت <a href="https://learn.microsoft.com/en-us/windows/wsl/" rel="external nofollow">WSL</a>.
</p>

<h3 id="-git">
	ما هو نظام Git
</h3>

<p>
	يوصف Git بأنه نظام للتحكم في الشيفرات المصدرية Source code أو الإصدارات البرمجية حيث يحتفظ بسجل من اللقطات snapshots لملفات المشروع بحيث تمثل كل لقطة منها حالة جميع الملفات التي يتتبعها النظام في لحظة زمنية ما، فتستطيع بهذا أن تعود إلى نقطة زمنية معينة أثناء العمل على المشروع لتعاينها.
</p>

<p>
	فإذا عملت على المشروع وأجريت بعض التغييرات لإضافة ميزة جديدة مثلًا، تستطيع إيداع تلك التغييرات حين تكون جاهزة إلى مستودع ملفات المشروع فيما يعرف بعملية commit، وهو المكان الذي تحفظ فيه شيفرة العمل، وهكذا تكون تلك التغييرات متاحة في المستودع repository للاطلاع عليها لمن له وصول إليه، كزملائك في العمل مثلًا.
</p>

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

<h4 id="-">
	اصطلاحات
</h4>

<p>
	<strong>نظام التحكم في الإصدارات أو نظام التحكم في الشيفرة المصدرية</strong>: هو برنامج يدير التغييرات التي تحدث في مشروع برمجي قد يكون فيه آلاف الملفات التي يكتبها أو يحررها مئات المطورين، ونظام Git هو أحد هذه الأنظمة، لكن يوجد غيره أيضًا.
</p>

<ul>
</ul>

<p>
	<strong>المستودع Repo/Repository</strong>: هو المكان الذي يُحفظ فيه مشروع برمجي ما داخل Git، ويكون لكل مشروع في العادة مستودع خاص، فقد تنشئ مستودعًا من أجل مشروع جديد تعمل عليه، ويكون إما موجودًا على حاسوبك الشخصي أو على حواسيب بعيدة.
</p>

<p>
	<strong>المستودع البعيد Remote Repo</strong>: هو نسخة من مستودع في مكان غير حاسوبك المحلي.
</p>

<p>
	<strong>الاستنساخ Clone</strong>: أن تنشئ نسخة محلية على حاسوبك من مستودع بعيد remote repo، وتسمى هذه النسخة نفسها clone أيضًا.
</p>

<p>
	<strong>شجرة العمل Working Tree</strong>: الدليل أو المجلد الذي تعدل فيه ملفات المشروع، تُنشأ هذه الشجرة عند إنشاء نسخة clone من المستودع.
</p>

<p>
	<strong>منطقة التحضير Stage</strong>: هي المكان الذي تضيف فيه الملفات التي تريد تحضيرها لعملية الإيداع commit، فإذا عدلت بعض الملفات لكن لم تضفها هنا أولًا فلن تشملها عملية الإيداع.
</p>

<p>
	<strong>الفهرس Index</strong>: اسم آخر أقل شيوعًا لمنطقة التحضير.
</p>

<p>
	<strong>الإيداع Commit</strong>: هو لقطة لحالة المشروع في لحظة معينة، تتضمن التعديلات التي أُضيفت إلى منطقة التحضير، ويُستخدم لتوثيق التغييرات وتتبعها عبر الزمن.
</p>

<p>
	أمثلة على رسائل الإيداع:
</p>

<ul>
	<li>
		إضافة الميزة س إلى القاعدة البرمجية
	</li>
	<li>
		إصلاح خطأ ص
	</li>
	<li>
		دمج تغييرات مساهمين آخرين في الشيفرة
	</li>
</ul>

<h3 id="-github">
	ما هو GitHub
</h3>

<p>
	هو موقع إلكتروني يوفر واجهة أمامية للكثير من مزايا Git إضافة إلى بعض المزايا الخاصة بـ GitHub نفسه كما يوفر مساحة تخزين سحابية لمستودعك ليغنيك عن إنشاء نسخة احتياطية، فيمكن القول أنه تطبيق ويب يعمل كواجهة أمامية لنظام Git ويتعامل مع نسخة مستودعك الموجودة في خوادم GitHub.
</p>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			ربما تكون قد سمعت عن <a href="https://about.gitlab.com/" rel="external nofollow">GitLab</a> أو <a href="https://docs.gitea.com/" rel="external nofollow">Gitea</a>، وهما منافسان لموقع GitHub، مع ميزة لموقع Gitea أنه مفتوح المصدر ويمكنك تشغيل واجهة تشبه GitHub على خادمك الخاص.
		</p>
	</div>
</blockquote>

<p>
	سيكون لديك أيضًا نسخ من المستودعات المخزنة محليًا على حاسوبك لتعمل عليها تعرف باسم Clones، مستقلة عن المستودعات الموجودة على GitHub، ثم تزامن أنت هذه النسخ من حاسوبك مع GitHub دوريًا أثناء مسار عملك المعتاد.
</p>

<h3 id="-git">
	مسار عمل بسيط في Git
</h3>

<p>
	يوجد عدة مسارات عمل قد تتبعها أثناء عملك مع Git، منها ما يكثر استخدامه ومنها الذي قد لا تستخدمه إلا نادرًا، فيما يلي أحد المسارات الشائعة:
</p>

<ol>
	<li>
		الاستنساخ clone: في البداية تنسخ مستودعًا بعيدًا، أي ليس محفوظًا على حاسوبك المحلي، يكون في العادة على GitHub.
	</li>
	<li>
		مرحلة التعديل: تنفذ بعض التغييرات في شجرة العمل working tree، وهي المكان الذي توجد فيه ملفات المشروع على جهازك.
	</li>
	<li>
		الإضافة إلى الفهرس: تضيف بعدها تلك التغييرات إلى منطقة التحضير stage، والتي تعرف أيضًا بالفهرس Index.
	</li>
	<li>
		الإيداع Commit: تودع هذه التغييرات لتسجيلها كلقطة snapshot.
	</li>
	<li>
		مرحلة الدفع Push: ادفع اللقطة التي سجلتها لنقلها إلى المستودع البعيد.
	</li>
	<li>
		عد إلى الخطوة رقم 2 وكرر المسار.
	</li>
</ol>

<h3 id="-">
	ما هو الاستنساخ
</h3>

<blockquote>
	<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
		<div class="ipsQuote_citation">
			اقتباس
		</div>

		<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
			<p>
				<strong>نظام التحكم الموزَّع في الإصدارات Distributed version control system</strong>
			</p>

			<p>
				هو نظام تتواجد فيه نسخ متعددة clones من المستودعات ولا توجد فيه سلطة مركزية للبيانات، فحين تنسخ مستودعًا يكون لديك نسختين منه، واحدة بعيدة remote، وأخرى على حاسوبك المحلي، وهي نسخ مستقلة بذاتها بمعنى أن التعديلات التي تجريها في نسختك المحلية لا تؤثر في النسخة البعيدة إلا إذا رغبت أن تتفاعل النسختين مع بعضهما البعض وربطتهما بنفسك.
			</p>
		</div>
	</blockquote>
</blockquote>

<p>
	يُعد Git في ضوء هذا التعريف نظام تحكم موزع في الإصدارات، هذا يعني أنه لا توجد سلطة واحدة مركزية للبيانات على عكس الأنظمة الأخرى للتحكم في الإصدارات، مع أن مستخدمي Git يعاملون GitHub على أنه نظام موزع كذلك.
</p>

<p>
	يعتمد Git هيكلًا من نُسخ المستودعات بدلًا من نظام السلطة المركزية، وهي نسخ كاملة ومستقلة فيها سجل بجميع الإيداعات لكل مستودع، فيمكن إعادة إنشاء أي نسخة من نسخة أخرى غيرها، وتتساوى جميع النسخ في الإمكانيات فلا توجد نسخة أقوى أو أفضل من غيرها.
</p>

<p>
	إذا راجعنا مسار العمل الذي ذكرناه قبل قليل سنرى أن استنساخ المستودع هو الخطوة الأولى، وإذا كنا نستنسخ المستودع من GitHub فهذا يعني إنشاء نسخة محلية من مستودع كامل في GitHub، ورغم أننا نستطيع إنشاء أي عدد نريد من النسخ إلا أن هذه عملية تتم مرة واحدة في العادة.
</p>

<h3 id="-">
	كيف تتفاعل النسخ مع بعضها
</h3>

<p>
	هناك عمليتان رئيسيتان نستخدمهما بعد إنشاء نسخة ما:
</p>

<ul>
	<li>
		الدفع Push: تأخذ عملية الدفع اللقطات التي أودعتها على حاسوبك المحلي وترفعها إلى المستودع البعيد.
	</li>
	<li>
		السحب Pull: بالعكس، تأخذ عملية السحب اللقطات الموجودة في المستودع البعيد وتنزلها إلى حاسوبك المحلي.
	</li>
</ul>

<p>
	كما توجد عملية أخرى تحدث خلف الكواليس هي عملية الدمج merge، وينبغي أن تعلم أن تعديلاتك التي تجريها على نسختك المحلية لن تظهر في المستودع البعيد إلا عندما تدفع بذلك التغيير إلى المستودع البعيد، وبالمثل لن تظهر التعديلات الموجودة في المستودع البعيد على حاسوبك المحلي إلا عند سحبك pull للقطة الموجودة في المستودع.
</p>

<h3 id="-git">
	الاستخدام الفعلي لنظام Git
</h3>

<p>
	يفترض هذا القسم أن أدوات سطر أوامر Git مثبتة لديك وأنك ستستخدم إحدى طرفيات يونكس مثل Bash أو Zsh.
</p>

<blockquote>
	<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
		<div class="ipsQuote_citation">
			اقتباس
		</div>

		<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
			<p>
				من أين تحصل على هذه الطرفيات؟
			</p>

			<p>
				إذا كنت تستخدم أحد الأنظمة الشبيهة بنظام يونكس، مثل لينكس أو BSD أو يونكس أو ماك فهي تأتي مع النظام، أما في حالة ويندوز فننصح بتثبيت <a href="https://learn.microsoft.com/en-us/windows/wsl/" rel="external nofollow">نظام ويندوز الفرعي للينكس</a>، أو استخدام طرفية Git Bash التي تأتي مع نظام Git.
			</p>
		</div>
	</blockquote>
</blockquote>

<p>
	سنفترض في حالتنا هنا أن لدينا مستودعًا في GitHub ونريد نسخه، وعليه سنمر بالخطوات التي ذكرناها قبل قليل، لكن سنضيف إليها خطوة جديدة تسبقها:
</p>

<h4 id="-0-">
	الخطوة 0: إعداد بياناتك لأول مرة
</h4>

<p>
	يجب أن تخبر النظام باسمك وبريدك الإلكتروني في أول مرة تستخدمه لأنها ستُضاف إلى كل اللقطات التي تودعها في المستودع، ويمكن تغيير هذه البيانات في أي وقت لاحقًا أو تخصيص تلك البيانات لتكون مختلفة لكل مستودع، أما لغرض هذا المثال فسنختار اسمك وعنوان البريد كبيانات عامة لنظام Git، أدخل كليهما في سطر الأوامر لضبط اسمك وبريدك الإلكتروني.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1418_8" style=""><span class="pln">$ git config set --global user.name "اسمك"
$ git config set --global user.email "بريدك الإلكتروني"</span></pre>

<blockquote>
	<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
		<div class="ipsQuote_citation">
			اقتباس
		</div>

		<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
			<p>
				إذا أردت تغيير الاسم أو عنوان البريد لاحقًا فكرر الأمرين في المثال السابق.
			</p>
		</div>
	</blockquote>
</blockquote>

<p>
	لا تنسخ المحث <code>$</code> في المثال أعلاه فهو يتغير وفقًا للطرفية التي تستخدمها، فقد يكون <code>$</code> أو <code>#</code> أو <code>%</code>، ونحن نضيفه في هذه الأمثلة ليدل على أن السطر الذي بعده يُكتب في الطرفية أو سطر الأوامر.
</p>

<p>
	أخيرًا، نحدد اسم الفرع الافتراضي default branch ليكون <code>main</code> من خلال الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1418_10" style=""><span class="pln">$ git config set --global init.defaultBranch main</span></pre>

<p>
	سيكون الفرع الافتراضي هو المسار الرئيسي أو نقطة البداية الرسمية لأي مشروع في Git وسيحتوي على النسخة الأساسية والمستقرة من الشيفرة المصدرية لأي مستودع، إذا كان المستودع يمثل شجرة فإن الفرع الافتراضي هو الجذع الرئيسي الذي تخرج منه بقية الأغصان "الفروع الأخرى".
</p>

<p>
	لكن اسم main ليس إجباريًا بل تستطيع اختيار ما شئت، حيث تسمى بعض المستودعات <code>master</code> مثلًا للفرع الرئيسي أو <code>main</code> و <code>trunk</code> و <code>development</code>، وكلها من الأسماء الشائعة للفروع الرئيسية للمستودعات.
</p>

<h4 id="-1-">
	الخطوة 1: استنساخ مستودع
</h4>

<p>
	سننسخ الآن مستودعًا بعيدًا لنتدرب ، ولا تقلق من تعطل هذا المستودع بسبب خطأ منك لأن النظام يحميه، فانتقل في سطر الأوامر إلى الدليل الذي تريد إنشاء النسخة داخله ثم نفذ هذا الأمر.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1418_12" style=""><span class="pln">$ git clone https://github.com/beejjorgensen/git-example-repo.git</span></pre>

<p>
	ستلاحظ ظهور مخرجات مثل هذه:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1418_14" style=""><span class="pln">Cloning into 'git-example-repo'...
remote: Enumerating objects: 10, done.
remote: Counting objects: 100% (10/10), done.
remote: Compressing objects: 100% (8/8), done.
remote: Total 10 (delta 2), reused 9 (delta 1), pack-reused 0
remote: (from 0)
Receiving objects: 100% (10/10), done.
Resolving deltas: 100% (2/2), done.</span></pre>

<p>
	ينبغي أن يكون لدينا المستودع على الحاسوب المحلي الآن، لقد افترضنا في هذا المثال أن المستودع البعيد هو الموجود أولًا ونسخناه إلى الحاسوب، لكن هذه ليست الحالة الوحيدة لاستخدام نظام Git، فأحيانًا ينشئ المطورون المستودع المحلي أولًا ثم يربطونه بمستودع بعيد. لنلقي الآن نظرة على المستودع المحلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1418_16" style=""><span class="pln">$ cd git-example-repo
$ ls -la</span></pre>

<p>
	ستظهر الملفات التي يحتوي عليها المستودع كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1418_18" style=""><span class="pln">os@machine:~$ cd git-example-repo
os@machine:~/git-example-repo$ ls -la
total 20
drwxr-xr-x  3 os os 4096 فبر 23 17:29 .
drwxr-xr-x 22 os os 4096 فبر 24 00:09 ..
drwxr-xr-x  8 os os 4096 فبر 24 00:01 .git
-rwxr-xr-x  1 os os  175 فبر 24 00:09 hello.py
-rw-r--r--  1 os os  162 فبر 23 17:29 README.md</span></pre>

<p>
	نلاحظ في خرج الأمر أعلاه أن المستودع يحتوي على الملفين <code>hello.py</code> و <code>README.md</code> إضافة إلى المجلد <code>‎.git</code> الذي يخزن فيه نظام Git جميع البيانات الوصفية meta data والإيداعات commits الخاصة بالمشروع، وينبغي تجنب العبث بملفات هذا المجلد أو تعديلها لأن وجوده هو ما يمنح المجلد الرئيسي صفة "مستودع Git". نستخدم الأمر <code>status</code> لمعرفة حالة المستودع المحلي كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1418_20" style=""><span class="pln">$ git status</span></pre>

<p>
	فتظهر لنا النتيجة التالية:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1418_22" style=""><span class="pln">On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean</span></pre>

<p>
	تخبرنا هذه النتيجة معلومات كثيرة عن حالة المستودع الراهنة، حيث نعرف منها ما يلي:
</p>

<ul>
	<li>
		يخبرنا السطر الأول أننا الآن في الفرع الرئيسي <code>main</code>.
	</li>
	<li>
		في السطر الثاني نعرف أن الفرع المحلي main متطابق ومحدّث مع النسخة البعيدة منه origin/main، أي لا يوجد أي اختلاف بينهما. لاحظ أن الفرع في نظام Git يمثل مرجعًا يشير إلى إيداع محدد، تمامًا كورقة ملاحظات توضع على هذه اللقطة من الشيفرة، وتمثل هذه اللقطة بدورها صورة لحالة الشيفرة في وقت معين.
	</li>
</ul>

<p>
	بالعودة إلى السطر الثاني نرى كلمة <code>origin</code> وهي مجرد اسم يشير إلى المستودع البعيد الذي أخذنا منه نسختنا وعليه فإن <code>origin/main</code> تعني فرع <code>main</code> من المستودع الأصلي الذي أخذت منه نسختك، وهذا يقودنا إلى ملاحظة أخرى وهي أن لدينا فرعين <code>main</code> هنا، واحد على المستودع المحلي والآخر على المستودع البعيد.
</p>

<p>
	ذكرنا من قبل أن النسخ تكون مستقلة بذاتها بمعنى أن التعديلات التي نجريها على نسخة ما لا تظهر تلقائيًا في نسختها الأخرى، وعليه نستطيع تعديل الفرع المحلي دون أن يتأثر فرع المستودع البعيد طالما لم ندفع تلك التغييرات إلى المستودع البعيد عن طريق <code>push</code>.
</p>

<h4 id="-">
	الخطوة الثانية: إجراء تعديلات على المستودع المحلي
</h4>

<p>
	لنحرر الآن أحد الملفات ونجري عليه بعض التعديلات، مرة أخرى لا تقلق بشأن تخريب بيانات المستودع البعيد فنحن لا نملك صلاحيات ذلك أصلًا. افتح الشيفرة في محرر النصوص الذي تفضله، وإذا كان محررك النصي هو VS Code فتستطيع تشغيله مباشرة في المجلد الحالي كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1418_25" style=""><span class="pln">$ code .</span></pre>

<p>
	لنعدّل الآن في ملف <code>hello.py</code> الذي كانت بياناته الأصلية كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1418_29" style=""><span class="com">#!/usr/bin/env python</span><span class="pln">

</span><span class="kwd">print</span><span class="pun">(</span><span class="str">"Hello World!"</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="str">"This is my program!"</span><span class="pun">)</span></pre>

<p>
	أضف الآن سطرًا يمثل تعديلًا جديدًا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1418_31" style=""><span class="com">#!/usr/bin/env python</span><span class="pln">

</span><span class="kwd">print</span><span class="pun">(</span><span class="str">"Hello World!"</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="str">"This is my program!"</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="str">"And this is my modification!"</span><span class="pun">)</span></pre>

<p>
	احفظ الآن هذا الملف، ولنسأل Git عن الحالة الجديدة للمستودع عن طريق الأمر <code>status</code>:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1418_33" style=""><span class="pln">$ git status</span></pre>

<pre><code>في النتيجة هذه المرة سنحصل على:
  </code></pre>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1418_37" style=""><span class="pln">On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
(use "git add </span><span class="tag">&lt;file&gt;</span><span class="pln">..." to update what will be committed)
  (use "git restore </span><span class="tag">&lt;file&gt;</span><span class="pln">..." to discard changes in working
  directory)
  modified:   hello.py

 no changes added to commit (use "git add" and/or "git commit -a")</span></pre>

<p>
	نرى من هذه الحالة الجديدة الآن ما يلي:
</p>

<p>
	اكتشف Git أننا عدلنا على ملف <code>hello.py</code> تحديدًا لكنه يقول أيضًا <code>no changes added to commit</code> ، وهذا يعني أننا لم نضف الملف المعدل إلى منطقة التحضير stage التي نضع فيها العناصر التي نريد إرسالها في عملية الإيداع، لكن نريد مراجعة التغييرات أولًا قبل إرسالها، وهو ما سنفعله الآن باستخدام أداة <code>git diff</code>.
</p>

<p>
	ترينا هذه الأداة -التي هي اختصار لكلمة difference- التفاصيل التي تغيرت في الملفات لكن مخرجاتها قد تكون غامضة بعض الشيء، انظر المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1418_39" style=""><span class="pln">$ git diff</span></pre>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1418_41" style=""><span class="pln"> diff --git a/hello.py b/hello.py
  index 9db78d2..1187d32 100755
  --- a/hello.py
  +++ b/hello.py
  @@ -2,3 +2,4 @@

   print("Hello, world!")
   print("This is my program!")
  +print("And this is my modification!")</span></pre>

<p>
	ملخص ما تريد الاطلاع عليه هو ما يلي:
</p>

<ol>
	<li>
		اسم الملف: حيث نرى أن التعديل جرى على الملف <code>hello.py</code>.
	</li>
	<li>
		السطر الذي يبدأ بعلامة <code>+</code> وهو الذي أضفناه.
	</li>
</ol>

<p>
	أما علامة <code>-</code> إذا جاءت في بداية السطر فهي تشير أن السطر قد حُذف، ورغم أننا لم نحذف السطر كله بل عدلناه فقط إلا أن نظام Git يظهر الأسطر المعدلة كأنها محذوفة ثم أضيفت كأسطر جديدة. 
</p>

<h4 id="-3-">
	الخطوة 3: إضافة التعديلات إلى منطقة التحضير
</h4>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1418_45" style=""><span class="pln">no changes added to commit (use "git add" and/or "git commit -a")</span></pre>

<p>
	في هذه المرحلة لم نقم بعد بإضافة التعديلات إلى منطقة التحضير، لذلك يخبرنا Git بعدم وجود تغييرات جاهزة للإيداع، ونحن نعلم أننا عدلنا الملف <code>hello.py</code> ونريد أن ننفذ عملية إيداع تجسد تلك التعديلات الجديدة لذا سنضيف الملف أولًا إلى منطقة التحضير باستخدام <code>git add</code>:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1418_47" style=""><span class="pln">os@machine:~/git-example-repo$ git add hello.py
os@machine:~/git-example-repo$ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged </span><span class="tag">&lt;file&gt;</span><span class="pln">..." to unstage)
        modified:   hello.py</span></pre>

<p>
	لقد تغيرت الرسالة الآن من <code>Changes not staged for commit</code> إلى <code>Changes to be committed</code>، وهذا يعني أننا نجحنا في نسخ الملف <code>hello.py</code> إلى منطقة التحضير.
</p>

<p>
	نرى رسالة أخرى في السطر قبل الأخير حول كيفية إلغاء تحضير الملف، فإذا فرضنا أننا أضفناه دون قصد ونريد حذفه منها وعدم تضمينه في الإيداع الذي سنرسله، فما علينا إلا تنفيذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1418_50" style=""><span class="pln">$ git restore --staged hello.py</span></pre>

<p>
	وسيعود الملف إلى مرحلة <code>Changes not staged for commit</code>.
</p>

<h4 id="-4-">
	الخطوة 4: إيداع التعديلات
</h4>

<p>
	نستطيع الآن تنفيذ عملية إيداع commit بما أننا نسخنا الملف إلى منطقة التحضير، ونعيد التذكير هنا أن هذا الإيداع يمثل لقطة snapshot لحالة المستودع بناء على الملفات المعدلة الموجودة في منطقة التحضير فقط -إضافةً إلى الملفات التي لم نعدلها-، أما الملفات التي عدلناها ثم لم نضفها إلى منطقة التحضير فلا تدخل في عملية الإيداع. هذا يعني أن عملية الإيداع تمثل حالة كاملة للمشروع، حيث تتضمن التعديلات التي أُضيفت إلى منطقة التحضير، بينما تبقى الملفات غير المعدلة كما هي من الإيداع السابق دون تغيير.
</p>

<p>
	نلاحظ أن النظام أظهر لنا الملفات التي عدلنا عليها فقط وليس الملفات غير المعدلة، هذا مقصود لأن إظهار الملفات غير المعدلة سيجعل القائمة طويلة جدًا وغير مفيدة، خاصة في حالة المشاريع الكبيرة. لتنفيذ عملية الإيداع نكتب ما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1418_52" style=""><span class="pln">$ git commit -m "Add another print line"
[main a3c7285] Add another print line
   1 file changed, 1 insertion(+)</span></pre>

<p>
	يسمح الخيار <code>‎-m</code> بتحديد رسالة الإيداع مباشرة -التي بين علامات التنصيص- أما إذا لم نضفها فسيفتح Git محرر نصي لتعديل رسالة الإيداع، وإذا فتحت هذا المحرر فاعلم أن كل رسالة إيداع تبدأ بعلامة <code>#</code> هي تعليق يتجاهله Git عند تسجيل اللقطة، لنتحقق مرة أخرى من حالة المستودع:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1418_54" style=""><span class="pln">$ git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean</span></pre>

<p>
	تعني الرسالة في السطر الأخير أنه لا توجد أي تعديلات محلية عير مسجلة في الفرع الحالي، لكن تجدر الإشارة هنا إلى أننا نستطيع إيداع ملف عدلناه مباشرة دون إضافته إلى منطقة التحضير عن طريق كتابة اسمه صراحة في سطر الأوامر، وهو اختصار بسيط لعملية الإيداع من المفيد تعلمه.
</p>

<p>
	فمثلًا، نستطيع إيداع ملف وليكن <code>foo.txt</code> دون إضافته إلى منطقة التحضير عن طريق ما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1418_56" style=""><span class="pln">$ git commit -m "jerbify the flurblux" foo.txt</span></pre>

<p>
	ينشئ هذا الأمر لقطة أو عملية إيداع فورًا متجاوزًا عملية الإضافة إلى منطقة التحضير، لكن اعلم أنك لا تستطيع فعل هذا إلا مع الملفات التي يعرفها المستودع ويتتبعها، ويصلح تنفيذ هذا على ملف واحد أو عدة ملفات أو حتى مجلد كامل دون التأثير على الملفات التي قد تكون موجودة بالفعل في منطقة التحضير.
</p>

<p>
	إذا عدنا إلى مخرجات <code>status</code> مرة أخرى سنرى أننا متقدمون على <code>origin/main</code> بعملية إيداع واحدة أو لقطة واحدة، وهذا يعني أن سجل الإيداع المحلي على فرع <code>main</code> يحتوي على لقطة ليست في سجل <code>main</code> الخاص بالمستودع البعيد، وهذا منطقي نظرًا لأن المستودع البعيد يمثل نسخة مستقلة تمامًا عن النسخة المحلية التي على حاسوبنا، ولا تتأثر به إلا إذا حاولنا عمدًا أن نزامن النسختين، أما عدا هذا فلن يعرف المستودع البعيد أننا أجرينا تعديلات محلية.
</p>

<p>
	يمثل Git دور المساعد الذكي حيث ينصحنا في الرسالة الظاهرة بين قوسين من المثال أعلاه بتنفيذ الأمر <code>git push</code> إذا أردنا تحديث المستودع البعيد ليستقبل التعديلات التي أجريناها لتتطابق النسختان معًا، وهو ما سنفعله في الخطوة التالية.
</p>

<h4 id="-5-">
	الخطوة 5: رفع التعديلات إلى المستودع البعيد
</h4>

<p>
	ننفذ الأمر <code>git push</code> لنرفع تعديلاتنا المحلية إلى المستودع البعيد:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1418_58" style=""><span class="pln">$ git push</span></pre>

<p>
	تظهر لنا النتيجة التالية:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1418_60" style=""><span class="pln">Username for 'https://github.com':</span></pre>

<p>
	ستحتاج هنا أن تدخل اسم المستخدم الخاص بك في موقع github وكلمة المرور، لكن تقابلنا الرسالة التالية:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1418_62" style=""><span class="pln">Username for 'https://github.com': my_username
Password for 'https://beejjorgensen@github.com': [my_password]
remote: Support for password authentication was removed on August
        13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-
        started-with-git/about-remote-repositories#cloning-with-
        https-urls for information on currently recommended modes
        of authentication.
fatal: Authentication failed for 'https://github.com/beejjorgensen/
       git-example-repo.git/'</span></pre>

<p>
	تظهر رسالة الفشل <code>fatal: Authentication failed</code> لعدة أسباب:
</p>

<ul>
	<li>
		أولًا: لا تملك تصريحًا بالكتابة أو التعديل في هذا المستودع لأنك لست صاحبه.
	</li>
	<li>
		ثانيًا: توقف دعم المصادقة بكلمات المرور التقليدية منذ 2021.
	</li>
</ul>

<p>
	لحل هذه المشكلة ينبغي أن تكون أنت مالك المستودع لتستطيع التعديل فيه، كما ينبغي أن نبحث عن وسيلة أخرى لإثبات هويتنا لموقع Github بدلًا من كلمة المرور العادية، وهو ما سنعرفه في المقال التالي من هذه السلسلة.
</p>

<h2 id="-">
	خاتمة
</h2>

<p>
	تعرفنا في هذا المقال على نظام Git للتحكم في الإصدارات وأساسياته، وتعلمنا بعض العمليات البسيطة فيه مثل الاستنساخ والرفع والإيداع وإجراء التعديلات، بما يمثل حالة استخدام بسيطة وشائعة لهذا النظام، وسنتعرف في المقال التالي من السلسلة على موقع GitHub وكيفية العمل عليه.
</p>

<h2 id="-">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/workflow/git/%D8%A7%D9%84%D8%AF%D9%84%D9%8I%D9%84-%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9%D9%8I-%D9%84%D9%84%D8%B9%D9%85%D9%84-%D8%B9%D9%84%D9%89-%D9%86%D8%B8%D8%A7%D9%85-%D8%BA%D9%8A%D8%AA-git-r1587/" rel=""><strong>الدليل المرجعي للعمل على نظام غيت Git</strong></a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/workflow/git/%D9%85%D8%A7-%D9%87%D9%88-git%D8%9F-r1592/" rel=""><strong>ما هو Git؟</strong></a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/workflow/git/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%B9%D8%A7%D8%AF%D8%A9-%D8%A5%D8%B5%D8%AF%D8%A7%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%A7%D9%84%D9%82%D8%AF%D9%8A%D9%85%D8%A9-%D9%81%D9%8A-%D8%AC%D9%8A%D8%AA-git-r1603/" rel=""><strong>كيفية استعادة إصدارات الملفات القديمة في جيت Git</strong></a>
	</li>
</ul>

<p>
	ترجمة -بتصرف- <a href="https://beej.us/guide/bggit/html/#git-basics" rel="external nofollow">للفصل الثاني من دليل استخدام Git</a> لصاحبه Brian “Beej Jorgensen” Hall
</p>
]]></description><guid isPermaLink="false">2606</guid><pubDate>Sat, 28 Mar 2026 13:12:00 +0000</pubDate></item><item><title>&#x627;&#x62E;&#x62A;&#x628;&#x627;&#x631; &#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x64A;&#x627;&#x62A; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; BlackBox AI</title><link>https://academy.hsoub.com/programming/artificial-intelligence/%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A7%D8%AA-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-blackbox-ai-r2605/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2026_03/blackbox.png.73d47e17e75daaec960319375663f1e4.png" /></p>
<p>
	نشرح في هذه المقالة كيفية استخدام أداة الذكاء الاصطناعي BlackBoxAI في اختبار البرامج، وهي المهمة التي تضمن استقرار عمل الأنظمة التي نطورها، خاصة في التطبيقات الحيوية التي تكون تكلفة الخطأ فيها كبيرة مثل تطبيقات التكنولوجيا المالية أو القيادة الذاتية للمركبات، إضافة إلى التصنيفات الأخرى من التطبيقات التي نرغب باختبارها.
</p>

<h2 id="-">
	تعريف اختبار البرامج
</h2>

<p>
	اختبار البرامج Software testing هو تقييم التطبيقات البرمجية التي نطورها من حيث الأداء والوظائف التي تؤديها لنتأكد من استيفاء هذه البرامج للمتطلبات المستهدفة منها وأنها تخلو من الأخطاء والعيوب، وتُختبر البرامج إما آليًا أو يدويًا.
</p>

<p>
	ويقسَّم اختبار البرامج من منظور آخر إلى اختبار وظيفي Functional testing واختبار غير وظيفي Non-Functional testing، حيث تقيس الاختبارات الوظيفية أداء الوظائف الأساسية المطلوبة من النظام مثل التحقق من معالجة البيانات المدخلة بشكل صحيح بينما يقيس الاختبار غير الوظيفي مزايا وصفية وليست وظيفية للنظام، مثل اختبار الأداء Performance testing أو <a href="https://academy.hsoub.com/design/user-experience/%D9%82%D8%A7%D8%A8%D9%84%D9%8A%D8%A9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%88%D8%A3%D9%87%D9%85%D9%8A%D8%AA%D9%87%D8%A7-%D9%81%D9%8A-%D8%AA%D8%AC%D8%B1%D8%A8%D8%A9-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-r728/" rel="">اختبار قابلية الاستخدام Usability</a> أو <a href="https://academy.hsoub.com/programming/html/%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D9%85%D8%B4%D8%A7%D9%83%D9%84-%D8%B3%D9%87%D9%88%D9%84%D8%A9-%D8%A7%D9%84%D9%88%D8%B5%D9%88%D9%84-accessibility-%D8%A7%D9%84%D8%B4%D8%A7%D8%A6%D8%B9%D8%A9-%D9%84%D9%84%D8%AA%D9%88%D8%A7%D9%81%D9%82-%D9%85%D8%B9-%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D9%81%D8%AD%D8%A7%D8%AA-r1983/" rel="">اختبار سهولة الوصول Accessibility</a>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="181747" href="https://academy.hsoub.com/uploads/monthly_2026_03/___000.png.a4a0b7e7908b25a2eea45c0d1d131624.png" rel=""><img alt="أنواع_اختبار_البرامج_000.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181747" data-ratio="56.33" data-unique="h2daqxrhe" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_03/___000.thumb.png.1c82031bf611e8eee7520c36e2974828.png"></a>
</p>

<h2 id="-">
	متطلبات العمل
</h2>

<ul>
	<li>
		<p>
			تنزيل محرر أكواد مناسب مثل <a href="https://code.visualstudio.com/download" rel="external nofollow">VS Code</a>
		</p>
	</li>
	<li>
		<p>
			تثبيت <a href="https://marketplace.visualstudio.com/items?itemName=Blackboxapp.blackbox" rel="external nofollow">إضافة BLACKBOXAI في VSCode</a>
		</p>
	</li>
	<li>
		<p>
			تثبيت مكتبات بايثون التالية <code>fastapi</code> و <code>uvicorn</code> و <code>locust</code> باستخدام الطرفية:
		</p>
	</li>
</ul>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_5195_8" style=""><span class="pln">pip install fastapi uvicorn locust</span></pre>

<h2 id="-software-testing-levels">
	مستويات اختبار البرامج Software testing levels
</h2>

<p>
	ذكرنا أول المقال أننا نختبر البرامج لنضمن عملها وأداءها لوظائفها، وأدنى هذه المستويات هو اختبار الوحدة unit test، ثم اختبار التكامل Integration test الذي يقيس توافقية الوحدات البرمجية المختلفة مع بعضها البعض عند دمجها، يلي هذا اختبار النظام بشكل شامل System testing، ثم اختبارات القبول في المستوى النهائي التي ينفذها المستخدم حيث يختبر الوظائف المطلوبة بشكل كامل ليقيس جاهزيتها للتسليم.
</p>

<h2 id="-unit-test">
	اختبار الوحدات البرمجية unit test
</h2>

<p>
	يمثل اختبار الوحدات البرمجية أدنى مستوى من اختبار البرامج حيث يفحص أصغر الأجزاء القابلة للتنفيذ في الشيفرة مثل الدوال البرمجية Functions أو الأصناف classes أو التوابع methods، ويتم هذا الاختبار في بيئة معزولة عن باقي مكونات النظام أو أي أنظمة خارجية مثل <a href="https://academy.hsoub.com/devops/servers/databases/%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-database/" rel="">قواعد البيانات</a> أو <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-api-r1314/" rel="">واجهات التطبيقات البرمجية APIs</a> لأن المشكلات التي قد تحدث في هذا المستوى ستؤثر على غيره، فإذا كنا نصمم <a href="https://academy.hsoub.com/programming/python/%D9%85%D8%B4%D8%A7%D8%B1%D9%8A%D8%B9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%AA%D9%86%D8%A7%D8%B3%D8%A8-%D8%A7%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-r2185/#:~:text=%D8%AE%D8%B7%D9%88%D8%A7%D8%AA%20%D8%AA%D9%86%D9%81%D9%8A%D8%B0%D9%87%20%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D9%8B%D8%A7.-,%D9%85%D8%B4%D8%B1%D9%88%D8%B9%20%D8%A2%D9%84%D8%A9%20%D8%AD%D8%A7%D8%B3%D8%A8%D8%A9%20%D8%A8%D9%84%D8%BA%D8%A9%20%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86,-%D9%8A%D8%B9%D8%AF%20%D9%85%D8%B4%D8%B1%D9%88%D8%B9%20%D8%AA%D9%86%D9%81%D9%8A%D8%B0" rel="">برنامج آلة حاسبة</a> سنحتاج للتأكد من عمل كل وظيفة بشكل مستقل، فنختبر دالتي الجمع والطرح وغيرها من الدوال الوظيفية اختبارات مستقلة لنضمن سلاسة وسلامة الانتقال للمستوي التالي الذي يدمج عدة عمليات رياضية معًا.
</p>

<h3 id="-">
	اختبار تحويل الأموال في نظام بنكي
</h3>

<p>
	لنفرض أن لدينا نظام بنكي فيه صنف class اسمه <code>BankAccount</code> مسؤول عن إنشاء كائنات حسابات بنكية، ويحتوي تابع method باسم <code>transfer</code> مسؤول عن تحويل الأموال من حساب لآخر.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5195_10" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> __init__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> balance</span><span class="pun">:</span><span class="pln"> float</span><span class="pun">):</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">=</span><span class="pln"> balance

    </span><span class="com"># دالة لإيداع الأموال</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> transfer</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> target_account</span><span class="pun">,</span><span class="pln"> amount</span><span class="pun">:</span><span class="pln"> float</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># التحقق من أن الحساب ليس مديونًا أو مفلسًا</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> amount </span><span class="pun">&lt;=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">:</span><span class="pln">
            </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">ValueError</span><span class="pun">(</span><span class="str">"ينبغي أن يكون المبلغ أكبر من الصفر."</span><span class="pun">)</span><span class="pln">
        </span><span class="com"># التحقق من أن الرصيد كافي</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">&lt;</span><span class="pln"> amount</span><span class="pun">:</span><span class="pln">
            </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">ValueError</span><span class="pun">(</span><span class="str">"الرصيد غير كافي"</span><span class="pun">)</span><span class="pln">

        </span><span class="com"># خصم المبلغ من الحساب المرسل</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">-=</span><span class="pln"> amount
        </span><span class="com"># إيداع المبلغ في الحساب المستهدف</span><span class="pln">
        target_account</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">+=</span><span class="pln"> amount</span></pre>

<p>
	نختبر الآن دالة تحويل الأموال في معزل عن باقي النظام باستخدام أداة BlackBox AI، من خلال توليد حالات اختبار مناسبة باستخدام مكتبة pytest لأنها من أبسط المكتبات المتاحة في لغة بايثون لتنفيذ اختبار على مستوى الوحدة، وبما أننا ثبتنا إضافة BlackBox AI في محرر Vscode فستكون موجودة في القائمة الجانبية في يسار المحرر، ونخبرها أن تنشئ ملف اختبار لوظيفة تحويل الأموال بين الحسابات البنكية كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_634_7" style=""><span class="pln">&gt; create a test file in python using pytest library to test the method of transfer in BankAccount class @/BankAccount.py</span></pre>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="181748" href="https://academy.hsoub.com/uploads/monthly_2026_03/____001.png.ad821abd1f95501a4d374eae126a5a86.png" rel=""><img alt="توجيه_أداة_الذكاء_الاصطناعي_001.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181748" data-ratio="99.21" data-unique="k96ytdc2l" style="width: 505px; height: auto;" width="605" src="https://academy.hsoub.com/uploads/monthly_2026_03/____001.thumb.png.438b9a7bcffff4bd08f8626ee26ce4d7.png"></a>
</p>

<p>
	نلاحظ أن BlackBoxAI وضعت خطة لتنفيذ الطلب ثم أنشأت ملف <code>test_BanckAccount.py</code> وتطلب مصادقتنا على التغييرات المقترحة والتي يمكن تصفحها قبل الموافقة عليها أو رفضها.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="181749" href="https://academy.hsoub.com/uploads/monthly_2026_03/___002.png.66fc9b4d68c23040f01f298dece41974.png" rel=""><img alt="خطة_التنفيذ_الأولية_002.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181749" data-ratio="45.33" data-unique="78auadkrc" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_03/___002.thumb.png.307b0ad5bad0b2457f3dd4564a461144.png"></a>
</p>

<p>
	بالنظر في ملف <code>test_BanckAccount.py</code> سنجد أن الأداة قد ولدت الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5195_16" style=""><span class="kwd">import</span><span class="pln"> pytest
</span><span class="kwd">from</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> test_transfer_successful_transfer</span><span class="pun">():</span><span class="pln">
    account1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">100</span><span class="pun">)</span><span class="pln">
    account2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">50</span><span class="pun">)</span><span class="pln">
    account1</span><span class="pun">.</span><span class="pln">transfer</span><span class="pun">(</span><span class="pln">account2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">50</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">assert</span><span class="pln"> account1</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">==</span><span class="pln"> </span><span class="lit">50</span><span class="pln">
    </span><span class="kwd">assert</span><span class="pln"> account2</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">==</span><span class="pln"> </span><span class="lit">100</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> test_transfer_with_zero_amount</span><span class="pun">():</span><span class="pln">
    account1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">100</span><span class="pun">)</span><span class="pln">
    account2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">50</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">with</span><span class="pln"> pytest</span><span class="pun">.</span><span class="pln">raises</span><span class="pun">(</span><span class="typ">ValueError</span><span class="pun">,</span><span class="pln"> match</span><span class="pun">=</span><span class="str">"ينبغي أن يكون المبلغ أكبر من الصفر."</span><span class="pun">):</span><span class="pln">
        account1</span><span class="pun">.</span><span class="pln">transfer</span><span class="pun">(</span><span class="pln">account2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> test_transfer_with_negative_amount</span><span class="pun">():</span><span class="pln">
    account1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">100</span><span class="pun">)</span><span class="pln">
    account2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">50</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">with</span><span class="pln"> pytest</span><span class="pun">.</span><span class="pln">raises</span><span class="pun">(</span><span class="typ">ValueError</span><span class="pun">,</span><span class="pln"> match</span><span class="pun">=</span><span class="str">"ينبغي أن يكون المبلغ أكبر من الصفر."</span><span class="pun">):</span><span class="pln">
        account1</span><span class="pun">.</span><span class="pln">transfer</span><span class="pun">(</span><span class="pln">account2</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">10</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> test_transfer_with_insufficient_balance</span><span class="pun">():</span><span class="pln">
    account1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">50</span><span class="pun">)</span><span class="pln">
    account2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">100</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">with</span><span class="pln"> pytest</span><span class="pun">.</span><span class="pln">raises</span><span class="pun">(</span><span class="typ">ValueError</span><span class="pun">,</span><span class="pln"> match</span><span class="pun">=</span><span class="str">"الرصيد غير كافي"</span><span class="pun">):</span><span class="pln">
        account1</span><span class="pun">.</span><span class="pln">transfer</span><span class="pun">(</span><span class="pln">account2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">)</span></pre>

<p>
	نشغل هذا الملف الاختباري بالأمر التالي في <a href="https://academy.hsoub.com/devops/linux/%D8%B4%D8%B1%D8%AD-%D8%AA%D9%85%D9%87%D9%8A%D8%AF%D9%8A-%D9%84%D8%B3%D8%B7%D8%B1-%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D9%84%D9%8A%D9%86%D9%83%D8%B3-r810/" rel="">سطر الأوامر Terminal</a>
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_5195_18" style=""><span class="pln">pytest test_BankAccount.py</span></pre>

<p>
	ونحصل على الخرج التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_5195_20" style=""><span class="pln">===========================
platform win32 -- Python 3.9.12, pytest-7.1.1, pluggy-1.0.0
rootdir: F:\AI_tools\Article_4
plugins: anyio-3.5.0
collected 4 items

test_BankAccount.py ....
============================= 4 passed in 0.21s =====================[100%]</span></pre>

<p>
	وهكذا نكون قد تجاوزنا جميع الاختبارات بنجاح، لكن هذا لا يعني أن الشيفرة ليست بها مشاكل، فيجب أن نتأكد من اختبار الحالات المتطرفة من الاستخدام edge cases التي قد تتسبب في انهيارها أو حدوث مشاكل فيها، سواء اعتمدنا على أدوات الذكاء الصناعي أو أجرينا الاختبارات يدويًا.
</p>

<p>
	وينبغي أن نسعى لاختبار أكبر عدد ممكن من السيناريوهات لاكتشاف المشاكل مبكرًا -رغم صعوبة التنبؤ بجميع الحالات المسببة للمشاكل- ذلك أن تكلفة معالجة الأخطاء أثناء مرحلة التطوير أقل من تكلفة إصلاحها بعد إطلاق البرنامج نفسه وتفاعل المستخدمين معه.
</p>

<p>
	لذا سنطلب من BlackBoxAI أن يغطي نطاقًا أوسع من حالات الاستخدام مع التركيز على الحالات المتطرفة لضمان استقرار النظام، كما يمكن مناقشته كذلك في حالات اختبار أخرى من من فهمنا لاحتياج النشاط التجاري -البنك في حالتنا- ومراجعة النتائج التي تعود بها علينا للتأكد من مناسبتها لحالة الاستخدام لدينا فلن تعطل وظيفة عاملة بالفعل، فالأداة عرضة للخطأ مهما بلغت من براعة. على سبيل المثال، نريد التأكد من قدرة النظام على التعامل مع الأخطاء غير المتوقعة، مثل فشل عملية تحويل الأموال لأي سبب:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_5195_22" style=""><span class="pln">&gt; I want to test edge cases to ensure all transactions are atomic, especially when a transaction fails. Specifically, I need to verify that the balance remains unchanged if the sending or receiving process fails, using pytest.</span></pre>

<p>
	اقترحت الأداة بعد هذا الموجه إجراء محاكاة عملية لوقوع خطأ بعد خصم مبلغ من حساب المرسل، ويفشل النظام إذا تم الخصم حقًا من حساب المرسل دون أن يستلم الطرف الثاني أي أموال.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5195_24" style=""><span class="kwd">import</span><span class="pln"> pytest
</span><span class="kwd">from</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> test_transfer_successful_transfer</span><span class="pun">():</span><span class="pln">
    account1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">100</span><span class="pun">)</span><span class="pln">
    account2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">50</span><span class="pun">)</span><span class="pln">
    account1</span><span class="pun">.</span><span class="pln">transfer</span><span class="pun">(</span><span class="pln">account2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">50</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">assert</span><span class="pln"> account1</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">==</span><span class="pln"> </span><span class="lit">50</span><span class="pln">
    </span><span class="kwd">assert</span><span class="pln"> account2</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">==</span><span class="pln"> </span><span class="lit">100</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> test_transfer_with_zero_amount</span><span class="pun">():</span><span class="pln">
    account1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">100</span><span class="pun">)</span><span class="pln">
    account2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">50</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">with</span><span class="pln"> pytest</span><span class="pun">.</span><span class="pln">raises</span><span class="pun">(</span><span class="typ">ValueError</span><span class="pun">,</span><span class="pln"> match</span><span class="pun">=</span><span class="str">"ينبغي أن يكون المبلغ أكبر من الصفر"</span><span class="pun">):</span><span class="pln">
        account1</span><span class="pun">.</span><span class="pln">transfer</span><span class="pun">(</span><span class="pln">account2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> test_transfer_with_negative_amount</span><span class="pun">():</span><span class="pln">
    account1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">100</span><span class="pun">)</span><span class="pln">
    account2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">50</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">with</span><span class="pln"> pytest</span><span class="pun">.</span><span class="pln">raises</span><span class="pun">(</span><span class="typ">ValueError</span><span class="pun">,</span><span class="pln"> match</span><span class="pun">=</span><span class="str">"ينبغي أن يكون المبلغ أكبر من الصفر"</span><span class="pun">):</span><span class="pln">
        account1</span><span class="pun">.</span><span class="pln">transfer</span><span class="pun">(</span><span class="pln">account2</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">10</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> test_transfer_with_insufficient_balance</span><span class="pun">():</span><span class="pln">
    account1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">50</span><span class="pun">)</span><span class="pln">
    account2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">100</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">with</span><span class="pln"> pytest</span><span class="pun">.</span><span class="pln">raises</span><span class="pun">(</span><span class="typ">ValueError</span><span class="pun">,</span><span class="pln"> match</span><span class="pun">=</span><span class="str">"الرصيد غير كافي"</span><span class="pun">):</span><span class="pln">
        account1</span><span class="pun">.</span><span class="pln">transfer</span><span class="pun">(</span><span class="pln">account2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> test_transfer_mid_transaction_failure</span><span class="pun">():</span><span class="pln">
    </span><span class="str">"""
    محاكاة فشل العملية بعد خصم المبلغ وقبل إيداعه.
    السلوك المتوقع: يجب ألا يفقد المرسل المال إذا فشلت العملية.
    """</span><span class="pln">
    account1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">100</span><span class="pun">)</span><span class="pln">
    account2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">50</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># إنشاء حساب يحتوي على خطأ محتمل عند محاولة إعادة تعيين الرصيد</span><span class="pln">
    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">ProblematicAccount</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">def</span><span class="pln"> __init__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> balance</span><span class="pun">):</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">=</span><span class="pln"> balance

        </span><span class="com"># الحصول على الرصيد بشكل آمن</span><span class="pln">
        </span><span class="com"># getter</span><span class="pln">
        </span><span class="lit">@property</span><span class="pln">
        </span><span class="kwd">def</span><span class="pln"> balance</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">_balance

        </span><span class="com"># تعيين الرصيد بشكل آمن</span><span class="pln">
        </span><span class="com"># setter</span><span class="pln">
        </span><span class="lit">@balance</span><span class="pun">.</span><span class="pln">setter
        </span><span class="kwd">def</span><span class="pln"> balance</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">):</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> hasattr</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> </span><span class="str">'_balance'</span><span class="pun">):</span><span class="pln">  </span><span class="com"># إن كانت القيمة معرفة مسبقًا</span><span class="pln">
                </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">RuntimeError</span><span class="pun">(</span><span class="str">"Unexpected failure before deposit!"</span><span class="pun">)</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">_balance </span><span class="pun">=</span><span class="pln"> value

    problem_account </span><span class="pun">=</span><span class="pln"> </span><span class="typ">ProblematicAccount</span><span class="pun">(</span><span class="lit">50</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># اختبار نقل الأموال</span><span class="pln">
    </span><span class="kwd">with</span><span class="pln"> pytest</span><span class="pun">.</span><span class="pln">raises</span><span class="pun">(</span><span class="typ">RuntimeError</span><span class="pun">,</span><span class="pln"> match</span><span class="pun">=</span><span class="str">"Unexpected failure before deposit!"</span><span class="pun">):</span><span class="pln">
        account1</span><span class="pun">.</span><span class="pln">transfer</span><span class="pun">(</span><span class="pln">problem_account</span><span class="pun">,</span><span class="pln"> </span><span class="lit">30</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># التحقق من عدم فقدان المرسل للمال بشكل غير متوقع</span><span class="pln">
    </span><span class="kwd">assert</span><span class="pln"> account1</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">==</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Sender lost money unexpectedly!"</span><span class="pln">
    </span><span class="kwd">assert</span><span class="pln"> problem_account</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">==</span><span class="pln"> </span><span class="lit">50</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Receiver should not gain money!"</span></pre>

<p>
	ينبغي أن نحصل على الخرج التالي بتشغيل تلك الشيفرة:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_5195_26" style=""><span class="pln">=========================== short test summary info ========================
FAILED test_BankAccount.py::test_transfer_mid_transaction_failure - AssertionError: Sender lost money unexpectedly!
================ 1 failed, 4 passed in 0.14s =================================</span></pre>

<p>
	ندرك بهذا أن الدالة <code>transfer</code> تحتاج للتعديل لتتمكن من التعامل مع حالات الفشل غير المتوقعة، خاصة تحقيق <a href="https://academy.hsoub.com/devops/servers/databases/%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%A8%D9%8A%D9%86-mysql-%D9%88-mongodb-r627/#:~:text=%D8%B0%D8%A7%D8%AA%20%D8%A3%D9%85%D8%A7%D9%86%20%D9%82%D9%88%D9%8A.-,%D8%AE%D8%B5%D8%A7%D8%A6%D8%B5%20ACID,-%D8%AA%D8%B9%D8%AF%20%D8%AE%D8%B5%D8%A7%D8%A6%D8%B5%20ACID" rel="">مبدأ الذرية Atomic</a> الذي ينص على أن العملية ينبغي أن تنجح بالكامل أو تفشل بالكامل، ونحقق ذلك بتطبيق آلية للتراجع في حالة حدوث خطأ غير متوقع، انظر الموجه التالي.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_5195_28" style=""><span class="pln">&gt; Given that failure on the atomicity test case (test_transfer_mid_transaction_failure) ,fix the  BankAccount.py transfer method.</span></pre>

<p>
	ينبغي أن نحصل على الشيفرة المعدلة لملف <code>BankAccount.py</code>
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5195_30" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> __init__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> balance</span><span class="pun">:</span><span class="pln"> float</span><span class="pun">):</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">=</span><span class="pln"> balance

    </span><span class="kwd">def</span><span class="pln"> transfer</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> target_account</span><span class="pun">,</span><span class="pln"> amount</span><span class="pun">:</span><span class="pln"> float</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># التحقق من أن الحساب ليس مديونًا أو مفلسًا</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> amount </span><span class="pun">&lt;=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">:</span><span class="pln">
            </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">ValueError</span><span class="pun">(</span><span class="str">"ينبغي أن يكون المبلغ أكبر من الصفر."</span><span class="pun">)</span><span class="pln">
        </span><span class="com"># التحقق من أن الرصيد كافي</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">&lt;</span><span class="pln"> amount</span><span class="pun">:</span><span class="pln">
            </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">ValueError</span><span class="pun">(</span><span class="str">"الرصيد غير كافي"</span><span class="pun">)</span><span class="pln">

        </span><span class="com"># حفظ الرصيد الحالي في حالة حدوث خطأ</span><span class="pln">
        original_balance </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">balance

        </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># خصم المبلغ من الحساب المرسل</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">-=</span><span class="pln"> amount
            </span><span class="com"># إيداع المبلغ في الحساب المستهدف</span><span class="pln">
            target_account</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">+=</span><span class="pln"> amount
        </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">Exception</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> e</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># استعادة الرصيد الأصلي في حالة حدوث خطأ</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">=</span><span class="pln"> original_balance
            </span><span class="com"># إعادة رفع الاستثناء</span><span class="pln">
            </span><span class="kwd">raise</span><span class="pln"> e</span></pre>

<p>
	نستخدم في الشيفرة المعدلة آلية استرجاع Rollback في حال حدوث خطأ في عملية التحويل لئلا تتغير أرصدة الحسابات، مما يضمن بقائها متزامنة وسليمة. نحصل على الخرج التالي عند تشغيل الاختبار:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_5195_32" style=""><span class="pln">================================ test session starts ==============
platform win32 -- Python 3.9.12, pytest-7.1.1, pluggy-1.0.0
rootdir: F:\Article_4
plugins: anyio-3.5.0
collected 5 items

test_BankAccount.py .....                                                                                                 [100%]
====================== 5 passed in 0.03s=========================</span></pre>

<h2 id="-">
	اختبار الانحدار
</h2>

<p>
	اختبار الانحدار Regression test هو نوع من اختبارات البرامج Software testing يستخدم لضمان أن تحديث الشيفرة لا يضر بأي وظيفة كانت تعمل سابقًا، وينفذ عادة بعد تطوير وظائف جديدة أو إصلاح أحد <a href="https://academy.hsoub.com/programming/general/%D9%83%D9%8A%D9%81-%D8%AA%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9%D8%9F-r2016/" rel="">الأخطاء البرمجية</a>، فإذا أردنا إضافة ميزة جديدة للنظام البنكي مثل فرض رسوم على التحويلات، سنطور هذه الميزة ونختبرها لنتأكد من أنها لم تُحدث انحدارًا في التطبيق الموجود، أي ينبغي أن تظل كل المزايا الأخرى وحالات الاختبار تعمل بكفاءة.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5195_34" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> __init__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> balance</span><span class="pun">:</span><span class="pln"> float</span><span class="pun">,</span><span class="pln"> fee_percentage</span><span class="pun">:</span><span class="pln"> float </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">):</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">=</span><span class="pln"> balance
        self</span><span class="pun">.</span><span class="pln">fee_percentage </span><span class="pun">=</span><span class="pln"> fee_percentage

    </span><span class="kwd">def</span><span class="pln"> transfer</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> target_account</span><span class="pun">,</span><span class="pln"> amount</span><span class="pun">:</span><span class="pln"> float</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># التحقق من أن الحساب ليس مديونًا أو مفلسًا</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> amount </span><span class="pun">&lt;=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">:</span><span class="pln">
            </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">ValueError</span><span class="pun">(</span><span class="str">"ينبغي أن يكون المبلغ أكبر من الصفر."</span><span class="pun">)</span><span class="pln">
        </span><span class="com"># حساب الرسوم المفروضة</span><span class="pln">
        fee </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">get_fee_amount</span><span class="pun">(</span><span class="pln">amount</span><span class="pun">)</span><span class="pln">
        total_amount </span><span class="pun">=</span><span class="pln"> amount </span><span class="pun">+</span><span class="pln"> fee

        </span><span class="com"># التحقق من أن الرصيد كافي</span><span class="pln">
        </span><span class="com"># مضيفين الرسوم إلى تكلفة التحويل</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">&lt;</span><span class="pln"> total_amount</span><span class="pun">:</span><span class="pln">
            </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">ValueError</span><span class="pun">(</span><span class="str">"الرصيد غير كافي"</span><span class="pun">)</span><span class="pln">

        </span><span class="com"># حفظ الرصيد الحالي في حالة حدوث خطأ</span><span class="pln">
        original_balance </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">balance

        </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># خصم المبلغ من الحساب المرسل</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">-=</span><span class="pln"> total_amount
            </span><span class="com"># إيداع المبلغ في الحساب المستهدف</span><span class="pln">
            </span><span class="com"># لا نودع إلا المبلغ المرسل فقد بدون الرسوم</span><span class="pln">
            target_account</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">+=</span><span class="pln"> amount
        </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">Exception</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> e</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># استعادة الرصيد الأصلي في حالة حدوث خطأ</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">=</span><span class="pln"> original_balance
            </span><span class="com"># إعادة رفع الاستثناء</span><span class="pln">
            </span><span class="kwd">raise</span><span class="pln"> e
    </span><span class="kwd">def</span><span class="pln"> get_fee_amount</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> transfer_amount</span><span class="pun">:</span><span class="pln"> float</span><span class="pun">):</span><span class="pln">
        </span><span class="str">"""حساب مبلغ الرسوم لتحويل معين"""</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> transfer_amount </span><span class="pun">*</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">fee_percentage </span><span class="pun">/</span><span class="pln"> </span><span class="lit">100</span></pre>

<p>
	نحتاج لاختبار الميزة الجديدة لنتأكد من عمل الوظائف السابقة أولًا، حيث نختبر الحالة التي تكون الرسوم فيها 0% ثم اختبار التحويل حين تكون الرسوم أكبر من الصفر -2% مثلًا-، ونوجه BlackBoxAI حينئذ لتوليد ملف يختبر عمل الوظائف السابقة بالإضافة لاختبار الحالات الجديدة. لنستخدم <a href="https://academy.hsoub.com/programming/artificial-intelligence/%D8%A3%D9%81%D8%B6%D9%84-%D9%85%D9%85%D8%A7%D8%B1%D8%B3%D8%A7%D8%AA-%D9%87%D9%86%D8%AF%D8%B3%D8%A9-%D8%A7%D9%84%D9%85%D9%8F%D9%88%D8%AC%D9%91%D9%90%D9%87%D8%A7%D8%AA/" rel="">الموجه</a> التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_5195_38" style=""><span class="pln">&gt; Create a regression test for the newly implemented fee feature on money transfers. Ensure the test verifies that the new functionality works as expected, while also confirming that existing features remain unaffected.</span></pre>

<p>
	ينبغي أن نحصل على ملف اختبار يكون كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5195_40" style=""><span class="kwd">import</span><span class="pln"> pytest
</span><span class="kwd">from</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pln">

</span><span class="com"># ---------- اختبارات الانحدار (Regression Tests) ----------</span><span class="pln">
</span><span class="com"># هذه هي الاختبارات الموجودة مسبقًا للتأكد من أن الوظائف الأساسية ما زالت تعمل</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> test_transfer_successful_transfer</span><span class="pun">():</span><span class="pln">
    account1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">100</span><span class="pun">)</span><span class="pln">
    account2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">50</span><span class="pun">)</span><span class="pln">
    account1</span><span class="pun">.</span><span class="pln">transfer</span><span class="pun">(</span><span class="pln">account2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">50</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">assert</span><span class="pln"> account1</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">==</span><span class="pln"> </span><span class="lit">50</span><span class="pln">
    </span><span class="kwd">assert</span><span class="pln"> account2</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">==</span><span class="pln"> </span><span class="lit">100</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> test_transfer_with_zero_amount</span><span class="pun">():</span><span class="pln">
    account1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">100</span><span class="pun">)</span><span class="pln">
    account2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">50</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">with</span><span class="pln"> pytest</span><span class="pun">.</span><span class="pln">raises</span><span class="pun">(</span><span class="typ">ValueError</span><span class="pun">,</span><span class="pln"> match</span><span class="pun">=</span><span class="str">"ينبغي أن يكون المبلغ أكبر من الصفر."</span><span class="pun">):</span><span class="pln">
        account1</span><span class="pun">.</span><span class="pln">transfer</span><span class="pun">(</span><span class="pln">account2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> test_transfer_with_negative_amount</span><span class="pun">():</span><span class="pln">
    account1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">100</span><span class="pun">)</span><span class="pln">
    account2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">50</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">with</span><span class="pln"> pytest</span><span class="pun">.</span><span class="pln">raises</span><span class="pun">(</span><span class="typ">ValueError</span><span class="pun">,</span><span class="pln"> match</span><span class="pun">=</span><span class="str">"ينبغي أن يكون المبلغ أكبر من الصفر"</span><span class="pun">):</span><span class="pln">
        account1</span><span class="pun">.</span><span class="pln">transfer</span><span class="pun">(</span><span class="pln">account2</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">10</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> test_transfer_with_insufficient_balance</span><span class="pun">():</span><span class="pln">
    account1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">50</span><span class="pun">)</span><span class="pln">
    account2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">100</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">with</span><span class="pln"> pytest</span><span class="pun">.</span><span class="pln">raises</span><span class="pun">(</span><span class="typ">ValueError</span><span class="pun">,</span><span class="pln"> match</span><span class="pun">=</span><span class="str">"الرصيد غير كافي"</span><span class="pun">):</span><span class="pln">
        account1</span><span class="pun">.</span><span class="pln">transfer</span><span class="pun">(</span><span class="pln">account2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> test_transfer_mid_transaction_failure</span><span class="pun">():</span><span class="pln">
    </span><span class="str">"""
    محاكاة فشل العملية بعد خصم المبلغ وقبل إيداعه.
    السلوك المتوقع: يجب ألا يفقد المرسل المال إذا فشلت العملية.
    """</span><span class="pln">
    account1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">100</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># إنشاء حساب يحتوي على خطأ محتمل عند محاولة إعادة تعيين الرصيد</span><span class="pln">
    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">ProblematicAccount</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">def</span><span class="pln"> __init__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> balance</span><span class="pun">):</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">=</span><span class="pln"> balance

        </span><span class="com"># الحصول على الرصيد بشكل آمن</span><span class="pln">
        </span><span class="lit">@property</span><span class="pln">
        </span><span class="kwd">def</span><span class="pln"> balance</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">_balance

        </span><span class="com"># تعيين الرصيد بشكل آمن</span><span class="pln">
        </span><span class="lit">@balance</span><span class="pun">.</span><span class="pln">setter
        </span><span class="kwd">def</span><span class="pln"> balance</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">):</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> hasattr</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> </span><span class="str">'_balance'</span><span class="pun">):</span><span class="pln">  </span><span class="com"># إن كانت القيمة معرفة مسبقًا</span><span class="pln">
                </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">RuntimeError</span><span class="pun">(</span><span class="str">"Unexpected failure before deposit!"</span><span class="pun">)</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">_balance </span><span class="pun">=</span><span class="pln"> value

    problem_account </span><span class="pun">=</span><span class="pln"> </span><span class="typ">ProblematicAccount</span><span class="pun">(</span><span class="lit">50</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># اختبار نقل الأموال</span><span class="pln">
    </span><span class="kwd">with</span><span class="pln"> pytest</span><span class="pun">.</span><span class="pln">raises</span><span class="pun">(</span><span class="typ">RuntimeError</span><span class="pun">,</span><span class="pln"> match</span><span class="pun">=</span><span class="str">"Unexpected failure before deposit!"</span><span class="pun">):</span><span class="pln">
        account1</span><span class="pun">.</span><span class="pln">transfer</span><span class="pun">(</span><span class="pln">problem_account</span><span class="pun">,</span><span class="pln"> </span><span class="lit">30</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># التحقق من عدم فقدان المرسل للمال بشكل غير متوقع</span><span class="pln">
    </span><span class="kwd">assert</span><span class="pln"> account1</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">==</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Sender lost money unexpectedly!"</span><span class="pln">
    </span><span class="kwd">assert</span><span class="pln"> problem_account</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">==</span><span class="pln"> </span><span class="lit">50</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Receiver should not gain money!"</span><span class="pln">

</span><span class="com"># ---------- اختبارات الميزة الجديدة (New Feature Tests) ----------</span><span class="pln">
</span><span class="com"># اختبارات للتحقق من ميزة رسوم التحويل الجديدة</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> test_transfer_with_fee</span><span class="pun">():</span><span class="pln">
    </span><span class="str">"""اختبار التحويل مع وجود رسوم"""</span><span class="pln">
    </span><span class="com"># %إنشاء حساب مع رسوم 2</span><span class="pln">
    account1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> fee_percentage</span><span class="pun">=</span><span class="lit">2</span><span class="pun">)</span><span class="pln">
    account2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">50</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># تحويل 50 وحدة مع رسوم 1</span><span class="pln">
    account1</span><span class="pun">.</span><span class="pln">transfer</span><span class="pun">(</span><span class="pln">account2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">50</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># التحقق من الأرصدة</span><span class="pln">
    </span><span class="kwd">assert</span><span class="pln"> account1</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">==</span><span class="pln"> </span><span class="lit">49</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Should be 100 - 50 - 1(fee) = 49"</span><span class="pln">
    </span><span class="kwd">assert</span><span class="pln"> account2</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">==</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Should be 50 + 50 = 100"</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> test_transfer_with_fee_insufficient_balance</span><span class="pun">():</span><span class="pln">
    </span><span class="str">"""اختبار فشل التحويل عند عدم كفاية الرصيد لتغطية المبلغ والرسوم"""</span><span class="pln">
    </span><span class="com"># إنشاء حساب مع رسوم %10</span><span class="pln">
    account1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> fee_percentage</span><span class="pun">=</span><span class="lit">10</span><span class="pun">)</span><span class="pln">
    account2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">50</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># محاولة تحويل مبلغ 95 يتطلب مبلغ 9.5 إضافي للرسوم</span><span class="pln">
    </span><span class="kwd">with</span><span class="pln"> pytest</span><span class="pun">.</span><span class="pln">raises</span><span class="pun">(</span><span class="typ">ValueError</span><span class="pun">,</span><span class="pln"> match</span><span class="pun">=</span><span class="str">"الرصيد غير كافي"</span><span class="pun">):</span><span class="pln">
        account1</span><span class="pun">.</span><span class="pln">transfer</span><span class="pun">(</span><span class="pln">account2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">95</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># التحقق من عدم تغيير الأرصدة</span><span class="pln">
    </span><span class="kwd">assert</span><span class="pln"> account1</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">==</span><span class="pln"> </span><span class="lit">100</span><span class="pln">
    </span><span class="kwd">assert</span><span class="pln"> account2</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">==</span><span class="pln"> </span><span class="lit">50</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> test_get_fee_amount</span><span class="pun">():</span><span class="pln">
    </span><span class="str">"""اختبار حساب مبلغ الرسوم"""</span><span class="pln">
    account </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">1000</span><span class="pun">,</span><span class="pln"> fee_percentage</span><span class="pun">=</span><span class="lit">2.5</span><span class="pun">)</span><span class="pln">

    fee </span><span class="pun">=</span><span class="pln"> account</span><span class="pun">.</span><span class="pln">get_fee_amount</span><span class="pun">(</span><span class="lit">200</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">assert</span><span class="pln"> fee </span><span class="pun">==</span><span class="pln"> </span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="str">"2.5% of 200 should be 5"</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> test_zero_fee_percentage</span><span class="pun">():</span><span class="pln">
    </span><span class="str">"""اختبار أن الحساب يعمل بشكل طبيعي عندما تكون الرسوم صفرًا"""</span><span class="pln">
    account1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> fee_percentage</span><span class="pun">=</span><span class="lit">0</span><span class="pun">)</span><span class="pln">
    account2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BankAccount</span><span class="pun">(</span><span class="lit">50</span><span class="pun">)</span><span class="pln">

    account1</span><span class="pun">.</span><span class="pln">transfer</span><span class="pun">(</span><span class="pln">account2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">50</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">assert</span><span class="pln"> account1</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">==</span><span class="pln"> </span><span class="lit">50</span><span class="pln">
    </span><span class="kwd">assert</span><span class="pln"> account2</span><span class="pun">.</span><span class="pln">balance </span><span class="pun">==</span><span class="pln"> </span><span class="lit">100</span></pre>

<p>
	بتشغيل ملف الاختبار نجد أنه تجاوز جميع الاختبارات، مما يعني أن الميزة المضافة لم تتسبب في إفساد الميزات الموجودة مسبقًا بالنظام.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="181752" href="https://academy.hsoub.com/uploads/monthly_2026_03/____003.png.12d046377480810b49a0a45e85e4d2e6.png" rel=""><img alt="خطة_تنفيذ_اختبار_الانحدار_003.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181752" data-ratio="39.17" data-unique="3q9mihgqt" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_03/____003.thumb.png.0399eea649f1804ad8dbe5713686d847.png"></a>
</p>

<h2 id="-">
	اختبارات الأداء
</h2>

<p>
	تعد اختبارات الأداء Performance tests أحد الطرق الكمية لقياس المتطلبات غير الوظيفية non-functional requirements مثل قدرة تحمل التطبيق وسرعته في التحميل أو عدد المستخدمين الذين يستطيع التعامل معهم وغيرها من المعايير التي تساعدنا في تحديد حدود الاستخدام المناسبة للتطبيق وتحديد حاجتنا للموارد الحاسوبية من ذاكرة وصول عشوائي RAM أو CPU أو مساحة تخزين.
</p>

<h3 id="-performance-test">
	أنواع اختبارات الأداء Performance test
</h3>

<ul>
	<li>
		<p>
			اختبار الحمل Load test : يقيس أداء النظام تحت الظروف المتوقعة أو الطبيعية وسرعة الرد واستخدام الموارد
		</p>
	</li>
	<li>
		<p>
			اختبار الضغط Stress test : يختبر حدود النظام بتعريضه لحمل أكبر من المتوقع أو الطبيعي، لمعرفة مدى استقرار النظام ونقاط الفشل وقدرة النظام على التعافي
		</p>
	</li>
	<li>
		<p>
			اختبار الحمل المفاجئ Spike test : يختبر أداء النظام تحت حمل عالي ومفاجئ، ويختبر قدرته على التوسع السريع scale up والتعامل مع الضغط المفاجئ
		</p>
	</li>
</ul>

<h3 id="-">
	اختبار الحمل في نظام إدارة مخزون
</h3>

<p>
	نفترض أن لدينا نظام إدارة مخزون Inventory بسيط يعمل من خلال <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-api-r1314/" rel="">واجهة تطبيق برمجية <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr></a> مبنية بمكتبة FastAPI ويرتبط بقاعدة بيانات بسيطة من نوع Sqlite تحفظ العناصر وكميتها في جدول.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5195_43" style=""><span class="kwd">import</span><span class="pln"> sqlite3
</span><span class="kwd">from</span><span class="pln"> fastapi </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">FastAPI</span><span class="pln">

app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">FastAPI</span><span class="pun">()</span><span class="pln">

</span><span class="com"># تهيئة قاعدة البيانات</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> init_db</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="str">"inventory.db"</span><span class="pun">)</span><span class="pln">
    cursor </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">cursor</span><span class="pun">()</span><span class="pln">

    </span><span class="com"># إنشاء جدول البيانات إذا لم يكن موجودًا</span><span class="pln">
    cursor</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"CREATE TABLE IF NOT EXISTS inventory (id INTEGER PRIMARY KEY, name TEXT, quantity INTEGER)"</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># إضافة بيانات افتراضية إذا كانت الجدول فارغة</span><span class="pln">
    cursor</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"SELECT COUNT(*) FROM inventory"</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> cursor</span><span class="pun">.</span><span class="pln">fetchone</span><span class="pun">()[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">:</span><span class="pln">
        sample_data </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
            </span><span class="pun">(</span><span class="str">"Laptop"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">),</span><span class="pln">
            </span><span class="pun">(</span><span class="str">"Keyboard"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">25</span><span class="pun">),</span><span class="pln">
            </span><span class="pun">(</span><span class="str">"Mouse"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">30</span><span class="pun">),</span><span class="pln">
            </span><span class="pun">(</span><span class="str">"Monitor"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">15</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">]</span><span class="pln">
        cursor</span><span class="pun">.</span><span class="pln">executemany</span><span class="pun">(</span><span class="str">"INSERT INTO inventory (name, quantity) VALUES (?, ?)"</span><span class="pun">,</span><span class="pln"> sample_data</span><span class="pun">)</span><span class="pln">
        conn</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">

    conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">

init_db</span><span class="pun">()</span><span class="pln">  </span><span class="com"># تهيئة كائن قاعدة البيانات</span><span class="pln">

</span><span class="com"># دالة لجلب العناصر الموجودة بالمخزون</span><span class="pln">
</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="str">"/inventory"</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> get_inventory</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="str">"inventory.db"</span><span class="pun">)</span><span class="pln">
    cursor </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">cursor</span><span class="pun">()</span><span class="pln">
    cursor</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"SELECT * FROM inventory"</span><span class="pun">)</span><span class="pln">
    items </span><span class="pun">=</span><span class="pln"> cursor</span><span class="pun">.</span><span class="pln">fetchall</span><span class="pun">()</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="str">"inventory"</span><span class="pun">:</span><span class="pln"> items</span><span class="pun">}</span></pre>

<p>
	لتشغيل هذا التطبيق نكتب في الطرفية الأمر التالي :
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_5195_45" style=""><span class="pln">uvicorn inventory_api:app --reload</span></pre>

<p>
	بهذا تصبح واجهة التطبيق البرمجية <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> تعمل وجاهزة للاستخدام من خلال الرابط المحلي <code>http://127.0.0.1:8000/inventory</code>، أو الرابط <code>http://127.0.0.1:8000/docs#/default/get_inventory_inventory_get</code> الذي يسمح لنا بتجربة نقاط النهاية End-points للواجهة البرمجية <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> باستخدام واجهة صفحة ويب.
</p>

<p>
	نوجه BlackBoxAI بعدها لتوليد ملف اختبار أداء باستخدام مكتبة Locust والتي ستسمح لنا باختبار واجهة التطبيق البرمجية <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> بمحاكاة استخدام عدد كبيرة من المستخدمين النظام في نفس الوقت وتساعد في تحليل أداء النظام بعرض رسومات بيانية توضح سرعة الرد وعدد مرات الفشل مقارنة بعدد الطلبات التي نفذها النظام بنجاح وغيرها من المزايا الأخرى.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5195_47" style=""><span class="kwd">from</span><span class="pln"> locust </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">HttpUser</span><span class="pun">,</span><span class="pln"> task</span><span class="pun">,</span><span class="pln"> between

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">InventoryUser</span><span class="pun">(</span><span class="typ">HttpUser</span><span class="pun">):</span><span class="pln">
    wait_time </span><span class="pun">=</span><span class="pln"> between</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln">  </span><span class="com"># كل مستخدم ينتظر بين 1-2 ثانية بين الطلبات</span><span class="pln">

    </span><span class="str">""" inventory محاكاة أكثر من مستخدم يتصل بنقطة النهاية """</span><span class="pln">

    </span><span class="lit">@task</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> load_test_inventory</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="pun">‫</span><span class="str">"""مستخدم افتراضي يقوم بطلب رابط inventory بشكل متكرر"""</span><span class="pln">
        response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="str">"/inventory"</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status_code </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">200</span><span class="pun">:</span><span class="pln">
            response</span><span class="pun">.</span><span class="pln">failure</span><span class="pun">(</span><span class="str">"Failed to fetch inventory"</span><span class="pun">)</span></pre>

<p>
	نشغل الأمر التالي من خلال الطرفية، حيث نمرر اسم الملف والرابط المحلي المؤدي للموقع.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_5195_49" style=""><span class="pln">locust -f load_test.py --host=http://127.0.0.1:8000</span></pre>

<p>
	سنحصل على الرابط التالي <code><a href="http://localhost:8089" ipsnoembed="false" rel="external nofollow">http://localhost:8089</a></code> لتجربة أداة الاختبار، حيث نجد عند فتح الصفحة بعض المدخلات التي نستطيع التحكم فيها، مثل عدد المستخدمين، وتزايد ذلك العدد في كل ثانية Ramp up حيث أن أداة الاختبار تضيف هذا العدد من المستخدمين كل ثانية حتى تصل لعدد المستخدمين الأقصى بشكل تدريجي، سنضبط عدد المستخدمين ليكون 1000 مستخدم، ومعدل تزايد المستخدمين ليكون 10 في الثانية.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="181753" href="https://academy.hsoub.com/uploads/monthly_2026_03/__004.png.697f817638196e0a2adbd8ad0170ad29.png" rel=""><img alt="إعدادات_المحاكاة_004.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181753" data-ratio="55.67" data-unique="fgxdxcpt6" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_03/__004.thumb.png.de401f95c38b451fd9093e987723d8f8.png"></a>
</p>

<p>
	نرى هنا الإحصائيات المولدة في الوقت الحقيقي، حيث يرينا الرسم البياني الأول عدد الطلبات المرسلة لواجهة التطبيق البرمجية <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> في كل ثانية، بينما يظهر الرسم الثاني سرعة تجاوب النظام، أما الرسم الثالث فيظهر عدد المستخدمين الذي ازداد تدريجيًا حتى وصل للحد الأقصى المحدد مسبقًا، ونلاحظ أن النظام كان قادرًا على التعامل مع ألف مستخدم بسهولة ومع عدد من الطلبات يصل إلى 650 طلب في الثانية بدون أي فشل في إجابة الطلبات المرسلة للواجهة البرمجية للتطبيق <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="181754" href="https://academy.hsoub.com/uploads/monthly_2026_03/__005.png.ad39d1886ac76c5867abcd1b886fad2c.png" rel=""><img alt="احصائيات_الأداء_005.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181754" data-ratio="86.80" data-unique="qsu52rdo5" style="width: 591px; height: auto;" width="691" src="https://academy.hsoub.com/uploads/monthly_2026_03/__005.thumb.png.d23d6084de1f6e265a7c22392ed7712d.png"></a>
</p>

<p>
	يمكننا تعريض النظام لمستوى أعلى من الضغط لنرى نسبة الفشل في إجابة الطلبات، بأن نجعل عدد المستخدمين 100 ألف مثلًا، يزيدون بمعدل 1000 في كل مرة، سيزيد احتمال حدوث الفشل لعجز نظامنا على التعامل مع هذا الكم من الضغط.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="181755" href="https://academy.hsoub.com/uploads/monthly_2026_03/__006.png.e8af56b85a6427ef3077a378e5ab9bf5.png" rel=""><img alt="احصائيات_الفشل_006.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181755" data-ratio="86.80" data-unique="l6b2p5673" style="width: 591px; height: auto;" width="691" src="https://academy.hsoub.com/uploads/monthly_2026_03/__006.thumb.png.38be01aa940d201773d30567bf9b0ecb.png"></a>
</p>

<p>
	وصلت نسبة الفشل إلى 88% حيث فشل 332008 طلب من أصل 377561، وكان متوسط سرعة الإجابة 174 ثانية، وهذا وقت طويل يدل أن النظام لم يستطع تحمل الزيادة في الحمل، كما نرى من الرسومات البيانية الموضحة بالأعلى أن النظام بدأ في التدهور عندما تجاوز عدد المستخدمين 50 ألف تقريبًا، هذه المعلومات تمكننا من تحسين كفاءة النظام تحت الضغط بإجراءات مثل تحجيم عدد الطلبات المسموح للمستخدم إرسالها في فترة زمنية محددة، واستخدام <a href="https://academy.hsoub.com/programming/general/%D9%87%D9%8A%D8%A7%D9%83%D9%84-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-data-structures/#:~:text=underflow%20%5C(%D9%82%D8%B9%D8%B1%20%D8%A7%D9%84%D9%85%D9%83%D8%AF%D8%B3%5C" rel="">رتل Queue</a> لتخزين الطلبات الفائضة عن سعة النظام لتعالج عندما يستطيع النظام استيعاب المزيد من الطلبات، فبدلًا من محاولة معالجة 100 ألف طلب نستطيع تخزين الطلبات في رتل Queue وندفع الطلبات للنظام على هيئة دفعات كل دفعة تتكون من 10000 طلب فقط.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="181756" href="https://academy.hsoub.com/uploads/monthly_2026_03/__007.png.4cf2fd03c0a5a33a3dba682407c34149.png" rel=""><img alt="ملخص_الاحصائيات_007.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181756" data-ratio="30.50" data-unique="of7q2vet0" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_03/__007.thumb.png.aa78950e7e78ee9a99cafd437bfaf7a3.png"></a>
</p>

<h2 id="-">
	خاتمة
</h2>

<p>
	تعرفنا في هذا المقال على كيفية استغلال قدرات أداة الذكاء الاصطناعي BlackBoxAI لتطبيق مبادئ اختبار البرامج Software testing بأنواعها المختلفة، مثل الاختبارات الوظيفية Functional testing كاختبار الوحدة Unit test واختبار الانحدار Regression test بالإضافة للاختبارات غير الوظيفية Non-functional testing التي تركز على اختبار أداء النظام وسرعة استجابته ونقاط الفشل المحتملة للنظام عن طريق محاكاة ظروف استخدام مختلفة، حيث استطعنا توليد الشيفرة المناسبة من خلال استخدام الموجهات Prompts المناسبة في BlackBoxAI وتحسينه حسب حاجتنا لذلك.
</p>

<p>
	وكما أن عملية توجيه أدوات الذكاء الاصطناعي عملية تكرارية ومستمرة فإن عملية اختبار البرامج كذلك تمر بدورات متتابعة من المحاولة والخطأ والتحسين المستمر للوصول لنسبة تغطية كافية لحالات الاختبار والحالات المتطرفة مما يضيق نطاق البحث عند وقوع أخطاء أو مشكلات في برامجنا، فيسهل اكتشافها وحلها بسرعة.
</p>

<h2 id="-">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="%5Bhttps://academy.hsoub.com/programming/artificial-intelligence/%D8%A3%D9%81%D8%B6%D9%84-%D9%85%D9%85%D8%A7%D8%B1%D8%B3%D8%A7%D8%AA-%D9%87%D9%86%D8%AF%D8%B3%D8%A9-%D8%A7%D9%84%D9%85%D9%8F%D9%88%D8%AC%D9%91%D9%90%D9%87%D8%A7%D8%AA/%5D%5C(https://academy.hsoub.com/programming/artificial-intelligence/%D8%A3%D9%81%D8%B6%D9%84-%D9%85%D9%85%D8%A7%D8%B1%D8%B3%D8%A7%D8%AA-%D9%87%D9%86%D8%AF%D8%B3%D8%A9-%D8%A7%D9%84%D9%85%D9%8F%D9%88%D8%AC%D9%91%D9%90%D9%87%D8%A7%D8%AA/%5C" rel="">أفضل ممارسات هندسة المُوجِّهات Prompt Engineering: نصائح وحيل وأدوات</a>
	</li>
</ul>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/artificial-intelligence/%D8%A7%D9%83%D8%AA%D8%B4%D9%81-%D8%A8%D8%AF%D8%A7%D8%A6%D9%84-chatgpt-%D9%85%D9%81%D8%AA%D9%88%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1-r2488/" rel="">اكتشف بدائل ChatGPT مفتوحة المصدر</a>
	</li>
</ul>

<ul>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/databases/%D9%83%D9%8A%D9%81-%D9%88%D9%85%D8%AA%D9%89-%D9%86%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-sqlite-r111/" rel="">كيف ومتى نستخدم SQLite</a>
	</li>
</ul>

<ul>
	<li>
		<p>
			<a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-api-r1314/" rel="">مدخل إلى الواجهات البرمجية <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr></a>
		</p>
	</li>
	<li>
		<p>
			<a href="https://academy.hsoub.com/programming/artificial-intelligence/%D8%AF%D9%85%D8%AC-%D9%85%D8%B3%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%B0%D9%83%D8%A7%D8%A1-%D8%A7%D9%84%D8%A7%D8%B5%D8%B7%D9%86%D8%A7%D8%B9%D9%8A-github-copilot-%D9%85%D8%B9-%D9%85%D8%AD%D8%B1%D8%B1-%D8%A7%D9%84%D8%A3%D9%83%D9%88%D8%A7%D8%AF-vs-code-r2521/" rel="">دمج مساعد الذكاء الاصطناعي GitHub Copilot مع محرر الأكواد VS Code</a>
		</p>
	</li>
</ul>
]]></description><guid isPermaLink="false">2605</guid><pubDate>Mon, 23 Mar 2026 11:17:01 +0000</pubDate></item><item><title>&#x643;&#x64A;&#x641; &#x62A;&#x633;&#x62A;&#x62E;&#x62F;&#x645; &#x62F;&#x648;&#x627;&#x644; &#x627;&#x644;&#x62A;&#x62C;&#x632;&#x626;&#x629; &#x644;&#x644;&#x639;&#x62B;&#x648;&#x631; &#x639;&#x644;&#x649; &#x627;&#x644;&#x645;&#x644;&#x641;&#x627;&#x62A; &#x627;&#x644;&#x645;&#x643;&#x631;&#x631;&#x629; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x644;&#x63A;&#x629; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646;</title><link>https://academy.hsoub.com/programming/python/%D9%83%D9%8A%D9%81-%D8%AA%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D8%AF%D9%88%D8%A7%D9%84-%D8%A7%D9%84%D8%AA%D8%AC%D8%B2%D8%A6%D8%A9-%D9%84%D9%84%D8%B9%D8%AB%D9%88%D8%B1-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D9%83%D8%B1%D8%B1%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2604/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2026_03/ChatGPTImageMar17202610_11_22AM.png.b2b705e55fb83e9fcb727952a1fcef85.png" /></p>
<p>
	لنفترض أننا نريد العثور على ملفات مكررة على الحاسوب مثل نسخ إضافية من صور أو مجموعة بيانات، قد نجري عملية بحث عادية ونحصل على أسماء الملفات المتشابهة لكن قد يغير المستخدم اسم الملف وهذا يعني أن المحتويات ستظل نفسها، فنحن نحتاج الآن إلى مقارنة تلك المحتويات، لكن إن كان لدينا الكثير من الملفات فستكون هذه عملية بطيئة.
</p>

<p>
	يمكن تقدير مدى بطء هذه العملية بحساب بسيط حيث يمكن ترتيب عدد n من العناصر في أزواج بعدد من الطرق يساوي n(n-1)، وإذا حذفنا الأزواج المكررة -إذا اعتبرنا أن الزوج A-B هو نفسه B-A يكون لدينا عدد من الأزواج الفريدة يساوي n(n-1)/2 أو (n²-n)/2 للتبسيط، وكلما زادت قيمة n تناسبت تقريبيًا مع n2، وهنا قد يقال أن تعقيد الوقت Time complexity لخوارزميتنا هو O(n²). بعبارة أبسط، حين يتضاعف عدد الملفات يزيد زمن التشغيل أربع مرات تقريبًا، أي أن الوقت المستغرق لكل ملف يزيد مع زيادة العدد الإجمالي للملفات.
</p>

<p>
	لا يمكن تجنب هذا النوع من التباطؤ في الغالب، لكن لدينا حل أفضل في مثالنا هنا، حيث سنتمكن تجميع الملفات التي لديها نفس المعرف ثم نقارن بين الملفات داخل كل مجموعة، إذا ولّدنا معرفًا identifier قصيرًا لكل ملف يعتمد على البايتات التي يحتويها، ويُعد هذا الأسلوب أسرع لأننا ننفذ عملية المقارنة المكلفة -مقارنة كل بايت مقابل بايت على حدة- على الملفات التي يُحتمل تطابقها فقط، بل قد نتجنب عملية المقارنة بالكلية إذا أتقنا توليد هذه المعرفات.
</p>

<h2 id="-">
	الأسلوب التقليدي
</h2>

<p>
	سنبدأ بتنفيذ الأسلوب غير الفعال الذي يعتمد على تعقيد الوقت O(N²) لكي نقارنه بالتصميمات المتطورة التي سنكتبها لاحقًا.
</p>

<p>
	يأخذ البرنامج القصير أدناه -لنسميه <code>brute_force_1.py</code>- قائمة بأسماء الملفات من سطر الأوامر ثم يبحث عن الملفات المكررة، ويطبع النتائج المتطابقة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_7" style=""><span class="kwd">import</span><span class="pln"> sys


</span><span class="com"># [bytes]</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> same_bytes</span><span class="pun">(</span><span class="pln">left_name</span><span class="pun">,</span><span class="pln"> right_name</span><span class="pun">):</span><span class="pln">
 left_bytes </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">left_name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"rb"</span><span class="pun">).</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
 right_bytes </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">right_name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"rb"</span><span class="pun">).</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
 </span><span class="kwd">return</span><span class="pln"> left_bytes </span><span class="pun">==</span><span class="pln"> right_bytes
</span><span class="com"># [/bytes]</span><span class="pln">


</span><span class="com"># [main]</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> find_duplicates</span><span class="pun">(</span><span class="pln">filenames</span><span class="pun">):</span><span class="pln">
 matches </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">
 </span><span class="kwd">for</span><span class="pln"> left </span><span class="kwd">in</span><span class="pln"> filenames</span><span class="pun">:</span><span class="pln">
 </span><span class="kwd">for</span><span class="pln"> right </span><span class="kwd">in</span><span class="pln"> filenames</span><span class="pun">:</span><span class="pln">
 </span><span class="kwd">if</span><span class="pln"> same_bytes</span><span class="pun">(</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">):</span><span class="pln">
 matches</span><span class="pun">.</span><span class="pln">append</span><span class="pun">((</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">))</span><span class="pln">
 </span><span class="kwd">return</span><span class="pln"> matches


</span><span class="kwd">if</span><span class="pln"> __name__ </span><span class="pun">==</span><span class="pln"> </span><span class="str">"__main__"</span><span class="pun">:</span><span class="pln">
 duplicates </span><span class="pun">=</span><span class="pln"> find_duplicates</span><span class="pun">(</span><span class="pln">sys</span><span class="pun">.</span><span class="pln">argv</span><span class="pun">[</span><span class="lit">1</span><span class="pun">:])</span><span class="pln">
 </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> duplicates</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">)</span><span class="pln">
</span><span class="com"># [/main]</span></pre>

<p>
	يستخدم هذا البرنامج دالة <code>same_bytes</code> تقرأ ملفين وتقارن بينهما "بايت مقابل بايت":
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_9" style=""><span class="kwd">def</span><span class="pln"> same_bytes</span><span class="pun">(</span><span class="pln">left_name</span><span class="pun">,</span><span class="pln"> right_name</span><span class="pun">):</span><span class="pln">
    left_bytes </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">left_name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"rb"</span><span class="pun">).</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
    right_bytes </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">right_name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"rb"</span><span class="pun">).</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> left_bytes </span><span class="pun">==</span><span class="pln"> right_bytes</span></pre>

<p>
	لاحظ في المثال أعلاه أننا نفتح الملفات في الوضع الثنائي binary mode باستخدام الوسم <code>rb</code> بدلًا من <code>r</code> المعتاد، وهذا يخبر لغة بايثون أن تقرأ البايتات تمامًا كما هي بدلًا من محاولة تحويلها إلى محارف نصية.
</p>

<p>
	ننشئ مجلدًا باسم <code>tests</code> من أجل اختبار هذا البرنامج والبرامج الأخرى، ويحتوي على ستة ملفات:
</p>

<table>
	<thead>
		<tr>
			<th>
				اسم الملف
			</th>
			<th>
				<code>a1.txt</code>
			</th>
			<th>
				<code>a2.txt</code>
			</th>
			<th>
				<code>a3.txt</code>
			</th>
			<th>
				<code>b1.txt</code>
			</th>
			<th>
				<code>b2.txt</code>
			</th>
			<th>
				<code>c1.txt</code>
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				المحتوى
			</td>
			<td style="text-align: center;">
				<code>aaa</code>
			</td>
			<td style="text-align: center;">
				<code>aaa</code>
			</td>
			<td style="text-align: center;">
				<code>aaa</code>
			</td>
			<td style="text-align: center;">
				<code>bb</code>
			</td>
			<td style="text-align: center;">
				<code>bb</code>
			</td>
			<td style="text-align: center;">
				<code>c</code>
			</td>
		</tr>
	</tbody>
</table>

<p>
	نتوقع أن يتم الإبلاغ عن ملفات <code>a</code> الثلاثية وملفات <code>b</code> الثنائية كملفات مكررة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_13" style=""><span class="pln">python brute_force_1</span><span class="pun">.</span><span class="pln">py tests</span><span class="pun">/*.</span><span class="pln">txt</span></pre>

<p>
	ينبغي أن نحصل على الخرج التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_15" style=""><span class="pln">tests</span><span class="pun">/</span><span class="pln">a1</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">a1</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">a1</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">a2</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">a1</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">a3</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">a2</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">a1</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">a2</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">a2</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">a2</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">a3</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">a3</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">a1</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">a3</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">a2</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">a3</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">a3</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">b1</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">b1</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">b1</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">b2</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">b2</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">b1</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">b2</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">b2</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">c1</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">c1</span><span class="pun">.</span><span class="pln">txt</span></pre>

<p>
	هذا الخرج غير مفيد وإن كان صحيحًا، إذ يبلغ عن كل ملف على أنه مطابق لنفسه، كما يُكرر الإبلاغ مرتين عن كل تطابق بين ملفين مختلفين، لنصلح هذه الحلقة المتداخلة nested loop في دالة <code>find_duplicates</code> كي نتأكد أننا نفحص أزواج الملفات التي يحتمل اختلافها مرة واحدة فقط:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_17" style=""><span class="kwd">import</span><span class="pln"> sys


</span><span class="kwd">def</span><span class="pln"> same_bytes</span><span class="pun">(</span><span class="pln">left_name</span><span class="pun">,</span><span class="pln"> right_name</span><span class="pun">):</span><span class="pln">
    left_bytes </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">left_name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"rb"</span><span class="pun">).</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
    right_bytes </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">right_name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"rb"</span><span class="pun">).</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> left_bytes </span><span class="pun">==</span><span class="pln"> right_bytes


</span><span class="com"># [dup]</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> find_duplicates</span><span class="pun">(</span><span class="pln">filenames</span><span class="pun">):</span><span class="pln">
    matches </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> i_left </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="pln">len</span><span class="pun">(</span><span class="pln">filenames</span><span class="pun">)):</span><span class="pln">
        left </span><span class="pun">=</span><span class="pln"> filenames</span><span class="pun">[</span><span class="pln">i_left</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">for</span><span class="pln"> i_right </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="pln">i_left</span><span class="pun">):</span><span class="pln">
            right </span><span class="pun">=</span><span class="pln"> filenames</span><span class="pun">[</span><span class="pln">i_right</span><span class="pun">]</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> same_bytes</span><span class="pun">(</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">):</span><span class="pln">
                matches</span><span class="pun">.</span><span class="pln">append</span><span class="pun">((</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">))</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> matches
</span><span class="com"># [/dup]</span><span class="pln">


</span><span class="kwd">if</span><span class="pln"> __name__ </span><span class="pun">==</span><span class="pln"> </span><span class="str">"__main__"</span><span class="pun">:</span><span class="pln">
    duplicates </span><span class="pun">=</span><span class="pln"> find_duplicates</span><span class="pun">(</span><span class="pln">sys</span><span class="pun">.</span><span class="pln">argv</span><span class="pun">[</span><span class="lit">1</span><span class="pun">:])</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> duplicates</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">)</span></pre>

<p style="text-align: center;">
	<img alt="triangle.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181742" data-ratio="100.00" data-unique="sokyug4mr" width="171" src="https://academy.hsoub.com/uploads/monthly_2026_03/triangle.png.45e700f5d91a0cfe478600476d156cb8.png">
</p>

<p style="text-align: center;">
	تحديد نطاق الحلقة الداخلية لإنتاج تركبيات فريدة.
</p>

<h2 id="-">
	تجزئة الملفات
</h2>

<p>
	نريد الآن أن نعالج كل ملف مرة واحدة فقط لإنتاج معرف قصير يعتمد على محتويات الملف ثم نقارن الملفات التي تحمل نفس المعرف، وهي الملفات التي يُحتمل أن تكون متطابقة.
</p>

<p>
	إذا قُسمت الملفات إلى عدد g من المجموعات فستحتوي كل مجموعة على عدد N/g من الملفات، ويصبح إجمالي العمل المطلوب O(g(N / g²)) - على سبيل المثال: عدد g مجموعة * عدد مقارنات يساوي (N/g²) داخل كل مجموعة-، أي أننا نحصل على (N²/g) مما يعني أن زمن التشغيل الكلي <strong>ينخفض</strong> بزيادة عدد المجموعات.
</p>

<p style="text-align: center;">
	<img alt="hash_group.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181743" data-ratio="51.89" data-unique="7zaxhm195" width="291" src="https://academy.hsoub.com/uploads/monthly_2026_03/hash_group.png.a9b9f6129db7b23487892b8d76076c9b.png">
</p>

<p style="text-align: center;">
	تجميع الملفات عبر شيفرة التجزئة يقلص عدد المقارنات من 15 إلى 4 فقط.
</p>

<p>
	نستطيع إنشاء معرفات للملفات باستخدام دالة تجزئة hash function لإنتاج شيفرة تجزئة hash code، وبما أن البايتات في الأصل عبارة عن أرقام فنستطيع إنشاء دالة تجزئة بسيطة عبر جمع قيم البايتات الموجودة في الملف ثم أخذ باقي القسم على رقم معين:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_19" style=""><span class="com"># [hash]</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> naive_hash</span><span class="pun">(</span><span class="pln">data</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> sum</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> </span><span class="lit">13</span><span class="pln">
</span><span class="com"># [/hash]</span><span class="pln">

</span><span class="kwd">if</span><span class="pln"> __name__ </span><span class="pun">==</span><span class="pln"> </span><span class="str">"__main__"</span><span class="pun">:</span><span class="pln">
    </span><span class="com"># [example]</span><span class="pln">
    example </span><span class="pun">=</span><span class="pln"> bytes</span><span class="pun">(</span><span class="str">"hashing"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"utf-8"</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> i </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">example</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">):</span><span class="pln">
        substring </span><span class="pun">=</span><span class="pln"> example</span><span class="pun">[:</span><span class="pln">i</span><span class="pun">]</span><span class="pln">
        hash </span><span class="pun">=</span><span class="pln"> naive_hash</span><span class="pun">(</span><span class="pln">substring</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">f</span><span class="str">"{hash:2} {substring}"</span><span class="pun">)</span><span class="pln">
    </span><span class="com"># [/example]</span></pre>

<p>
	إليك اختبارًا سريعًا يحسب شيفرة التجزئة لأجزاء من كلمة hashing يتزايد طولها:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_21" style=""><span class="pln">example </span><span class="pun">=</span><span class="pln"> bytes</span><span class="pun">(</span><span class="str">"hashing"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"utf-8"</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> i </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">example</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">):</span><span class="pln">
    substring </span><span class="pun">=</span><span class="pln"> example</span><span class="pun">[:</span><span class="pln">i</span><span class="pun">]</span><span class="pln">
    hash </span><span class="pun">=</span><span class="pln"> naive_hash</span><span class="pun">(</span><span class="pln">substring</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">f</span><span class="str">"{hash:2} {substring}"</span><span class="pun">)</span></pre>

<p>
	يكون الخرج ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_23" style=""><span class="lit">0</span><span class="pln"> b</span><span class="str">'h'</span><span class="pln">
 </span><span class="lit">6</span><span class="pln"> b</span><span class="str">'ha'</span><span class="pln">
 </span><span class="lit">4</span><span class="pln"> b</span><span class="str">'has'</span><span class="pln">
 </span><span class="lit">4</span><span class="pln"> b</span><span class="str">'hash'</span><span class="pln">
 </span><span class="lit">5</span><span class="pln"> b</span><span class="str">'hashi'</span><span class="pln">
</span><span class="lit">11</span><span class="pln"> b</span><span class="str">'hashin'</span><span class="pln">
</span><span class="lit">10</span><span class="pln"> b</span><span class="str">'hashing'</span></pre>

<p>
	يبدو الخرج عشوائيًا، نريد إجراء اختبار أكثر صرامة عبر تجربة تجزئة كل سطر نصي في <a href="https://www.gutenberg.org/" rel="external nofollow">رواية دراكولا -نسخة مشروع جوتنبرج-</a>، ثم نرسم مخططًا بيانيًا للتوزيع:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="181744" href="https://academy.hsoub.com/uploads/monthly_2026_03/naive_dracula.png.78af9cd6a1260e0f5440955c3d91df6a.png" rel=""><img alt="naive_dracula.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181744" data-ratio="40.50" data-unique="aakrgk5xt" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_03/naive_dracula.thumb.png.b48437924849f17c75a161d37c5ce320.png"></a>
</p>

<p style="text-align: center;">
	توزيع شيفرات التجزئة لكل سطر في رواية دراكولا.
</p>

<p>
	تظهر أغلب الدلاء buckets - أي الأعمدة في الرسم البياني- متساوية في الطول، باستثناء ارتفاع ملحوظ عند القيمة صفر، لقد اعتمد تقديرنا لترميز Big O حول مدى كفاءة خوارزميتنا على توزيع الملفات بالتساوي بين المجموعات، فلن تعمل الشيفرة بالسرعة المطلوبة إذا لم يتحقق هذا الشرط.
</p>

<p>
	يتبين بالقليل من البحث أن الملف النصي الذي نستخدمه يستخدم سطرًا فارغًا للفصل بين الفقرات، تنتج هذه الأسطر قيمة تجزئة تساوي صفر، لذا يعكس ذلك الارتفاع توزيعًا غير عادل في البيانات، و ستصبح النتيجة أكثر توازنًا إذا رسمنا توزيع شيفرة التجزئة للأسطر الفريدة فقط:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="181745" href="https://academy.hsoub.com/uploads/monthly_2026_03/naive_dracula_unique.png.018165c116176db93b1d603b63bb26fd.png" rel=""><img alt="naive_dracula_unique.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181745" data-ratio="40.50" data-unique="2v4zbkfbm" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_03/naive_dracula_unique.thumb.png.32fce3c0951103cad53eb4accc62d34c.png"></a>
</p>

<p style="text-align: center;">
	توزيع شيفرات التجزئة للأسطر الفريدة في رواية دراكولا.
</p>

<p>
	تُعد التجزئة أداة قوية للغاية، حيث تجزئ القواميس في لغة بايثون مثلًا مفاتيحها لتسريع عملية البحث، وبما أننا الآن نستطيع تجزئة الملفات فإننا نستطيع بناء قاموس يستخدم شيفرات التجزئة كمفاتيح ومجموعات أسماء الملفات كقيم:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_27" style=""><span class="kwd">import</span><span class="pln"> sys
</span><span class="kwd">from</span><span class="pln"> naive_hash </span><span class="kwd">import</span><span class="pln"> naive_hash


</span><span class="kwd">def</span><span class="pln"> same_bytes</span><span class="pun">(</span><span class="pln">left_name</span><span class="pun">,</span><span class="pln"> right_name</span><span class="pun">):</span><span class="pln">
    left_bytes </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">left_name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"rb"</span><span class="pun">).</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
    right_bytes </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">right_name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"rb"</span><span class="pun">).</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> left_bytes </span><span class="pun">==</span><span class="pln"> right_bytes


</span><span class="kwd">def</span><span class="pln"> find_duplicates</span><span class="pun">(</span><span class="pln">filenames</span><span class="pun">):</span><span class="pln">
    matches </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> i_left </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="pln">len</span><span class="pun">(</span><span class="pln">filenames</span><span class="pun">)):</span><span class="pln">
        left </span><span class="pun">=</span><span class="pln"> filenames</span><span class="pun">[</span><span class="pln">i_left</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">for</span><span class="pln"> i_right </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="pln">i_left</span><span class="pun">):</span><span class="pln">
            right </span><span class="pun">=</span><span class="pln"> filenames</span><span class="pun">[</span><span class="pln">i_right</span><span class="pun">]</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> same_bytes</span><span class="pun">(</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">):</span><span class="pln">
                matches</span><span class="pun">.</span><span class="pln">append</span><span class="pun">((</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">))</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> matches


</span><span class="com"># [group]</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> find_groups</span><span class="pun">(</span><span class="pln">filenames</span><span class="pun">):</span><span class="pln">
    groups </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> fn </span><span class="kwd">in</span><span class="pln"> filenames</span><span class="pun">:</span><span class="pln">
        data </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">fn</span><span class="pun">,</span><span class="pln"> </span><span class="str">"rb"</span><span class="pun">).</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
        hash_code </span><span class="pun">=</span><span class="pln"> naive_hash</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> hash_code </span><span class="kwd">not</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> groups</span><span class="pun">:</span><span class="pln">
            groups</span><span class="pun">[</span><span class="pln">hash_code</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> set</span><span class="pun">()</span><span class="pln">
        groups</span><span class="pun">[</span><span class="pln">hash_code</span><span class="pun">].</span><span class="pln">add</span><span class="pun">(</span><span class="pln">fn</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> groups
</span><span class="com"># [/group]</span><span class="pln">


</span><span class="kwd">if</span><span class="pln"> __name__ </span><span class="pun">==</span><span class="pln"> </span><span class="str">"__main__"</span><span class="pun">:</span><span class="pln">
    </span><span class="com"># [main]</span><span class="pln">
    groups </span><span class="pun">=</span><span class="pln"> find_groups</span><span class="pun">(</span><span class="pln">sys</span><span class="pun">.</span><span class="pln">argv</span><span class="pun">[</span><span class="lit">1</span><span class="pun">:])</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> filenames </span><span class="kwd">in</span><span class="pln"> groups</span><span class="pun">.</span><span class="pln">values</span><span class="pun">():</span><span class="pln">
        duplicates </span><span class="pun">=</span><span class="pln"> find_duplicates</span><span class="pun">(</span><span class="pln">list</span><span class="pun">(</span><span class="pln">filenames</span><span class="pun">))</span><span class="pln">
        </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> duplicates</span><span class="pun">:</span><span class="pln">
            </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">)</span><span class="pln">
    </span><span class="com"># [/main]</span></pre>

<p>
	تتحقق الشيفرة أعلاه في كل مرة يحسب البرنامج فيها شيفرة تجزئة مما إذا كانت القيمة قد ظهرت من قبل، وينشئ إدخالًا جديدًا إذا لم تكن موجودة في قاموس المجموعات <code>groups</code>، بحيث تكون شيفرة التجزئة هي المفتاح، ويضع مجموعة فارغة كقيمة لها، وهكذا يضمن وجود مجموعة جاهزة لإضافة اسم الملف إليها. انظر المقطع التالي من الشيفرة الذي يوضح منطق تجميع الملفات بناءً على شيفرة التجزئة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_29" style=""><span class="kwd">def</span><span class="pln"> find_groups</span><span class="pun">(</span><span class="pln">filenames</span><span class="pun">):</span><span class="pln">
    groups </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> fn </span><span class="kwd">in</span><span class="pln"> filenames</span><span class="pun">:</span><span class="pln">
        data </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">fn</span><span class="pun">,</span><span class="pln"> </span><span class="str">"rb"</span><span class="pun">).</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
        hash_code </span><span class="pun">=</span><span class="pln"> naive_hash</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> hash_code </span><span class="kwd">not</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> groups</span><span class="pun">:</span><span class="pln">
            groups</span><span class="pun">[</span><span class="pln">hash_code</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> set</span><span class="pun">()</span><span class="pln">
        groups</span><span class="pun">[</span><span class="pln">hash_code</span><span class="pun">].</span><span class="pln">add</span><span class="pun">(</span><span class="pln">fn</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> groups</span></pre>

<p>
	نستطيع الآن إعادة استخدام معظم الشيفرة التي كتبناها سابقًا للبحث عن التكرارات داخل كل مجموعة على حدة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_31" style=""><span class="pln">groups </span><span class="pun">=</span><span class="pln"> find_groups</span><span class="pun">(</span><span class="pln">sys</span><span class="pun">.</span><span class="pln">argv</span><span class="pun">[</span><span class="lit">1</span><span class="pun">:])</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> filenames </span><span class="kwd">in</span><span class="pln"> groups</span><span class="pun">.</span><span class="pln">values</span><span class="pun">():</span><span class="pln">
    duplicates </span><span class="pun">=</span><span class="pln"> find_duplicates</span><span class="pun">(</span><span class="pln">list</span><span class="pun">(</span><span class="pln">filenames</span><span class="pun">))</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> duplicates</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">)</span></pre>

<p>
	فيكون الخرج كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_33" style=""><span class="pln">tests</span><span class="pun">/</span><span class="pln">a2</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">a1</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">a3</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">a1</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">a3</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">a2</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">b1</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">b2</span><span class="pun">.</span><span class="pln">txt</span></pre>

<h2 id="-">
	تجزئة أفضل
</h2>

<p>
	بالعودة إلى صيغة O(n²/g) التي تخبرنا بمقدار الجهد المطلوب إذا قسمنا n من الملفات على g من المجموعات، فإذا كان لدينا عدد مجموعات يساوي عدد الملفات تمامًا -أي حين تكون n مساوية لـ g- فإن الجهد المطلوب لمعالجة n ملف سيكون O(n²/n) أو O(N) للتبسيط، وهذا يعني أن الجهد المطلوب سيتناسب طرديًا مع عدد الملفات، وبما أننا نحتاج إلى قراءة كل ملف مرة واحدة على الأقل على أي حال فلن نتمكن من تحقيق نتيجة أفضل من هذه، لكن كيف نضمن أن كل ملف فريد سيكون في مجموعته التي ينتمي لها؟
</p>

<p>
	يكمن الحل في استخدام دالة تجزئة تشفيرية Cryptographic hash function تتميز مخرجاتها أنها حتمية، فإذا أعطيتها نفس البايتات بنفس الترتيب ستنتج نفس الخرج في كل مرة، ومع هذا فإن الخرج يتوزع مثل متغير عشوائي منتظم بحيث يتساوى احتمال كل خرج منها مما يضمن توزيع الملفات بالعدل بين المجموعات.
</p>

<p>
	تصعب كتابة دوال التجزئة بالتشفير كما يصعب كذلك إثبات أن خوارزمية ما لديها الخصائص التي نريدها، لذا سنستخدم دالة من مكتبة التجزئة <code>hashlib</code> في لغة بايثون التي تنفذ خوارزمية التجزئة SHA-256.
</p>

<p>
	تنتج هذه الدالة تجزئة بحجم 256 بت عند إعطائها بعض البايتات كمدخلات، وتُكتب عادة كسلسلة نصية من 64 محرف بالنظام الست عشري حيث يستخدم هذا النظام الأحرف A حتى F لتمثيل الأرقام من 10 إلى 15، فتحسب القيمة 3D5 مثلًا كالتالي:
</p>

<p>
	3 × 16² + 13 × 16¹ + 5 × 16⁰ = 981 بالنظام العشري.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_35" style=""><span class="kwd">from</span><span class="pln"> hashlib </span><span class="kwd">import</span><span class="pln"> sha256

</span><span class="kwd">if</span><span class="pln"> __name__ </span><span class="pun">==</span><span class="pln"> </span><span class="str">"__main__"</span><span class="pun">:</span><span class="pln">
    </span><span class="com"># [example]</span><span class="pln">
    example </span><span class="pun">=</span><span class="pln"> bytes</span><span class="pun">(</span><span class="str">"hash"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"utf-8"</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> i </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">example</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">):</span><span class="pln">
        substring </span><span class="pun">=</span><span class="pln"> example</span><span class="pun">[:</span><span class="pln">i</span><span class="pun">]</span><span class="pln">
        hash </span><span class="pun">=</span><span class="pln"> sha256</span><span class="pun">(</span><span class="pln">substring</span><span class="pun">).</span><span class="pln">hexdigest</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">f</span><span class="str">"{substring}\n{hash}"</span><span class="pun">)</span><span class="pln">
    </span><span class="com"># [/example]</span></pre>

<p>
	ويكون خرجها كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_37" style=""><span class="pln">b</span><span class="str">'h'</span><span class="pln">
aaa9402664f1a41f40ebbc52c9993eb66aeb366602958fdfaa283b71e64db123
b</span><span class="str">'ha'</span><span class="pln">
</span><span class="lit">8693873cd8f8a2d9c7c596477180f851e525f4eaf55a4f637b445cb442a5e340</span><span class="pln">
b</span><span class="str">'has'</span><span class="pln">
</span><span class="lit">9150c74c5f92d51a92857f4b9678105ba5a676d308339a353b20bd38cd669ce7</span><span class="pln">
b</span><span class="str">'hash'</span><span class="pln">
d04b98f48e8f8bcc15c6ae5ac050801cd6dcfd428fb5f9e65c4e16e7807340fa</span></pre>

<h2 id="-">
	مسألة عيد الميلاد
</h2>

<p>
	يمثل احتمال تشارك شخصين في نفس عيد الميلاد 1/365 مع تجاهل يوم 29 فبراير، ومن ثم تكون احتمالية ألا يتشاركاه هو 364/365، وإذا أضفنا شخصًا ثالثًا يكون احتمال ألا يتشارك عيد الميلاد مع الشخصين السابقين هي 363/365، وهكذا يكون الاحتمال الإجمالي لعدم تشارك هؤلاء الأشخاص في نفس عيد الميلاد هي (364/365)*(363/365)، وإذا استمر الحال على هذا النمط سنجد أن هناك احتمالًا بنسبة 50% لوجود شخصين يتشاركان يوم الميلاد نفسه في مجموعة مكونة من 23 شخصًا فقط، تزيد هذه النسبة إلى 99.9% مع وجود 70 شخصًا.
</p>

<h3 id="-">
	تطبيق المنطق على الملفات
</h3>

<p>
	يمكن استخدام نفس المنطق الرياضي لنرى عدد الملفات التي نحتاج إلى تجزئتها قبل أن نصل إلى احتمال حدوث تصادم collision باستخدام تجزئة 256-بت -التصادم في التجزئة يعني أن قيمتين أو أكثر لهما نفس شيفرة التجزئة-، وتخبرنا ويكيبيديا أن الإجابة هي
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_3335_7" style=""><span class="pln">$4 \times 10^{38}$</span></pre>

<p>
	ملفًا تقريبًا، وبما أن الرقم كبير للغاية فنحن مستعدون للمخاطرة. يسهم استخدام دالة المكتبة هذه في جعل برنامج البحث عن الملفات المكررة أقصر بكثير:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_39" style=""><span class="kwd">import</span><span class="pln"> sys
</span><span class="kwd">from</span><span class="pln"> hashlib </span><span class="kwd">import</span><span class="pln"> sha256

</span><span class="kwd">def</span><span class="pln"> find_groups</span><span class="pun">(</span><span class="pln">filenames</span><span class="pun">):</span><span class="pln">
    groups </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> fn </span><span class="kwd">in</span><span class="pln"> filenames</span><span class="pun">:</span><span class="pln">
        data </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">fn</span><span class="pun">,</span><span class="pln"> </span><span class="str">"rb"</span><span class="pun">).</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
        hash_code </span><span class="pun">=</span><span class="pln"> sha256</span><span class="pun">(</span><span class="pln">data</span><span class="pun">).</span><span class="pln">hexdigest</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> hash_code </span><span class="kwd">not</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> groups</span><span class="pun">:</span><span class="pln">
            groups</span><span class="pun">[</span><span class="pln">hash_code</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> set</span><span class="pun">()</span><span class="pln">
        groups</span><span class="pun">[</span><span class="pln">hash_code</span><span class="pun">].</span><span class="pln">add</span><span class="pun">(</span><span class="pln">fn</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> groups


</span><span class="kwd">if</span><span class="pln"> __name__ </span><span class="pun">==</span><span class="pln"> </span><span class="str">"__main__"</span><span class="pun">:</span><span class="pln">
    groups </span><span class="pun">=</span><span class="pln"> find_groups</span><span class="pun">(</span><span class="pln">sys</span><span class="pun">.</span><span class="pln">argv</span><span class="pun">[</span><span class="lit">1</span><span class="pun">:])</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> filenames </span><span class="kwd">in</span><span class="pln"> groups</span><span class="pun">.</span><span class="pln">values</span><span class="pun">():</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="str">", "</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">sorted</span><span class="pun">(</span><span class="pln">filenames</span><span class="pun">)))</span></pre>

<p>
	وعند تشغيل البرنامج:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_41" style=""><span class="pln">python dup</span><span class="pun">.</span><span class="pln">py tests</span><span class="pun">/*.</span><span class="pln">txt</span></pre>

<p>
	نحصل على النتائج التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_43" style=""><span class="pln">tests</span><span class="pun">/</span><span class="pln">a1</span><span class="pun">.</span><span class="pln">txt</span><span class="pun">,</span><span class="pln"> tests</span><span class="pun">/</span><span class="pln">a2</span><span class="pun">.</span><span class="pln">txt</span><span class="pun">,</span><span class="pln"> tests</span><span class="pun">/</span><span class="pln">a3</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">b1</span><span class="pun">.</span><span class="pln">txt</span><span class="pun">,</span><span class="pln"> tests</span><span class="pun">/</span><span class="pln">b2</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">c1</span><span class="pun">.</span><span class="pln">txt</span></pre>

<p>
	المهم هنا أن هذا المنظور الجديد يستطيع التعامل مع مجموعات ملفات ضخمة جدًا، فنحن لا نحتاج إلا إلى فحص كل ملف مرة واحدة فقط مما يقلل زمن التشغيل إلى أدنى مستوى ممكن.
</p>

<h2 id="-">
	خاتمة
</h2>

<p>
	تلخص الصورة أدناه الأفكار الرئيسية الواردة في هذا الفصل، ولعل أهمها أن بعض الخوارزميات تتفوق على غيرها من حيث الكفاءة والأداء.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="181746" href="https://academy.hsoub.com/uploads/monthly_2026_03/concept_map.png.8db8be0c2dd32385cac2a72d8f4c68ee.png" rel=""><img alt="concept_map.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181746" data-ratio="51.50" data-unique="a7rx9sg1f" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_03/concept_map.thumb.png.e62b86bd56976e46b7e46b8fe1118fdb.png"></a>
</p>

<p>
	خريطة تصور اكتشاف الملفات المكررة باستخدام التجزئة.
</p>

<ul>
	<li>
		توضح هذه الخريطة كيف ننتقل من المقارنة الثنائية البطيئة O(n²) إلى التوزيع الذكي باستخدام التجزئة.
	</li>
	<li>
		تحول دالة التجزئة محتوى الملف إلى بصمة رقمية فريدة.
	</li>
	<li>
		يؤدي استخدام خوارزميات مثل SHA-256 إلى تقليل احتمالية التصادمات إلى أدنى مستوياتها.
	</li>
	<li>
		يتحسن أداء البرنامج ليصبح زمن التشغيل خطيًا O(n) مما يسمح لنا بمعالجة كميات هائلة من البيانات في وقت قياسي.
	</li>
</ul>

<h2 id="-">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D9%85%D9%81%D8%B3%D8%B1-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-interpreter-r2601/" rel=""><strong>تطوير مفسر بايثون Python Interpreter</strong></a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%A8%D9%86%D8%A7%D8%A1-%D9%84%D8%B9%D8%A8%D8%A9-%D8%B1%D8%B3%D9%88%D9%85%D9%8I%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D9%88%D9%88%D8%AD%D8%AF%D8%A9-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-pygame-r1704/" rel=""><strong>بناء لعبة رسومية باستخدام بايثون ووحدة الألعاب Pygame</strong></a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%25%D8%A3%D9%87%D9%85-%D8%A7%D9%84%D8%A3%D8%B3%D8%A6%D9%84%D8%A9-%D8%A7%D9%84%D9%86%D8%B8%D8%B1%D9%258I%D8%A9-%D8%A7%D9%84%D8%AA%D9%258I-%D9%82%D8%AF-%D8%AA%D8%B7%D8%B1%D8%AD-%D9%81%D9%258I-%D8%A7%D9%84%D9%85%D9%82%D8%A7%D8%A8%D9%84%D8%A7%D8%AA-%D9%84%D8%AA%D9%88%D8%B8%D9%258I%D9%81-%D9%85%D8%B7%D9%88%D8%B1-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2595/" rel=""><strong>أهم الأسئلة النظرية التي قد تطرح في المقابلات لتوظيف مطور بايثون</strong></a>
	</li>
</ul>

<p>
	ترجمة -بتصرف- للفصل <a href="https://third-bit.com/sdxpy/dup/" rel="external nofollow"><strong>Chapter 3: Finding Duplicate Files</strong></a> من كتاب Software Design by Example.
</p>
]]></description><guid isPermaLink="false">2604</guid><pubDate>Tue, 17 Mar 2026 07:12:00 +0000</pubDate></item><item><title>&#x645;&#x631;&#x627;&#x62D;&#x644; &#x62A;&#x635;&#x645;&#x64A;&#x645; &#x627;&#x644;&#x645;&#x633;&#x62A;&#x648;&#x64A;&#x627;&#x62A; &#x641;&#x64A; &#x623;&#x644;&#x639;&#x627;&#x628; &#x627;&#x644;&#x641;&#x64A;&#x62F;&#x64A;&#x648;</title><link>https://academy.hsoub.com/programming/game-development/%D9%85%D8%B1%D8%A7%D8%AD%D9%84-%D8%AA%D8%B5%D9%85%D9%8A%D9%85-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%88%D9%8A%D8%A7%D8%AA-%D9%81%D9%8A-%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%A7%D9%84%D9%81%D9%8A%D8%AF%D9%8A%D9%88-r2603/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2026_03/levels.png.4c6c4cbc9ab7b256da27452c9a854818.png" /></p>
<p>
	تعرفنا في <a href="https://academy.hsoub.com/programming/game-development/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B5%D9%85%D9%8A%D9%85-%D9%85%D8%B3%D8%AA%D9%88%D9%8A%D8%A7%D8%AA-%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%A7%D9%84%D9%81%D9%8A%D8%AF%D9%8A%D9%88-r2602/" rel="">مقال مدخل إلى تصميم مستويات ألعاب الفيديو</a> على بعض المفاهيم الأساسية في تصميم مستويات الألعاب والمنظور الذي ينبغي أن يدركه مصمم الألعاب لفهم عملية التصميم، وسنشرح في هذا المقال الخطوات التي يمر بها المصمم أثناء تصميم مستوى داخل لعبة فيديو.
</p>

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

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

<p>
	تمر معظم مشاريع تصميم المستويات ثلاثية الأبعاد في ألعاب الفيديو بعدة مراحل:
</p>

<ol>
	<li>
		<strong>ما قبل الإنتاج Pre-production</strong>. هي مرحلة وضع الخطوط العريضة وتصميم تجربة الاستخدام العامة.
	</li>
	<li>
		<strong>المعارك Combat</strong>. هذه مرحلة اختيارية خاصة بالألعاب القتالية، وفيها تُحدد طبيعة التفاعل بين اللاعبين وأنواع الأعداء التي ستكون في اللعبة.
	</li>
	<li>
		<strong>المخطط Layout</strong>. مرحلة رسم مخطط مرئي ثنائي الأبعاد من منظور علوي top view.
	</li>
	<li>
		<strong>النماذج الهيكلية Blockout</strong>. مرحلة بناء مسودة أولية ثلاثية الأبعاد للنماذج الهيكلية (الأجسام والمباني وغيرها) لاختبار قابليتها للعب.
	</li>
	<li>
		<strong>البرمجة النصية أو كتابة السكربتات Scripting</strong>. مرحلة ربط الأحداث بالسلوكيات الخاصة بها، وتشمل المهمات والأبواب -أسلوب فتحها مثلًا-، الأزرار ووظائفها، الذكاء الاصطناعي، إلخ، وقد كان هناك فرق كبير في الأجيال السابقة من محركات الألعاب بين شيفرة محرك اللعبة ومنطق السكربت أو البرمجة النصية في مستوى معين داخل اللعبة.
	</li>
	<li>
		<strong>الإضاءة</strong>. مرحلة توزيع مصادر الإضاءة للعناصر داخل اللعبة لوضوح رؤيتها ولإضفاء عمق واقعي عليها.
	</li>
	<li>
		<strong>فن البيئة Environment Art</strong>. هي مرحلة وضع اللمسة الفنية النهائية من خلال إضافة الأجسام والهياكل والأثاث وغيرها.
	</li>
	<li>
		<strong>الإطلاق Release</strong>. مرحلة توثيق المشروع والتسويق له ونشره.
	</li>
</ol>

<h2 id="-">
	تخصيص المراحل
</h2>

<p>
	لا توجد طريقة ثابتة لتصميم مستوى ما في لعبة، ذلك أن كل مشروع أو مرحلة يحتاج إلى عناصر مختلفة عن غيره، لذا ستجد نفسك كمصمم ألعاب تعدل وتطوّع تلك المراحل وفقًا لاحتياجاتك، ويُنصح أحيانًا بالشروع في هذه المراحل جميعها في نفس الوقت إذا كنت محتارًا في الترتيب المناسب لمشروعك أو حتى المراحل التي تحتاج إليها فيه، وستتعلم مع الوقت ما ينبغي التركيز عليه أو تجاوزه إلى غيره. للتوضيح، لننظر في حالات الألعاب التالية:
</p>

<h3 id="-">
	المشاريع الجماعية
</h3>

<p>
	تحتاج المشاريع الجماعية إلى تركيز أكبر على المرحلة الأولى -ما قبل الإنتاج، والتخطيط- متمثلًا في رسم المخططات العامة وتحديد الأنشطة والتحديات التي سيواجهها اللاعب في المستوى، فيما يعرف باسم Pacing، فغياب الصورة الواضحة لهذه المرحلة سيجعل من الصعب أن يتعاون المصممون أو العاملون على تصميم المستوى معًا.
</p>

<h3 id="-">
	ألعاب القتال أو خرائط الألعاب الجماعية
</h3>

<p>
	تحتاج ألعاب القتال combat games أو خرائط الألعاب الجماعية multiplayer maps إلى التركيز على إنشاء النماذج الهيكلية Blockouts من أجل إخراج متقن للمواجهات encounters التي يواجهها اللاعب، خاصة مع الشخصيات غير القابلة للعب Non playable characters (NPC)، وكذلك التركيز على موازنة الخرائط map balancing للتحكم في الخيارات المتاحة للاعب داخل المستوى، واختبارات مكثفة لتجربة اللعب نفسها playtests لمعرفة إن كان المستوى شيقًا أم مملًا، واضحًا أم محيرًا للاعب، وهكذا.
</p>

<h3 id="-">
	الألعاب الفردية القصصية
</h3>

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

<p>
	وستحتاج إذا تغيرت الحبكة داخل اللعبة إلى إعادة العمل من البداية على كل عناصرها، وقد تواجه صعوبة في إبقاء عناصر القصة متسقة مع بعضها في ظل التعديلات والتجارب التي تجريها، لذا تحتاج هذه أيضًا إلى تركيز عليها. سيظهر الفشل في هذه المراحل إذا شعر اللاعب أن قصة اللعبة ليست منطقية أو بها خلل في عناصرها. لننظر الآن في هذه المراحل بتفصيل أكبر.
</p>

<h2 id="-">
	مرحلة ما قبل الإنتاج
</h2>

<p>
	تدور مرحلة ما قبل الإنتاج حول وضع الشكل الأساسي للمشروع وحدوده العامة وأهداف التصميم والقيود التي تحكمها، وغالبًا ما يتجاهل المصمم المبتدئ هذه المرحلة -أي تخطيط المشروع- في حين يقع المصمم الخبير في فخ آخر حيث يستغرق في تخطيط مفرط أكثر من اللازم، وقد تصل مرحلة التخطيط للمشروع في استديوهات تصميم الألعاب الكبيرة إلى شهور أو سنوات حتى.
</p>

<p>
	تكتسب مرحلة التخطيط أهمية بالغة عند العمل في فريق لأنها التي توضع فيها الرؤية العامة للمشروع وتوقعاته، لكن تستطيع تجاوزها إن كنت تعمل وحدك كمصمم مستقل على مشروع صغير نسبيًا بعد أن تصف الخطوط العريضة للمشروع وتوقعاته وأهدافه، ويكون الشكل العام لهذه الخطوط إما وثائق نصية تشرح الرؤية العامة للمشروع، أو لوحات مهام إما حقيقية أو على تطبيقات مثل تريلّو Trello أو جايرا Jira، أو جداول بيانات Spreadsheets.
</p>

<h2 id="-">
	مرحلة المعارك (مرحلة اختيارية)
</h2>

<p>
	ننتقل الآن إلى مرحلة المعارك، سنحتاج إليها إذا كانت اللعبة قتالية أو فيها معارك بين اللاعبين أو بين اللاعبين والشخصيات غير القابلة للعب NPCs، نحتاج هنا أن نعرف الصورة التي ينبغي أن تكون عليها المعركة داخل اللعبة، من حيث مكوناتها واستعداد اللاعبين لها، وكيف نجعل المعارك متجددة وغير مكررة.
</p>

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

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			نظرًا للجهد الكبير الذي تحتاج إليه هذه المرحلة فإننا ننصح أن تبدأ بتعديل ألعاب قتال جاهزة، كي تكتسب خبرة عملية من خلال التدرب على أنظمة قتالية مجهزة ومختبرة مسبقًا.
		</p>
	</div>
</blockquote>

<p style="text-align: center;">
	<img alt="spreadsheet.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181650" data-ratio="29.07" data-unique="p0st0ohvl" width="829" src="https://academy.hsoub.com/uploads/monthly_2026_03/spreadsheet.png.2c512c870dc73f2ce7fb79fcd4b97edc.png">
</p>

<p style="text-align: center;">
	صورة نشرها <a href="https://x.com/jasondeheras/status/1376005158656638977" rel="external nofollow">المصمم جيسون دي هيراس Jason de Hera</a> على تويتر توضح مصفوفة الحركات لشخصيات فالكيري في لعبة God Of War.
</p>

<h2 id="-layouts">
	مرحلة المخططات Layouts
</h2>

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

<p>
	تكون المخططات في العادة مساقط علوية ثنائية الأبعاد للمستوى، وإن كانت مساقط المنظور أو المخططات الأيزومترية Isometric شائعة كذلك -هذه المخططات ترسم المستوى من منظور أشبه بالرسم ثلاثي الأبعاد-، تذكر أن الهدف هنا ليس أن تصير فنانًا مبدعًا بل أن تستطيع إيصال فكرتك ورسالتك من المخطط الذي ترسمه، فالرسم هنا عبارة عن خطة بصرية تحدد أين يمكن للاعب أن يذهب، وما الذي يستطيع أن يفعله داخل المستوى.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="181651" href="https://academy.hsoub.com/uploads/monthly_2026_03/layout.jpeg.65ee14bb2672b93071d0d5cd47249562.jpeg" rel=""><img alt="layout.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="181651" data-ratio="75.72" data-unique="ssxa74prz" style="width: 692px; height: auto;" width="792" src="https://academy.hsoub.com/uploads/monthly_2026_03/layout.thumb.jpeg.6bfa01aa46ec44c55d35d7c466d52a2d.jpeg"></a>
</p>

<p style="text-align: center;">
	صورة توضح رسمًا لخريطة Warpath متعددة اللاعبين في لعبة Team Fortress Classic، من رسم المصمم روبن ووكر Robin Walker.
</p>

<h2 id="-">
	مرحلة النماذج الهيكلية
</h2>

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

<p>
	هذه المرحلة -أي اختبار اللعب- مهمة للغاية في ألعاب القتال أو أي لعبة يعتمد سلوك اللاعب فيها على بنية المستوى -بحيث قد يؤدي تغيير تصميم غرفة مثلًا إلى الحاجة لتغيير سلوك اللاعب في هذه المرحلة-، فإذا اكتشفنا أن أحد النماذج يحدث مشكلة أثناء اللعب -كأن يكون تصميم الغرفة معيقًا للعب في مثالنا هنا- نستطيع تعديله وتغييره وإعادة تجربته بتكلفة قليلة بما أن النموذج نفسه مكون من أشكال بسيطة غير معقدة.
</p>

<p>
	هذا يعني أن ملفات النماذج الهيكلية هي مستويات كاملة أو ملفات مشاهد من اللعبة scene files يمكن لعبها وتجربتها وتحميلها داخل محرك اللعبة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="181652" href="https://academy.hsoub.com/uploads/monthly_2026_03/blockout.jpeg.417cfe92f0e9d5d1a8333ee25e1331ec.jpeg" rel=""><img alt="blockout.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="181652" data-ratio="56.29" data-unique="pp0nnb8z1" style="width: 700px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_03/blockout.thumb.jpeg.3b705a9ac3729e6616a881260bca83ce.jpeg"></a>
</p>

<p style="text-align: center;">
	صورة لنموذج هيكلي لمستوى في لعبة Uncharted 4.
</p>

<h2 id="-">
	مرحلة البرمجة النصية
</h2>

<p>
	تدور مرحلة البرمجة النصية أو كتابة السكربتات حول دمج السلوكيات والأحداث ومنطق اللعبة معًا في مستوى اللعبة، وننصح هنا بالبدء ببرمجة سلوك الأزرار والعناصر القابلة للجمع collectables قبل النظر في الكائنات الأعقد، فصدق أو لا تصدق، تمثل برمجة الأبواب واحدة من أعقد المشاكل في تطوير الألعاب! وبرمجة المنصات أو الكائنات المتحركة مثل القطارات أشد تعقيدًا.
</p>

<p>
	تعتمد أهداف المهمات الكبيرة والتحديات الصغيرة داخل المستوى quests في الغالب على البرمجة النصية، وبالمثل في حالة المشاهد السينمائية cutscenes وتصميم المواجهات والمعارك والذكاء الاصطناعي، لكن لا تقلق إذا لم تكن متقنًا للبرمجة هنا إذ توفر معظم محركات الألعاب لغات نصية وأدوات لتبسيط العملية.
</p>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			يميل البعض في مجتمعات تصميم المستويات إلى التقليل من قيمة المبرمجين الذين يكتبون هذه السكربتات النصية رغم أن عملهم هو الذي يجعل المرحلة تنبض بالحياة وهو العنصر الحاسم لتحويل الخريطة الصماء إلى تجربة تفاعلية.
		</p>
	</div>
</blockquote>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="181653" href="https://academy.hsoub.com/uploads/monthly_2026_03/door.jpeg.0fa9cb74486642ac13f505fee8d4394d.jpeg" rel=""><img alt="door.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="181653" data-ratio="56.29" data-unique="s5sisb4ju" style="width: 700px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_03/door.thumb.jpeg.f97cd1dbda09bb766c6570707e3cfa90.jpeg"></a>
</p>

<p style="text-align: center;">
	صورة لنسخة أولية من لعبة Gone Home، لم تعمل الأبواب بالطريقة الصحيحة، من <a href="https://fullbright.company/2012/07/02/code-judo/" rel="external nofollow">مقالة Code Judo</a>.
</p>

<h2 id="-">
	مرحلة الإضاءة
</h2>

<p>
	تضيف الإضاءة ظلالًا وعمقًا إلى النماذج الهيكلية مما يساعد اللاعب على استيعاب الشكل الرئيسي للمستوى، فغياب الإضاءة المناسبة يجعل المستوى يبدو كأنه صورة مسطحة ثنائية الأبعاد وسيصعب تقدير المسافات أو فهم المخطط الكامل لمحيط اللعب، كذلك تلعب الإضاءة والظلال دورًا آخر بكونها طبقة معلومات مهمة وضرورية في ألعاب القتال.
</p>

<p>
	تتعامل صناعة الألعاب عادة مع الإضاءة كعنصر تجميلي يتبع فن البيئة، لكن الحقيقة أن لها وظائف محورية في أسلوب اللعب نفسه، لذا يجب أن تُصمَّم بعد تنسيق دقيق مع مصمم المستوى لفهم كيف ستؤدي دورها في خدمة الهدف من المستوى واللعبة ككل.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="181654" href="https://academy.hsoub.com/uploads/monthly_2026_03/lighting.jpeg.b4f94433ef2966f68515074e488e2e8c.jpeg" rel=""><img alt="lighting.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="181654" data-ratio="56.29" data-unique="qokqctmua" style="width: 700px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_03/lighting.thumb.jpeg.8c19520afe29e9c2d6d777c0d36b5409.jpeg"></a>
</p>

<p style="text-align: center;">
	صورة لإضاءة غرفة من العصور الوسطى نفذها <a href="https://www.artstation.com/artwork/lBLzJ" rel="external nofollow">هارلي ويلسون Harley Wilson</a> توضح توزيع الإضاءة في المشهد.
</p>

<h2 id="-">
	مرحلة فن البيئة
</h2>

<p>
	ينبغي أن يكون الشكل العام للمستوى وتسلسل المرحلة قد ظهر واقترب من اكتماله بعد المراحل السابقة، وهنا تأتي مرحلة التفاصيل البصرية أو فن البيئة حيث نضيف هذه التفاصيل الفنية فيما يعرف <strong>بعملية Art Pass</strong>، حيث تحتاج أغلب المشاريع إلى تكرار هذه العملية عدة مرات.
</p>

<p>
	يظن الكثير من الناس أن إضافة التفاصيل الفنية عملية ممتعة وسهلة، لذا يندفع المصمم المبتدئ إلى هذه المرحلة دون المرور بمرحلة التخطيط ودون أن يكون لديه مخططات أو نماذج هيكلية واضحة، وهذا يتسبب في ثبات أخطاء التصميم الأولية لأن تغيير المستوى لاحقًا سيكون مكلفًا جدًا إذ سنحتاج إلى إعادة تنفيذ المستوى من جديد بما فيه من تفاصيل فنية، لذا ينبغي أن نتجنب الانسياق وراء الرغبة في البدء بالتفاصيل الفنية قبل إتمام المراحل السابقة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="gif" data-fileid="181655" href="https://academy.hsoub.com/uploads/monthly_2026_03/artpass.gif.7e587f432b19ed4cdd72ebe5dbc45c36.gif" rel=""><img alt="artpass.gif" class="ipsImage ipsImage_thumbnailed" data-fileid="181655" data-ratio="70.71" data-unique="pu6uqurif" style="width: 700px; height: 495px;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_03/artpass.thumb.gif.fbb726fdfa57869bb56a83579d668a32.gif"></a>
</p>

<p style="text-align: center;">
	صورة متحركة تظهر تطور قاعدة عسكرية بحرية في لعبة Sniper Elite.
</p>

<h2 id="-">
	مرحلة الإطلاق
</h2>

<p>
	عند إتمام العمل على المراحل السابقة نصل إلى موعد إطلاق اللعبة والنشر للجمهور المستهدف، ويمثل إطلاق اللعبة بالنسبة للشركات الكبيرة بداية كابوس جديد إذ عليهم الآن متابعة العمل في تسويق اللعبة والدعاية لها وتحليل آراء المستخدمين وإصلاح الأخطاء البرمجية التي ستظهر، بل وتطوير محتوى إضافي بعد الإطلاق.
</p>

<p>
	لكن في حالة مصمم الألعاب المستقل فعليه توثيق تصميم مستوى اللعبة توثيقًا احترافيًا لضمان أن يفهم المطلع على المشروع ما أنجزته فيه، وتبرز أهمية هذه النصيحة إذا علمنا أن أغلب المبتدئين يفترضون أن أعمالهم "ستتحدث عن نفسها"، لكن الواقع المشاهد يقول أنه إذا لم تحسن تسويق المشروع وإتقان تفاصيل توثيقه فلن يعرف أحد أن هذا المشروع موجود أصلًا.
</p>

<h2 id="-">
	خاتمة
</h2>

<p>
	يشمل تصميم مستويات الألعاب عدة خطوات هي على الترتيب: ما قبل الإنتاج، تصميم المعارك -في حالة الألعاب القتالية-، المخططات العامة، النماذج الهيكلية، البرمجة النصية، الإضاءة، فن البيئة، والإطلاق.
</p>

<p>
	وعرفنا أن المشاريع تختلف عن بعضها في احتياجها لواحدة أو أكثر من هذه المراحل، وأن على المبتدئ في تعلم تصميم مستويات الألعاب أن يبدأ بتجربتها جميعها في نفس الوقت ليكتسب خبرة كافية في تحديد المراحل التي يحتاج إليها لكل مشروع.
</p>

<h2 id="-">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B4%D8%AE%D8%B5%D9%8A%D8%A9-%D9%88%D8%AA%D8%AD%D8%B1%D9%8A%D9%83%D9%87%D8%A7-%D9%81%D9%8A-%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%A7%D9%84%D9%85%D9%86%D8%B5%D8%A7%D8%AA-%D8%AB%D9%86%D8%A7%D8%A6%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-r2570/" rel="">إنشاء شخصية وتحريكها في ألعاب المنصات ثنائية الأبعاد </a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%88%D8%AD%D8%AF%D8%A9-%D8%AA%D8%AD%D9%83%D9%85-%D9%88%D8%A7%D9%82%D8%B9%D9%8A%D8%A9-%D9%84%D9%84%D8%B3%D9%8A%D8%A7%D8%B1%D8%A7%D8%AA-%D9%81%D9%8A-%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%A7%D9%84%D9%81%D9%8A%D8%AF%D9%8A%D9%88-%D8%AB%D9%86%D8%A7%D8%A6%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-r2572/" rel="">إنشاء وحدة تحكم واقعية للسيارات في ألعاب الفيديو ثنائية الأبعاد </a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-r2304/" rel="">دليلك الشامل إلى برمجة الألعاب </a>
	</li>
</ul>

<p>
	ترجمة -بتصرف- <a href="https://book.leveldesignbook.com/process/overview" rel="external nofollow">للفصل How to make a level من كتاب The Level Design Book</a>.
</p>
]]></description><guid isPermaLink="false">2603</guid><pubDate>Thu, 12 Mar 2026 12:01:02 +0000</pubDate></item><item><title>&#x645;&#x62F;&#x62E;&#x644; &#x625;&#x644;&#x649; &#x62A;&#x635;&#x645;&#x64A;&#x645; &#x645;&#x633;&#x62A;&#x648;&#x64A;&#x627;&#x62A; &#x623;&#x644;&#x639;&#x627;&#x628; &#x627;&#x644;&#x641;&#x64A;&#x62F;&#x64A;&#x648;</title><link>https://academy.hsoub.com/programming/game-development/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B5%D9%85%D9%8A%D9%85-%D9%85%D8%B3%D8%AA%D9%88%D9%8A%D8%A7%D8%AA-%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%A7%D9%84%D9%81%D9%8A%D8%AF%D9%8A%D9%88-r2602/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2026_03/videogamelevels.png.26d68d710de5732bf461091f97b8eac3.png" /></p>
<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="181638" href="https://academy.hsoub.com/uploads/monthly_2026_03/basketballovermap.jpeg.6905ec94074a774f92c77c8960b04b19.jpeg" rel=""><img alt="basketball over map.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="181638" data-ratio="54.83" data-unique="edrgttn8v" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_03/basketballovermap.thumb.jpeg.55b2d86c7e9cadce700ebc9743ae018f.jpeg"></a>
</p>

<p style="text-align: center;">
	صورة لملعب كرة سلة متراكب فوق مخطط عام لخريطة من لعبة Halo 1 بواسطة <a href="https://twitter.com/neilsonks/status/1658549380691968003" rel="external nofollow">@neilsonks</a>
</p>

<p>
	يشير اصطلاح المستوى Level في ألعاب الفيديو إلى المساحة التي تقع فيها أحداث اللعبة مثل جزيرة فورتنايت fortnite island، وهي مسار من التحديات على <a href="https://www.roblox.com/" rel="external nofollow">منصة روبلوكس Roblox</a>، أو ملعب كرة سلة أو مسار سباق أو ساحة لعب عامة، أو حتى لوحة لعب مونوبولي الشهيرة أو الكلمات المتقاطعة أو كتب التلوين المخصصة للأطفال، فكل هذه تعد مستويات لعب مختلفة الشكل والصعوبة.
</p>

<p>
	تشترك هذه المستويات والمساحات في أنها تضع حدودًا لللاعبين وتفاعلاتهم مع اللعبة ومع بعضهم البعض، وتوفر المستويات المختلفة تنوعًا في تجربة اللعب، فتتشابه ملاعب كرة السلة في أشكالها مثلًا لكن الملعب المكشوف يختلف عن صالة لعب مغطاة في تجربته والمزاج العام له، وهنا يبرز دور مصمم المستوى في اللعبة إذ يصب تركيزه على كيفية تطويع هذه المساحات للتأثير في مشاعر اللاعبين وتوجيه سلوكياتهم.
</p>

<h2 id="-">
	تعريف
</h2>

<p>
	يُعرَّف تصميم المستوى في الألعاب على أنه تخطيط وبناء المساحات المخصصة لألعاب الفيديو وغالبًا ما يكون هذا في ألعاب التصويب من منظور الشخص الأول أو الثالث، رغم أن كثيرًا من مبادئ تصميمها قد تصلح للألعاب ثنائية البعد التي تتحرك يمينًا أو يسارًا مع اللاعب، أو الألعاب الاستراتيجية أو غير القتالية حتى، لكننا ذكرنا ألعاب المنظور الأول والثالث لأن أغلب نظريات تصميم المستويات تتخذ من ألعاب التصويب ثلاثية الأبعاد نموذجًا قياسيًا لها، ويعود ذلك إلى نشأة الدور الوظيفي لمصمم مستويات الألعاب في التسعينات من القرن الماضي والتي هيمنت عليها ألعاب التصويب.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="181639" href="https://academy.hsoub.com/uploads/monthly_2026_03/uncharted4.jpeg.97ab02d4ca75fe20f597b38fbd40ec65.jpeg" rel=""><img alt="uncharted 4.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="181639" data-ratio="56.33" data-unique="ium630x9o" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_03/uncharted4.thumb.jpeg.3fb8497c35bde8f5475af263b2449a44.jpeg"></a>
</p>

<p style="text-align: center;">
	صورة للقطة شاشة لمرحلة الوصول إلى الجزيرة أثناء عملية التطوير بنظام blockout بواسطة إم شاتز للعبة Uncharted 4
</p>

<h2 id="-">
	التصميم الوظيفي للمستويات وفن البيئة
</h2>

<p>
	يركز مصمم المراحل level designer على صياغة سلوك اللاعب، فيكون مسؤولًا في استديوهات التصميم الكبيرة عن كتابة الوثائق التقنية ورسم المخططات الأولية وبناء النماذج الهيكلية Blockouts ومراقبة اختبارات اللعب وكذلك موازنة خيارات اللعب المتاحة في المستوى الواحد فيما يعرف بموازنة الخريطة Map balance، والمواجهات encounters التي يتعرض لها في ساحة مفتوحة مع شخصيات اللعبة الأخرى، خاصة غير القابلة للعب منها Non Playable Characters (NPC).
</p>

<p>
	في المقابل نرى أن فنان البيئة environment artist يركز اهتمامه على الجوانب الرسومية حيث يضيف اللمسات الفنية بتصميم النماذج وإكسائها بالمواد والخامات المناسبة وتنسيق العناصر الديكورية وتوزيع الإضاءة بهدف صقل المظهر البصري للمستوى، ورغم أن هذا يبدو في ظاهره تنسيقًا للديكور العام إلا أن إتقانه يدعم أهداف تصميم تجربة الاستخدام لبيئة اللعب ويساعد اللاعب على فهم المجريات العامة للعبة.
</p>

<p>
	وبناء على ما سبق نستطيع فهم تصميم المستويات من منظورين:
</p>

<ul>
	<li>
		المنظور الرسمي للمجال، وهو تصميم المستويات بمعناه التخصصي المهني.
	</li>
	<li>
		المنظور العام لتصميم المستويات بمعناه الشامل الذي يضم المنظور الرسمي إضافة إلى فن البيئة وكل ما يمكن أن يشمله المستوى.
	</li>
</ul>

<p>
	سنركز في شرحنا على المنظور الرسمي لغرض التعليم، لكن اعلم أن اللاعب يتفاعل مع المنظور العام أكثر.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="181640" href="https://academy.hsoub.com/uploads/monthly_2026_03/Dust514.png.7305c62bd1cacc6d9cbb1b57d044ea40.png" rel=""><img alt="Dust514.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181640" data-ratio="59.17" data-unique="6ei3iqi98" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_03/Dust514.thumb.png.09562bf7651dc17f6a3a4b9cd18200a1.png"></a>
</p>

<p style="text-align: center;">
	صورة توضح مخطط يقارن المنظور الوظيفي لتصميم المستوى مع المنظور العام باستخدام صور عمليات من مراحل بناء منشأة أبحاث جالينت من لعبة Dust 514
</p>

<h2 id="-">
	الفرق بين تصميم غرفة وتصميم عالم افتراضي
</h2>

<p>
	يستغرق مصمم المستوى أيامًا أو أسابيع في تصميم غرفة واحدة، لكن هذا التدقيق غير عملي في حالة الألعاب الضخمة مثل العوالم المفتوحة أو الألعاب الجماعية كثيرة اللاعبين Massively Multiplayer Online (MMO) التي تضم مئات أو آلاف الغرف.
</p>

<p>
	وهنا يبرز دور مصمم العالم الافتراضي حيث يهتم بتصميم الحركة العام في المستوى وإرشاد اللاعب wayfinding عبر أحياء كاملة بدلًا من منازل فردية، ومناطق حيوية Bioms بدلًا من أماكن صغيرة، والفئات العامة categories بدلًا من الحالات الخاصة instances، ويتيح هذا المنظور الشمولي مساحة كافية لتفاعل اللاعبين والأنظمة بحرية والتي سيكون العالم الافتراضي فارغًا أو باهتًا بدونها.
</p>

<p>
	وعلى هذا فإذا أردت بناء تجربة موجهة أو تخضع لسيناريو محدد فينبغي أن تصب تركيزك على تفاصيل كل غرفة كأنك مهندس معماري، أما في حالة الألعاب التي تحتوي على كثير من اللاعبين والأنظمة والمساحات الكبيرة فإن تصميم العالم الافتراضي يمنحك لمسة أشبه بالمخطِّط العمراني للمدن الكبيرة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="181641" href="https://academy.hsoub.com/uploads/monthly_2026_03/fortnite.jpeg.050a95a028e835ebc9be1e1321dfc585.jpeg" rel=""><img alt="fortnite.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="181641" data-ratio="37.50" data-unique="1w8syuqzh" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_03/fortnite.thumb.jpeg.d609536f53abaf9e8791207e780d2b1f.jpeg"></a>
</p>

<p style="text-align: center;">
	صورة في مستوى pleasant park في لعبة فورتنايت حيث لا تهم تفاصيل كل غرفة في المنازل الموجودة بقدر الشكل العام للحي كله
</p>

<h2 id="-">
	النظرية مقابل البناء الفعلي
</h2>

<p>
	بما أن هذا المقال مقروء فمن البديهي أننا نؤمن بأهمية القراءة للتعلم، لكن المادة النصية تضعك على أعتاب المجال المهني فقط إذ يتحتم عليك أن تترك المقال أو المادة النصية في مرحلة ما لتبدأ التنفيذ فعليًا.
</p>

<p>
	وقد جرت العادة في مجتمع مصممي خرائط لعبة Quake حين يُكثر المبتدئ من طرح الأسئلة أن يأتيه الرد الحازم "اذهب وصمم خريطة"، وعلى جفاء هذه الرد فإن فيه نية صادقة للتحفيز كأنه ينهاه عن المماطلة في التنظير وأن ينتقل إلى التطبيق العملي الذي سيجيب كثيرًا من أسئلته. إن السبيل الوحيد لأن تصبح مصمم مستويات ألعاب هو بناء المستويات فعلًا، بل الكثير منها.
</p>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			نستخدم أحيانًا مصطلح خريطة map بدلًا من مستوى level، إذ توحي الخريطة بوجود مساحة حرة تدعم أنماطًا مختلفة من اللعب، في حين يشير المستوى إلى مسار أكثر خضوعًا للسيناريوهات البرمجية scripts، لكن لا حرج في استخدام المصطلحين بالتبادل.
		</p>
	</div>
</blockquote>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="181642" href="https://academy.hsoub.com/uploads/monthly_2026_03/quake.jpeg.71818e8c7c9fabc782df62da6cd69975.jpeg" rel=""><img alt="quake.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="181642" data-ratio="56.33" data-unique="2b1wqgs4v" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_03/quake.thumb.jpeg.742786891148718627d38e995c17fd54.jpeg"></a>
</p>

<p style="text-align: center;">
	صورة للقطة شاشة لمنطقة "مركز البداية" في تعديل Arcane Dimensions للعبة Quake 1، من تصميم سايمون "sock" أوكالاهان Simon "sock" O'Callaghan وآخرين
</p>

<h2 id="-">
	نقاء نظرية التصميم
</h2>

<p>
	تنزلق معظم الشروحات المتعلقة بتصميم المستويات إما إلى التنظير الأكاديمي المعقد أو التبسيط التجاري المخل، لذا نهدف هنا إلى شرح واستخدام نفس اللغة التقنية التي يتداولها المصممون المحترفون في بيئات العمل الحقيقية مع الحفاظ على نظرة نقدية تكفي لتنقية الأفكار من السطحية المخلة.
</p>

<p>
	يفتقر المصمم في الغالب إلى المستوى الاصطلاحي المطلوب لوصف الأشكال والأحجام رغم استفادته الكبيرة من مفهوم التكتيل massing المعماري المتعلق بالإحساس والمنطق العام حول الأشكال الهندسية، لهذا يكثر الاستشهاد في تصميم الألعاب بمصطلحات ومفاهيم خارجية دون إدراك جذورها العميقة، فنشتق مثلًا مفهوم المسارات الحرجة critical paths من طريقة المسار الحرج، وهي أداة هندسية تستخدم لتحديد نطاق العمل وارتباط العناصر ببعضها، ونحن نذكر هذا لبيان أن غياب الدقة في اختيار الكلمات التي تبني التصور النظري فإن جودة التفكير وفعالية التواصل تتضرر حتمًا.
</p>

<p style="text-align: center;">
	<img alt="book.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181643" data-ratio="58.01" data-unique="vdutcclw6" style="width: 662px; height: auto;" width="862" src="https://academy.hsoub.com/uploads/monthly_2026_03/book.png.db9100388909853ab33908cf1a5a079d.png">
</p>

<p style="text-align: center;">
	 صورة توضح الاستراتيجيات المتنوعة للإضاءة النهارية المستخدمة في الهندسة المعمارية، فرانسيس تشينج من كتاب "Form, Space, and Order"
</p>

<h2 id="-">
	النظرة الشمولية
</h2>

<p>
	رغم أنه من الضروري أن يركز مصمم المستوى على سلوك اللاعب إلا أننا نحتاج أحيانًا إلى توسيع المنظور ليشمل الصورة الكاملة، فالمستوى أكبر من مجرد بناء تقني، بل هو مزيج معقد يجمع بين التصميم المكاني spatial design والفن وعلم النفس والبرمجة والثقافة، ولا يوجد فاصل حقيقي من منظور اللاعب بين تصميم المستوى وفن البيئة إذ يشمل تصميم المستوى كل ما يؤثر في عالم اللعبة.
</p>

<p>
	لهذا يفضل تجنب الانسياق خلف القواعد البسيطة التي تقوم على "افعل ولا تفعل" لأن المستويات تتجاوز كونها مجرد مجموعة غرف وصناديق حماية أو مساحات طبيعية تناثرت فيها بعض الصخور والأشجار، فكل مستوى له مرحلة تاريخية وهدف تصميمي، وينبغي عليك كمصمم أن تضع تجربة اللعب كلها في الحسبان أثناء التصميم.
</p>

<h2 id="-">
	خاتمة
</h2>

<p>
	تعرفنا في هذا المقال على بعض المفاهيم الأساسية في تصميم مستويات الألعاب والمنظور الذي ينبغي أن يدركه مصمم الألعاب، وسنشرح في مقالات تالية من السلسلة كيفية إنشاء المستويات ومراحلها، وتصميم المستوى من المنظور الثقافي.
</p>

<p>
	ترجمة بتصرف للفصل <a href="https://book.leveldesignbook.com/introduction" rel="external nofollow">What is level design</a> من كتاب <a href="https://book.leveldesignbook.com/" rel="external nofollow">The Level Design Book</a>.
</p>
]]></description><guid isPermaLink="false">2602</guid><pubDate>Tue, 10 Mar 2026 08:41:36 +0000</pubDate></item><item><title>&#x62A;&#x637;&#x648;&#x64A;&#x631; &#x645;&#x641;&#x633;&#x631; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646; Python Interpreter</title><link>https://academy.hsoub.com/programming/python/%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D9%85%D9%81%D8%B3%D8%B1-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-interpreter-r2601/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2026_03/12.------.png.c1ee0e628df9e3f614cafa383d9c226b.png" /></p>
<p id="-python-interpreter">
	يهدف هذا المقال إلى مساعدة المبرمج في توسيع معرفته البرمجية من خلال الاطلاع على شيفرة برمجية مفتوحة المصدر توضح له آلية العمل وتزيد من خبرته البرمجية، لذا سنوضح الشيفرة البرمجية لمفسر بايثون بسيط هو المفسر Byterun المكتوب بلغة بايثون نفسها والمحدود بحوالي 500 سطر، مما يبسط كيفية عمل المفسر ودراسة شيفرته البرمجية مفتوحة المصدر، وبالتالي سيساعد في تحسين المهارات البرمجية وفهم عمل المفسر ويعلمنا كيفية بناء مفسر لغة برمجية خاص بنا.
</p>

<p>
	كتب Ned Batchelder و Allison Kaptur مفسر بايثون Byterun بناءً على عمل Paul Swartz، وبنيته مماثلة لمفسر بايثون الأساسي CPython، لذا سيساعدنا فهم مفسر Byterun على فهم المفسرات وخاصةً مفسر CPython شائع الاستخدام. يستطيع مفسر Byterun تشغيل معظم برامج بايثون البسيطة بالرغم من عدد سطور شيفرته القصير.
</p>

<h2 id="-">
	مفسر بايثون
</h2>

<p>
	لنحدد أولًا ما يعنيه مفسر بايثون، إذ يمكن استخدام مصطلح <strong>مفسر Interpreter</strong> بطرق مختلفة في سياق الحديث عن <a href="https://academy.hsoub.com/programming/python/" rel="">بايثون</a>، حيث يشير أحيانًا المفسر إلى Python REPL، وهو الموجِّه التفاعلي الذي نحصل عليه من خلال كتابة <code>python</code> في سطر الأوامر، وقد يُستخدَم مفسر بايثون للدلالة على التنفيذ الكامل لشيفرة بايثون البرمجية، ولكن سيدل مصطلح المفسر في هذا المقال على مجال أضيق، وهو الخطوة الأخيرة من عملية تنفيذ برنامج بايثون.
</p>

<p>
	يسبق تشغيل المفسر ثلاث خطوات في بايثون وهي: تحليل المفردات Lexing والتحليل Parsing و<a href="https://academy.hsoub.com/programming/c/%D8%A7%D9%84%D9%81%D8%B5%D9%84-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D9%85%D9%81%D9%87%D9%88%D9%85-%D8%A7%D9%84%D8%AA%D8%B5%D8%B1%D9%8A%D9%81-compilation-%D9%81%D9%8A-%D9%84%D8%BA%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-r976/" rel="">التصريف Compiling</a>، حيث تعمل هذه الخطوات مع بعضها البعض لتحويل شيفرة المبرمج المصدرية من أسطر نصية إلى كائنات شيفرة Code Objects منظمة تحتوي على تعليمات Instructions يمكن للمفسر فهمها، وتتمثل مهمة المفسر في أخذ هذه الكائنات واتباع التعليمات.
</p>

<p>
	يُعَد التصريف خطوة من خطوات تنفيذ شيفرة بايثون بالرغم من اعتبارها لغة مُفسَّرة مثل لغتي <a href="https://wiki.hsoub.com/Ruby" rel="external">Ruby</a> أو Perl، بينما تُعَد <a href="https://academy.hsoub.com/programming/c/" rel="">C</a> أو <a href="https://academy.hsoub.com/programming/rust/" rel="">Rust</a> لغات مُصرَّفة. تتضمن معظم اللغات المفسَّرة بما في ذلك لغة بايثون خطوة تصريف، وسبب وصف بايثون بأنها لغة مفسَّرة هو أن خطوة التصريف تنجر عملًا أقل نسبيًا من العمل الذي تنجزه في لغة مُصرَّفة. ينجز المفسر في لغة بايثون العمل الأكبر نسبيًا، إذ يحتوي مُصرِّف بايثون على معلومات أقل بكثير حول سلوك البرنامج مقارنةً بمصرِّف لغة سي C.
</p>

<h2 id="-">
	تطوير مفسر بايثون باستخدام بايثون
</h2>

<p>
	Byterun هو مفسر بايثون مكتوب بلغة بايثون نفسها كما هو الحال مع مصرِّف C المستخدَم على نطاق واسع gcc المكتوب بلغة C، ولكن يمكننا كتابة مفسر بايثون بأي لغة تقريبًا.
</p>

<p>
	توجد مزايا وعيوب لكتابة مفسر بايثون باستخدام بايثون، فالعيب الأكبر هو السرعة، إذ يُعَد تنفيذ الشيفرة البرمجية في Byterun أبطأ بكثير من تنفيذها في مفسر CPython المكتوب بلغة C، ولكن صُمِّم مفسر Byterun في الأصل كتطبيق تعليمي، لذا لم تكن السرعة ذات أهمية كبيرة.
</p>

<p>
	الميزة الأهم لاستخدام بايثون هي أنه يمكننا تنفيذ المفسّر فقط بسهولة دون تنفيذ باقي وقت تشغيل بايثون وخاصة نظام الكائنات، إذ يمكن لمفسر Byterun مثلًا الرجوع إلى شيفرة بايثون الحقيقية عندما يحتاج إلى إنشاء صنف. يتميز مفسر Byterun بأنه سهل الفهم، ويعود ذلك إلى كتابته بلغة عالية المستوى هي لغة بايثون التي يجدها الكثيرون سهلة القراءة، ولا حاجة للتحسينات في مفسر Byterun بسبب تفضيل الوضوح والبساطة على السرعة فيه.
</p>

<h2 id="-">
	بناء مفسر
</h2>

<p>
	لنتعرف أولًا على كيفية عمل مفسر بايثون قبل البدء بشيفرة Byterun البرمجية، حيث يُعَد مفسر بايثون آلة افتراضية Virtual Machine، أي أنه برنامج يحاكي حاسوبًا حقيقيًا، وتكون هذه الآلة الافتراضية عبارة عن آلة مكدس Stack Machine، أي أنها تعالج عدة مكدسات لأداء عملياتها على عكس آلة التسجيل Register Machine التي تكتب وتقرأ من مواقع ذاكرة معينة.
</p>

<p>
	مفسّر بايثون هو مفسر بايت كود Bytecode، فمدخلاته هي مجموعات من التعليمات تسمى بايت كود، حيث يولّد محلل المفردات والمحلل والمصرِّف كائنات شيفرة ليعمل عليها المفسّر عندما نكتب شيفرة بايثون الخاصة بنا، ويحتوي كل كائن من هذه الكائنات على مجموعة من التعليمات التي يجب تنفيذها والتي هي البايت كود بالإضافة إلى معلومات أخرى سيحتاجها المفسر. يُعَد البايت كود تمثيلًا وسيطًا لشيفرة بايثون البرمجية، لأنها تعبّر عن الشيفرة المصدرية المكتوبة بطريقة يمكن للمفسّر فهمها، وهي تشبه الطريقة التي تعمل بها لغة التجميع Assembly كتمثيل وسيط بين شيفرة C والعتاد.
</p>

<h3 id="-">
	بناء مفسر بسيط
</h3>

<p>
	لنبدأ ببناء مفسر بسيط يستطيع جمع أعداد فقط ولا يفهم إلّا ثلاث تعليمات، حيث تتكون الشيفرة البرمجية التي يمكنه تنفيذها من التعليمات الثلاث التالية ضمن مجموعات مختلفة:
</p>

<ul>
	<li>
		<code>LOAD_VALUE</code>
	</li>
	<li>
		<code>ADD_TWO_VALUES</code>
	</li>
	<li>
		<code>PRINT_ANSWER</code>
	</li>
</ul>

<p>
	لن نهتم في هذا المقال بمحلل المفردات والمحلل والمصرِّف، وبالتالي لن نهتم بكيفية توليد مجموعات التعليمات. لنفترض كتابة العملية ‎<code>7 + 5</code>‎ التالية، حيث يصدر المصرِّف مجموعة من التعليمات الثلاث السابقة، أو إذا كان لدينا المصرِّف الصحيح، فيمكننا الكتابة بصيغة لغة Lisp المُحوَّلة إلى مجموعة التعليمات نفسها. لا يهتم المفسر بذلك، فما يهمه هو أن يحصل على تنسيق مناسب من هذه التعليمات.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1449_7" style=""><span class="pln">7 + 5</span></pre>

<p>
	وستنتج العملية السابقة مجموعة التعليمات التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_15" style=""><span class="pln">what_to_execute </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"instructions"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[(</span><span class="str">"LOAD_VALUE"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">),</span><span class="pln">  </span><span class="com"># العدد الأول</span><span class="pln">
                     </span><span class="pun">(</span><span class="str">"LOAD_VALUE"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">),</span><span class="pln">  </span><span class="com"># العدد الثاني</span><span class="pln">
                     </span><span class="pun">(</span><span class="str">"ADD_TWO_VALUES"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">),</span><span class="pln">
                     </span><span class="pun">(</span><span class="str">"PRINT_ANSWER"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">)],</span><span class="pln">
    </span><span class="str">"numbers"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">7</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}</span></pre>

<p>
	مُفسِّر بايثون هو آلة مكدس، لذا يجب أن يعالج المكدسات لجمع عددين كما هو موضح في الشكل الآتي، حيث سيبدأ المفسر بتنفيذ التعليمة الأولى <code>LOAD_VALUE</code> ودفع العدد الأول إلى المكدس، ثم يدفع العدد الثاني إلى المكدس، ثم تستخرج التعليمة الثالثة <code>ADD_TWO_VALUES</code> العددين وتجمعهما وتدفع النتيجة إلى المكدس، وأخيرًا يستخرج المفسر الإجابة مرة أخرى من المكدس ويطبعها.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="181521" href="https://academy.hsoub.com/uploads/monthly_2026_03/001__.png.0d2b00ef494abc605390e5b5abe6586f.png" rel=""><img alt="001_مكدس_المفسر.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181521" data-ratio="20.00" data-unique="x54vrtv67" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_03/001__.thumb.png.e8ad70741ced84a6000b9ccb816c1ce3.png"></a><span id="cke_bm_901E" style="display: none;"> </span>
</p>

<p>
	تخبر التعليمة <code>LOAD_VALUE</code> المفسر بدفع عدد إلى المكدس، ولكن لا تحدد التعليمات وحدها هذا العدد، إذ تحتاج التعليمة إلى معلومات إضافية تخبر المفسر بمكان العثور على العدد الذي يجب تحميله، لذا تحتوي مجموعة التعليمات في مثالنا على معلومتين هما: التعليمات نفسها وقائمة الثوابت التي ستحتاجها التعليمات. التعليمات هي البايت كود في بايثون، والكائن الذي يجب تنفيذه <code>what_to_execute</code> هو كائن الشيفرة.
</p>

<p>
	ولكن لماذا لا نضع الأعداد في التعليمات مباشرةً؟ حسنًا، لنفترض أننا نريد جمع سلاسل نصية بدلًا من جمع أعداد، إذ لن نرغب في جعل التعليمات كبيرة الحجم عندما نضع هذه السلاسل النصية مع التعليمات، وتسمح هذه الطريقة بالحصول على نسخة واحدة فقط من كل كائن نحتاجه، وبالتالي يمكن أن تكون الأعداد <code>"numbers"</code> هي <code>[7]</code> فقط لإجراء العملية <code>7 + 7</code> مثلًا.
</p>

<p>
	قد لا يكون سبب وجود التعليمات الأخرى المغايرة عن التعليمة <code>ADD_TWO_VALUES</code> واضحًا في مثالنا البسيط، ولكن تشكّل هذه التعليمات الأساس لبناء برامج أكثر تعقيدًا، إذ يمكننا مثلًا جمع ثلاث قيم أو أيّ عدد من القيم مع المجموعة الصحيحة من هذه التعليمات المذكورة في مثالنا. يوفر هذا المكدس طريقة واضحة لتتبع حالة المفسر، ويدعم مزيدًا من التعقيد لاحقًا.
</p>

<p>
	لنبدأ الآن في كتابة المفسر، حيث يحتوي كائن المفسر على مكدس نمثله بقائمة، ويحتوي على تابع يحدد كيفية تنفيذ التعليمات، إذ سيدفع المفسر القيمة إلى المكدس مثلًا بالنسبة للتعليمة <code>LOAD_VALUE</code>.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_17" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Interpreter</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> __init__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># مكدّس لتخزين القيم أثناء التنفيذ</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">stack </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> LOAD_VALUE</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> number</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># تحميل/إدخال قيمة إلى المكدّس</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">number</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> PRINT_ANSWER</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إخراج آخر قيمة من المكدّس وطباعتها</span><span class="pln">
        answer </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">answer</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> ADD_TWO_VALUES</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># سحب آخر قيمتين من المكدّس ثم جمعهما وإرجاع الناتج إلى المكدّس</span><span class="pln">
        first_num </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        second_num </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        total </span><span class="pun">=</span><span class="pln"> first_num </span><span class="pun">+</span><span class="pln"> second_num
        self</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">total</span><span class="pun">)</span></pre>

<p>
	تنفّذ الدوال السابقة التعليمات الثلاث التي يفهمها المفسر الذي يحتاج إلى طريقة لربطها معًا وتنفيذها، إذ تتمثل هذه الطريقة في التابع <code>run_code</code> الذي يأخذ وسيطًا هو القاموس <code>what_to_execute</code>، ويمر ضمن حلقة على كل تعليمة لمعالجة الوسطاء الخاصة بها إن كانت موجودة، ثم يستدعي التابع المقابل مع كائن المفسر.<code> </code>
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9793_7" style=""><span class="kwd">def</span><span class="pln"> run_code</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> what_to_execute</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># استخراج قائمة التعليمات وقائمة القيم العددية من المدخل</span><span class="pln">
    instructions </span><span class="pun">=</span><span class="pln"> what_to_execute</span><span class="pun">[</span><span class="str">"instructions"</span><span class="pun">]</span><span class="pln">
    numbers </span><span class="pun">=</span><span class="pln"> what_to_execute</span><span class="pun">[</span><span class="str">"numbers"</span><span class="pun">]</span><span class="pln">

    </span><span class="com"># تنفيذ التعليمات بالترتيب (كمفسّر بسيط)</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> each_step </span><span class="kwd">in</span><span class="pln"> instructions</span><span class="pun">:</span><span class="pln">
        </span><span class="com"># كل خطوة تحتوي اسم التعليمة ووسيطها إن وجد</span><span class="pln">
        instruction</span><span class="pun">,</span><span class="pln"> argument </span><span class="pun">=</span><span class="pln"> each_step

        </span><span class="kwd">if</span><span class="pln"> instruction </span><span class="pun">==</span><span class="pln"> </span><span class="str">"LOAD_VALUE"</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># جلب قيمة عددية حسب الفهرس ثم وضعها في المكدّس</span><span class="pln">
            number </span><span class="pun">=</span><span class="pln"> numbers</span><span class="pun">[</span><span class="pln">argument</span><span class="pun">]</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">LOAD_VALUE</span><span class="pun">(</span><span class="pln">number</span><span class="pun">)</span><span class="pln">

        </span><span class="kwd">elif</span><span class="pln"> instruction </span><span class="pun">==</span><span class="pln"> </span><span class="str">"ADD_TWO_VALUES"</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># جمع آخر قيمتين في المكدّس ثم وضع الناتج في المكدّس</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">ADD_TWO_VALUES</span><span class="pun">()</span><span class="pln">

        </span><span class="kwd">elif</span><span class="pln"> instruction </span><span class="pun">==</span><span class="pln"> </span><span class="str">"PRINT_ANSWER"</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># طباعة آخر قيمة في المكدّس (غالبًا النتيجة)</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">PRINT_ANSWER</span><span class="pun">()</span></pre>

<p>
	يمكننا اختبار ذلك من خلال إنشاء نسخة من الكائن ثم استدعاء التابع <code>run_code</code> مع مجموعة التعليمات الخاصة بجمع العددين ‎<code>7 + 5</code>‎، وستظهر الإجابة 12 بالتأكيد.<code><span class="hljs-attribute"> </span></code><code> </code>
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_33" style=""><span class="pln">  interpreter </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Interpreter</span><span class="pun">()</span><span class="pln">
    interpreter</span><span class="pun">.</span><span class="pln">run_code</span><span class="pun">(</span><span class="pln">what_to_execute</span><span class="pun">)</span></pre>

<p>
	يُعَد هذا المفسر محدودًا جدًا، ولكنه يمثل بالضبط الطريقة التي يجمع بها مفسر بايثون الحقيقي الأعداد، إذ تحتاج بعض التعليمات إلى وسطاء، حيث تحتوي كثير من التعليمات على وسطاء في البايت كود الحقيقي لبايثون، وتُضمَّن الوسطاء مع التعليمات كما في مثالنا، ولكن تختلف وسطاء التعليمات عن وسطاء التوابع المستدعاة. لا تتطلب التعليمة <code>ADD_TWO_VALUES</code> وسطاء، إذ أُخرِجت القيم المراد جمعها من مكدس المفسر، ويُعَد ذلك من السمات المميزة للمفسر القائم على المكدس.
</p>

<p>
	يمكننا جمع أكثر من عددين في وقت واحد باستخدام مجموعات تعليمات صالحة دون أي تعديلات على المفسر الخاص بنا، إذًا ليكن لدينا مجموعة التعليمات التالية، ولنحاول توقع ما يحدث وما هي الشيفرة البرمجية المناسبة لتوليد هذه المجموعة من التعليمات عند وجود مصرِّف مناسب:<code> </code>
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_35" style=""><span class="pln"> what_to_execute </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">"instructions"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[(</span><span class="str">"LOAD_VALUE"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">),</span><span class="pln">
                         </span><span class="pun">(</span><span class="str">"LOAD_VALUE"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">),</span><span class="pln">
                         </span><span class="pun">(</span><span class="str">"ADD_TWO_VALUES"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">),</span><span class="pln">
                         </span><span class="pun">(</span><span class="str">"LOAD_VALUE"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">),</span><span class="pln">
                         </span><span class="pun">(</span><span class="str">"ADD_TWO_VALUES"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">),</span><span class="pln">
                         </span><span class="pun">(</span><span class="str">"PRINT_ANSWER"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">)],</span><span class="pln">
        </span><span class="str">"numbers"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">7</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}</span></pre>

<p>
	يمكننا تصور كيفية توسيع هذه البنية من خلال إضافة توابع إلى كائن المفسر لإجراء العديد من العمليات الأخرى مع وجود مصرِّف يزودنا بمجموعات تعليمات ذات تنسيق مناسب.
</p>

<h4 id="-">
	المتغيرات
</h4>

<p>
	نحتاج الآن إلى إضافة متغيرات إلى المفسر، حيث تتطلب المتغيرات تعليمات لتخزين قيمة متغير <code>STORE_NAME</code> وتعليمات لاسترجاعها <code>LOAD_NAME</code> مع ربط أسماء المتغيرات بقيمها. سنتجاهل حاليًا مساحات الأسماء والمجالات لنتمكن من تخزين ربط المتغيرات مع كائن المفسر. يجب التأكد من أن المتغير <code>what_to_execute</code> لديه قائمة بأسماء المتغيرات بالإضافة إلى قائمة الثوابت الخاصة به.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9793_9" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">def</span><span class="pln"> s</span><span class="pun">():</span><span class="pln">
</span><span class="pun">...</span><span class="pln">     a </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
</span><span class="pun">...</span><span class="pln">     b </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2</span><span class="pln">
</span><span class="pun">...</span><span class="pln">     </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">a </span><span class="pun">+</span><span class="pln"> b</span><span class="pun">)</span><span class="pln">

</span><span class="com"># هذا قاموس يصف بايت كود مبسّط للدالة s: ماذا ننفّذ وبأي ترتيب</span><span class="pln">

what_to_execute </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com"># قائمة التعليمات بالترتيب</span><span class="pln">
    </span><span class="com"># كل عنصر عبارة عن: (اسم التعليمة، وسيطها) والوسيط قد يكون فهرسًا أو لا يوجد</span><span class="pln">
    </span><span class="str">"instructions"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
        </span><span class="pun">(</span><span class="str">"LOAD_VALUE"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">),</span><span class="pln">         </span><span class="com"># وضع القيمة الأولى في المكدّس</span><span class="pln">
        </span><span class="pun">(</span><span class="str">"STORE_NAME"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">),</span><span class="pln">         </span><span class="com"># تخزين أعلى قيمة في المكدّس داخل المتغير الأول</span><span class="pln">
        </span><span class="pun">(</span><span class="str">"LOAD_VALUE"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">),</span><span class="pln">         </span><span class="com"># وضع القيمة الثانية في المكدّس</span><span class="pln">
        </span><span class="pun">(</span><span class="str">"STORE_NAME"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">),</span><span class="pln">         </span><span class="com"># تخزين أعلى قيمة في المكدّس داخل المتغير الثاني</span><span class="pln">
        </span><span class="pun">(</span><span class="str">"LOAD_NAME"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">),</span><span class="pln">          </span><span class="com"># تحميل قيمة المتغير الأول إلى المكدّس</span><span class="pln">
        </span><span class="pun">(</span><span class="str">"LOAD_NAME"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">),</span><span class="pln">          </span><span class="com"># تحميل قيمة المتغير الثاني إلى المكدّس</span><span class="pln">
        </span><span class="pun">(</span><span class="str">"ADD_TWO_VALUES"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">),</span><span class="pln">  </span><span class="com"># جمع آخر قيمتين في المكدّس ووضع الناتج في المكدّس</span><span class="pln">
        </span><span class="pun">(</span><span class="str">"PRINT_ANSWER"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">)</span><span class="pln">     </span><span class="com"># طباعة آخر قيمة في المكدّس</span><span class="pln">
    </span><span class="pun">],</span><span class="pln">

    </span><span class="com"># قائمة القيم العددية التي تشير إليها التعليمات عبر فهارس</span><span class="pln">
    </span><span class="str">"numbers"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">],</span><span class="pln">

    </span><span class="com"># قائمة أسماء المتغيرات التي تشير إليها التعليمات عبر فهارس</span><span class="pln">
    </span><span class="str">"names"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="str">"a"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"b"</span><span class="pun">]</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يمكننا تتبع الأسماء المرتبطة بالقيم من خلال إضافة القاموس <code>environment</code> إلى التابع ‎<code>__init__</code>‎ مع إضافة <code>STORE_NAME</code> و <code>LOAD_NAME</code>، حيث تبحث هذه التوابع أولًا عن اسم المتغير ثم تستخدم القاموس لتخزين قيمة المتغير أو استرجاعها.
</p>

<p>
	يمكن أن تكون الوسطاء الخاصة بالتعليمة إما فهرسًا في قائمة الأعداد <code>"numbers"</code>، أو يمكن أن تكون فهرسًا في قائمة الأسماء <code>"names"</code>، إذ يعرف المفسر ما يجب أن تكون عليه من خلال التحقق من التعليمة التي ينفذها. لنضع هذه الشيفرة البرمجية مع ربط التعليمات مع ما تمثله وسطاؤها في تابع منفصل كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_39" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Interpreter</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> __init__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># مكدّس لتخزين القيم مؤقتًا أثناء التنفيذ</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">stack </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">
        </span><span class="com"># بيئة لحفظ قيم المتغيرات (اسم المتغير ← قيمته)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">environment </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> STORE_NAME</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># أخذ آخر قيمة من المكدّس وتخزينها باسم متغير</span><span class="pln">
        val </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">environment</span><span class="pun">[</span><span class="pln">name</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> val

    </span><span class="kwd">def</span><span class="pln"> LOAD_NAME</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># جلب قيمة متغير ووضعها في المكدّس</span><span class="pln">
        val </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">environment</span><span class="pun">[</span><span class="pln">name</span><span class="pun">]</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">val</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> parse_argument</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> instruction</span><span class="pun">,</span><span class="pln"> argument</span><span class="pun">,</span><span class="pln"> what_to_execute</span><span class="pun">):</span><span class="pln">
        </span><span class="str">"""تفسير معنى الوسيط لكل تعليمة"""</span><span class="pln">
        </span><span class="com"># تعليمات تستخدم قائمة الأرقام</span><span class="pln">
        numbers </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">"LOAD_VALUE"</span><span class="pun">]</span><span class="pln">
        </span><span class="com"># تعليمات تستخدم قائمة أسماء المتغيرات</span><span class="pln">
        names </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">"LOAD_NAME"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"STORE_NAME"</span><span class="pun">]</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> instruction </span><span class="kwd">in</span><span class="pln"> numbers</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># تحويل الفهرس إلى رقم فعلي</span><span class="pln">
            argument </span><span class="pun">=</span><span class="pln"> what_to_execute</span><span class="pun">[</span><span class="str">"numbers"</span><span class="pun">][</span><span class="pln">argument</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">elif</span><span class="pln"> instruction </span><span class="kwd">in</span><span class="pln"> names</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># تحويل الفهرس إلى اسم متغير فعلي</span><span class="pln">
            argument </span><span class="pun">=</span><span class="pln"> what_to_execute</span><span class="pun">[</span><span class="str">"names"</span><span class="pun">][</span><span class="pln">argument</span><span class="pun">]</span><span class="pln">

        </span><span class="kwd">return</span><span class="pln"> argument

    </span><span class="kwd">def</span><span class="pln"> run_code</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> what_to_execute</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># أخذ التعليمات وتنفيذها بالترتيب</span><span class="pln">
        instructions </span><span class="pun">=</span><span class="pln"> what_to_execute</span><span class="pun">[</span><span class="str">"instructions"</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">for</span><span class="pln"> each_step </span><span class="kwd">in</span><span class="pln"> instructions</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># كل خطوة: (اسم التعليمة، وسيطها)</span><span class="pln">
            instruction</span><span class="pun">,</span><span class="pln"> argument </span><span class="pun">=</span><span class="pln"> each_step
            </span><span class="com"># تحويل الوسيط من فهرس إلى قيمة مفهومة</span><span class="pln">
            argument </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">parse_argument</span><span class="pun">(</span><span class="pln">instruction</span><span class="pun">,</span><span class="pln"> argument</span><span class="pun">,</span><span class="pln"> what_to_execute</span><span class="pun">)</span><span class="pln">

            </span><span class="kwd">if</span><span class="pln"> instruction </span><span class="pun">==</span><span class="pln"> </span><span class="str">"LOAD_VALUE"</span><span class="pun">:</span><span class="pln">
                </span><span class="com"># إدخال رقم إلى المكدّس</span><span class="pln">
                self</span><span class="pun">.</span><span class="pln">LOAD_VALUE</span><span class="pun">(</span><span class="pln">argument</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">elif</span><span class="pln"> instruction </span><span class="pun">==</span><span class="pln"> </span><span class="str">"ADD_TWO_VALUES"</span><span class="pun">:</span><span class="pln">
                </span><span class="com"># جمع آخر قيمتين في المكدّس</span><span class="pln">
                self</span><span class="pun">.</span><span class="pln">ADD_TWO_VALUES</span><span class="pun">()</span><span class="pln">
            </span><span class="kwd">elif</span><span class="pln"> instruction </span><span class="pun">==</span><span class="pln"> </span><span class="str">"PRINT_ANSWER"</span><span class="pun">:</span><span class="pln">
                </span><span class="com"># طباعة آخر قيمة في المكدّس</span><span class="pln">
                self</span><span class="pun">.</span><span class="pln">PRINT_ANSWER</span><span class="pun">()</span><span class="pln">
            </span><span class="kwd">elif</span><span class="pln"> instruction </span><span class="pun">==</span><span class="pln"> </span><span class="str">"STORE_NAME"</span><span class="pun">:</span><span class="pln">
                </span><span class="com"># تخزين قيمة في متغير</span><span class="pln">
                self</span><span class="pun">.</span><span class="pln">STORE_NAME</span><span class="pun">(</span><span class="pln">argument</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">elif</span><span class="pln"> instruction </span><span class="pun">==</span><span class="pln"> </span><span class="str">"LOAD_NAME"</span><span class="pun">:</span><span class="pln">
                </span><span class="com"># تحميل قيمة متغير إلى المكدّس</span><span class="pln">
                self</span><span class="pun">.</span><span class="pln">LOAD_NAME</span><span class="pun">(</span><span class="pln">argument</span><span class="pun">)</span></pre>

<p>
	قد تصبح بنية التابع <code>run_code</code> معقدة بعض الشيء بالرغم من وجود خمس تعليمات فقط، إذ نحتاج فرعًا واحدًا من تعليمة <code>if</code> لكل تعليمة، لذا يمكن الاستفادة من البحث الديناميكي عن تابع في بايثون بحيث نعرّف تابعًا اسمه <code>FOO</code> ينفّذ التعليمة التي اسمها <code>FOO</code>، وبالتالي نستخدم الدالة <code>getattr</code> في بايثون للبحث عن التابع بسرعة بدلًا من استخدام تعليمة <code>if</code> السابقة المعقدة. سيبدو الآن التابع <code>run_code</code> على النحو التالي
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_41" style=""><span class="kwd">def</span><span class="pln"> execute</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> what_to_execute</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># جلب قائمة التعليمات</span><span class="pln">
    instructions </span><span class="pun">=</span><span class="pln"> what_to_execute</span><span class="pun">[</span><span class="str">"instructions"</span><span class="pun">]</span><span class="pln">

    </span><span class="com"># تنفيذ التعليمات خطوة بخطوة</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> each_step </span><span class="kwd">in</span><span class="pln"> instructions</span><span class="pun">:</span><span class="pln">
        </span><span class="com"># استخراج اسم التعليمة ووسيطها</span><span class="pln">
        instruction</span><span class="pun">,</span><span class="pln"> argument </span><span class="pun">=</span><span class="pln"> each_step

        </span><span class="com"># تحويل الوسيط من فهرس إلى قيمة فعلية (رقم أو اسم متغير)</span><span class="pln">
        argument </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">parse_argument</span><span class="pun">(</span><span class="pln">instruction</span><span class="pun">,</span><span class="pln"> argument</span><span class="pun">,</span><span class="pln"> what_to_execute</span><span class="pun">)</span><span class="pln">

        </span><span class="com"># الحصول على الدالة المطابقة لاسم التعليمة داخل هذا الصنف</span><span class="pln">
        bytecode_method </span><span class="pun">=</span><span class="pln"> getattr</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> instruction</span><span class="pun">)</span><span class="pln">

        </span><span class="com"># تنفيذ التعليمة: إن لم يكن لها وسيط ننفذها مباشرة، وإلا نمرر الوسيط</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> argument </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">:</span><span class="pln">
            bytecode_method</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            bytecode_method</span><span class="pun">(</span><span class="pln">argument</span><span class="pun">)</span></pre>

<h2 id="-">
	بناء بايت كود حقيقي في بايثون
</h2>

<p>
	سننتقل الآن إلى البايت كود الحقيقي في بايثون الذي يشبه مجموعات التعليمات في المفسر السابق، ولكنه يستخدم بايتًا واحدًا بدلًا من اسم طويل لتحديد كل تعليمة. إذًا ليكن لدينا المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9793_12" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">def</span><span class="pln"> cond</span><span class="pun">():</span><span class="pln">
</span><span class="pun">...</span><span class="pln">     x </span><span class="pun">=</span><span class="pln"> </span><span class="lit">3</span><span class="pln">                 
</span><span class="pun">...</span><span class="pln">     </span><span class="kwd">if</span><span class="pln"> x </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">5</span><span class="pun">:</span><span class="pln">             
</span><span class="pun">...</span><span class="pln">         </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'yes'</span><span class="pln">      
</span><span class="pun">...</span><span class="pln">     </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">                 
</span><span class="pun">...</span><span class="pln">         </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'no'</span><span class="pln">       </span></pre>

<p>
	يكشف بايثون عن مجموعة كبيرة من مكوناته الداخلية في وقت التشغيل بحيث يمكن الوصول إليها مباشرةً من بيئة REPL. يكون ‎ <code>cond.__code__</code>‎‎ هو كائن الشيفرة المرتبط بكائن الدالة <code>cond</code>، ويكون <code>cond.__code__.co_code</code> هو البايت كود، حيث لا يوجد سبب وجيه لاستخدام هذه السمات مباشرةً عند كتابة شيفرة بايثون، ولكنها تسمح لنا بالتعامل مع جميع أنواع المشاكل والاطلاع على المكونات الداخلية لفهمها.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_49" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> cond</span><span class="pun">.</span><span class="pln">__code__</span><span class="pun">.</span><span class="pln">co_code  </span><span class="com"># الوصول إلى البايت كود الخام للدالة على شكل بايتات</span><span class="pln">
b</span><span class="str">'d\x01\x00}\x00\x00|\x00\x00d\x02\x00k\x00\x00r\x16\x00d\x03\x00Sd\x04\x00Sd\x00
   \x00S'</span><span class="pln">                  </span><span class="com"># هذه هي البايتات نفسها التي ينفّذها مفسّر بايثون</span><span class="pln">

</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> list</span><span class="pun">(</span><span class="pln">cond</span><span class="pun">.</span><span class="pln">__code__</span><span class="pun">.</span><span class="pln">co_code</span><span class="pun">)</span><span class="pln">  </span><span class="com"># تحويل نفس البايتات إلى قائمة أعداد لقراءتها بسهولة</span><span class="pln">
</span><span class="pun">[</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">125</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">124</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">107</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">114</span><span class="pun">,</span><span class="pln"> </span><span class="lit">22</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">83</span><span class="pun">,</span><span class="pln"> 
 </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">83</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">83</span><span class="pun">]</span><span class="pln">  </span><span class="com"># كل رقم يمثّل جزءًا من تعليمة أو وسيطها داخل البايت كود</span></pre>

<p>
	يُعَد البايت كود غير مفهوم، إذ لا يمكننا تمييز إلّا سلسلة من البايتات، ولكن توجد أداة يمكننا استخدامها لفهمها، والتي هي وحدة <code>dis</code> في مكتبة بايثون المعيارية، حيث تمثل هذه الوحدة مفككًا للبايت كود الذي يأخذ شيفرة منخفضة المستوى ومكتوبة للأجهزة مثل شيفرة التجميع أو البايت كود، ويطبعها بطريقة يمكن أن يقرأها الإنسان، فمثلًا يؤدي تشغيل <code>dis.dis</code> إلى عرض شرحٍ للبايت كود الممرَّر إليه.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5407_9" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> dis</span><span class="pun">.</span><span class="pln">dis</span><span class="pun">(</span><span class="pln">cond</span><span class="pun">)</span><span class="pln">  </span><span class="com"># عرض البايت كود بصيغة مقروءة (تعليمات + عناوين)</span><span class="pln">

  </span><span class="lit">2</span><span class="pln">           </span><span class="lit">0</span><span class="pln"> LOAD_CONST               </span><span class="lit">1</span><span class="pln"> </span><span class="pun">(</span><span class="lit">3</span><span class="pun">)</span><span class="pln">      </span><span class="com"># تحميل الثابت رقم 1 (القيمة 3)</span><span class="pln">
              </span><span class="lit">3</span><span class="pln"> STORE_FAST               </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="pln">x</span><span class="pun">)</span><span class="pln">      </span><span class="com"># تخزين القيمة في المتغير المحلي رقم 0 (x)</span><span class="pln">

  </span><span class="lit">3</span><span class="pln">           </span><span class="lit">6</span><span class="pln"> LOAD_FAST                </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="pln">x</span><span class="pun">)</span><span class="pln">      </span><span class="com"># x تحميل قيمة المتغير المحلي </span><span class="pln">
              </span><span class="lit">9</span><span class="pln"> LOAD_CONST               </span><span class="lit">2</span><span class="pln"> </span><span class="pun">(</span><span class="lit">5</span><span class="pun">)</span><span class="pln">      </span><span class="com"># تحميل الثابت رقم 2 (القيمة 5)</span><span class="pln">
             </span><span class="lit">12</span><span class="pln"> COMPARE_OP               </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(&lt;)</span><span class="pln">      </span><span class="com"># (&lt;) مقارنة القيمتين باستخدام العامل</span><span class="pln">
             </span><span class="lit">15</span><span class="pln"> POP_JUMP_IF_FALSE       </span><span class="lit">22</span><span class="pln">          </span><span class="com"># إذا كانت نتيجة الشرط غير صحيحة اقفز إلى العنوان 22</span><span class="pln">

  </span><span class="lit">4</span><span class="pln">          </span><span class="lit">18</span><span class="pln"> LOAD_CONST               </span><span class="lit">3</span><span class="pln"> </span><span class="pun">(</span><span class="str">'yes'</span><span class="pun">)</span><span class="pln">  </span><span class="com"># 'yes' تحميل الثابت </span><span class="pln">
             </span><span class="lit">21</span><span class="pln"> RETURN_VALUE                        </span><span class="com"># إرجاع القيمة</span><span class="pln">

  </span><span class="lit">6</span><span class="pln">     </span><span class="pun">&gt;&gt;</span><span class="pln">   </span><span class="lit">22</span><span class="pln"> LOAD_CONST               </span><span class="lit">4</span><span class="pln"> </span><span class="pun">(</span><span class="str">'no'</span><span class="pun">)</span><span class="pln">   </span><span class="com"># 'no' تحميل الثابت</span><span class="pln">
             </span><span class="lit">25</span><span class="pln"> RETURN_VALUE                        </span><span class="com"># إرجاع القيمة</span><span class="pln">
             </span><span class="lit">26</span><span class="pln"> LOAD_CONST               </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">None</span><span class="pun">)</span><span class="pln">   </span><span class="com"># قيمة افتراضية عند عدم الإرجاع صراحة</span><span class="pln">
             </span><span class="lit">29</span><span class="pln"> RETURN_VALUE                        </span></pre>

<p>
	لنوضح التعليمة الأولى <code>LOAD_CONST</code> كمثال، حيث يمثل العدد في العمود الأول (2) رقم السطر في شيفرة بايثون المصدرية الخاصة بنا، ويمثل العمود الثاني فهرسًا إلى البايت كود يخبرنا أن التعليمة <code>LOAD_CONST</code> تظهر في الموضع صفر، ويمثل العمود الثالث التعليمة مع ربطها باسمها القابل للقراءة. يمثل العمود الرابع -إن وجد- وسيط هذه التعليمة، والعمود الخامس -إن وجد- هو تلميح حول ما يعنيه الوسيط.
</p>

<p>
	تمثل البايتات الستة الأولى من البايت كود‎ <code>[100, 1, 0, 125, 0, 0]</code> تعليمتين مع وسطائهما، ويمكننا استخدام <code>dis.opname</code> التي تربط البايتات مع سلاسل نصية مفهومة لمعرفة ما تعنيه التعليمتان 100 و 125 كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_53" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> dis</span><span class="pun">.</span><span class="pln">opname</span><span class="pun">[</span><span class="lit">100</span><span class="pun">]</span><span class="pln">        </span><span class="com"># إرجاع اسم التعليمة المقابل لرقم العملية 100</span><span class="pln">
</span><span class="str">'LOAD_CONST'</span><span class="pln">               </span><span class="com"># الرقم 100 يعني: تحميل ثابت</span><span class="pln">

</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> dis</span><span class="pun">.</span><span class="pln">opname</span><span class="pun">[</span><span class="lit">125</span><span class="pun">]</span><span class="pln">        </span><span class="com"># إرجاع اسم التعليمة المقابل لرقم العملية 125</span><span class="pln">
</span><span class="str">'STORE_FAST'</span><span class="pln">               </span><span class="com"># الرقم 125 يعني: تخزين قيمة في متغير محلي</span></pre>

<p>
	يمثل البايتان الثاني والثالث ‎1 و 0‎ وسطاء التعليمة <code>LOAD_CONST</code>، ويمثل البايتان الخامس والسادس ‎0 و 0‎ وسطاء التعليمة <code>STORE_FAST</code>، إذ تحتاج التعليمة <code>LOAD_CONST</code> إلى معرفة مكان العثور على الثابت لتحميله، وتحتاج التعليمة <code>STORE_FAST</code> إلى العثور على الاسم لتخزينه. تمثل هذه البايتات الستة السطر الأول من الشيفرة البرمجية <code>x = 3</code>، حيث تقابل التعليمة <code>LOAD_CONST</code> في بايثون التعليمة <code>LOAD_VALUE</code> في المفسر السابق، وتقابل التعليمة <code>LOAD_FAST</code> التعليمة <code>LOAD_NAME</code>.
</p>

<p>
	يُستخدَم بايتان لكل وسيط، لأنه إذا استخدم بايثون بايتًا واحدًا فقط لتحديد مكان الثوابت والأسماء بدلًا من استخدام بايتين، فلن نتمكن إلا من الحصول على 256 اسمٍ أو ثابت مرتبط بكائن شيفرة واحد، بينما يمكن الحصول على ما يصل إلى مربع العدد 256 أو 65536 عند استخدام بايتين.
</p>

<h3 id="-">
	التعليمات الشرطية والحلقات
</h3>

<p>
	نفّذَ المفسر حتى الآن الشيفرة البرمجية ببساطة من خلال تنفيذ التعليمات واحدة تلو الأخرى، ولكن قد يشكّل ذلك مشكلة، لأننا في أغلب الاحيان نريد تكرار تنفيذ تعليمات معينة، أو قد نرغب في تخطي بعض التعليمات في شروط محددة، لذا يجب أن يكون المفسر قادرًا على التنقل عبر مجموعة التعليمات للسماح بكتابة الحلقات والتعليمات الشرطية <code>if</code> في الشيفرة البرمجية، حيث يتعامل بايثون مع الحلقات والتعليمات الشرطية باستخدام تعليمات <code>GOTO</code> في البايت كود. لنطلع الآن على تفكيك الدالة <code>cond</code> مرة أخرى:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5407_11" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> dis</span><span class="pun">.</span><span class="pln">dis</span><span class="pun">(</span><span class="pln">cond</span><span class="pun">)</span><span class="pln">  </span><span class="com"># عرض تعليمات الدالة بصيغة مقروءة (العنوان + اسم التعليمة + الوسيط)</span><span class="pln">

  </span><span class="lit">2</span><span class="pln">           </span><span class="lit">0</span><span class="pln"> LOAD_CONST               </span><span class="lit">1</span><span class="pln"> </span><span class="pun">(</span><span class="lit">3</span><span class="pun">)</span><span class="pln">      </span><span class="com"># تحميل الثابت 3 إلى المكدّس</span><span class="pln">
              </span><span class="lit">3</span><span class="pln"> STORE_FAST               </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="pln">x</span><span class="pun">)</span><span class="pln">      </span><span class="com"># x تخزين القيمة في المتغير المحلي </span><span class="pln">

  </span><span class="lit">3</span><span class="pln">           </span><span class="lit">6</span><span class="pln"> LOAD_FAST                </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="pln">x</span><span class="pun">)</span><span class="pln">      </span><span class="com"># تحميل قيمة x إلى المكدّس</span><span class="pln">
              </span><span class="lit">9</span><span class="pln"> LOAD_CONST               </span><span class="lit">2</span><span class="pln"> </span><span class="pun">(</span><span class="lit">5</span><span class="pun">)</span><span class="pln">      </span><span class="com"># تحميل الثابت 5 إلى المكدّس</span><span class="pln">
             </span><span class="lit">12</span><span class="pln"> COMPARE_OP               </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(&lt;)</span><span class="pln">      </span><span class="com"># مقارنة: هل x أصغر من 5؟</span><span class="pln">
             </span><span class="lit">15</span><span class="pln"> POP_JUMP_IF_FALSE       </span><span class="lit">22</span><span class="pln">          </span><span class="com"># إذا كانت المقارنة خطأ انتقل إلى العنوان 22</span><span class="pln">

  </span><span class="lit">4</span><span class="pln">          </span><span class="lit">18</span><span class="pln"> LOAD_CONST               </span><span class="lit">3</span><span class="pln"> </span><span class="pun">(</span><span class="str">'yes'</span><span class="pun">)</span><span class="pln">  </span><span class="com"># تحميل 'yes' (فرع if)</span><span class="pln">
             </span><span class="lit">21</span><span class="pln"> RETURN_VALUE                        </span><span class="com"># إرجاع القيمة وإنهاء الدالة</span><span class="pln">

  </span><span class="lit">6</span><span class="pln">     </span><span class="pun">&gt;&gt;</span><span class="pln">   </span><span class="lit">22</span><span class="pln"> LOAD_CONST               </span><span class="lit">4</span><span class="pln"> </span><span class="pun">(</span><span class="str">'no'</span><span class="pun">)</span><span class="pln">   </span><span class="com"># تحميل 'no' (فرع else)</span><span class="pln">
             </span><span class="lit">25</span><span class="pln"> RETURN_VALUE                        </span><span class="com"># إرجاع القيمة وإنهاء الدالة</span><span class="pln">
             </span><span class="lit">26</span><span class="pln"> LOAD_CONST               </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">None</span><span class="pun">)</span><span class="pln">   </span><span class="com"># تحميل None كقيمة افتراضية</span><span class="pln">
             </span><span class="lit">29</span><span class="pln"> RETURN_VALUE                        </span><span class="com"># إرجاع None (مسار افتراضي/احتياطي)</span></pre>

<p>
	تُصرَّف التعليمة الشرطية ‎<code>if x &lt; 5</code>‎ في السطر 3 من الشيفرة البرمجية إلى أربع تعليمات هي: <code>LOAD_FAST</code> و <code>LOAD_CONST</code> و <code>COMPARE_OP</code> و <code>POP_JUMP_IF_FALSE</code>، حيث يولد الشرط ‎<code>x &lt; 5</code>‎ الشيفرة البرمجية لتحميل <code>x</code> وتحميل القيمة 5 ومقارنة القيمتين. تكون التعليمة <code>POP_JUMP_IF_FALSE</code> مسؤولة عن تنفيذ تعليمة <code>if</code>، إذ تؤدي هذه التعليمة إلى سحب القيمة العليا من مكدس المفسر، حيث إذا كانت القيمة صحيحة، فلن يحدث شيء، ويمكن أن تكون القيمة صحيحة دون أن تكون كائن <code>True</code> حرفيًا، بينما إذا كانت القيمة خاطئة، فسينتقل المفسر إلى تعليمة أخرى.
</p>

<p>
	تسمى التعليمة التي يجب الوصول إليها بهدف عملية القفز Jump Target، وتكون وسيطًا للتعليمة <code>POP_JUMP</code>، فهدف القفز هو 22 في مثالنا، والتعليمة ذات الفهرس 22 هي <code>LOAD_CONST</code> عند السطر 6، حيث تضع وحدة <code>dis</code> الرمز <code>&lt;&lt;</code> على أهداف القفز. إذا كانت نتيجة الشرط ‎<code>x &lt; 5</code>‎ خاطئة، فسيقفز المفسر إلى السطر 6 مباشرةً <code>return "no"</code>‎‎ مع تجاوز السطر 4 الذي هو ‎<code>return "yes"</code>، وبالتالي يستخدم المفسر تعليمات القفز للتخطي الانتقائي لأجزاء من مجموعة التعليمات.
</p>

<p>
	تعتمد حلقات Loops بايثون على القفز، فمثلًا يولّد السطر <code>while x &lt; 5</code> في البايت كود التالي بايت كود متطابقًا تقريبًا مع بايت كود التعليمة <code>if x &lt; 10</code>، حيث تُطبَّق المقارنة في كلتا الحالتين ثم تتحكم التعليمة <code>POP_JUMP_IF_FALSE</code> بالتعليمة التالية التي ستُنفَّذ. ترسل التعليمة <code>JUMP_ABSOLUTE</code> في نهاية السطر 4 الذي يمثل نهاية جسم الحلقة دائمًا المفسر مرة أخرى إلى التعليمة 9 في أعلى الحلقة، وتؤدي التعليمة <code>POP_JUMP_IF_FALSE</code> إلى قفز المفسر إلى ما بعد نهاية الحلقة إلى التعليمة 34 عندما يصبح الشرط <code>x &lt; 5</code>‎ خاطئًا.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5407_13" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">def</span><span class="pln"> loop</span><span class="pun">():</span><span class="pln">
</span><span class="pun">...</span><span class="pln">      x </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">                  
</span><span class="pun">...</span><span class="pln">      </span><span class="kwd">while</span><span class="pln"> x </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">5</span><span class="pun">:</span><span class="pln">          
</span><span class="pun">...</span><span class="pln">          x </span><span class="pun">=</span><span class="pln"> x </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pln">             
</span><span class="pun">...</span><span class="pln">      </span><span class="kwd">return</span><span class="pln"> x            
</span><span class="pun">...</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> dis</span><span class="pun">.</span><span class="pln">dis</span><span class="pun">(</span><span class="pln">loop</span><span class="pun">)</span><span class="pln">  </span><span class="com"># عرض تعليمات الدالة بصيغة مقروءة</span><span class="pln">

  </span><span class="lit">2</span><span class="pln">           </span><span class="lit">0</span><span class="pln"> LOAD_CONST               </span><span class="lit">1</span><span class="pln"> </span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">      </span><span class="com"># تحميل الثابت 1 إلى المكدّس</span><span class="pln">
              </span><span class="lit">3</span><span class="pln"> STORE_FAST               </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="pln">x</span><span class="pun">)</span><span class="pln">      </span><span class="com"># x تخزينه في المتغير المحلي </span><span class="pln">

  </span><span class="lit">3</span><span class="pln">           </span><span class="lit">6</span><span class="pln"> SETUP_LOOP              </span><span class="lit">26</span><span class="pln"> </span><span class="pun">(</span><span class="pln">to </span><span class="lit">35</span><span class="pun">)</span><span class="pln">  </span><span class="com"># تجهيز إطار الحلقة (بداية/نهاية ومسار الخروج)</span><span class="pln">
        </span><span class="pun">&gt;&gt;</span><span class="pln">    </span><span class="lit">9</span><span class="pln"> LOAD_FAST                </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="pln">x</span><span class="pun">)</span><span class="pln">      </span><span class="com"># تحميل x لفحص شرط الحلقة</span><span class="pln">
             </span><span class="lit">12</span><span class="pln"> LOAD_CONST               </span><span class="lit">2</span><span class="pln"> </span><span class="pun">(</span><span class="lit">5</span><span class="pun">)</span><span class="pln">      </span><span class="com"># تحميل الثابت 5</span><span class="pln">
             </span><span class="lit">15</span><span class="pln"> COMPARE_OP               </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(&lt;)</span><span class="pln">      </span><span class="com"># مقارنة: هل x أصغر من 5؟</span><span class="pln">
             </span><span class="lit">18</span><span class="pln"> POP_JUMP_IF_FALSE       </span><span class="lit">34</span><span class="pln">          </span><span class="com"># إذا كان الشرط غير صحيح اقفز إلى نهاية الحلقة (العنوان 34)</span><span class="pln">

  </span><span class="lit">4</span><span class="pln">          </span><span class="lit">21</span><span class="pln"> LOAD_FAST                </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="pln">x</span><span class="pun">)</span><span class="pln">      </span><span class="com"># تحميل x لتنفيذ جسم الحلقة</span><span class="pln">
             </span><span class="lit">24</span><span class="pln"> LOAD_CONST               </span><span class="lit">1</span><span class="pln"> </span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">      </span><span class="com"># تحميل الثابت 1</span><span class="pln">
             </span><span class="lit">27</span><span class="pln"> BINARY_ADD                          </span><span class="com"># (x + 1) جمع القيمتين </span><span class="pln">
             </span><span class="lit">28</span><span class="pln"> STORE_FAST               </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="pln">x</span><span class="pun">)</span><span class="pln">      </span><span class="com"># x تخزين الناتج في </span><span class="pln">
             </span><span class="lit">31</span><span class="pln"> JUMP_ABSOLUTE            </span><span class="lit">9</span><span class="pln">          </span><span class="com"># الرجوع لبداية فحص الشرط (العنوان 9)</span><span class="pln">
        </span><span class="pun">&gt;&gt;</span><span class="pln">   </span><span class="lit">34</span><span class="pln"> POP_BLOCK                          </span><span class="com"># إنهاء إطار الحلقة بعد الخروج منها</span><span class="pln">

  </span><span class="lit">5</span><span class="pln">     </span><span class="pun">&gt;&gt;</span><span class="pln">   </span><span class="lit">35</span><span class="pln"> LOAD_FAST                </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="pln">x</span><span class="pun">)</span><span class="pln">      </span><span class="com"># تحميل x بعد انتهاء الحلقة</span><span class="pln">
             </span><span class="lit">38</span><span class="pln"> RETURN_VALUE                        </span><span class="com"># x إرجاع </span></pre>

<h3 id="-">
	استكشاف البايت كود
</h3>

<p>
	يمكننا تجربة تشغيل <code>dis.dis</code> مع الدوال التي نكتبها للإجابة عن الأسئلة التالية:
</p>

<ul>
	<li>
		ما الفرق بين حلقة for وحلقة while بالنسبة لمفسر بايثون؟
	</li>
	<li>
		كيف يمكننا كتابة دوال مختلفة تولد بايت كود متطابق؟
	</li>
	<li>
		كيف تعمل تعليمة <code>elif</code>، وما هو استيعاب القوائم List Comprehensions؟
	</li>
</ul>

<h2 id="-frames">
	الإطارات Frames
</h2>

<p>
	تعلمنا حتى الآن أن آلة بايثون الافتراضية هي آلة مكدس، لأنها تتنقل وتقفز عبر التعليمات مع دفع وسحب القيم من المكدس وإليه، ولكن لا تزال هناك بعض الأمور الغامضة مثل التعليمة الأخيرة <code>RETURN_VALUE</code> التي تقابل تعليمة <code>return</code> في الشيفرة البرمجية، إذ لا نعرف المكان الذي تعود إليه هذه التعليمة.
</p>

<p>
	نحتاج إلى إضافة طبقةٍ من التعقيد تتمثل في الإطار Frame لمعرفة المكان الذي تعود إليه التعليمة <code>RETURN_VALUE</code>، فالإطار هو مجموعة من المعلومات والسياق لجزء من الشيفرة البرمجية، وينشأ ويُدمَّر أثناء تنفيذ شيفرة بايثون الخاصة بنا. يوجد إطار واحد يقابل كل استدعاء لدالة، وبالتالي يمكن لكائن الشيفرة أن يكون له العديد من الإطارات بينما يكون لكل إطار كائن شيفرة واحد. إذا كان لدينا دالة تستدعي نفسها تعاوديًا عشر مرات، فسيكون لدينا أحد عشر إطارًا، إطار لكل مستوى من التعاود وإطار للوحدة التي بدأنا منها. يوجد إطار لكل مجال في برنامج بايثون، إذ يوجد مثلًا إطار لكل وحدة وإطار لكل استدعاء دالة وإطار لكل تعريف صنف.
</p>

<p>
	توجد الإطارات في مكدس الاستدعاءات Call Stack الذي هو مكدس مختلف تمامًا عن المكدس الذي تحدثنا عنه سابقًا، فمكدس الاستدعاءات هو مكدسٌ رأيناه مسبقًا في عمليات التتبع العكسي Tracebacks للاستثناءات، إذ يقابل كل سطر في عملية التتبع العكسي يبدأ بعبارة ‎<code>"File 'program.py', line 10"</code>‎‎ إطارًا واحدًا في مكدس الاستدعاءات. نسمي المكدس الذي يتعامل معه المفسر أثناء تنفيذ البايت كود بمكدس البيانات Data Stack. يوجد مكدس ثالث اسمه مكدس الكتل Block Stack، حيث تُستخدَم الكتل لأنواع معينة من تدفق التحكم مثل التكرار ضمن حلقة ومعالجة الاستثناءات، ويكون لكل إطارٍ في مكدس الاستدعاءات مكدس بيانات ومكدس كتل خاص به.
</p>

<p>
	لنفترض مثلًا أن مفسر بايثون ينفّذ حاليًا السطر المحدد بالرقم 3، حيث يكون المفسر عند استدعاء الدالة <code>foo</code> التي تستدعي الدالة <code>bar</code> بدورها. يوضح الرسم البياني الآتي مخطط مكدس الاستدعاءات للإطارات ومكدسات الكتل ومكدسات البيانات، وتعَد هذه الشيفرة البرمجية مكتوبة بطريقة مشابهة لجلسة REPL، لذلك جرى تعريف الدوال المطلوبة أولًا. ينفذ المفسر حاليًا الدالة <code>foo()‎</code>‎ التي تصل بعد ذلك إلى جسم الدالة <code>foo</code> ثم إلى <code>bar</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5407_15" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">def</span><span class="pln"> bar</span><span class="pun">(</span><span class="pln">y</span><span class="pun">):</span><span class="pln">
</span><span class="pun">...</span><span class="pln">     z </span><span class="pun">=</span><span class="pln"> y </span><span class="pun">+</span><span class="pln"> </span><span class="lit">3</span><span class="pln">             </span><span class="com"># (3) داخل الدالة bar: حساب z بجمع y مع 3</span><span class="pln">
</span><span class="pun">...</span><span class="pln">     </span><span class="kwd">return</span><span class="pln"> z              
</span><span class="pun">...</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">def</span><span class="pln"> foo</span><span class="pun">():</span><span class="pln">
</span><span class="pun">...</span><span class="pln">     a </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">                 
</span><span class="pun">...</span><span class="pln">     b </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2</span><span class="pln">                 
</span><span class="pun">...</span><span class="pln">     </span><span class="kwd">return</span><span class="pln"> a </span><span class="pun">+</span><span class="pln"> bar</span><span class="pun">(</span><span class="pln">b</span><span class="pun">)</span><span class="pln">     </span><span class="com"># (2) </span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> foo</span><span class="pun">()</span><span class="pln">                     </span><span class="com"># (1) استدعاء الدالة foo لتنفيذ ما بداخلها</span><span class="pln">
</span><span class="lit">3</span><span class="pln">                             </span><span class="com"># foo الناتج النهائي بعد تنفيذ</span></pre>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="181522" href="https://academy.hsoub.com/uploads/monthly_2026_03/002___.png.74b33ee166344ef5e169cb02ac1713f8.png" rel=""><img alt="002_مكدس_استدعاءات_المفسر.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181522" data-ratio="40.67" data-unique="0wwmaf6fa" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_03/002___.thumb.png.e3afab3eb62b33f85a3880f9d962f62a.png"></a><span id="cke_bm_3420E" style="display: none;"> </span>
</p>

<p>
	يكون المفسر حاليًا عند استدعاء الدالة <code>bar</code>، وتوجد ثلاثة إطارات في مكدس الاستدعاءات هي: إطار على مستوى الوحدة، وإطار للدالة <code>foo</code>، وإطار للدالة <code>bar</code> كما هو موضح في الشكل السابق، حيث تعود الدالة <code>bar</code> ثم يُخرَج ويُهمَل الإطار المرتبط بها من مكدس الاستدعاءات. تخبر تعليمة البايت كود <code>RETURN_VALUE</code> المفسرَ بتمرير قيمةٍ بين الإطارات، حيث يخرِج أولًا القيمة العليا من مكدس البيانات الخاص بالإطار العلوي من مكدس الاستدعاءات، ثم يخرِج الإطار بالكامل من مكدس الاستدعاءات ويتخلص منه، وتُدفَع أخيرًا القيمة إلى مكدس البيانات في الإطار التالي.
</p>

<p>
	واجه مطورو مفسر Byterun لفترة طويلة خطأً كبيرًا في التنفيذ، إذ كان يوجد مكدس بيانات واحد فقط على الآلة الافتراضية بالكامل بدلًا من وجود مكدس بيانات لكل إطار، حيث أُجريت كثير من الاختبارات المكوَّنة من مقاطع شيفرة بايثون والمنفَّذة باستخدام Byterun ومفسر بايثون الحقيقي للتأكد من حدوث الشيء نفسه عليهما. نجحت جميع هذه الاختبارات تقريبًا باستثناء دوال التوليد Generators، حيث اُكتشِف الخطأ أخيرًا عند قراءة شيفرة CPython بعناية، ولكن أدى استخدام مكدس بيانات لكل إطار إلى حل المشكلة.
</p>

<p>
	لا تعتمد بايثون على أن يكون لكل إطار مكدس بيانات، إذ تنظّف جميعُ العمليات تقريبًا في مفسّر بايثون مكدسَ البيانات بعناية، لذا لم يكن مهمًا أن تشترك الإطارات في المكدس نفسه، حيث سيكون مكدس البيانات الخاص بالدالة <code>bar</code> فارغًا في المثال السابق عند انتهاء تنفيذها. ستكون القيم أقل حتى إذا تشاركت <code>foo</code> في المكدس نفسه، ولكن تكمن الميزة الرئيسية لدوال التوليد في القدرة على إيقاف إطار مؤقتًا والعودة إلى إطار آخر ثم العودة إلى إطار الدالة المولدة لاحقًا بحيث يكون في الحالة نفسها تمامًا التي تركناه عليها.
</p>

<h2 id="-byterun">
	مفسر Byterun
</h2>

<p>
	تعرّفنا على بعض المفاهيم المتعلقة بمفسر بايثون ولنبدأ الآن بمفسر Byterun، حيث توجد أربعة أنواع من الكائنات في Byterun وهي:
</p>

<ul>
	<li>
		الصنف <code>VirtualMachine</code>: يدير البنية ذات المستوى الأعلى وخاصة مكدس استدعاءات الإطارات، ويحتوي على ربط بين التعليمات والعمليات، حيث يُعَد نسخة أكثر تعقيدًا من الكائن <code>Intepreter</code>.
	</li>
	<li>
		الصنف <code>Frame</code>: تحتوي كل نسخة من هذا الصنف على كائن شيفرة واحد، وتدير عددًا من بتات الحالة الضرورية الأخرى وخاصة مساحات الأسماء المحلية والعامة ومرجعًا للإطار المستدعِي وتعليمة البايت كود الأخيرة المُنفَّذة.
	</li>
	<li>
		الصنف <code>Function</code>: يُستخدَم بدلًا من دوال بايثون الحقيقية، حيث ينشئ استدعاء دالة إطارًا جديدًا في المفسر، ونُفِّذ هذا الصنف للتحكم في إنشاء إطارات جديدة.
	</li>
	<li>
		الصنف <code>Block</code>: يغلِّف سمات Attributes الكتل، ولا تُعَد تفاصيل الكتل أساسية بالنسبة لمفسر بايثون، لذا لن نخوض بتفاصيلها، ولكنها مُدرجَة في هذا المقال حتى يتمكّن مفسر Byterun من تشغيل شيفرة بايثون الحقيقية.
	</li>
</ul>

<h3 id="-virtualmachine">
	الصنف VirtualMachine
</h3>

<p>
	تنشأ نسخة واحدة فقط من الصنف <code>VirtualMachine</code> عند كل تشغيل للبرنامج بسبب وجود مفسر بايثون واحد فقط، ويخزّن هذا الصنف مكدس الاستدعاءات وحالة الاستثناءات والقيم المُعادة أثناء تمريرها بين الإطارات. يبدأ تنفيذ الشيفرة البرمجية من التابع <code>run_code</code> الذي يأخذ كائن شيفرة مُصرَّف كوسيط، ويبدأ من خلال إعداد إطار وتشغيله، وقد ينشئ هذا الإطار إطارات أخرى، إذ سيكبر مكدس الاستدعاءات ويصغر عند تنفيذ البرنامج، وينتهي التنفيذ عند إعادة الإطار الأول.<code> </code>
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_69" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">VirtualMachine</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> __init__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">frames </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">   </span><span class="com"># مكدس استدعاءات الإطارات</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">frame </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">  </span><span class="com"># الإطار الحالي</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">return_value </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">last_exception </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> run_code</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> code</span><span class="pun">,</span><span class="pln"> global_names</span><span class="pun">=</span><span class="kwd">None</span><span class="pun">,</span><span class="pln"> local_names</span><span class="pun">=</span><span class="kwd">None</span><span class="pun">):</span><span class="pln">
        </span><span class="str">""" An entry point to execute code using the virtual machine."""</span><span class="pln">
        frame </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">make_frame</span><span class="pun">(</span><span class="pln">code</span><span class="pun">,</span><span class="pln"> global_names</span><span class="pun">=</span><span class="pln">global_names</span><span class="pun">,</span><span class="pln"> 
                                local_names</span><span class="pun">=</span><span class="pln">local_names</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">run_frame</span><span class="pun">(</span><span class="pln">frame</span><span class="pun">)</span></pre>

<h3 id="-frame">
	الصنف Frame
</h3>

<p>
	الإطار هو مجموعة من السمات بدون توابع، وتتضمن هذه السمات كائن الشيفرة الذي أنشأه المصرف ومساحات الأسماء المحلية والعامة والمضمَّنة ومرجعًا إلى الإطار السابق ومكدس بيانات ومكدس كتل والتعليمة الأخيرة المنفَّذة. يحتاج الوصول إلى مساحة الأسماء المضمنة إلى عمل إضافي لأن بايثون يعامل هذه المساحة بطريقة مختلفة لكل وحدة، ولكن لا تُعَد هذه التفاصيل مهمة للآلة الافتراضية.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_71" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Frame</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> __init__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> code_obj</span><span class="pun">,</span><span class="pln"> global_names</span><span class="pun">,</span><span class="pln"> local_names</span><span class="pun">,</span><span class="pln"> prev_frame</span><span class="pun">):</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">code_obj </span><span class="pun">=</span><span class="pln"> code_obj              </span><span class="com"># كائن الكود الذي سيتم تنفيذه</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">global_names </span><span class="pun">=</span><span class="pln"> global_names      </span><span class="com"># مساحة الأسماء العامة</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">local_names </span><span class="pun">=</span><span class="pln"> local_names        </span><span class="com"># مساحة الأسماء المحلية</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">prev_frame </span><span class="pun">=</span><span class="pln"> prev_frame          </span><span class="com"># الإطار السابق (للاستدعاءات المتداخلة)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">stack </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">                       </span><span class="com"># مكدّس القيم أثناء التنفيذ</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> prev_frame</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># وراثة مساحة الأسماء المضمنة من الإطار السابق</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">builtin_names </span><span class="pun">=</span><span class="pln"> prev_frame</span><span class="pun">.</span><span class="pln">builtin_names
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># في أول إطار: أخذ مساحة الأسماء المضمنة من __builtins__</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">builtin_names </span><span class="pun">=</span><span class="pln"> local_names</span><span class="pun">[</span><span class="str">'__builtins__'</span><span class="pun">]</span><span class="pln">
            </span><span class="com"># إذا كانت كائنًا يحتوي قاموس أسماء، نستخدم قاموسه مباشرة</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> hasattr</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">builtin_names</span><span class="pun">,</span><span class="pln"> </span><span class="str">'__dict__'</span><span class="pun">):</span><span class="pln">
                self</span><span class="pun">.</span><span class="pln">builtin_names </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">builtin_names</span><span class="pun">.</span><span class="pln">__dict__

        self</span><span class="pun">.</span><span class="pln">last_instruction </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pln">             </span><span class="com"># مؤشر آخر تعليمة تم تنفيذها</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">block_stack </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">                 </span><span class="com"># مكدّس الكتل (مثل الحلقات/الاستثناءات) لإدارة القفزات والخروج</span></pre>

<p>
	نحتاج بعد ذلك إلى إضافة معالجةٍ للإطارات إلى الآلة الافتراضية، حيث توجد ثلاث دوال مساعدة للإطارات هي: دالة إنشاء إطارات جديدة تفرز مساحات الأسماء للإطار الجديد، ودالة لدفع الإطارات، ودالة لسحبها من مكدس الإطارات. تنجز الدالة الرابعة <code>run_frame</code> العمل الأساسي المتمثل في تنفيذ الإطار، والذي سنشرحه بمزيدٍ من التفصيل لاحقًا.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_73" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">VirtualMachine</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="pun">[...</span><span class="pln"> snip </span><span class="pun">...]</span><span class="pln">

    </span><span class="com"># إنشاء إطار جديد للتنفيذ</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> make_frame</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> code</span><span class="pun">,</span><span class="pln"> callargs</span><span class="pun">={},</span><span class="pln"> global_names</span><span class="pun">=</span><span class="kwd">None</span><span class="pun">,</span><span class="pln"> local_names</span><span class="pun">=</span><span class="kwd">None</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># تحديد مساحات الأسماء التي سيعمل عليها هذا الإطار</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> global_names </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> </span><span class="kwd">None</span><span class="pln"> </span><span class="kwd">and</span><span class="pln"> local_names </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># عند تمريرهما معًا: اجعل المحلية نفس العامة (مساحة واحدة)</span><span class="pln">
            local_names </span><span class="pun">=</span><span class="pln"> global_names
        </span><span class="kwd">elif</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frames</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># إذا كان هناك إطار حالي: ورّث العامة منه وابدأ محلية جديدة</span><span class="pln">
            global_names </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">global_names
            local_names </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># إذا كان هذا أول إطار: أنشئ مساحة أسماء ابتدائية</span><span class="pln">
            global_names </span><span class="pun">=</span><span class="pln"> local_names </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                </span><span class="str">'__builtins__'</span><span class="pun">:</span><span class="pln"> __builtins__</span><span class="pun">,</span><span class="pln">
                </span><span class="str">'__name__'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'__main__'</span><span class="pun">,</span><span class="pln">
                </span><span class="str">'__doc__'</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">,</span><span class="pln">
                </span><span class="str">'__package__'</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">

        </span><span class="com"># إدخال معاملات الاستدعاء داخل المحلية</span><span class="pln">
        local_names</span><span class="pun">.</span><span class="pln">update</span><span class="pun">(</span><span class="pln">callargs</span><span class="pun">)</span><span class="pln">

        </span><span class="com"># بناء الإطار وربطه بالإطار الحالي كإطار سابق</span><span class="pln">
        frame </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Frame</span><span class="pun">(</span><span class="pln">code</span><span class="pun">,</span><span class="pln"> global_names</span><span class="pun">,</span><span class="pln"> local_names</span><span class="pun">,</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> frame

    </span><span class="kwd">def</span><span class="pln"> push_frame</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> frame</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># دفع الإطار الجديد ليصبح الإطار الحالي</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">frames</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">frame</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">frame </span><span class="pun">=</span><span class="pln"> frame

    </span><span class="kwd">def</span><span class="pln"> pop_frame</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إزالة الإطار الحالي والعودة للإطار السابق إن وُجد</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">frames</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frames</span><span class="pun">:</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">frame </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frames</span><span class="pun">[-</span><span class="lit">1</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">frame </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> run_frame</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">pass</span><span class="pln">
        </span><span class="com"># تنفيذ الإطار (سنضيف منطق التنفيذ لاحقًا)</span></pre>

<h3 id="-function">
	الصنف Function
</h3>

<p>
	يُعَد تنفيذ الكائن <code>Function</code> معقدًا بعض الشيء، ولكن ليست معظم هذه التفاصيل ذات أهمية كبيرة لفهم المفسر، فالأمر المهم الذي يجب ملاحظته هو أن استدعاء دالة أو استدعاء التابع <code>__call__</code> يؤدي إلى إنشاء كائن <code>Frame</code> جديد وبدء تشغيله.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_75" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Function</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="str">"""
    إنشاء كائن دالة قريب من دوال بايثون الحقيقية، بالشكل الذي يتوقعه المفسّر.
    """</span><span class="pln">
    __slots__ </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
        </span><span class="str">'func_code'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'func_name'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'func_defaults'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'func_globals'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'func_locals'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'func_dict'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'func_closure'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'__name__'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'__dict__'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'__doc__'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'_vm'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'_func'</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">]</span><span class="pln">  </span><span class="com"># تحديد الحقول المسموح بها لتقليل الذاكرة ومنع إضافة حقول عشوائية</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __init__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">,</span><span class="pln"> code</span><span class="pun">,</span><span class="pln"> globs</span><span class="pun">,</span><span class="pln"> defaults</span><span class="pun">,</span><span class="pln"> closure</span><span class="pun">,</span><span class="pln"> vm</span><span class="pun">):</span><span class="pln">
        </span><span class="str">"""لا تحتاج لفهم كل التفاصيل هنا لفهم المفسّر."""</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">_vm </span><span class="pun">=</span><span class="pln"> vm                          </span><span class="com"># مرجع للآلة الافتراضية التي ستنفّذ هذه الدالة</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">func_code </span><span class="pun">=</span><span class="pln"> code                  </span><span class="com"># كائن الكود الخاص بالدالة</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">func_name </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">__name__ </span><span class="pun">=</span><span class="pln"> name </span><span class="kwd">or</span><span class="pln"> code</span><span class="pun">.</span><span class="pln">co_name  </span><span class="com"># اسم الدالة (المعطى أو من كائن الكود)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">func_defaults </span><span class="pun">=</span><span class="pln"> tuple</span><span class="pun">(</span><span class="pln">defaults</span><span class="pun">)</span><span class="pln">   </span><span class="com"># القيم الافتراضية للوسطاء</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">func_globals </span><span class="pun">=</span><span class="pln"> globs              </span><span class="com"># مساحة الأسماء العامة</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">func_locals </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">_vm</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">f_locals  </span><span class="com"># مساحة الأسماء المحلية من الإطار الحالي</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">__dict__ </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">                     </span><span class="com"># قاموس خصائص إضافية للدالة</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">func_closure </span><span class="pun">=</span><span class="pln"> closure            </span><span class="com"># معلومات الإغلاق (إن وُجدت)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">__doc__ </span><span class="pun">=</span><span class="pln"> code</span><span class="pun">.</span><span class="pln">co_consts</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> code</span><span class="pun">.</span><span class="pln">co_consts </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">  </span><span class="com"># نص التوثيق إن وُجد</span><span class="pln">

        </span><span class="com"># إنشاء دالة بايثون "حقيقية" لنستفيد منها في ربط الوسطاء والتحقق من التواقيع</span><span class="pln">
        kw </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="str">'argdefs'</span><span class="pun">:</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">func_defaults</span><span class="pun">,</span><span class="pln">     </span><span class="com"># تمرير القيم الافتراضية</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> closure</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># تجهيز خلايا للإغلاق بشكل صوري عند الحاجة</span><span class="pln">
            kw</span><span class="pun">[</span><span class="str">'closure'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> tuple</span><span class="pun">(</span><span class="pln">make_cell</span><span class="pun">(</span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> _ </span><span class="kwd">in</span><span class="pln"> closure</span><span class="pun">)</span><span class="pln">

        self</span><span class="pun">.</span><span class="pln">_func </span><span class="pun">=</span><span class="pln"> types</span><span class="pun">.</span><span class="typ">FunctionType</span><span class="pun">(</span><span class="pln">code</span><span class="pun">,</span><span class="pln"> globs</span><span class="pun">,</span><span class="pln"> </span><span class="pun">**</span><span class="pln">kw</span><span class="pun">)</span><span class="pln">  </span><span class="com"># بناء كائن دالة بايثون من كائن الكود</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __call__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> </span><span class="pun">*</span><span class="pln">args</span><span class="pun">,</span><span class="pln"> </span><span class="pun">**</span><span class="pln">kwargs</span><span class="pun">):</span><span class="pln">
        </span><span class="str">"""عند استدعاء الدالة: ننشئ إطارًا جديدًا وننفّذه."""</span><span class="pln">
        callargs </span><span class="pun">=</span><span class="pln"> inspect</span><span class="pun">.</span><span class="pln">getcallargs</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">_func</span><span class="pun">,</span><span class="pln"> </span><span class="pun">*</span><span class="pln">args</span><span class="pun">,</span><span class="pln"> </span><span class="pun">**</span><span class="pln">kwargs</span><span class="pun">)</span><span class="pln">  </span><span class="com"># ربط القيم بأسماء الوسطاء حسب التوقيع</span><span class="pln">
        </span><span class="com"># استخدام هذا الربط لتمرير الوسطاء إلى الإطار الجديد</span><span class="pln">
        frame </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">_vm</span><span class="pun">.</span><span class="pln">make_frame</span><span class="pun">(</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">func_code</span><span class="pun">,</span><span class="pln"> callargs</span><span class="pun">,</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">func_globals</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">
        </span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">_vm</span><span class="pun">.</span><span class="pln">run_frame</span><span class="pun">(</span><span class="pln">frame</span><span class="pun">)</span><span class="pln">       </span><span class="com"># تنفيذ الإطار وإرجاع الناتج</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> make_cell</span><span class="pun">(</span><span class="pln">value</span><span class="pun">):</span><span class="pln">
    </span><span class="str">"""إنشاء خلية إغلاق حقيقية (لاستخراج مرجع خلية من إغلاق بايثون)."""</span><span class="pln">
    </span><span class="com"># إنشاء دالة داخل دالة لإجبار بايثون على صنع خلية إغلاق</span><span class="pln">
    fn </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">lambda</span><span class="pln"> x</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">lambda</span><span class="pun">:</span><span class="pln"> x</span><span class="pun">)(</span><span class="pln">value</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> fn</span><span class="pun">.</span><span class="pln">__closure__</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln">                  </span><span class="com"># استخراج الخلية نفسها</span></pre>

<p>
	نعود الآن إلى كائن <code>VirtualMachine</code>، ونضيف بعض التوابع المساعدة لمعالجة مكدس البيانات، حيث يشغّل البايت كود الذي يعالج المكدس دائمًا مكدسَ بيانات الإطار الحالي، مما يؤدي إلى جعل تنفيذ تعليمة <code>POP_TOP</code> و <code>LOAD_FAST</code> وجميع التعليمات الأخرى التي تتعامل مع المكدس أكثر قابلية للقراءة.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5407_17" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">VirtualMachine</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="pun">[...</span><span class="pln"> snip </span><span class="pun">...]</span><span class="pln">

    </span><span class="com"># عمليات على مكدّس القيم</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> top</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إرجاع آخر قيمة في المكدّس دون إزالتها</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">[-</span><span class="lit">1</span><span class="pun">]</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> pop</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إزالة آخر قيمة من المكدّس وإرجاعها</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> push</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> </span><span class="pun">*</span><span class="pln">vals</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إضافة قيمة أو أكثر إلى المكدّس</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">.</span><span class="pln">extend</span><span class="pun">(</span><span class="pln">vals</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> popn</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> n</span><span class="pun">):</span><span class="pln">
        </span><span class="str">"""إزالة عدد من القيم من المكدّس وإرجاعها كقائمة."""</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> n</span><span class="pun">:</span><span class="pln">
            ret </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">[-</span><span class="pln">n</span><span class="pun">:]</span><span class="pln">    </span><span class="com"># أخذ آخر n قيم</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">[-</span><span class="pln">n</span><span class="pun">:]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">     </span><span class="com"># حذفها من المكدّس</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> ret
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># إذا كان صفرًا، نرجع قائمة فارغة</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">[]</span></pre>

<p>
	نحتاج إلى تابعين آخرين قبل تشغيل الإطار وهما: التابع الأول <code>parse_byte_and_args</code> الذي يأخذ بايت كود، ويتحقق من احتوائه على وسطاء، ويحلل هذه الوسطاء عند وجودها، ويحدّث هذا التابع سمة الإطار <code>last_instruction</code> التي هي مرجع إلى آخر تعليمة مُنفَّذة. يبلغ طول التعليمة بايتًا واحدًا عند عدم وجود وسيط، وثلاثة بايتات إذا كان لها وسيط بحيث يمثل البايتان الأخيران الوسيط. يعتمد معنى الوسيط لكل تعليمة على نوعها، فمثلًا يمثل وسيط التعليمة <code>POP_JUMP_IF_FALSE</code> هدف القفز، ويمثل وسيط التعليمة <code>BUILD_LIST</code> عدد العناصر في القائمة، ويمثل وسيط التعليمة <code>LOAD_CONST</code> فهرسًا في قائمة الثوابت.
</p>

<p>
	تستخدم بعض التعليمات أعدادًا بسيطة كوسطاء لها، بينما يجب على الآلة الافتراضية إنجاز عمل إضافي لاكتشاف معنى الوسطاء بالنسبة لتعليمات أخرى. تعطي وحدة <code>dis</code> في المكتبة المعيارية شرحًا يوضح معنى الوسطاء، مما يحسن من شيفرتنا البرمجية، فمثلًا تخبرنا القائمة <code>dis.hasname</code> أن الوسطاء الخاصة بالتعليمات <code>LOAD_NAME</code> و <code>IMPORT_NAME</code> و <code>LOAD_GLOBAL</code> وتسعة تعليمات أخرى لها المعنى نفسه، حيث يمثل وسيط هذه التعليمات فهرسًا في قائمة الأسماء الموجودة في كائن الشيفرة.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_79" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">VirtualMachine</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="pun">[...</span><span class="pln"> snip </span><span class="pun">...]</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> parse_byte_and_args</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        f </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame
        opoffset </span><span class="pun">=</span><span class="pln"> f</span><span class="pun">.</span><span class="pln">last_instruction
        byteCode </span><span class="pun">=</span><span class="pln"> f</span><span class="pun">.</span><span class="pln">code_obj</span><span class="pun">.</span><span class="pln">co_code</span><span class="pun">[</span><span class="pln">opoffset</span><span class="pun">]</span><span class="pln">
        f</span><span class="pun">.</span><span class="pln">last_instruction </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
        byte_name </span><span class="pun">=</span><span class="pln"> dis</span><span class="pun">.</span><span class="pln">opname</span><span class="pun">[</span><span class="pln">byteCode</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> byteCode </span><span class="pun">&gt;=</span><span class="pln"> dis</span><span class="pun">.</span><span class="pln">HAVE_ARGUMENT</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># فهرس إلى البايت كود</span><span class="pln">
            arg </span><span class="pun">=</span><span class="pln"> f</span><span class="pun">.</span><span class="pln">code_obj</span><span class="pun">.</span><span class="pln">co_code</span><span class="pun">[</span><span class="pln">f</span><span class="pun">.</span><span class="pln">last_instruction</span><span class="pun">:</span><span class="pln">f</span><span class="pun">.</span><span class="pln">last_instruction</span><span class="pun">+</span><span class="lit">2</span><span class="pun">]</span><span class="pln">  
            f</span><span class="pun">.</span><span class="pln">last_instruction </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">2</span><span class="pln">   </span><span class="com"># تقدّم مؤشر التعليمة</span><span class="pln">
            arg_val </span><span class="pun">=</span><span class="pln"> arg</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="pun">(</span><span class="pln">arg</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="lit">256</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> byteCode </span><span class="kwd">in</span><span class="pln"> dis</span><span class="pun">.</span><span class="pln">hasconst</span><span class="pun">:</span><span class="pln">   </span><span class="com"># البحث عن ثابت</span><span class="pln">
                arg </span><span class="pun">=</span><span class="pln"> f</span><span class="pun">.</span><span class="pln">code_obj</span><span class="pun">.</span><span class="pln">co_consts</span><span class="pun">[</span><span class="pln">arg_val</span><span class="pun">]</span><span class="pln">
            </span><span class="kwd">elif</span><span class="pln"> byteCode </span><span class="kwd">in</span><span class="pln"> dis</span><span class="pun">.</span><span class="pln">hasname</span><span class="pun">:</span><span class="pln">  </span><span class="com"># البحث عن اسم</span><span class="pln">
                arg </span><span class="pun">=</span><span class="pln"> f</span><span class="pun">.</span><span class="pln">code_obj</span><span class="pun">.</span><span class="pln">co_names</span><span class="pun">[</span><span class="pln">arg_val</span><span class="pun">]</span><span class="pln">
            </span><span class="kwd">elif</span><span class="pln"> byteCode </span><span class="kwd">in</span><span class="pln"> dis</span><span class="pun">.</span><span class="pln">haslocal</span><span class="pun">:</span><span class="pln"> </span><span class="com"># البحث عن اسم محلي</span><span class="pln">
                arg </span><span class="pun">=</span><span class="pln"> f</span><span class="pun">.</span><span class="pln">code_obj</span><span class="pun">.</span><span class="pln">co_varnames</span><span class="pun">[</span><span class="pln">arg_val</span><span class="pun">]</span><span class="pln">
            </span><span class="kwd">elif</span><span class="pln"> byteCode </span><span class="kwd">in</span><span class="pln"> dis</span><span class="pun">.</span><span class="pln">hasjrel</span><span class="pun">:</span><span class="pln">  </span><span class="com"># حساب القفزة النسبية</span><span class="pln">
                arg </span><span class="pun">=</span><span class="pln"> f</span><span class="pun">.</span><span class="pln">last_instruction </span><span class="pun">+</span><span class="pln"> arg_val
            </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
                arg </span><span class="pun">=</span><span class="pln"> arg_val
            argument </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">arg</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            argument </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">

        </span><span class="kwd">return</span><span class="pln"> byte_name</span><span class="pun">,</span><span class="pln"> argument</span></pre>

<p>
	التابع الثاني هو <code>dispatch</code> الذي يبحث عن العمليات لتعليمة معينة وينفّذها. يُنفَّذ هذا التابع للإرسال في مفسّر CPython باستخدام تعليمة <code>switch</code> ضخمة بحجم 1500 سطر، ولكن ستكون الأمور أفضل بكثير عند كتابة الشيفرة البرمجية باستخدام بايثون، حيث سنعرّف تابعًا لكل اسم تعليمة ثم نستخدم الدالة <code>getattr</code> للبحث عنه، فمثلًا إذا كان اسم التعليمة <code>FOO_BAR</code> كما في مثالنا للمفسر البسيط السابق، فسيكون اسم التابع المقابل هو <code>byte_FOO_BAR</code>. يعيد كل تابع بايت كود القيمةَ <code>None</code> أو سلسلةً نصية اسمها <code>why</code> تمثل جزءًا إضافيًا من الحالة التي قد يحتاجها المفسّر في بعض الحالات. تُستخدَم هذه القيم المعادة لتوابع التعليمات كمؤشرات داخلية فقط لحالة المفسّر، لذا يجب عدم الخلط بينها وبين القيم المعادة من إطارات التنفيذ.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_81" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">VirtualMachine</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="pun">[...</span><span class="pln"> snip </span><span class="pun">...]</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> dispatch</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> byte_name</span><span class="pun">,</span><span class="pln"> argument</span><span class="pun">):</span><span class="pln">
        </span><span class="str">""" Dispatch by bytename to the corresponding methods.
        Exceptions are caught and set on the virtual machine."""</span><span class="pln">

        </span><span class="com"># نحتاج إلى تتبع سبب فك مكدس الكتل لاحقًا</span><span class="pln">
        why </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">
        </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
            bytecode_fn </span><span class="pun">=</span><span class="pln"> getattr</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> </span><span class="str">'byte_%s'</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> byte_name</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> bytecode_fn </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">:</span><span class="pln">
                </span><span class="kwd">if</span><span class="pln"> byte_name</span><span class="pun">.</span><span class="pln">startswith</span><span class="pun">(</span><span class="str">'UNARY_'</span><span class="pun">):</span><span class="pln">
                    self</span><span class="pun">.</span><span class="pln">unaryOperator</span><span class="pun">(</span><span class="pln">byte_name</span><span class="pun">[</span><span class="lit">6</span><span class="pun">:])</span><span class="pln">
                </span><span class="kwd">elif</span><span class="pln"> byte_name</span><span class="pun">.</span><span class="pln">startswith</span><span class="pun">(</span><span class="str">'BINARY_'</span><span class="pun">):</span><span class="pln">
                    self</span><span class="pun">.</span><span class="pln">binaryOperator</span><span class="pun">(</span><span class="pln">byte_name</span><span class="pun">[</span><span class="lit">7</span><span class="pun">:])</span><span class="pln">
                </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
                    </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">VirtualMachineError</span><span class="pun">(</span><span class="pln">
                        </span><span class="str">"unsupported bytecode type: %s"</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> byte_name
                    </span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
                why </span><span class="pun">=</span><span class="pln"> bytecode_fn</span><span class="pun">(*</span><span class="pln">argument</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">except</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># التعامل مع الاستثناءات التي واجهتنا أثناء تنفيذ العملية</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">last_exception </span><span class="pun">=</span><span class="pln"> sys</span><span class="pun">.</span><span class="pln">exc_info</span><span class="pun">()[:</span><span class="lit">2</span><span class="pun">]</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">None</span><span class="pun">,)</span><span class="pln">
            why </span><span class="pun">=</span><span class="pln"> </span><span class="str">'exception'</span><span class="pln">

        </span><span class="kwd">return</span><span class="pln"> why

    </span><span class="kwd">def</span><span class="pln"> run_frame</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> frame</span><span class="pun">):</span><span class="pln">
        </span><span class="str">"""Run a frame until it returns (somehow).
        Exceptions are raised, the return value is returned.
        """</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push_frame</span><span class="pun">(</span><span class="pln">frame</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">while</span><span class="pln"> </span><span class="kwd">True</span><span class="pun">:</span><span class="pln">
            byte_name</span><span class="pun">,</span><span class="pln"> arguments </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">parse_byte_and_args</span><span class="pun">()</span><span class="pln">

            why </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">dispatch</span><span class="pun">(</span><span class="pln">byte_name</span><span class="pun">,</span><span class="pln"> arguments</span><span class="pun">)</span><span class="pln">

            </span><span class="com"># التعامل مع إدارة الكتل التي نحتاجها</span><span class="pln">
            </span><span class="kwd">while</span><span class="pln"> why </span><span class="kwd">and</span><span class="pln"> frame</span><span class="pun">.</span><span class="pln">block_stack</span><span class="pun">:</span><span class="pln">
                why </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">manage_block_stack</span><span class="pun">(</span><span class="pln">why</span><span class="pun">)</span><span class="pln">

            </span><span class="kwd">if</span><span class="pln"> why</span><span class="pun">:</span><span class="pln">
                </span><span class="kwd">break</span><span class="pln">

        self</span><span class="pun">.</span><span class="pln">pop_frame</span><span class="pun">()</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> why </span><span class="pun">==</span><span class="pln"> </span><span class="str">'exception'</span><span class="pun">:</span><span class="pln">
            exc</span><span class="pun">,</span><span class="pln"> val</span><span class="pun">,</span><span class="pln"> tb </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">last_exception
            e </span><span class="pun">=</span><span class="pln"> exc</span><span class="pun">(</span><span class="pln">val</span><span class="pun">)</span><span class="pln">
            e</span><span class="pun">.</span><span class="pln">__traceback__ </span><span class="pun">=</span><span class="pln"> tb
            </span><span class="kwd">raise</span><span class="pln"> e

        </span><span class="kwd">return</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">return_value</span></pre>

<h3 id="-block">
	الصنف Block
</h3>

<p>
	سنوضح الآن الكتل بإيجاز قبل تنفيذ التوابع لكل تعليمة بايت كود، حيث تُستخدَم الكتلة لأنواع معينة من التحكم في التدفق وخاصة معالجة الاستثناءات والتكرار ضمن حلقة، إذ تُعَد الكتلة مسؤولة عن التأكد من أن مكدس البيانات في الحالة المناسبة عند انتهاء العملية، فمثلًا يبقى كائن تكرار خاص في المكدس أثناء تشغيل الحلقة، ولكنه يخرج عند انتهائها، لذا يجب على المفسر تتبع ما إذا كانت الحلقة مستمرة أم أنها انتهت.
</p>

<p>
	يتتبع المفسر هذه المعلومة الإضافية من خلال وضع رايةٍ Flag للإشارة إلى حالتها، حيث تكون هذه الراية متغيرًا بالاسم <code>why</code>، والذي يمكن أن تكون قيمته <code>None</code> أو إحدى السلاسل النصية <code>"continue"</code> أو <code>"break"</code> أو <code>"exception"</code> أو <code>"return"</code>، إذ تشير هذه القيم إلى كيفية التعامل مع مكدس الكتل ومكدس البيانات، حيث إذا كان الجزء العلوي من مكدس الكتل في مثالنا هو كتلة <code>loop</code> وكانت قيمة <code>why</code> هي <code>continue</code>، فيجب أن يبقى كائن التكرار في مكدس البيانات، ولكن إذا كانت قيمة <code>why</code> هي <code>break</code>، فيجب إخراجه من هذا المكدس.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5498_9" style=""><span class="typ">Block</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> collections</span><span class="pun">.</span><span class="pln">namedtuple</span><span class="pun">(</span><span class="str">"Block"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"type, handler, stack_height"</span><span class="pun">)</span><span class="pln">  </span><span class="com"># بنية خفيفة لتمثيل كتلة تنفيذ (نوعها + وجهة القفز + ارتفاع المكدّس)</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">VirtualMachine</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="pun">[...</span><span class="pln"> snip </span><span class="pun">...]</span><span class="pln">

    </span><span class="com"># معالجة مكدّس الكتل</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> push_block</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> b_type</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">=</span><span class="kwd">None</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># حفظ ارتفاع مكدّس القيم عند دخول الكتلة لنستطيع تنظيفه عند الخروج</span><span class="pln">
        stack_height </span><span class="pun">=</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">block_stack</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="typ">Block</span><span class="pun">(</span><span class="pln">b_type</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">,</span><span class="pln"> stack_height</span><span class="pun">))</span><span class="pln">  </span><span class="com"># دفع كتلة جديدة إلى مكدّس الكتل</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> pop_block</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إزالة أحدث كتلة من مكدّس الكتل</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">block_stack</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> unwind_block</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> block</span><span class="pun">):</span><span class="pln">
        </span><span class="str">"""تنظيف القيم من مكدّس البيانات بما يناسب الكتلة."""</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> block</span><span class="pun">.</span><span class="pln">type </span><span class="pun">==</span><span class="pln"> </span><span class="str">'except-handler'</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># في معالجة الاستثناء: تُحجز ثلاث قيم عادة (نوع/قيمة/تتبّع)</span><span class="pln">
            offset </span><span class="pun">=</span><span class="pln"> </span><span class="lit">3</span><span class="pln">
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            offset </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pln">

        </span><span class="com"># حذف أي قيم زائدة في مكدّس القيم للوصول لارتفاع الكتلة وقت الدخول</span><span class="pln">
        </span><span class="kwd">while</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> block</span><span class="pun">.</span><span class="pln">level </span><span class="pun">+</span><span class="pln"> offset</span><span class="pun">:</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> block</span><span class="pun">.</span><span class="pln">type </span><span class="pun">==</span><span class="pln"> </span><span class="str">'except-handler'</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># استرجاع معلومات الاستثناء لتخزينها كآخر استثناء</span><span class="pln">
            traceback</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">,</span><span class="pln"> exctype </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">popn</span><span class="pun">(</span><span class="lit">3</span><span class="pun">)</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">last_exception </span><span class="pun">=</span><span class="pln"> exctype</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">,</span><span class="pln"> traceback

    </span><span class="kwd">def</span><span class="pln"> manage_block_stack</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> why</span><span class="pun">):</span><span class="pln">
        </span><span class="str">"""تقرير ما يجب فعله عند الخروج من كتلة بسبب سبب معيّن (كسر/متابعة/استثناء/إرجاع)."""</span><span class="pln">
        frame </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame
        block </span><span class="pun">=</span><span class="pln"> frame</span><span class="pun">.</span><span class="pln">block_stack</span><span class="pun">[-</span><span class="lit">1</span><span class="pun">]</span><span class="pln">  </span><span class="com"># النظر إلى أحدث كتلة دون حذفها</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> block</span><span class="pun">.</span><span class="pln">type </span><span class="pun">==</span><span class="pln"> </span><span class="str">'loop'</span><span class="pln"> </span><span class="kwd">and</span><span class="pln"> why </span><span class="pun">==</span><span class="pln"> </span><span class="str">'continue'</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># متابعة الحلقة: قفز إلى بداية الحلقة (العنوان مخزّن في return_value هنا)</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">jump</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">return_value</span><span class="pun">)</span><span class="pln">
            why </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> why

        </span><span class="com"># الخروج من الكتلة: حذفها ثم تنظيف مكدّس القيم المرتبط بها</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">pop_block</span><span class="pun">()</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">unwind_block</span><span class="pun">(</span><span class="pln">block</span><span class="pun">)</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> block</span><span class="pun">.</span><span class="pln">type </span><span class="pun">==</span><span class="pln"> </span><span class="str">'loop'</span><span class="pln"> </span><span class="kwd">and</span><span class="pln"> why </span><span class="pun">==</span><span class="pln"> </span><span class="str">'break'</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># كسر الحلقة: القفز إلى نهاية الحلقة (المعالج هو وجهة الخروج)</span><span class="pln">
            why </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">jump</span><span class="pun">(</span><span class="pln">block</span><span class="pun">.</span><span class="pln">handler</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> why

        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">block</span><span class="pun">.</span><span class="pln">type </span><span class="kwd">in</span><span class="pln"> </span><span class="pun">[</span><span class="str">'setup-except'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'finally'</span><span class="pun">]</span><span class="pln"> </span><span class="kwd">and</span><span class="pln"> why </span><span class="pun">==</span><span class="pln"> </span><span class="str">'exception'</span><span class="pun">):</span><span class="pln">
            </span><span class="com"># عند وقوع استثناء: تجهيز كتلة معالجة الاستثناء ودفع معلوماته للمكدّس ثم القفز لمعالج الاستثناء</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">push_block</span><span class="pun">(</span><span class="str">'except-handler'</span><span class="pun">)</span><span class="pln">
            exctype</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">,</span><span class="pln"> tb </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">last_exception
            self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">tb</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">,</span><span class="pln"> exctype</span><span class="pun">)</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">tb</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">,</span><span class="pln"> exctype</span><span class="pun">)</span><span class="pln">  </span><span class="com"># تُدفع مرتين لتوافق تهيئة بايثون في هذا التبسيط</span><span class="pln">
            why </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">jump</span><span class="pun">(</span><span class="pln">block</span><span class="pun">.</span><span class="pln">handler</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> why

        </span><span class="kwd">elif</span><span class="pln"> block</span><span class="pun">.</span><span class="pln">type </span><span class="pun">==</span><span class="pln"> </span><span class="str">'finally'</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># عند finally: نمرّر سبب الخروج، وأحيانًا قيمة الإرجاع/المتابعة</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> why </span><span class="kwd">in</span><span class="pln"> </span><span class="pun">(</span><span class="str">'return'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'continue'</span><span class="pun">):</span><span class="pln">
                self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">return_value</span><span class="pun">)</span><span class="pln">

            self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">why</span><span class="pun">)</span><span class="pln">  </span><span class="com"># حفظ سبب الخروج ليُستخدم داخل finally</span><span class="pln">

            why </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">jump</span><span class="pun">(</span><span class="pln">block</span><span class="pun">.</span><span class="pln">handler</span><span class="pun">)</span><span class="pln">  </span><span class="com"># الانتقال إلى بداية finally</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> why

        </span><span class="com"># إن لم تُعالج الحالة هنا، نُعيد السبب كما هو</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> why</span></pre>

<h2 id="-">
	التعليمات
</h2>

<p>
	نحتاج الآن إلى تنفيذ التوابع الخاصة بالتعليمات، والذي يُعَد جزءًا مملًا من بناء المفسر، لذا إليك بعضًا منها، والتي تتضمن تعليمات كافية لتنفيذ الشيفرة البرمجية الموضحة سابقًا في هذا المقال، ولكن التنفيذ الكامل متاح على <a href="https://github.com/nedbat/byterun" rel="external nofollow">GitHub</a>.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5498_7" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">VirtualMachine</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="pun">[...</span><span class="pln"> snip </span><span class="pun">...]</span><span class="pln">

    </span><span class="com">## معالجة المكدس</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_LOAD_CONST</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> const</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># وضع ثابت (قيمة جاهزة) في المكدّس</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">const</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_POP_TOP</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># حذف أعلى قيمة من المكدّس دون استخدامها</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">

    </span><span class="com">## الأسماء</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> byte_LOAD_NAME</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># تحميل اسم: نبحث عنه محليًا ثم عالميًا ثم ضمن الأسماء المضمنة</span><span class="pln">
        frame </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame
        </span><span class="kwd">if</span><span class="pln"> name </span><span class="kwd">in</span><span class="pln"> frame</span><span class="pun">.</span><span class="pln">f_locals</span><span class="pun">:</span><span class="pln">
            val </span><span class="pun">=</span><span class="pln"> frame</span><span class="pun">.</span><span class="pln">f_locals</span><span class="pun">[</span><span class="pln">name</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">elif</span><span class="pln"> name </span><span class="kwd">in</span><span class="pln"> frame</span><span class="pun">.</span><span class="pln">f_globals</span><span class="pun">:</span><span class="pln">
            val </span><span class="pun">=</span><span class="pln"> frame</span><span class="pun">.</span><span class="pln">f_globals</span><span class="pun">[</span><span class="pln">name</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">elif</span><span class="pln"> name </span><span class="kwd">in</span><span class="pln"> frame</span><span class="pun">.</span><span class="pln">f_builtins</span><span class="pun">:</span><span class="pln">
            val </span><span class="pun">=</span><span class="pln"> frame</span><span class="pun">.</span><span class="pln">f_builtins</span><span class="pun">[</span><span class="pln">name</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># إذا لم يُعثر عليه في أي مكان نرمي خطأ اسم غير معرّف</span><span class="pln">
            </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">NameError</span><span class="pun">(</span><span class="str">"name '%s' is not defined"</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> name</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">val</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_STORE_NAME</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># تخزين قيمة في اسم محلي</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">f_locals</span><span class="pun">[</span><span class="pln">name</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_LOAD_FAST</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># تحميل متغير محلي "سريع" (يجب أن يكون قد أُسنِد مسبقًا)</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> name </span><span class="kwd">in</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">f_locals</span><span class="pun">:</span><span class="pln">
            val </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">f_locals</span><span class="pun">[</span><span class="pln">name</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># استخدام متغير محلي قبل إسناده</span><span class="pln">
            </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">UnboundLocalError</span><span class="pun">(</span><span class="pln">
                </span><span class="str">"local variable '%s' referenced before assignment"</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> name
            </span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">val</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_STORE_FAST</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># تخزين قيمة في متغير محلي "سريع"</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">f_locals</span><span class="pun">[</span><span class="pln">name</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_LOAD_GLOBAL</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># تحميل اسم من المجال العام أو من الأسماء المضمنة</span><span class="pln">
        f </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame
        </span><span class="kwd">if</span><span class="pln"> name </span><span class="kwd">in</span><span class="pln"> f</span><span class="pun">.</span><span class="pln">f_globals</span><span class="pun">:</span><span class="pln">
            val </span><span class="pun">=</span><span class="pln"> f</span><span class="pun">.</span><span class="pln">f_globals</span><span class="pun">[</span><span class="pln">name</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">elif</span><span class="pln"> name </span><span class="kwd">in</span><span class="pln"> f</span><span class="pun">.</span><span class="pln">f_builtins</span><span class="pun">:</span><span class="pln">
            val </span><span class="pun">=</span><span class="pln"> f</span><span class="pun">.</span><span class="pln">f_builtins</span><span class="pun">[</span><span class="pln">name</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">NameError</span><span class="pun">(</span><span class="str">"global name '%s' is not defined"</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> name</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">val</span><span class="pun">)</span><span class="pln">

    </span><span class="com">## المعاملات</span><span class="pln">

    BINARY_OPERATORS </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">'POWER'</span><span class="pun">:</span><span class="pln">    pow</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'MULTIPLY'</span><span class="pun">:</span><span class="pln"> operator</span><span class="pun">.</span><span class="pln">mul</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'FLOOR_DIVIDE'</span><span class="pun">:</span><span class="pln"> operator</span><span class="pun">.</span><span class="pln">floordiv</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'TRUE_DIVIDE'</span><span class="pun">:</span><span class="pln">  operator</span><span class="pun">.</span><span class="pln">truediv</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'MODULO'</span><span class="pun">:</span><span class="pln">   operator</span><span class="pun">.</span><span class="pln">mod</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'ADD'</span><span class="pun">:</span><span class="pln">      operator</span><span class="pun">.</span><span class="pln">add</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'SUBTRACT'</span><span class="pun">:</span><span class="pln"> operator</span><span class="pun">.</span><span class="pln">sub</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'SUBSCR'</span><span class="pun">:</span><span class="pln">   operator</span><span class="pun">.</span><span class="pln">getitem</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'LSHIFT'</span><span class="pun">:</span><span class="pln">   operator</span><span class="pun">.</span><span class="pln">lshift</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'RSHIFT'</span><span class="pun">:</span><span class="pln">   operator</span><span class="pun">.</span><span class="pln">rshift</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'AND'</span><span class="pun">:</span><span class="pln">      operator</span><span class="pun">.</span><span class="pln">and_</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'XOR'</span><span class="pun">:</span><span class="pln">      operator</span><span class="pun">.</span><span class="pln">xor</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'OR'</span><span class="pun">:</span><span class="pln">       operator</span><span class="pun">.</span><span class="pln">or_</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">  </span><span class="com"># جدول يربط اسم العملية بدالة تنفيذها</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> binaryOperator</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> op</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># أخذ قيمتين من المكدّس وتنفيذ العملية الثنائية ثم إعادة الناتج للمكدّس</span><span class="pln">
        x</span><span class="pun">,</span><span class="pln"> y </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">popn</span><span class="pun">(</span><span class="lit">2</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">BINARY_OPERATORS</span><span class="pun">[</span><span class="pln">op</span><span class="pun">](</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">))</span><span class="pln">

    COMPARE_OPERATORS </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
        operator</span><span class="pun">.</span><span class="pln">lt</span><span class="pun">,</span><span class="pln">
        operator</span><span class="pun">.</span><span class="pln">le</span><span class="pun">,</span><span class="pln">
        operator</span><span class="pun">.</span><span class="pln">eq</span><span class="pun">,</span><span class="pln">
        operator</span><span class="pun">.</span><span class="pln">ne</span><span class="pun">,</span><span class="pln">
        operator</span><span class="pun">.</span><span class="pln">gt</span><span class="pun">,</span><span class="pln">
        operator</span><span class="pun">.</span><span class="pln">ge</span><span class="pun">,</span><span class="pln">
        </span><span class="kwd">lambda</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">:</span><span class="pln"> x </span><span class="kwd">in</span><span class="pln"> y</span><span class="pun">,</span><span class="pln">
        </span><span class="kwd">lambda</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">:</span><span class="pln"> x </span><span class="kwd">not</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> y</span><span class="pun">,</span><span class="pln">
        </span><span class="kwd">lambda</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">:</span><span class="pln"> x </span><span class="kwd">is</span><span class="pln"> y</span><span class="pun">,</span><span class="pln">
        </span><span class="kwd">lambda</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">:</span><span class="pln"> x </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> y</span><span class="pun">,</span><span class="pln">
        </span><span class="kwd">lambda</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">:</span><span class="pln"> issubclass</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Exception</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">and</span><span class="pln"> issubclass</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">),</span><span class="pln">
    </span><span class="pun">]</span><span class="pln">  </span><span class="com"># جدول عمليات المقارنة حسب رقم العملية</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_COMPARE_OP</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> opnum</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># مقارنة قيمتين من المكدّس حسب رقم عملية المقارنة</span><span class="pln">
        x</span><span class="pun">,</span><span class="pln"> y </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">popn</span><span class="pun">(</span><span class="lit">2</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">COMPARE_OPERATORS</span><span class="pun">[</span><span class="pln">opnum</span><span class="pun">](</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">))</span><span class="pln">

    </span><span class="com">## السمات والفهرسة</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_LOAD_ATTR</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> attr</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># قراءة سمة من كائن (مثل obj.attr)</span><span class="pln">
        obj </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        val </span><span class="pun">=</span><span class="pln"> getattr</span><span class="pun">(</span><span class="pln">obj</span><span class="pun">,</span><span class="pln"> attr</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">val</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_STORE_ATTR</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># تعيين سمة لكائن (مثل obj.attr = val)</span><span class="pln">
        val</span><span class="pun">,</span><span class="pln"> obj </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">popn</span><span class="pun">(</span><span class="lit">2</span><span class="pun">)</span><span class="pln">
        setattr</span><span class="pun">(</span><span class="pln">obj</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">,</span><span class="pln"> val</span><span class="pun">)</span><span class="pln">

    </span><span class="com">## البناء</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_BUILD_LIST</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> count</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># بناء قائمة من عدد عناصر مأخوذة من المكدّس</span><span class="pln">
        elts </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">popn</span><span class="pun">(</span><span class="pln">count</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">elts</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_BUILD_MAP</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> size</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إنشاء قاموس فارغ (الحجم هنا غير مستخدم في هذا التبسيط)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">({})</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_STORE_MAP</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إضافة (مفتاح، قيمة) إلى قاموس موجود ثم إرجاعه للمكدّس</span><span class="pln">
        the_map</span><span class="pun">,</span><span class="pln"> val</span><span class="pun">,</span><span class="pln"> key </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">popn</span><span class="pun">(</span><span class="lit">3</span><span class="pun">)</span><span class="pln">
        the_map</span><span class="pun">[</span><span class="pln">key</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> val
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">the_map</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_LIST_APPEND</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> count</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إضافة عنصر إلى قائمة موجودة أعمق في المكدّس</span><span class="pln">
        val </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        the_list </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">[-</span><span class="pln">count</span><span class="pun">]</span><span class="pln">  </span><span class="com"># الوصول للقائمة دون سحبها</span><span class="pln">
        the_list</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">val</span><span class="pun">)</span><span class="pln">

    </span><span class="com">## القفزات</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_JUMP_FORWARD</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> jump</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># الانتقال إلى تعليمة أخرى للأمام</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">jump</span><span class="pun">(</span><span class="pln">jump</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_JUMP_ABSOLUTE</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> jump</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># الانتقال إلى تعليمة برقم محدد</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">jump</span><span class="pun">(</span><span class="pln">jump</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_POP_JUMP_IF_TRUE</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> jump</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># سحب قيمة شرطية: إذا كانت صحيحة نقفز</span><span class="pln">
        val </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> val</span><span class="pun">:</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">jump</span><span class="pun">(</span><span class="pln">jump</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_POP_JUMP_IF_FALSE</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> jump</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># سحب قيمة شرطية: إذا كانت خاطئة نقفز</span><span class="pln">
        val </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> val</span><span class="pun">:</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">jump</span><span class="pun">(</span><span class="pln">jump</span><span class="pun">)</span><span class="pln">

    </span><span class="com">## الكتل</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_SETUP_LOOP</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> dest</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># تسجيل كتلة حلقة ومعرفة مكان الخروج منها</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push_block</span><span class="pun">(</span><span class="str">'loop'</span><span class="pun">,</span><span class="pln"> dest</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_GET_ITER</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># تحويل قيمة إلى مُكرّر ووضعه في المكدّس</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">iter</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()))</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_FOR_ITER</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> jump</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># جلب العنصر التالي من المُكرّر، أو القفز عند انتهاء التكرار</span><span class="pln">
        iterobj </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">top</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
            v </span><span class="pun">=</span><span class="pln"> next</span><span class="pun">(</span><span class="pln">iterobj</span><span class="pun">)</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">v</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">StopIteration</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># عند انتهاء التكرار: إزالة المُكرّر والقفز للخروج</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">jump</span><span class="pun">(</span><span class="pln">jump</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_BREAK_LOOP</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إشارة إلى رغبة في كسر الحلقة</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'break'</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_POP_BLOCK</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إنهاء أحدث كتلة مسجّلة</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">pop_block</span><span class="pun">()</span><span class="pln">

    </span><span class="com">## الدوال</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_MAKE_FUNCTION</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> argc</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إنشاء دالة من كائن الكود + الاسم + القيم الافتراضية</span><span class="pln">
        name </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        code </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        defaults </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">popn</span><span class="pun">(</span><span class="pln">argc</span><span class="pun">)</span><span class="pln">
        globs </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">f_globals
        fn </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Function</span><span class="pun">(</span><span class="pln">name</span><span class="pun">,</span><span class="pln"> code</span><span class="pun">,</span><span class="pln"> globs</span><span class="pun">,</span><span class="pln"> defaults</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">,</span><span class="pln"> self</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">fn</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_CALL_FUNCTION</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> arg</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># استدعاء دالة بعد تجهيز معاملات الموضع فقط (دون معاملات مسماة هنا)</span><span class="pln">
        lenKw</span><span class="pun">,</span><span class="pln"> lenPos </span><span class="pun">=</span><span class="pln"> divmod</span><span class="pun">(</span><span class="pln">arg</span><span class="pun">,</span><span class="pln"> </span><span class="lit">256</span><span class="pun">)</span><span class="pln">  </span><span class="com"># المعاملات المسماة غير مدعومة هنا</span><span class="pln">
        posargs </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">popn</span><span class="pun">(</span><span class="pln">lenPos</span><span class="pun">)</span><span class="pln">

        func </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        retval </span><span class="pun">=</span><span class="pln"> func</span><span class="pun">(*</span><span class="pln">posargs</span><span class="pun">)</span><span class="pln">  </span><span class="com"># تنفيذ الدالة فعليًا</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">retval</span><span class="pun">)</span><span class="pln">        </span><span class="com"># وضع الناتج في المكدّس</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_RETURN_VALUE</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إنهاء الدالة الحالية وإرجاع قيمة</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">return_value </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"return"</span></pre>

<h2 id="-dynamic-typing">
	تحديد الأنواع الديناميكي Dynamic Typing
</h2>

<p>
	تُعَد لغة بايثون لغةً ديناميكية، إذ يمكن تحديد الأنواع ديناميكيًا، فأحد المعاني التي تعنيها كلمة ديناميكية في هذا السياق هو إنجاز الكثير من العمل في وقت التشغيل. رأينا سابقًا أن مصرِّف بايثون لا يمتلك كثيرًا من المعلومات حول ما تفعله الشيفرة البرمجية، فمثلًا إذا كان لدينا الدالة <code>mod</code> الآتية، حيث تأخذ هذه الدالة وسيطين وتعيد باقي قسمة قيمة الوسيط الأول على قيمة الوسيط الثاني، حيث نرى في البايت كود تحميل المتغيرين <code>a</code> و <code>b</code>، ثم يجري البايت كود <code>BINARY_MODULO</code> عملية إيجاد باقي القسمة.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_87" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">def</span><span class="pln"> mod</span><span class="pun">(</span><span class="pln">a</span><span class="pun">,</span><span class="pln"> b</span><span class="pun">):</span><span class="pln">
</span><span class="pun">...</span><span class="pln">    </span><span class="kwd">return</span><span class="pln"> a </span><span class="pun">%</span><span class="pln"> b
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> dis</span><span class="pun">.</span><span class="pln">dis</span><span class="pun">(</span><span class="pln">mod</span><span class="pun">)</span><span class="pln">
  </span><span class="lit">2</span><span class="pln">           </span><span class="lit">0</span><span class="pln"> LOAD_FAST                </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="pln">a</span><span class="pun">)</span><span class="pln">
              </span><span class="lit">3</span><span class="pln"> LOAD_FAST                </span><span class="lit">1</span><span class="pln"> </span><span class="pun">(</span><span class="pln">b</span><span class="pun">)</span><span class="pln">
              </span><span class="lit">6</span><span class="pln"> BINARY_MODULO
              </span><span class="lit">7</span><span class="pln"> RETURN_VALUE
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> mod</span><span class="pun">(</span><span class="lit">19</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">)</span><span class="pln">
</span><span class="lit">4</span></pre>

<p>
	تعطي عملية حساب ‎<code>19 % 5</code>‎ النتيجة 4، ولكن قد نتساءل عمّا يحدث عند استدعائها مع وسطاء مختلفة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_89" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> mod</span><span class="pun">(</span><span class="str">"by%sde"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"teco"</span><span class="pun">)</span><span class="pln">
</span><span class="str">'bytecode'</span></pre>

<p>
	هذا شيء غريب، ولكنه يشبه ما يحدث في سياق مختلف كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_91" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"by%sde"</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> </span><span class="str">"teco"</span><span class="pun">)</span><span class="pln">
bytecode</span></pre>

<p>
	يؤدي استخدام الرمز <code>%</code> في تنسيق سلسلة نصية لطباعتها إلى استدعاء التعليمة <code>BINARY_MODULO</code> التي تعطي ناتج القسمة لأعلى قيمتين في المكدس عند تنفيذ التعليمة بغض النظر عمّا إذا كانت سلاسل نصية أو أعدادًا صحيحة أو نسخًا من صنف عرّفناه بأنفسنا، حيث يولّد البايت كود عند تصريف الدالة أو عند تعريفها ويُستخدَم البايت كود نفسه مع أنواع مختلفة من الوسطاء.
</p>

<p>
	يجهل مصرّف بايثون التأثير الذي يحدثه البايت كود، لذا فالأمر متروك للمفسر لتحديد نوع الكائن الذي تعمل عليه التعليمة <code>BINARY_MODULO</code> وإجراء الأمر الصحيح المناسب لهذا النوع، وهذا هو سبب وصف بايثون بأنها تحدد الأنواع ديناميكيًا، إذ لن تعرف أنواع وسطاء الدالة حتى تشغيلها، بينما يخبر المبرمج المُصرِّف مسبقًا بنوع الوسطاء أو يكتشفها بنفسه في اللغات ذات الأنواع الثابتة.
</p>

<p>
	يُعَد جهل المصرف من التحديات التي تواجه تحسين أو تحليل بايثون بطريقة ثابتة، إذ لا نعرف ما الذي ستفعله التعليمات عند النظر إلى البايت كود فقط دون تشغيل الشيفرة البرمجية فعليًا، ولكن يمكننا تعريف صنف ينفذ التابع <code>__mod__</code>، وسيستدعي بايثون هذا التابع إذا استخدمنا <code>%</code> مع الكائنات الخاصة بنا، وبالتالي يمكن للتعليمة <code>BINARY_MODULO</code> تشغيل أيّ شيفرة برمجية نريدها.
</p>

<p>
	سنعرف أن العملية التالية <code>a % b</code> بدون فائدة بنظرة واحدة إليها:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_93" style=""><span class="kwd">def</span><span class="pln"> mod</span><span class="pun">(</span><span class="pln">a</span><span class="pun">,</span><span class="pln">b</span><span class="pun">):</span><span class="pln">
    a </span><span class="pun">%</span><span class="pln"> b
    </span><span class="kwd">return</span><span class="pln"> a </span><span class="pun">%</span><span class="pln">b</span></pre>

<p>
	لا يمكن لتحليل هذه الشيفرة البرمجية الثابت -الذي يمكن تطبيقه دون تشغيلها- التأكد من أن التعليمة <code>a % b</code> لا تفعل شيئًا، ولكن قد يؤدي استدعاء التابع <code>__mod__</code> مع <code>%</code> إلى الكتابة في ملف أو التفاعل مع جزء آخر من برنامجنا أو فعل أي شيء آخر ممكن في بايثون، إذ من الصعب تحسين دالة عندما لا نعرف ما تفعله. وضع كل من Russell Power و Alex Rubinsteyn في ورقتهما العلمية التي تدرس مقدار السرعة التي يمكن الوصول إليها لتفسير بايثون ملاحظةً مفادها بأنه: يجب التعامل مع كل تعليمة على أنها <code>INVOKE_ARBITRARY_METHOD</code> مع عدم وجود معلومات عن النوع.
</p>

<h2 id="-">
	الخلاصة
</h2>

<p>
	Byterun هو مفسر بايثون أبسط وأسهل في الفهم من CPython، حيث يكرر هذا المفسر تفاصيل CPython البنيوية الرئيسية، فهو مفسر قائم على المكدس ويعمل على مجموعات من التعليمات تسمى البايت كود، حيث يتنقل أو يقفز عبر هذه التعليمات، ويدفعها إلى مكدس من البيانات ويسحبها منه، وينشئ المفسر الإطارات ويدمرها ويقفز بينها عند استدعاء الدوال والمولدات والعودة منها. يتشارك Byterun في قيود المفسر الحقيقي، إذ يجب أن يعمل المفسر في وقت التشغيل لتحديد السلوك الصحيح للبرنامج لأن بايثون يستخدم تحديد الأنواع الديناميكي.
</p>

<p>
	يُنصَح بتفكيك برامجنا الخاصة وتشغيلها باستخدام Byterun، ولكن سنعثر على تعليمات لا تنفذها هذه النسخة القصيرة من Byterun، لذا يمكن العثور على التنفيذ الكامل على <a href="https://github.com/nedbat/byterun" rel="external nofollow">Github</a> أو من خلال القراءة الدقيقة لملف <code>ceval.c</code> الخاص بمفسر CPython الحقيقي.
</p>

<p>
	<strong>ملاحظة</strong>: يعتمد هذا المقال على البايت كود الناتج من إصدار Python 3.5 أو الإصدارات الأقدم، حيث كانت هناك بعض التغييرات على مواصفات البايت كود في الإصدار Python 3.6.
</p>

<p>
	ترجمة -وبتصرّف- للمقال <a href="https://aosabook.org/en/500L/a-python-interpreter-written-in-python.html" rel="external nofollow">A Python Interpreter Written in Python</a> لصاحبته Allison Kaptur.
</p>
]]></description><guid isPermaLink="false">2601</guid><pubDate>Sun, 01 Mar 2026 09:19:00 +0000</pubDate></item><item><title>&#x62F;&#x644;&#x64A;&#x644; &#x627;&#x644;&#x645;&#x628;&#x62A;&#x62F;&#x626; &#x644;&#x62F;&#x631;&#x627;&#x633;&#x629; &#x639;&#x644;&#x648;&#x645; &#x627;&#x644;&#x62D;&#x627;&#x633;&#x648;&#x628;</title><link>https://academy.hsoub.com/programming/general/%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6-%D9%84%D8%AF%D8%B1%D8%A7%D8%B3%D8%A9-%D8%B9%D9%84%D9%88%D9%85-%D8%A7%D9%84%D8%AD%D8%A7%D8%B3%D9%88%D8%A8-r2600/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2026_02/computerscience.png.db8210ba8599efcc262da75ca0e715dc.png" /></p>
<p>
	سنتحدث في هذا الدليل حول دراسة علوم الحاسوب كطالب مبتدئ سواء كنت تريد البدء في دراستها أو تفكر في نيل شهادة جامعية فيها أو حتى بدأت رحلتك الدراسية بالفعل، وهو خلاصة خبرة أكثر من 40 عامًا لكاتبه برايان "بيج جورجينسن" هول Brian “Beej Jorgensen” Hall، حيث تعلمها ذاتيًا قبل الجامعة ثم أضاف لها عشرين سنة من العمل في السوق، ثم 8 أعوام أخرى في تدريسها للطلبة، وقد بنى الشرح المذكور هنا على الملاحظات التي جمعها من مراقبته للطلاب أثناء ارتكابهم لأخطاء شتى في تعلمهم لتتجنب أنت الوقوع فيها.
</p>

<h2 id="-">
	الجمهور المستهدف
</h2>

<p>
	لقد أعُد هذا الدليل خصيصًا لطلبة الجامعات الذين بدؤوا تعلم البرمجة حديثًا لكنه يصلح أيضًا لطلبة المراحل الثانوية أو أي شخص يرغب في تعلم البرمجة عمومًا.
</p>

<h2 id="-">
	الهدف الرئيسي
</h2>

<p>
	يتعلم الطلاب في الجامعات كيفية البرمجة باللغات المختلفة مثل Flutter و React و Rust وجافاسكربت و C++ و C وباسكال و LISP وفورتران FORTRAN وكوبول COBOL وغيرها من اللغات والتقنيات البرمجية، لكن هنا تبرز المشاكل التالية:
</p>

<ul>
	<li>
		هناك العديد من التقنيات التي يصعب تغطيتها في أربع سنوات.
	</li>
	<li>
		كل هذه اللغات ستُستبدل عاجلًا أم آجلًا وتحل محلها لغات وتقنيات أحدث، كما في حالة لغة كوبول المشار إليها أعلاه.
	</li>
</ul>

<p>
	إن التصرف المنطقي لك كطالب في هذه الحالة هو صرف وقتك وجهدك في تعلم كيفية حل المشاكل البرمجية سواء كنت تعرضت لتلك المشاكل من قبل أو لا، بدلًا من السعي خلف تعلم التقنيات التي لا حصر لها إذ لن تستطيع تعلمها جميعًا، فليس المهم أن تتعلم كيف تصبح مطور تطبيقات آيفون أو أندرويد أو مطور لغة Go إذ أن هذا تحصيل حاصل إذا تعلمت الهدف الأساسي وهو "حل المشاكل البرمجية".
</p>

<p>
	وصحيح أنك لن تتعلم لغة Go في الجامعة لكن ستتعلم كيفية تعلمها بنفسك، فهذه المهارة -أي التعلم الذاتي- مطلوبة في مجال تطوير البرمجيات إذ يستبعد أن تكون وظيفتك الأولى هي استخدام تقنيات استخدمتها في الجامعة من قبل، بل في الواقع قد يستغرب بعض الخريجين الجدد حين يرون أن التقنيات التي استخدموها في الجامعة لا تُستخدم في الوظيفة الأولى لهم.
</p>

<p>
	لماذا إذًا ندرس أربع سنوات لتعلم نظم التشغيل ولغة التجميع Assembly Language وتحليل الخوارزميات وغيرها؟ إذا راجعت مقررات هذه السنوات سترى أنها تدور حول كيفية حل المشاكل البرمجية، وليس تلك التقنيات بذاتها، وذلك لأن الجامعات تريد تعليمك كيفية حل المشكلات البرمجية من خلال التدرب المستمر لصقل مهاراتك، لأنك سترى في مقابلات العمل أو في أثناء عملك نفسه مشاكل لم تتعرض لها من قبل، وليس من المنطق حفظ كل اللغات البرمجية لاستخدام إحداها لحل مشكلة في أثناء مقابلة أو أثناء تنفيذ مشروع، ولن ينفعك حينها إلا المهارة الأساسية التي تبنى عليها تلك اللغات، أي القدرة على حل المشكلات نفسها.
</p>

<p>
	وإياك والغش، حتى في الأمور التي تعلم يقينًا أنك لن تستخدمها في مسيرتك المهنية فيما بعد، فليس الهدف هو تعلم كيفية حذف العنصر الأول في قائمة مرتبطة Linked List مثلًا، وإنما هدفنا التدرب على كيفية حل مثل هذه المشكلات، فاعلم أن استسهال البحث في الويب أو أدوات الذكاء الاصطناعي عن إجابة جاهزة يحرمك من هذا التمرين الذهني، وسيظل مستواك في هذه المهارة متدنيًا غير مصقول.
</p>

<h2 id="-">
	عقلية النمو
</h2>

<p>
	هذه النقطة قد تكون صعبة، خصوصًا لمن لا يتقبّل الفشل. فبعض الناس يتوتر جدًا لدرجة تؤثر على معدته، وقد يبالغ في لوم نفسه وينتقدها بقسوة أكثر مما ينتقد الآخرين، حتى لو كان يعلم أن القسوة في النقد لا طائل منها، وإن كان الأولى هو التوازن بين اللامبالاة والقسوة المفرطة على النفس، أو ما تسميه عالمة النفس كارول دويك Carol Dweck مصطلح “عقلية النمو Growth Mindset”، وملخصه فيما يلي:
</p>

<ul>
	<li>
		المهارات الشخصية قابلة للتطوير مع التدريب المستمر
	</li>
	<li>
		التعلم عملية مستمرة مدى الحياة
	</li>
	<li>
		التحديات هي فرص للنمو
	</li>
	<li>
		تعلم من النقد والفشل
	</li>
	<li>
		لا تستسلم أبدًا
	</li>
</ul>

<p>
	هذا على النقيض من الشخصية التي تقسو على نفسها قسوة تضر بها، كأن تستغرق في اللوم والتقريع على فشل في محاولة لعبة مثلًا، وهذا النوع من الشخصيات هو ما تطلق عليه كارول “العقلية الثابتة Fixed Mindset”، وهو الاعتقاد الخاطئ أنه مهما لعبت فستصطدم بحدود قدراتك الذاتية التي لن تتجاوزها أبدًا حتى لو درست 500000 ساعة، لكن من المستحيل عمليًا لأي شخص ناضج عاقل أن يقضي نصف مليون ساعة في أمر ما ولا يتعلم منه شيئًا.
</p>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		Quote
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			اخسر أول 50 مباراة لك بأسرع ما يمكن. مثل شهير من لعبة Go (لعبة استراتيجية مثل الشطرنج)
		</p>
	</div>
</blockquote>

<p>
	إذا كان هذا في حال نصف مليون ساعة، فكيف بخمسين ألف ساعة أو 50 ساعة، أو خمس ساعات فقط؟ سترى إن تفكرت في الأمر أن أي قدر من الممارسة سيؤدي حتمًا إلى التحسن، حتى لو شعرت أنك عالق في مكانك فإنك على الحقيقة لا تزال تستكشف طرقًا ومسارات ولو تبين أنها مسدودة، فعلى الأقل تعلمت أنها مسدودة.
</p>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		Quote
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			إنما العلم بالتعلم، وإنما الحِلم بالتحلُّم، ومن يتحرَّ الخير يُعطه
		</p>

		<p>
			تعلم فليس المرء يولد عالمًا، وليس أخو علمٍ كمن هو جاهلُ
		</p>

		<p>
			بقدر الكد تُكتسب المعالي، ومن طلب العلا سهر الليالي
		</p>

		<p>
			مع المحبرة إلى المقبرة
		</p>
	</div>
</blockquote>

<p>
	وهكذا فكل نجاح هو تجربة تعلم، وكل فشل هو تجربة تعلم كذلك، وكل تجربة تعلم تطور مهاراتك، فلا تخش الفشل بل استخدمه لتطوير مستواك.
</p>

<h3 id="-">
	الإصرار
</h3>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		Quote
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			لقد فشل المحترف مرات أكثر مما حاول المبتدئ أصلًا
		</p>

		<p>
			ستيفن ماكريني Stephen McCranie
		</p>
	</div>
</blockquote>

<p>
	يرى برايان أن المثال هذا هو أحد سمات من يصل لمرحلة التميز في أي شيء، فمن تظن أنه سيتقدم في حياته، من يستسلم بعد الفشل أم الذي يفشل ويفشل ويفشل ولا يزال ينهض بعد فشله ليحاول مرة أخرى؟
</p>

<p>
	هذا هو الفرق بين المطورين الأكفاء ومن سواهم، فهؤلاء الأكفاء فشلوا مرات ومرات لكن لم يستسلموا، بل أعادوا المحاولة مرة بعد مرة حتى تمكنوا من حل المشكلة التي بأيديهم في النهاية وقد تعلموا شيئًا جديدًا في كل مرة فشلوا فيها.
</p>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		Quote
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			لم أفشل، بل وجدت 10000 طريقة لا تصلح.
		</p>

		<p>
			توماس إديسون
		</p>
	</div>
</blockquote>

<p>
	قد يغيب عن الطالب حقيقة أن المعلِّم فشل مرات لا حصر لها، فإذا نفذ المعلم مثلًا عملية حذف عنصر من قائمة مترابطة Linked List أثناء شرح درس برمجي ونجح الأمر معه من أول محاولة فهذا لا يعني أن تلك أول محاولة له على الحقيقة، بل لعلها كانت المرة المائة التي ينفذ فيها هذه العملية، فإذا تدرب الطالب على عملية كهذا من الصفر مائة مرة ستنجح معه كذلك من أول محاولة إذ يكون قد تعلم دروسًا لا حصر لها في كل مرة.
</p>

<p>
	فلو تسرب اليأس إلى نفسك لأنك ترى مادة ما مستحيلة على الفهم مع رؤيتك للمعلم يبسطها كأنها أمر تافه وحدثتك نفسك أن هذا المعلم أو هؤلاء الزملاء قد ولدوا بموهبة للبرمجة ليست لديك، فاعلم أن هذا صوت العقلية الجامدة Fixed Mindset التي تريد أن تظل في مكانك دون تطور، لكن تيقن أن لا شيء يميز المعلم عنك سوى عدد مرات الفشل التي تجرعها.
</p>

<p>
	فاثبت أثناء تعرضك لهذه المحاولات الفاشلة التي تمر بها أثناء التعلم، واعلم أن وصولك للتميز في مهارة ما لا يأتي إلا بالكد والمثابرة، فنادرًا ما تجد مبرمجًا بالفطر بل الغالب أن نحو 99.999% قد ذاق مر الصبر ليتعلم هذه المهارة، واعلم أن السمة الأبرز التي يتحلى بها كبار المبرمجين في العالم هي الإصرار الذي لا يعرف اللين والانكسار.
</p>

<h3 id="-">
	الدافع للتعلم
</h3>

<p>
	حين أراد برايان -كاتب الدليل- نيل درجة الدكتوراة نصحه مشرفه قائلًا “لابد أن يكون لديك دافع لنيل هذه الدرجة العلمية” لأن الحصول عليها يتطلب جهدًا كبيرًا لا بد له من سبب يدفع المرء لبذل الوقت والجهد الذي تحتاج إليه شهادة كهذه، وتنطبق هذه النصيحة على علوم الحاسوب عمومًا فهناك الكثير من الأمور الصعبة التي تحتاج إلى فهم عميق، مما يتطلب وجود دافع قوي لتعلمها.
</p>

<p>
	يضرب برايان مثلًا بنفسه لو كان محاسبًا، فهو يدرك يقينًا أن لديه المحك المعرفي المطلوب ليعمل محاسبًا بما أنه درس الرياضيات في الجامعة، لكنه يكره المحاسبة كمجال عمل، إذ فتح مرة كتاب محاسبة بالخطأ لأحد زملائه في الجامعة وتأكد مما رآه أن المحاسبة ليست له، بل يكاد عقله يتوقف حين يسمع من حوله يتحدث عن المحاسبة، فما بالك بدراستها لأربع سنوات في الجامعة، لذا يظن أنه من الصعب تحفيز النفس لعمل لا ترغب فيه.
</p>

<p>
	يتابع برايان أنه واجه الكثير من الانتقادات على موقع Hacker News بسبب هذا الادعاء منذ مدة إذ كتب البعض أنهم يكرهون البرمجة ومع هذا فقد نجحوا في بناء مسيرة مهنية فيها، ورد عليهم أن من يحب علوم الحاسوب سيكون أسهل عليه نيل شهادة فيها مقارنة بمن لا يحبها، رغم إمكانية دراستها والعمل فيها لمن لا يحبها أيضًا، لكن الشاهد أن الذي يحب عمله سيقضي وقتًا أكبر في ممارسته والتفكير فيه مما يجعله في مكانة متقدمة عمن لا يحب ذلك المجال ويعطيه اهتمامًا بالقدر المطلوب للنجاح فيه فقط وليس التميز.
</p>

<p>
	فصحيح أنك قد تنجح مهنيًا في عمل لا تحبه، لكن الأولى أن تبحث في مجال يحرك فضولك لتعلم المزيد فيه على أن تتجرع غصة العمل في أمر لا تطيقه لأنه يدر مالًا فقط، وكي نتحدث من الواقع فقد لا يكون لديك رفاهية اختيار ما تحب، فاحرص في كل حال أن تبحث عما يبعثك على العمل، سواء تحقيق الكفاية المادية أو اهتمامك بحل المشكلات أو غيرها، المهم أن يكون لديك دافع لتعلم المجال، وأن تجعل هذا الدافع واضحًا نصب عينيك.
</p>

<h3 id="-">
	عملية التعلم ليست سهلة
</h3>

<p>
	إذا تعودت فهم المادة العلمية دون كثير من الدراسة فقد تصدمك بعض المفاهيم هنا لصعوبتها، إذ لا تُعد علوم الحاسوب عادة من العلوم السهلة، لكن اعلم أنك لن تكون وحدك في هذا فكل من يخوض هذا المجال يعاني صعوبته، لئلا تشعر أن عندك قصور في إدراكك أو قدرتك على تعلم علوم الحاسوب، فكما أن قيادة الطائرات صعبة حتى على الذين أتقنوها، فكذلك علوم الحاسوب، وما ينجح فيها من ينجح إلا بالممارسة المستمرة والتدريب الذي لا يعرف الكلل.
</p>

<p>
	المبشر في هذا الصدد أن الدراسة تكون أصعب في العادة من البيئة العملية، حيث تتعلم تقنيات ومفاهيم جديدة باستمرار وما تكاد تستوعب أحد المفاهيم حتى تنتقل إلى مفهوم جديد يتحدى قدراتك المعرفية، فمدة التعلم تلك طالت أو قصرت تمثل محاولات من الصمود بشق الأنفس أمام سيل من المواد المعرفية المتجددة.
</p>

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

<h2 id="-">
	حل المشكلات
</h2>

<p>
	يقتبس هذا القسم فكرته من كتاب How to Solve it لمؤلفه جورج بوليا George Pólya، وهو كتاب يتناول كيفية التعامل مع المشاكل الرياضية، وذلك أن علوم الحاسوب في جوهرها إنما هي فرع من أفرع الرياضيات، فيمكن تطويع قواعد حل المشكلات الرياضية لتناسب علوم الحاسوب، وتتلخص منهجية الكتاب فيما يلي:
</p>

<ol>
	<li>
		فهم المشكلة
	</li>
	<li>
		التخطيط لكيفية حلها
	</li>
	<li>
		كتابة الحل برمجيًا
	</li>
	<li>
		مراجعة الحل لتحسينه
	</li>
</ol>

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

<p>
	وبناء على هذا المنطق فإن البرمجة ليست مجرد كتابة شيفرات أو أكواد برمجية، فلو نظرت إلى خطوات حل المشكلات التي ذكرناها أعلاه لوجدت أن ثلاثة منها ليس لها علاقة بالبرمجة، إذ تجري هذه الخطوات في ذهنك أو على الورق وقد لا تتطلب حاسوبًا حتى من الأساس، وعلى قدر ما يبدو هذا الأمر منافيًا للمنطق إلا أن هذا هو محل العمل الحقيقي لحل المشكلة، خاصة في الخطوة الثانية المتعلقة بوضع خطة للحل، فهو الجزء الأصعب والسبب الذي يجعلك تتقاضى راتبًا مجزيًا، فأي شخص يستطيع كتابة برنامج ما بمجرد أن تُحل المشكلة نفسها، فما هي إلا تدوين للحل الذي تم التوصل إليه.
</p>

<p>
	واعلم أنه يُستبعد أن يكون مسار تعلمك متسلسلًا إذا اتبعت هذه الخطوات، بل ستجد نفسك مضطرًا لمراجعة خطوات سابقة من حين لآخر، لكن المهم أن تظل تحاول وتتقدم للأمام، كما ينبغي أن تتجنب البحث عن إجابات جاهزة على الإنترنت، فالهدف هنا هو تمرين عضلات "حل المشكلات" لديك، فلن يوظفك أحد دون أن تكون لديك تلك المهارة، ذلك أن أي مشكلة تقريبًا تراها ستكون مشروحة بحلولها في الإنترنت أو باستخدام أدوات الذكاء الاصطناعي، لكن ليس الهدف أن تصل إلى الإجابة الصحيحة أثناء التعلم، وإنما التعود على المنهج الصحيح لحل المشكلات لتتمكن من إيجاد حل لأي معضلة تواجهك في المستقبل. لننظر الآن في تفصيل هذه الخطوات الأربعة.
</p>

<h3 id="-">
	فهم المشكلة
</h3>

<p>
	يحلو للكثير من الناس -خاصة الطلاب- تجاوز خطوة "فهم" المشكلة إلى ما بعدها، لذا يلجأ المعلم الخبير إلى وضع اختبار قصير يختبر فهم الطلاب واستيعابهم للمشكلة قبل البدء في وضع خطة لحلها أو كتابة ذلك الحل، لكن إذا كنت تدرس البرمجة ذاتيًا فيجب أن تعود نفسك تلك الخطوة لتكون ركنًا لديك لا تتجاوزه إلى ما بعده دون إتمامه، ذلك أنك إذا لم تستوعب المشكلة وتفهمها فهمًا عميقًا ستخرج بحل مثالي في النهاية ولكن للمشكلة الخطأ، وهذا في أفضل أحواله هدر لموارد صاحب العمل أو مديرك إذا كنت موظفًا، وهدر لوقتك كطالب في حين كان الأجدر بك استغلاله في أمر نافع. إليك بعض الاقتراحات التي تسهل عليك عملية الفهم للمشكلة التي تتعرض لها:
</p>

<ul>
	<li>
		اقرأ نص المشكلة ببطء وبمنهجية حتى لو عنى ذلك أن تعيد قراءة الجمل عدة مرات، فتوصيفات المشاكل عادة ما تكون موجزة ومعقدة
	</li>
	<li>
		دون ملاحظات عما تقرؤه، خاصة حول التناقضات أو النقاط المحذوفة أو الأخطاء أو الأسئلة التي تطرأ على ذهنك أثناء قراءتك
	</li>
	<li>
		أعد صياغة المشكلة بأسلوبك الخاص، فهذا أسلوب فعال لاكتشاف الفجوات في فهمك للمشكلة
	</li>
	<li>
		ابحث في مصادر خارجية عما يستشكل عليك من النقاط المذكورة في وصف المشكلة
	</li>
	<li>
		اكتب أسئلة تستوضح النقاط التي أشكلت عليك
	</li>
</ul>

<p>
	ستعرف أنك أتممت خطوة "فهم المشكلة" إذا استطعت شرح المشكلة لشخص آخر، ذلك أنه يصعب كتابة وصف كامل لمشكلة ما إذا لم تستوعبها بنفسك أولًا، وستجد أثناء دراستك أو عملك أن توصيفات المشاكل التي تقابلك لا بد أن يعتريها وجه أو أكثر من وجوه النقص، فأكاد أجزم أنك لو حاولت أن تكتب قواعد لعبة بسيطة مثل XO ستجد فيما كتبت تفصيلًا نسيته هنا أو هناك، وهنا تبرز أهمية الأسئلة الاستيضاحية.
</p>

<p>
	يضرب برايان مثلًا على هذا بمشروع عمل عليه من قبل حيث تولى مهمة برمجة الواجهة الأمامية Front-end فيه، بينما تولى شخص آخر مهمة الواجهة الخلفية، وكان بينهما مستند يصف بدقة آلية العمل بين الواجهتين، حيث نصت إحدى آليات العمل على إرسال نتيجة عملية حسابية، مع مثال يوضح النتيجة بصيغة ست عشرية Hexadecimal كما يلي: إذا كانت المدخلات هي <code>abc</code> و <code>xyz</code> فتكون النتيجة هي الرقم <code>f319c2c6dcfb</code>.
</p>

<p>
	كتب برايان هذه العملية برمجيًا واستخرج النتيجة الصحيحة ثم أضاف الشيفرة اللازمة لإرسال الرقم المكون من 6 بايتات إلى الخادم، لكن المسؤول عن الخادم راسله قائلًا أن هذا الاستنتاج خاطئ، وتبين بعد عدة محادثات بينهما أن المسؤول عن الخادم فهم من المستند أنه سيرسل سلسلة نصية string تحتوي على تلك الأرقام الست عشرية، بينما كان ما فهمه برايان هو أنه سيرسل بايتات خام raw bytes، وفي النهاية كان الحل الأسهل هو تحويل الرقم إلى سلسلة نصية وإرسالها. يوضح هذا المثال كيف أن وصف المشكلة قد يكون غامضًا ويمكن فهمه على أكثر من وجه، وأهمية الفهم الصحيح للمشكلة قبل البدء في حلها.
</p>

<h3 id="-">
	وضع خطة الحل
</h3>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		Quote
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			البرمجة هي أن تخبر إنسانًا آخر بما تريد من الحاسوب أن يفعله
		</p>

		<p>
			دونالد نوث Donald Knuth، عالم حاسوب
		</p>
	</div>
</blockquote>

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

<p>
	يجب أولًا أن نفهم المشكلة إلى الدرجة التي تمكنك من شرحها للآخرين رغم أن هذا لا يعني أنك ستعرف كيفية حلها، وقد يكون وضع خطة الحل سهلًا وسريعًا إذا كانت المشكلة بسيطة ومألوفة لك خاصة حين تزيد خبرتك مع الوقت والممارسة، لكن قد تواجهك مشكلات لم ترها من قبل، فحينئذ ستحتاج إلى وقت طويل لوضع خطة الحل، حتى لو وصلت إلى مستوى خبرة عالي. إليك بعض المحاور التي ينبغي التفكير فيها للوصول إلى الحل، ربما يفيدك استخدام القلم والورقة هنا:
</p>

<ul>
	<li>
		كيف ستتدفق البيانات في النظام وكيف تتحول من مدخلات معروفة إلى المنتج النهائي المطلوب؟
	</li>
	<li>
		ما هي الأنظمة الفرعية التي ستنفذ كل خطوة أثناء عمليات التحول تلك؟
	</li>
	<li>
		ما هي التقنيات التي ستحتاجها لتنفيذ هذه الخطوات؟
	</li>
	<li>
		ما هي المجاهيل المعروفة، أي الأمور التي تدرك أنك لا تملك لها إجابة بعد؟
	</li>
</ul>

<p>
	لاحظ أننا نتحدث هنا عن تفكيك المشكلة إلى أجزاء صغيرة، قد يكون من الصعب فعل ذلك في بداية تعلمك لكن الأمر يصير أسهل مع الممارسة، فأحد طرق تفكيك المشكلات مثلًا هو أن تقول لنفسك أن المشروع سيكون أسهل لو كانت المدخلات على الصيغة كذا، ثم تحاول كتابة شيفرة برمجية تحول البيانات إلى تلك الصيغة (سنتناول هذا بالتفصيل لاحقًا).
</p>

<p>
	كذلك فإن تفكيك المشروع إلى أجزاء صغيرة يوجهك تلقائيًا إلى تقسيم شيفرتك البرمجية إلى دوال Functions أو أصناف Classes مما يسهل عملية قراءة الشيفرة البرمجية وصيانتها فيما بعد، ومن المتوقع في هذه المرحلة أن تلجأ إلى البحث لتعلم الأدوات التي ستستخدمها في حل المشكلة، وأن تكتشف قصورًا في فهمك الأولي للمشكلة، فإذا رأيت هذا فارجع إلى مرحلة الفهم السابقة لتبني تصورك وفقًا للبيانات الجديدة التي جمعتها، ثم استأنف مرحلة التخطيط بعدها.
</p>

<p>
	ستعلم أنك انتهيت من مرحلة التخطيط حين تستطيع محاكاة تشغيل البرنامج أو تنفيذ الحل على الورق وفي ذهنك، وتثق تمامًا في نجاحه، وكذلك حين تستطيع كتابة شيفرة وصفية Pseudocode عالية المستوى، وهنا من المفيد أن تعرض حلك على بعض الأشخاص لنقده وتحسينه.
</p>

<h3 id="-">
	كتابة الحل البرمجي
</h3>

<p>
	كل ما عليك فعله هنا هو ترجمة الخطط المكتوبة بالشيفرة الوصفية إلى شيفرة برمجية حقيقية، فإذا كنت قد استوعبت المشكلة جيدًا ووضعت خطة مناسبة لها فستعمل الشيفرة البرمجية بنجاح، بل ربما تنجح من المحاولة الأولى حتى، أما إذا أهملت المراحل السابقة من فهم للمشكلة ووضع خطة مناسبة فلن تكون كتابة الحل برمجيًا سهلة، بل سترى مشاكل لا نهاية لها، وقد يكون مآل المشروع إلى إخفاق مخيب، فهذا يبرز الأهمية البالغة للفهم والتخطيط الصحيح قبل الشروع في الحل.
</p>

<p>
	صحيح أننا بشر وقد نرتكب أخطاءً أو هفوات أثناء كتابة الحل، لكن تصحيح تلك الأخطاء أسهل من محاولة إصلاح الخطة التي نبني عليها ذلك الحل نفسه، وأسهل من تصحيح فهم خاطئ للمشكلة من الأساس. سترى أن التحديات التي تواجهها في هذه المرحلة تتمثل فيما يلي:
</p>

<ul>
	<li>
		إتقان قواعد اللغة البرمجية التي تستخدمها
	</li>
	<li>
		معرفة المكتبات البرمجية المتاحة والتي يمكن استخدامها
	</li>
	<li>
		تجنب الوقوع في الأخطاء البديهية أو الصغيرة
	</li>
	<li>
		الالتزام بخطة الحل الموضوع بدقة
	</li>
</ul>

<p>
	سنتحدث عن كيفية تعلم اللغات البرمجية لاحقًا، لكن النقطتين الأخيرتين يمكن معالجتهما عبر توخي الحذر أو اتباع أسلوب البرمجة الثنائية Pair Programming -أسلوب برمجة يتشارك فيه اثنان من المبرمجين في كتابة الحل، حيث يكتب أحدهما الشيفرة البرمجية بينما يراقبه الثاني لتصحيح الأخطاء أو تقديم الملاحظات، ويتبادلان هذين الدورين أثناء كتابة الشيفرة البرمجية-، أو الاستعانة بالذكاء الاصطناعي.
</p>

<p>
	ويكثر هنا أن تكتشف ثغرة في خطتك أثناء كتابة الحل البرمجي، وعندئذ يجب العودة فورًا إلى المرحلة التي تسببت في هذه الثغرة، سواء كانت مرحلة التخطيط أو حتى مرحلة الفهم الأولي للمشكلة. ستعلم أنك أتممت مرحلة كتابة الحل البرمجي إذا عملت الشيفرة البرمجية بنجاح واجتازت جميع اختباراتك لها.
</p>

<h3 id="-">
	المراجعة والتحسين
</h3>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		Quote
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			البرمجة حرفة، فافتخر بما صنعته يداك
		</p>

		<p>
			برايان هول (مؤلف الدليل)
		</p>
	</div>
</blockquote>

<p>
	نأتي هنا إلى مرحلة المراجعة لما أنجزناه لنلقي عليه نظرة ناقدة، صحيح أن الشيفرة تعمل واجتازت اختباراتنا لها، لكن هل هي سهلة القراءة والصيانة، وهل توجد أجزاء هشة قد تنهار أمام مدخلات غير متوقعة؟ ثمة فرص للتحسين مهما كنت خبيرًا في مجالك، وتُعد مراجعة الشيفرات القديمة التي كتبناها هي إحدى أنجح الطرق التي نتعلم بها ونطور مهاراتنا، لنبحث في كيفية تحسينها وكتابتها بشكل أفضل، إما بمراجعتها بنفسك أو بطلب ذلك من مبرمج آخر لتقديم مقترحات للتحسين قد تقبلها وتنفذها أو ترى أنها غير مناسبة لشيفرتك، فكلا الأمرين وارد، لكن العملية نفسها، أي مراجعة الشيفرات Code Reviews لا غنى عنها لصقل قريحتك البرمجية.
</p>

<p>
	وتستطيع هنا الاستعانة بالذكاء الاصطناعي عند حل المشكلة ورؤية الحل يعمل بكفاءة، حيث تعرض الحل على أداة ذكاء اصطناعي مناسبة لاقتراح تحسينات على شيفرتك وتراجع تلك الاقتراحات لترى إن كان فيها ما يناسب الحالة التي لديك، لكن اعلم أن هذا الاقتراح مناسب للشيفرات الصغيرة فقط وليس المشاريع الكبيرة، مما يجعله مناسبة للمساعدة في فهم التكليفات والمشاكل التي يطلب منك حلها أثناء الدراسة.
</p>

<p>
	ونؤكد هنا أن عليك حل المشكلة بنفسك أولًا قبل النظر في مساعدة خارجية لتحسينها، فالهدف من التدريب أثناء الدراسة هو نفسه الهدف من التدريب الرياضي، أي ممارسة التمرين والحصول على الفائدة الرياضية للجسد وتقييم الأداء، لا أن يقوم شخص آخر بأداء التمرين بدلًا منك.
</p>

<p>
	قد تمتلئ الشيفرة البرمجية بالأخطاء bugs أو تفتقر للكفاءة أو التنسيق الجيد، أو قد تكون صعبة القراءة، فينبغي الانتباه هنا إلى أن الشيفرة البرمجية يجب أن تكون سهلة القراءة والفهم من قبل غيرك من المبرمجين كي تكون سهلة الصيانة، وهذا يعني أن يكون مظهر الشيفرة البرمجية منسقًا، ذلك أن شكل الشيفرة قد لا يهم بالنسبة للمصرِّف compiler، لكن لا ينبغي أن نعذب البشر بقراءة طلاسم صعبة الفهم.
</p>

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

<p>
	وتكون مرحلة المراجعة في بيئة العمل أثناء جلسات تحليل ما بعد المشروع، حيث يجتمع العاملون على المشروع لدراسة ما تم فيه بشكل صحيح وما كان ينبغي تجنبه أو تحسينه.
</p>

<h3 id="-">
	توقع أخطاء المستخدمين
</h3>

<p>
	من المفيد عند التفكير في حل المشكلات أن تفكر من منظور المستخدم الذي قد يستخدم البرنامج أو النظام الذي تبنيه استخدامًا خاطئًا عامدًا أو غير متعمد من أجل تلافي أخطاء المستخدمين غير المتوقعة قدر الإمكان. على سبيل المثال، إذا كان لدينا دالة لحساب الجذر التربيعي فإن التفكير المنطقي يقول أن الاختبار الجيد لمثل تلك الدالة هو إعطاؤها أرقامًا مربعة كاملة وغير كاملة وكسورًا كذلك، وتكون الدالة سليمة إذا نجحت في إعطاء نتائج صحيحة لكل تلك المدخلات التي أدخلناها، لكن ماذا لو أدخلنا إليها أرقامًا سالبة مثلًا، كيف ستتصرف الدالة حينها؟ هذا ما نقصده بتوقع الأخطاء التي قد يُستبعد أن يفكر أحد فيها، لكنها واردة الحدوث من المستخدمين.
</p>

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

<p>
	ويذكر مثالًا لزميل له عمل لصالح الجيش حيث كان يفحص مخططات لآلات عسكرية مثل الدبابات قبل دخولها خط الإنتاج، وكانت مهمته تتلخص في التفكير بالحالات غير المتوقعة وطرح أسئلة مثل "كيف ستدخل مفتاح ربط بين هذه الأنابيب لتثبيت هذا المسمار؟"، فهذا المنهج في التفكير يساعدك على رصد المشكلات التي قد تغيب عن بالك، وإلى فهم أعمق للمشروع مما ينتج عنه شيفرة برمجية أمتن وأكثر قابلية للصيانة في المستقبل.
</p>

<h3 id="-">
	تطبيق هذه المبادئ في مقابلات العمل
</h3>

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

<p>
	قد ينتابك الذعر تحت ضغط المقابلة حين ترى مشكلة تبدو مستحيلة الحل في البداية، فتبهتك وطأة الموقف وتتبخر كل معلوماتك فجأة وتشعر أن الوظيفة قد ضاعت من يديك، لكن انتبه أن هذا طبيعي ووارد فينبغي أن تتمالك نفسك وتطمئنها أن المهم الآن هو فهم المشكلة، ولا تشغل بالك بالحل ولا كتابة الشيفرة البرمجية، بل هذه الخطوة فقط، أي فهم المشكلة.
</p>

<p>
	وأهمية هذا النهج تظهر إذا علمت أن من يجري معك المقابلة يريدك أن تبدأ من نقطة فهم المشكلة، وهذا في حد ذاته يُعد سببًا كافيًا، وأن البدء بفهم المشكلة سيحفز عقلك على أن يعمل بمنطقية ويبتكر استراتيجية للحل، وهي الخطوة الثانية في إطار العمل الذي وضحناه من قبل، أي خطوة وضع خطة الحل.
</p>

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

<p>
	وقد تتفاجأ إذا علمت كثيرًا ممن يجرون مقابلات التوظيف البرمجية يهمهم رؤيتك تفكك مشكلة بطريقة منهجية وفعالة أكثر من وصولك للإجابة الصحيحة، بل إن إعطاء إجابة صحيحة دون إظهار مهاراتك وكيف وصلت إلى تلك الإجابة قد يتسبب في استبعادك من تلك الوظيفة.
</p>

<p>
	يضرب كاتب الدليل مثلًا على هذا بمقابلة أجراها في شركة Activision حيث فشل في الوصول إلى حل صحيح لسؤالين، لكنه واصل التفكير بصوت عالي وبذل قصارى جهده لتوضيح كيفية تفكيره في المشكلة التي أمامه، ونال تلك الوظيفة في النهاية، وكان السؤالان اللذان أخفق فيهما هما "ما هي أسرع طريقة لعكس ترتيب البتات داخل البايت الواحد؟" و"حسِّن كفاءة عملية حسابية تبني شبكة مسافات بين جميع لاعبي كرة القدم في الملعب".
</p>

<p>
	شاهد المثال السابق أن تجعل عملية التفكير كلها واضحة لمن تجري معه المقابلة، ولا تنس خطوة المراجعة والتحسين: ما الذي كنت ستفعله بشكل أفضل، أو كيف يمكن تطوير الحل وما هي المزايا التي يمكن إضافتها في المستقبل، فإن المحاورين يحبون هذه التفاصيل في المقابلات البرمجية.
</p>

<h3 id="-">
	تكلفة كل مرحلة
</h3>

<p>
	تجدر الإشارة إلى ملاحظة هامة حول إطار العمل الخاص بحل المشكلات، وهي أن تكلفة التعديل على البرمجيات تزداد أضعافًا مضاعفة كلما تقدمنا في مراحل تطويرها، ففي مرحلة الفهم الأولى تكون التعديلات قليلة التكلفة في الوقت والجهد والثمن، بل مجانية أحيانًا إذ ليس لدينا خطة بعد حيث نكون في مرحلة العصف الذهني، وتظل تكلفة التعديلات قليلة في مرحلة التخطيط، لكن لن تكون مجانية قطعًا، فإجراء تعديل واحد قد يؤثر على أجزاء أخرى من الخطة، مما يتطلب إعادة رسمها، أو العودة إلى مرحلة الفهم السابقة.
</p>

<p>
	أما عند الوصول إلى المرحلة الثالثة، وهي مرحلة البرمجة Coding، فهنا تكون التعديلات عالية التكلفة، فقد تكتشف أن عليك التخلص من شيفرات برمجية تكلف تطويرها آلاف أو ملايين الدولارات، ثم إعادة كتابتها مرة أخرى، رغم أن مثل هذه الحالات يرد حدوثها كثيرًا في الشركات، لكنها تلجأ إلى مثل هذه العملية بعد إجراء تحليل عميق للتكلفة مقابل القيمة Cost-Benefit لترى إن كان التعديل يستحق.
</p>

<p>
	وتصل تكلفة التعديلات إلى مداها بعد إطلاق البرنامج لأن العملاء لا يحبون فرض التحديثات عليهم بعد أن بنوا أنماط عملهم حول النسخة التي بأيديهم، مما قد يحمل هنا تكاليف خفية وغير مباشرة مرتبطة بالتغيير، إضافة إلى التكلفة الظاهرة في إعادة التخطيط والبرمجة والبناء والاختبار وإرسال التحديثات إلى العملاء.
</p>

<p>
	لكنك في أثناء دراستك لعلوم الحاسوب لن تشغل بالك كثيرًا بالتكاليف والخسائر التي سيتكبدها المشروع، بل سيكون همك امتلاك الوقت الكافي لإنهاء المهمة التي بيديك والحصول على تقييم جيد فيها، لذا ينبغي أن تركز على مرحلتين الفهم والتخطيط حيث تكون التعديلات قليلة التكلفة من حيث الوقت، لتضمن الوصول إلى أفضل النتائج بسرعة وكفاءة وبأقل قدر مطلوب من الجهد، وقبل موعد التسليم أيضًا.
</p>

<h2 id="-">
	تفكيك المشكلات
</h2>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		Quote
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			ما خفي كان أعظم
		</p>
	</div>
</blockquote>

<p>
	تساعدك الخطوات التي ذكرناها سابقًا لحل المشكلات (الفهم، التخطيط، كتابة الحل البرمجي، المراجعة والتحسين) على تجاوز أي مشكلة تتعرض لها تقريبًا، لكن الواقع يخبرنا أن المشاكل تكون أجزاءً أصغر من مشاكل أكبر منها، وتلك المشاكل تمثل أجزاءًا في مشاكل أكبر، وهكذا. على سبيل المثال، إذا أردنا أن نبني طاولة فسيكون وصف المشكلة هو "اصنع طاولة" وكفى، لكن الواقع يقول أن هذا الوصف لا يكفي إذا لم تكن قد صنعت طاولة من قبل، فينبغي في تلك الحالة أن نقسم المشكلة إلى أجزاء أصغر منها، مثال:
</p>

<ul>
	<li>
		صناعة الطاولة
	</li>
	<li>
		تثبيت أرجل الطاولة في القرص العلوي
		<ul>
			<li>
				صناعة القرص العلوي للطاولة
			</li>
			<li>
				صناعة أرجل الطاولة
			</li>
		</ul>
	</li>
</ul>

<p>
	وحتى هذا التفصيل قد لا يكون كافيًا، فكيف نصنع أرجل الطاولة؟ وكيف نصنع القرص العلوي لها؟ ستكون التفاصيل وفقًا لهذه الأسئلة كما يلي:
</p>

<ul>
	<li>
		صناعة الطاولة
		<ul>
			<li>
				تثبيت أرجل الطاولة في القرص العلوي
				<ul>
					<li>
						صناعة القرص العلوي
						<ul>
							<li>
								صنفرة سطح القرص العلوي
								<ul>
									<li>
										لصق حواف جانبية للقرص العلوي
										<ul>
											<li>
												قص الجزء العلوي الأساسي
												<ul>
													<li>
														تعلم كيفية استخدام منشار الطاولة
													</li>
												</ul>
											</li>
											<li>
												قص حواف الجزء العلوي
											</li>
										</ul>
									</li>
								</ul>
							</li>
						</ul>
					</li>
					<li>
						صناعة أرجل الطاولة
						<ul>
							<li>
								قص الأرجل
							</li>
							<li>
								استخدم المخرطة لصناعة الشكل النهائي للأرجل
								<ul>
									<li>
										تعلم استخدام المخرطة
									</li>
								</ul>
							</li>
						</ul>
					</li>
				</ul>
			</li>
		</ul>
	</li>
</ul>

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

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

<p>
	وينبغي عند التعامل مع أي من هذه المشاكل الفرعية أن تتعامل معها كأنها المشكلة الوحيدة التي لديك، لتركز عليها وتحاول حلها بمعزل عن غيرها، وإذا تعسر ذلك فحاول أن تقسمها إلى مشاكل فرعية أصغر وهكذا، ستجد مع كثرة الممارسة أنك لم تعد بحاجة إلى تفكيك المشكلات إلى مستويات عميقة كما في السابق، بل ستشرع فيها كخبير يستطيع تمييز الأنماط المتكررة التي يمكنه إعادة استخدامها.
</p>

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

<h3 id="-pseudocode">
	الشيفرة الوصفية Pseudocode
</h3>

<p>
	تعد الشيفرة الوصفية من أهم الأدوات التي يستخدمها المطور لاستكشاف الأفكار، فهي موجهة للبشر وليس للحواسيب، أي لا يستطيع الحاسوب قراءتها، رغم أن البعض يرى لغة بايثون قريبة جدًا من الشيفرة الوصفية، وتستطيع استخدام الشيفرة الوصفية لرسم الخطوط العريضة لخطوات خوارزمية أو عملية ما لإجراء فحص سريع أو لمجرد اكتشاف كيفية إنجاز مهمة ما، فتستطيع مثلًا أن تكتب شيفرة وصفية تدرج قيمة في قائمة من القيم مرتبة مسبقًا كما يلي:
</p>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		Quote
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			 <code>ابحث عن المكان الصحيح في القائمة<br>
			أدخل القيمة هناك</code>
		</p>
	</div>
</blockquote>

<p>
	لكن هذه الخطوات لا تصف المطلوب بدقة، لذا سنفككها أكثر:<code> </code>
</p>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		Quote
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			<code>ابحث عن المكان الصحيح في القائمة</code>
		</p>

		<p>
			<code>ابحث عن أول عنصر أكبر من القيمة الجديدة</code>
		</p>

		<p>
			<code>أدرج القيمة هناك </code>
		</p>

		<p>
			<code>أزح جميع القيم الأكبر إلى اليمين</code>
		</p>

		<p>
			<code>ضع القيمة الجديدة في المكان الذي فرغ للتو</code>
		</p>
	</div>
</blockquote>

<p>
	هكذا تكون الصورة قد اتضحت قليلًا، لكن لا زال يمكن تحسينها كما يلي:<code> </code>
</p>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		Quote
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			<code>ابحث عن المكان الصحيح في القائمة </code>
		</p>

		<p>
			<code>ابحث عن أول عنصر أكبر من القيمة الجديدة </code>
		</p>

		<p>
			<code>أنشئ حلقة تكرارية‫ <span class="hljs-keyword">Loop</span> تمر على العناصر</code>
		</p>

		<p>
			<code>أوقف الحلقة حين تعثر على عنصر أكبر</code>
		</p>

		<p>
			<code>أدرج القيمة هناك </code>
		</p>

		<p>
			<code>أزح جميع القيم الأكبر إلى اليمين </code>
		</p>

		<p>
			<code>ضع القيمة الجديدة في المكان الذي فرغ للتو</code>
		</p>
	</div>
</blockquote>

<p>
	لا زال لدينا مرحلة أخرى من التحسين على هذه الخطوات:
</p>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		Quote
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			<code>ابحث عن المكان الصحيح في القائمة </code>
		</p>

		<p>
			<code>ابحث عن أول عنصر أكبر من القيمة الجديدة </code>
		</p>

		<p>
			<code>أنشئ حلقة تكرارية‫ <span class="hljs-keyword">Loop</span> تمر على العناصر</code>
		</p>

		<p>
			<code>أوقف الحلقة حين تعثر على عنصر أكبر</code>
		</p>

		<p>
			<code>سجّل الفهرس الذي يسبق هذا العنصر </code>
		</p>

		<p>
			<code>أدرج القيمة هناك </code>
		</p>

		<p>
			<code>أزح جميع القيم الأكبر إلى اليمين </code>
		</p>

		<p>
			<code>ضع القيمة الجديدة في المكان الذي فرغ للتو </code>
		</p>

		<p>
			<code>اضبط قيمة العنصر عند ذلك الفهرس لتكون هي القيمة الجديدة</code>
		</p>
	</div>
</blockquote>

<p>
	هكذا نكون قد اقتربنا كثيرًا من تحويل شيفرتنا الوصفية إلى شيفرة حقيقية، فمثلًا لا تزال الآلية الخاصة بإزاحة القيم غير واضحة وربما ينبغي أن نفككها أكثر.
</p>

<p>
	يلجأ المطورون أحيانًا إلى إضافة الشيفرة الوصفية كتعليقات داخل الشيفرة الحقيقية للبرنامج، بحيث تكون الشيفرة الحقيقية تحت وصفها لتسهيل فهمها وصيانتها في المستقبل، وهذه الطريقة -أي الشيفرة الوصفية- وسيلة ممتازة أثناء مرحلة التخطيط لأنها تعينك على إتقان مرحلة التفكير والتخطيط للعملية البرمجية كلها.
</p>

<h3 id="-proof-of-concept">
	بناء النموذج الأولي Proof of Concept
</h3>

<p>
	إلى هنا نكون قد فهمنا المشكلة الأصلية التي نريد حلها وقسمناها إلى أجزاء أصغر منها تمثل مشكلات فرعية، لكننا لا نعرف بعد إن كانت تلك المشكلات الفرعية قابلة للتنفيذ أصلًا أم لا، فهل تستطيع مثلًا معالجة صورة في عنصر <code>canvas</code> في HTML ثم حفظ تلك الصورة مباشرة إلى معرض الصور في هاتف محمول؟
</p>

<p>
	لعلك لم تنفذ هذه العملية من قبل، ولا تعرف هل يمكن تنفيذها تقنيًا أم لا، وهنا تبرز أهمية بناء نموذج أولي proof of concept يثبت إمكانية تنفيذها، وتطبيقًا على مثال الصورة هنا، سننشئ صفحة ويب ونضيف عنصر <code>canvas</code> ثم نرسم فيه شكلًا بسيطًا مثل مربع أو مستطيل، ثم نضيف الشيفرة التي تحمّل هذا الشكل كصورة إلى المعرض عند النقر على زر التحميل.
</p>

<p>
	كانت هذه الخطوة في السابق تتطلب الكثير من البحث وقراءة المراجع والكتب خاصة إن لم تكن تعرضت من قبل للمفهوم أو المشكلة التي تحاول حلها، لكن صار الأمر أسهل بالاعتماد الصحيح على أدوات الذكاء الاصطناعي للإجابة على أسئلة مثل "هل كذا ممكن؟ وكيف؟"، بل وكتابة بعض الشيفرات التي يمكن استخدامها في النموذج الأولي، سنخرج باستنتاجين إذا بنينا هذا النموذج الأولي وعمل بدون مشاكل:
</p>

<ol>
	<li>
		أن الفكرة يمكن تنفيذها عمليًا
	</li>
	<li>
		أننا عرفنا كيف نكتب الشيفرة الحقيقية المطلوبة لتنفيذها
	</li>
</ol>

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

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		Quote
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			التخلص من إحدى النسخ أمر حتمي
		</p>

		<p>
			فريد بروكس Fred Brooks، كتاب The Mythical Man-Month
		</p>
	</div>
</blockquote>

<p>
	تصلح شيفرة النموذج الأولي في عرض شكل المنتج النهائي أو كيفية عمله للآخرين، ذلك أن المطورين يكتبون نماذج محاكاة mock implementation للتطبيق أو البرنامج الذي يعملون عليه، لا يعمل فيه إلا جزء صغير فقط من واجهة المستخدم لكنه يعطي فكرة عامة عن آلية عمل البرنامج مستقبلًا.
</p>

<h2 id="-">
	استخدام الأداة المناسبة
</h2>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		Quote
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			ما هي أفضل لغة برمجة؟
		</p>
	</div>
</blockquote>

<p>
	يُعد هذا السؤال من أكثر الأسئلة شيوعًا بين المبتدئين في مجال البرمجة، ويكثر أن يكون لدى كل واحد انحياز معين لهذه اللغة أو تلك، خاصة في بداية تعلمهم للبرمجة، لكن الواقع المشاهد لأي شخص قضى فترة في هذا المجال يقول أنه لا توجد لغة "فضلى" على إطلاق الوصف دون معرفة سياق المشكلة التي تحاول حلها، فلا أحد يقول مثلًا أن لغة Bash هي أفضل لغة في العالم، لكن توجد يقينًا مشكلات تحلها هذه اللغة بالطريقة المثلى.
</p>

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

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

<p>
	يذكر برايان -كاتب الدليل- أن مدربه في رياضة الغوص قدم له نصيحة حين أراد شراء بعض المعدات وكان يفاضل بين نوعين منها، إذ نصحه قائلًا أن المهم أن يشغل باله بالغوص نفسه وإتقانه، وليس الأدوات التي يستخدمها للغوص.
</p>

<p>
	وبإسقاط هذا المثال على مجال البرمجة سترى أن أفضل المطورين يتقنون أدوات كثيرة، ويعرفون متى يستخدمون كلًا منها، فإذا حدثتك نفسك بالابتعاد عن لغة ما لأنك تكره أمرًا فيها، فابحث عن الحالات التي تُستخدم فيها تلك اللغة وتصلح لها فهي لم تُبتكر عبثًا، فإذا عرفت حالات استخدامها عرفت مدى أهميتها لرحلة تعلمك ومتى يتعين عليك استخدامها تحديدًا، فإذا سألت مطورًا خبيرًا عن لغته المفضلة ربما يقول لك أنها Rust، أو بايثون، أو C أو جافاسكربت أو SQL أو AWK أو Bash، لكن دقة هذه الإجابة تتوقف على حالة الاستخدام التي تسأل عنها.
</p>

<h3 id="-">
	تبني الآراء التقنية
</h3>

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

<p>
	يجب أن يكون لك رأيك الخاص في الأدوات التي تختارها، ويكون ذلك الرأي مدعومًا بمنطق مقبول وسليم لديك، سواء في لغة البرمجة أو المحرر النصي أو هذه المكتبة أو تلك، ينبغي أن تستطيع تبرير سبب اختيارك لتلك الأدوات بعينها لحالتك رغم أن غيرها موجود ويفي بالغرض.
</p>

<p>
	كذلك ينبغي أن يكون لك رأي في هياكل البيانات التي تختارها، ذلك أن البرمجة عمل إبداعي تستطيع فيه تحويل أي شيء تقريبًا إلى آلة تيورنج وحل مشكلتك بها، لكن في نفس الوقت توجد خيارات لا حصر لها يمكن استخدامها، فاختر الذي تراه الأفضل منها بناءً على منطقك وحاجتك في المشروع الذي تعمل عليه أو المشكلة التي تحلها.
</p>

<p>
	كن صاحب رأي كذلك في الخوارزميات التي تختارها، بنفس منطق الفقرتين السابقتين فستجد الكثير من الخوارزميات التي يمكن استخدامها، فانظر أيها تخدم حالتك أو تحل مشكلتك وبناء على ذلك تستطيع اتخاذ قرار باستخدام خورازمية فرز الدمج Mergesort مثلًا أو الفرز بالإدراج Insertion sort، وبالمثل هل الأفضل أن نستخدم البحث الثنائي Binary search أم البحث الخطي Linear search.
</p>

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

<ul>
	<li>
		كفاءة تلك التقنية في حل المشكلة التي لديك وأداؤها مقارنة بغيرها
	</li>
	<li>
		سهولة قراءة الشيفرة الخاصة بالتقنية أو الخوارزمية أو لغة البرمجة وصيانتها مستقبلًا
	</li>
	<li>
		الوقت المستغرق في كتابتها وتطويرها
	</li>
	<li>
		تكلفة الأداة أو التقنية نفسها
	</li>
	<li>
		التقنيات المستخدمة بالفعل في المشروع ومدى توافق الأداة الجديدة معها
	</li>
</ul>

<p>
	واعلم أنه حتى بعد اعتبار هذه العوامل وغيرها في اتخاذ قرارك لا بد أن تتهيأ نفسيًا للتعلم المستمر لما لا تعرفه أو تضطر لاستخدامه، فهما كانت التقنية التي تستخدمها ستجد من يخالفك الرأي فيها، أو قد تجد نفسك تتخلى عنها بعد مدة لتغير العوامل التي جعلتك تستخدمها من قبل، ولا يهم ذلك التغير أو التحول طالما أنه مبني على منطق سليم وواعي، فستكتشف مع التعلم حالات خاصة قد تكون فيها تقنية هي أفضل من غيرها على خلاف ما كنت تظن من قبل، فتقبّل فرص التعلم الجديدة هذه واترك قناعاتك الأولى إذا وجدت غيرها أفضل لحالتك، سواء كان ذلك في أمر بسيط مثل اختيار أسماء المصفوفات في الشيفرة أو كبيرًا مثل اختيار إطار عمل Framework للمنتج الأساسي لشركتك.
</p>

<h2 id="-">
	نصائح للتعلم
</h2>

<p>
	توجد العديد من النصائح الخاصة بزيادة سرعة تعلمك وضمان استدامته، ويدرك المطورون مدى فائدة هذه النصائح لكنهم يصرون على تجاهلها في أغلب الأحيان، ورغم أننا لا نضمن نجاح تلك النصائح مع جميع المتعلمين إلا أننا نأمل أن تجد من بينها ما يفيدك في تعلمك للبرمجة.
</p>

<p>
	أحد الأمثلة على هذا هو الأسلوب الذي تحقق به التركيز المطلوب للدراسة أو تنفيذ المهمة التي بيديك، فقد يفضل البعض الصمت التام وبيئة خالية من المشتتات السمعية أو البصرية، أي لا نوافذ مفتوحة أو إضاءة وهاجة أو أصوات في الخلفية أو ألوان فاقعة أو هاتف بجوارهم، بينما يصل غيرهم إلى حالة التركيز تلك بسماع الضوضاء البيضاء White noise التي تمثل موجات أو أصوات لا تتغير وتيرتها فتحقق لهم عزلًا عن المحيط الذي حولهم، وهكذا، فاختر ما يصلح حالتك منها.
</p>

<h3 id="-">
	حالة التركيز العميق
</h3>

<p>
	توصف حالة التركيز العميق -تسمى أحيانًا حالة التدفق flow- في سياق الدراسة أو العمل بأنها الحالة الذهنية التي تبلغ فيها قمة تركيزك حيث تترابط أفكارك بلا مشتتات أو عوائق، وهذه الحالة مطلب كثير من المبرمجين الذين يريدون تحقيق أقصى قدر من الإنتاجية. إليك بعض خصائص هذه الحالة مقتبسة من موسوعة ويكيبيديا:
</p>

<ul>
	<li>
		تركيز عميق على ما يحدث في الوقت الحالي فقط
	</li>
	<li>
		اندماج بين الوعي والإدراك والفعل المترتب على ذلك الوعي
	</li>
	<li>
		تلاشي الشعور بالذات النقدية أو صوتك الداخلي
	</li>
	<li>
		إحساس بتمام السيطرة على الموقف أو النشاط الذي بين يديك
	</li>
	<li>
		اختلال في إدراك الوقت، حيث يتغير وعيك بالوقت الذي استغرقته في العمل أو التفكير في المهمة التي بيدك
	</li>
	<li>
		الشعور أن النشاط نفسه الذي تبذله أثناء الدراسة أو العمل على المهمة يمثل مكافأة في ذاته
	</li>
</ul>

<p>
	قد تكون مررت بهذه التجربة من قبل أثناء العمل على أمر أثار فضولك أو تطلب تركيزًا منك، فتيقن أن مثل هذه الحالة الذهنية عظيمة الفائدة للمبرمجين.
</p>

<h3 id="-">
	القراءة المسبقة
</h3>

<p>
	يغلب على الطلاب تأجيل العمل على المهام حتى اللحظة الأخيرة، فإذا كان عندك تكليف يوم الأحد فقد تطلع عليه صباح ذلك اليوم لأول مرة، لكن الأفضل أن تطلع على هذا التكليف بمجرد أن يطلب منك، حتى لو لم تكن لديك المعرفة الكافية لاستيعابه ولم تقرؤه بتركيز أو تعمق، بل من المفيد كذلك أن تعيد الاطلاع عليه بعد أن تكون قد أنهيت مهامًا غيره ولكن المهم أن يكون ذلك قبل الشروع في تنفيذ التكليف بأيام، وكل ذلك قراءة فقط دون أن تشغل بالك بكيفية الحل.
</p>

<p>
	فائدة هذه الطريقة أنها تهيئ عقلك بمعلومات عما ستعمل عليه فيما بعد، ومن المنطقي ألا تستوعب كل ما تقرؤه أو تحفظه، لكن عقلك سيعالج هذه البيانات في الخلفية، وسيسهل عليك التعامل مع التكليف أو المشروع نفسه عند الشروع في العمل عليه متبعًا الخطوات الأربع التي ذكرناها من قبل في حل المشاكل، وهي الفهم والتخطيط وكتابة الحل برمجيًا ثم مراجعته وتحسينه. ثمة أسلوب آخر ممتاز مشتق من هذه الطريقة، وهو أن تتم مرحلة الفهم للمشكلة في مرحلة مبكرة دون أن تشغل بالك بكيفية بتخطيط أو تنفيذ الحل نفسه.
</p>

<h3 id="-">
	نسخ الحلول الجاهزة
</h3>

<p>
	قد تواجهك عقبة ما أثناء العمل على مشروع أو تكليف فتبحث عن حل لها في المواقع المشهورة مثلما كان يحدث في Stackoverflow قبل تطور أدوات الذكاء الاصطناعي، أو باستخدام أدوات الذكاء الاصطناعي نفسها، وستجد الحل ببضع نقرات، فتريث هنا قبل أن تنسخ شيفرة أو حلًا إلى المشكلة التي بيدك، بل يفضل تجنب هذا السلوك بالمرة في بداية تعلمك أو بداية مسيرتك المهنية، ذلك أن الهدف الرئيسي هنا هو تطوير مهارات استثنائية في حل المشكلات ولن يفيدك النسخ واللصق في التعلم أبدًا، بل على النقيض يعود ذهنك استسهال الحلول الجاهزة دون إعمال العقل فيها.
</p>

<p>
	يوجد استثناء واحد لتلك القاعدة، حين تحاول حل المشكلة بنفسك، كما سنوضح في القسم التالي، ولكن حتى إذا وصلت إلى طريق مسدود ينبغي أن تكون مدركًا للشيفرة التي تنسخها وتستوعب وظيفتها وأداءها 100% قبل استخدامها.
</p>

<h3 id="-30-">
	قاعدة 30 دقيقة
</h3>

<p>
	قد يحدث أن تحاول الوصول إلى حل في مشكلة ما دون جدوى وعندئذ لا بأس من الاستعانة بأحد الحلول الجاهزة، لكن تأكد أنك قد حاولت أكثر من طريقة وقضيت فيها نصف ساعة على الأقل، يضرب برايان -كاتب الدليل- مثلًا على هذه النقطة بكتاب هيكلة وتفسير برامج الحاسوب The Structure and Interpretation of Computer Programs وهو أحد كتب تعلم البرمجة، إذ حدد لنفسه ست ساعات لحل كل مشكلة برمجية مذكورة في الكتاب لا ينظر إلى الحل فيها قبل نهاية ذلك الوقت.
</p>

<p>
	ويوضح أنه حتى لو لم يصل إلى الحل الصحيح فسيكون بنهاية الساعات الست قد بنى تصورًا ذهنيًا عما ينبغي أن يكون عليه الحل، هذا يعينه في الحكم على الحلول الجاهزة التي قد يجدها واختيار ما يصلح حالته منها، قارن ذلك بمحاولة البحث عن إجابة جاهزة باستخدام الذكاء الاصطناعي أو موقع stackoverflow دون أن يكون لديك تصور وفهم عميق للمشكلة التي تريد حلها، فلن يكون من السهل استيعاب المنطق وراء ذلك الحل الجاهز الذي تجده.
</p>

<p>
	وستدرك هنا أن المعاناة التي رأيتها أثناء محاولتك حل المشكلة بنفسك -حتى لو لم تصل إلى حل- ضرورية لبناء مهارة حل المشكلات لديك، لكن لا تطل البقاء في تلك المرحلة إذا وصلت لطريق مسدود، فلا بأس بطلب المساعدة من زميل أو معلم أو حتى ذكاءً اصطناعيًا إذا كان مناسبًا أو مسموحًا به في دراستك. ربما حان الوقت بعد هذا الجهد لقليل من التمشي في الهواء الطلق.
</p>

<h3 id="-">
	الخروج للتنزه
</h3>

<p>
	يمثل الخروج للتنزه قليلًا محاولة للتحرر من الطرق التي جربتها لحل المشكلة التي استعصت عليك، فقد تستغرق في تجربة حلول مختلفة لها لكنك تصل إلى نهايات مسدودة لأن عقلك يدور في نفس المسارات التي جربتها كأنها أخاديد أو آثار سير تركتها في المحاولات التي جربتها ولم تنجح، وستجد نفسك حبيس الأساليب التي جربتها من قبل دون أن تشعر مهما حاولت التفكير في تجربة حل مختلف، ونحن نقع في هذا كثيرًا بغض النظر عن المشكلة التي نحلها سواء برمجية أو غيرها، فقد تقول لنفسك "لو أني غيرت هذا الجزء أو تعديله قليلًا لربما نجح"، لكن دون جدوى.
</p>

<p>
	إذا وصلت إلى هذه المرحلة فجرب تغيير نشاطك إلى غيره مما لا يحتاج طاقة ذهنية، كأن تخرج للمشي قليلًا أو حتى الوقوف في شرفة أو أمام المبنى أو فوق السطح، واترك التفكير في حل المشكلة، وسترى أن هذه الخطوة على بساطتها ستعين عقلك على التحرر من أنماط التفكير التي علقت فيها، فلربما خطر على بالك الحل أو طريقة جديدة بمجرد أن تخطو خطوتين بعيدًا عن الحاسوب.
</p>

<h3 id="-">
	اشرح المشكلة لغيرك
</h3>

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

<p>
	والمثير للدهشة هنا أن الشخص الذي تحدثه قد لا يحتاج إلى أن ينطق بكلمة أصلًا، كما ذكرنا أنك تعيد المشكلة على سمعك أنت أولًا بصوت عالي كأنك تشرحها، فقد تكتشف تفصيلة هنا أو هناك غفلت عنها، أو ثغرة لم تنتبه لها من قبل، حيث يذكر برايان -كاتب الدليل- أن أحد زملائه أتاه يسأله عن حل مشكلة تواجهه، لكنه بمجرد أن وصل إليه وهم بالتحدث توقف لحظة ثم قال "لا عليك، عرفت الحل!"، فكفاه هنا أن يقوم من مكتبه ويتمشى قليلًا وهو يرتب المشكلة في ذهنه ليشرحها لصديقه.
</p>

<h3 id="-">
	تدوين الأسئلة
</h3>

<p>
	ستتعرض لنوعين من الأسئلة عند تعمقك في قراءة وصف مشكلة ما أو عند تعلم لغة أو أداة جديدة:
</p>

<ul>
	<li>
		أسئلة معيقة Blocking Questions: وهي الأسئلة التي تعيق تقدمك بحيث لا تستطيع إنجاز شيء آخر قبل معرفة إجابتها
	</li>
	<li>
		أسئلة غير معيقة: وهي أمور تثير فضولك أثناء عملك على المشكلة، لكن تستطيع مواصلة العمل دون معرفة إجابتها في الوقت الحالي
	</li>
</ul>

<p>
	قد تكون الأسئلة غير المعيقة على شاكلة هل تدعم هذه اللغة تفكيك الكائنات destructuring assignments؟ أو هل توفر هذه المكتبة أرقامًا عشوائية داخل نطاق أرقام صحيحة؟ أو ما هي بروتوكولات الشبكة الأخرى المدمجة في المكتبة القياسية؟ وهذه أسئلة لا تؤثر معرفة إجابتها على سير عملي الحالي لكنها قد تطرأ على ذهنك أثناء العمل فالأفضل تدوينها الآن ثم العودة لاحقًا للإجابة عنها.
</p>

<h3 id="-">
	بناء النسيج المعرفي
</h3>

<p>
	قد تشعر في بداية تعلمك للبرمجة أنك في وسط عالم شاسع غير مستكشف بعد، ربما تكون قد نجحت في طباعة عبارة Hello World! على الشاشة، لكن تشعر أنك بعيد عن المستوى الحقيقي لسوق العمل أو للمطور المحترف، فلا يدفعك هذا إلى اليأس بل إلى محاولة رسم تضاريس هذا العالم الجديد حيث ستكتشف وجود الدوال والمتغيرات وعمليات الإدخال والإخراج I/O، وستدرك كيف تترابط هذه العناصر ثم تتعرف إلى الشبكات وعلاقتها بنظم الإدخال والإخراج في نظم التشغيل.
</p>

<p>
	ومع اتساع هذه الخريطة المعرفية سترسم روابط بين هذه المفاهيم التي تتعلمها وتكتشف أن عالم تطوير البرمجيات مرتبط ببعضه ارتباطًا وثيقًا حيث تلاحظ تشابه العديد من المشكلات في هذه المفاهيم المختلفة مع بعضها، وستدرك مدى تأثير هذه المعرفة التي ستجمعها حين تتعرف على كثير من هذه المشكلات وتحاول إسقاطها على التحديات الجديدة التي تواجهك حيث ستلاحظ أنماط التشابه بين تلك التحديات الجديدة وبين ما تعرفت عليه أثناء دراستك للبرمجة، فترى أن مشكلة س تشبه ص التي رأيتها من قبل، فربما يصلح نفس أسلوب الحل معها كذلك. انظر المثال التالي:
</p>

<blockquote class="ipsQuote" data-gramm="false" data-ipsquote="">
	<div class="ipsQuote_citation">
		Quote
	</div>

	<div class="ipsQuote_contents ipsClearfix" data-gramm="false">
		<p>
			لديك مجموعة من 10 أشخاص مرقمين من 0 إلى 9، ويصطفون جميعًا أمام نافذة في بنك ما، تريد أن تحاكي عملية ترتيبهم عشوائيًا بدون تكرار في الوقت الفعلي O(n)‎، كيف تكتب شيفرة تحل هذه المشكلة؟
		</p>
	</div>
</blockquote>

<p>
	ربما تكون قد كتبت برنامجًا من قبل يخلط أوراق اللعب باستخدام خوارزمية Fisher-Yates، فهذه مشكلة قد تشبه التحدي الذي لدينا هنا، فربما نجرب إنشاء قائمة بهؤلاء الأشخاص من 0 إلى 9 ثم خلط هذه القائمة تمامًا كأوراق اللعب، فهي نفس المشكلة عند التفكر فيها.
</p>

<p>
	يميل المبرمجون المبتدئون إلى حل المشكلات التي يواجهونها من خلال المنطق والاستنتاج المحض، وكذلك يفعل المطور الخبير أيضًا إلا أنه يسأل نفسه "ما هو أفضل نمط برمجي أعرفه ويحل هذا النوع من المشكلات؟"، فهو يعتمد على النسيج المعرفي الذي تكون لديه من تراكم خبرة سنوات من البرمجة، فحري بك أن تبحث من الآن عن الطرق التي تترابط بها المفاهيم التي تتعلمها كي تستطيع استخدامها في المستقبل حين تواجهك مشكلة تستعصي عليك في الحل.
</p>

<h3 id="-">
	مراجعة الشيفرات البرمجية
</h3>

<p>
	قد يصعب على البعض عرض شيفراتهم البرمجية على غيرهم من المبرمجين لطلب نصيحة فيها أو مراجعتها لتحسين كفاءتها، ذلك أن من السهل جدًا الانزلاق إلى أخذ النصائح على محمل شخصي مما يمنع الاستفادة منها، لكن مقاومة هذا الشعور مفيد لتطوير مهاراتك البرمجية.
</p>

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

<p>
	أما إذا لم تجد مبرمجًا حولك يراجع شيفرتك فمن الممكن عرضها على أداة ذكاء اصطناعي لمراجعتها، وإذا طُلب منك مراجعة شيفرة برمجية فلا تتردد حتى لو لم تكن خبيرًا باللغة التي كتبت بها، فمجرد قراءة ما كتبه غيرك يعرض لأساليب برمجة وأنماط خوارزمية قد لا تكون تعرضت لها من قبل.
</p>

<p>
	لكن تذكر النصيحة التي قدمناها من قبل في هذا الدليل حول تكوين رأي مستقل وإسقاطها على هذه النقطة هنا، بأن يبني رأيك الخاص حول الملاحظات التي تتلقاها وتحكم وفقًا لمنطقك والبيانات التي لديك حول المشكلة، فأنت صاحب القرار الأخير في تعديل شيفرتك وفقًا لهذه الملاحظات وإضافتها إلى الشيفرة النهائية للبرنامج.
</p>

<h3 id="-">
	الانضمام إلى المجتمعات البرمجية
</h3>

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

<p>
	ومن أهم الفوائد الأخرى لهذه المجتمعات هو بناء شبكة علاقات قوية، فكثير من هذه المجتمعات تعقد مؤتمرات إما على الأرض أو على الإنترنت تستطيع حضورها أو حتى التحدث فيها، فهذا يخفف وطأة الشعور بالوحدة من ناحية، ويبني جسورًا من العلاقات المهنية في نفس مجالك تفيدك في مسيرتك المهنية مستقبلًا.
</p>

<h2 id="-debugging">
	تنقيح الأخطاء البرمجية Debugging
</h2>

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

<h2 id="-">
	النموذج الذهني
</h2>

<p>
	يقصد بالنموذج الذهني هنا أن تستطيع كمطور أن تتخيل ما سينتج عن الشيفرة التي تقرؤها، مثال:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7461_24" style=""><span class="kwd">def</span><span class="pln"> foo</span><span class="pun">(</span><span class="pln">n</span><span class="pun">):</span><span class="pln">
    i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pln">

    </span><span class="kwd">while</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> n</span><span class="pun">:</span><span class="pln">
        i </span><span class="pun">=</span><span class="pln"> i </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="pun">(</span><span class="pln">i </span><span class="pun">%</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">i</span><span class="pun">)</span><span class="pln">

foo</span><span class="pun">(</span><span class="lit">5</span><span class="pun">)</span></pre>

<p>
	تأمل شيفرة بايثون أعلاه وحاول محاكاتها في عقلك واستنتاج مخرجاتها ثم تحقق إن كنت محقًا في استنتاجك، واعلم أن هذه المهارة من الأهمية بمكان بحيث إن لم تستطع محاكاة تشغيل الشيفرة في رأسك فلن يسهل عليك تنقيح الأخطاء البرمجية، فالتنقيح هو فن العثور على الموضع من الشيفرة الذي تنفصل فيه محاكاتك العقلية لها عند تشغيلها عما تفعله الشيفرة في تشغيلها الفعلي، ثم إصلاح ذلك الموضع، ومن ثم فإن لم يكن لديك نموذج ذهني جيد فلن تجد ما تقارن به ما يحدث أمامك على الشاشة. إليك كيفية تطوير هذا النموذج الذهني:
</p>

<ul>
	<li>
		دراسة الشيفرات: توجد ملايين الشيفرات البرمجية على مواقع مثل Github و HackerRank، إضافة إلى شيفرات زملائك أو حتى الشيفرات التي كتبتها منذ مدة ونسيت كيفية عملها، فكل ذلك يمكن استغلاله في عملية التعلم وتطوير قدرتك على محاكاة تشغيل الشيفرة ذهنيًا.
	</li>
	<li>
		توقع المخرجات: حاول أثناء دراسة الشيفرة أن تتوقع كيف ستتصرف عند تشغيلها.
	</li>
	<li>
		تتبع مسار الشيفرة يدويًا: استخدم سبورة بيضاء لتتبع القيم التي تأخذها المتغيرات والدوال التي تُستدعى، والسطر البرمجي الذي يُنفذ حاليًا.
	</li>
	<li>
		كتابة المواصفات التقنية: اختر شيفرة وادرسها ثم اعكس هندستها، أي اكتشف وظيفتها ثم اكتب وصفًا يشرح الخوارزمية بدقة تجعل من يقرؤها قادرًا على إعادة برمجتها من الصفر.
	</li>
	<li>
		خاصية الخطوة الواحدة: اجعل الحاسوب يريك مسار تدفق البرنامج خطوة بخطوة باستخدام أداة التنقيح Debugger، سنتحدث عن هذه بالتفصيل لاحقًا.
	</li>
</ul>

<p>
	إذا اتبعت هذه الخطوات مرة بعد مرة ستتطور لديك مهارة المحاكاة الذهنية.
</p>

<h3 id="-">
	إعادة إنتاج الخطأ
</h3>

<p>
	يجب أن نتحقق أولًا إذا كنا نستطيع جعل الخطأ يتكرر حدوثه بانتظام من أجل فهم سبب حدوثه ومن ثم معرفة كيفية إصلاحه، تسمى هذه العملية أحيانًا repro كاختصار إلى الكلمة الإنجليزية reproduction أو إعادة إنتاج الخطأ، وهي أول خطوة نحو إصلاح الخطأ، بل قد تكون أحيانًا هي الجزء الأصعب إذ تكون أنت الذي رأيت الخطأ يحدث مرة واحدة فقط -أو أبلغ شخص عن حدوثه معه- لكن لم يتكرر مرة أخرى ولا تستطيع إعادة إنتاجه.
</p>

<p>
	فهنا قد ترجو أن يكون ما رأيته مجرد سراب أو ألا يظهر هذا الخطأ مرة أخرى، لكن الواقع يقول أنه إذا رأى شخص واحد فقط هذا الخطأ أثناء اختبار البرنامج فسيظهر لآلاف أو ملايين الأشخاص حتمًا بعد إطلاق البرنامج رسميًا، ربما يفسر هذه الظاهرة كل من الإحصائيات وقانون ميرفي، حيث ينص قانون ميرفي على أن الأخطاء التي يحتمل حدوثها ستقع لا محالة.
</p>

<p>
	أما إذا لم تستطع إعادة إنتاج الخطأ فستكون كل محاولات إصلاحك كمن يطلق النار في الظلام أملًا أن يصيب هدفه، لهذا يجب أن تجعل الخطأ يتكرر بأي ثمن، فعد إلى الوراء قليلًا وفكر بمنطقية: ما الذي يمكن أن يكون قد حدث أثناء تشغيل البرنامج لكي يقع هذا الخطأ، ولا تستبعد أي شرط أو عامل يمكن أن يكون سببًا في حدوث الخطأ حتى لو بدا مستحيل الحدوث، فطالما أن الخطأ قد وقع وهذا الشرط لازم لحدوثه فلا بد أن يكون ذلك الشرط قد تحقق، فلا تتوقف عن تتبع هذا التسلسل المنطقي حتى تصل إلى النقطة التي تنفصل فيها محاكاة تشغيل البرنامج التي تصورتها في عقلك عن سير التشغيل الفعلي للبرنامج، فهي النقطة التي حدث عندها ذلك الخطأ.
</p>

<p>
	وقد يحدث أن تتمكن من إعادة إنتاج هذا الخطأ بشكل متقطع، أي قد يحدث بضع مرات فقط لكن ليس كل مرة يعمل فيها البرنامج، فهذا أفضل من لا شيء لكن سيحتاج الأمر جهدًا منك حتى تصل إلى إعادة إنتاج ثابتة للخطأ Consistent repro كي توفر على نفسك وقتًا طويلًا تحاول الإمساك بخطأ نادر الحدوث.
</p>

<p>
	فإذا توصلت إلى جعل الخطأ يحدث في كل مرة تشغيل للبرنامج استطعت الانتقال إلى طريق أكثر منهجية في إصلاحه بأن تبحث عن الحد الأدنى من الخطوات التي تؤدي إلى حدوثه، فعلى سبيل المثال إذا كنت تلعب لعبة ووجدت خطأ يظهر بعد إكمال 20 لفة حول مضمار سباق والاصطدام بشجرة، فينبغي أن تجرب الاصطدام بالشجرة أولًا فربما يحدث الخطأ فتعرف أن المشكلة في الشجرة وتوفر على نفسك عناء تنفيذ 20 لفة قبل أن ترى الخطأ، أما إذا لم يحدث شيء فتعلم أن هذه اللفات العشرين جزء من المشكلة، فتجرب حينها لفة واحدة ثم تصطدم بالشجرة وترى هل يتكرر الخطأ أم لا.
</p>

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

<h3 id="-">
	العثور على الخطأ
</h3>

<p>
	لنفترض الآن أنك عرفت بوجود خطأ ما لأنك تحصل على مخرجات لم تكن تتوقعها من المدخلات التي أعطيتها لشيفرتك، لكن كل ما تعرفه إلى الآن هو أن هناك خطأ ما بين 10000 سطر برمجي وأنك إذا أعطيت الشيفرة الرقم 2 ينبغي أن تخرج لك الرقم <code>3490</code> لكنها أعطتك <code>299792458</code> بدلًا منها، إذًا لا بد أن يكون الخطأ في مكان ما بين المدخلات والمخرجات.
</p>

<p>
	قد تغريك فكرة البدء بتغيير أشياء عشوائيًا هنا وهناك في الشيفرة لترى إن كانت ستصلح نفسها، يسمى هذا أحيانًا بتنقيح الطلقات العشوائية shotgun debugging ويندر أن ينجح، بل قد تزيد الطين بلة وتصعب على نفسك عملية العثور على الموضع الأصلي للخلل، كأنك تحاول إصلاح عطلًا كهربائيًا في سيارتك من خلال قص أسلاك وتوصيل أسلاك أخرى عشوائيًا، هل ترى أن هذا قد يصلح السيارة أم يزيدها تعطلًا؟
</p>

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

<p>
	ابدأ بفحص نقاط مختلفة في البرنامج لتحدد النقطة التي انحرف فيها القطار عن مساره الصحيح، ومن المفيد هنا اتباع تقنية البحث الثنائي Binary Search، بمعنى أن تختار نقطة تتوسط المسار الزمني لتنفيذ البرنامج وتفحص القيم الموجودة هناك أثناء تشغيل البرنامج، فإذا كانت سليمة علمت أنه لا توجد مشكلة إلى هذه النقطة، ولا بد أن تكون المشكلة بين هذه النقطة ونهاية البرنامج، وهكذا تكرر هذه العملية حتى تضيق نطاق البحث إلى أن ترى الخطأ.
</p>

<p>
	يزعم برايان -كاتب الدليل- أن الخطأ لا يكون مكتشَفًا حتى تفهم سلوكه فهمًا تامًا، أي عندما تفهم كيف أنتج برنامجك القيمة <code>299792458</code> بدلًا من <code>3490</code>، ثم تصلح الخطأ جذريًا وفقًا لهذا الفهم. وهذا الأسلوب له عدة فوائد منها مثلًا:
</p>

<ul>
	<li>
		تصبح أكثر ثقة أنك أصلحت الخطأ إصلاحًا جذريًا
	</li>
	<li>
		تتعلم كيفية تمييز الأنماط التي أدت إلى حدوث هذا الخطأ لتتجنبها في المستقبل أو تدركها مبكرًا
	</li>
	<li>
		تصقل مهارتك في حل المشكلات أثناء معالجتك للخطأ وفهمه
	</li>
</ul>

<p>
	وإذا كانت وظيفتك هي اكتشاف الخطأ فقط وتقديم تقرير عنه ليصلحه غيرك فإن تتبع الخطأ وفهم سلوكه يجعلك أقدر على تقديم الحد الأدنى من الخطوات المطلوبة لإعادة إنتاج الخطأ، وهذا يزيد أهميتك عند من تكتب له هذا التقرير لأنك تسهل عليه مهمة إيجاد الخطأ وإصلاحه.
</p>

<h3 id="-">
	التنقيح باستخدام أوامر الطباعة
</h3>

<p>
	تعتمد طريقة التنقيح بأوامر الطباعة Print Debugging أو printf debugging كما يسميها مبرمجو لغة C على توزيع أوامر الطباعة توزيعًا مدروسًا داخل الشيفرة لمعرفة حالة البرنامج في لحظة معينة، وهي الطريقة الكلاسيكية لفحص البرمجيات أثناء التشغيل ويكثر استخدامها في حالتين مشهورتين:
</p>

<ol>
	<li>
		التحقق من مسار التدفق: حيث نجعل البرنامج يطبع أي شيء في لحظة ما لنرى إن كانت الشيفرة تعمل عند ذلك الجزء.
	</li>
</ol>

<p>
	 
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7461_28" style=""><span class="kwd">print</span><span class="pun">(</span><span class="str">"A"</span><span class="pun">)</span><span class="pln">

x </span><span class="pun">=</span><span class="pln"> foo</span><span class="pun">()</span><span class="pln">

</span><span class="kwd">print</span><span class="pun">(</span><span class="str">"B"</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">if</span><span class="pln"> x </span><span class="pun">==</span><span class="pln"> </span><span class="lit">3</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"C"</span><span class="pun">)</span><span class="pln">
    x </span><span class="pun">*=</span><span class="pln"> </span><span class="lit">2</span><span class="pln">
</span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"D"</span><span class="pun">)</span><span class="pln">
    bar</span><span class="pun">()</span><span class="pln">

</span><span class="kwd">print</span><span class="pun">(</span><span class="str">"E"</span><span class="pun">)</span></pre>

<p>
	لاحظ هنا أننا نستطيع عند تشغيل البرنامج تحديد المسافة التي وصل إليها قبل أن ينهار crash ومعرفة إذا كانت قيمة <code>x</code> تساوي <code>3</code> أم لا بناء على المحارف المطبوعة على الشاشة.
</p>

<p>
	      2. فحص القيم: بأن نطبع قيم متغيرات معينة لرؤيتها ومعرفة ما هي، في المثال أدناه نحصل على بيانات من مستشعر sensor داخل حلقة تكرارية، ونشك أن بعض           هذه البيانات مغلوطة لكن لا نعرف إن كان المستشعر هو السبب فهنا نطبع البيانات على الشاشة لنرى ما نحصل عليه:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7461_30" style=""><span class="kwd">while</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> done</span><span class="pun">:</span><span class="pln">
    data </span><span class="pun">=</span><span class="pln"> get_sensor_data</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">f</span><span class="str">"Got sensor data: {data}"</span><span class="pun">)</span><span class="pln">

    process_sensor_data</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln">

    done </span><span class="pun">=</span><span class="pln"> data </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">0</span></pre>

<p>
	هنا تجدر الإشارة إلى نصيحة أخلاقية وعملية، لا تستخدم ألفاظًا نابية في عبارات التنقيح داخل شيفرتك، فهي سلوك غير لائق فضلًا عن أنك ستنسى إزالتها بعد إنهاء العمل على البرنامج وستراها تظهر أثناء عرض تجريبي مع مديرك أو العميل الذي طلب البرنامج، وستتمنى حينها لو أنك لم تكتبها أصلًا.
</p>

<p>
	وإن كانت حالة الانزعاج التي تحدث أثناء تنقيح الأخطاء والبحث عنها متفهمة لكن احرص ألا يخرج هذا إلى الشيفرة، بل استبدلها برمز تعبيري أو حتى حرفًا مكررًا كأن تطبع شيئًا كهذا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7461_32" style=""><span class="kwd">print</span><span class="pun">(</span><span class="str">"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"</span><span class="pun">);</span></pre>

<p>
	فهي جملة تظهر بوضوح على الشاشة وإذا رآها العميل فلن تعدو كونها أكثر من مجرد هفوة أو خطأ كتابي لا أكثر.
</p>

<p>
	بالعودة إلى التنقيح بالطباعة، يرى البعض أن هذا الأسلوب بدائي مقارنة باستخدام منقح حديث كما سنرى في القسم التالي، لكنك كمبرمج ستلجأ إلى هذا الأسلوب عاجلًا أو آجلًا، ويظهر تميزها في حالتين:
</p>

<ul>
	<li>
		حين تريد جمع بيانات كثيرة لملاحظة نمط عام يظهر على المدى الطويل.
	</li>
	<li>
		حين تريد اكتشاف خطأ نادر يحدث مرة كل 10000 مرة تشغيل مثلًا، لأن تتبع الخطأ بالمنقح سيأخذ وقتًا طويلًا جدًا، على عكس طريقة التنقيح بالطباعة التي تستطيع بها إضافة بعض تعليمات الطباعة وسكربت يشغل البرنامج 10000 مرة وتراقب المخرجات لترى متى يحدث الخطأ.
	</li>
</ul>

<p>
	لكن ينبغي هنا أن ننبه على صعوبة قراءة المخرجات بصريًا إن كانت كثيرة جدًا، وقد تضيع رسائل الخطأ الحقيقية وسطها، فهنا ينبغي توجيه المخرجات إلى ملف نصي لتفتحه بمحرر نصوص ليسهل البحث عليك، ثم لا تنسى إزالة تعليمات الطباعة قبل تسليم النسخة النهائية من البرنامج.
</p>

<h3 id="-debuggers">
	المنقحات Debuggers
</h3>

<p>
	المنقحات هي أدوات تساعدك على العثور على الأخطاء البرمجية، وتشترك جميعها رغم تعدد أنواعها في مزايا جوهرية:
</p>

<ul>
	<li>
		إضافة نقاط التوقف breakpoints التي تأمر البرنامج أن يتوقف عندها لتتحكم أنت في عملية التنقيح
	</li>
	<li>
		التنفيذ خطوة خطوة Single step الذي يمرر البرنامج سطرًا واحدًا في كل مرة
	</li>
	<li>
		فحص قيم المتغيرات
	</li>
</ul>

<p>
	إضافة إلى مزايا أخرى تشمل ما يلي:
</p>

<ul>
	<li>
		الدخول إلى الدالة Step into لتتبع ما يحدث فيها
	</li>
	<li>
		الخروج من الدالة Continue out للرجوع إلى الشيفرة المستدعاة بعد فحص الدالة
	</li>
	<li>
		تجاوز الدالة Step over لتنفيذ الدالة كاملة والانتقال لما بعدها دون دخول تفاصيلها
	</li>
	<li>
		ضبط قيم المتغيرات، حيث تستطيع تعديل البيانات أثناء التشغيل لتجربة سيناريوهات مختلفة
	</li>
	<li>
		فحص مكدس الاستدعاءات Call Stack لمعرفة تسلسل الدوال التي أدت إلى الوصول إلى النقطة الحالية
	</li>
	<li>
		نقاط التوقف المشروطة التي لا توقف البرنامج إلا عند تحقق شرط معين
	</li>
</ul>

<p>
	توجد منقحات أخرى تسمى منقحات "السفر عبر الزمن" Time-travel debuggers وإن كانت نادرة، وهي تسمح لك بالذهاب لنقطة متقدمة في شيفرتك أو الرجوع إلى الخلف فيها، وهذا مفيد جدًا إذا تجاوزت النقطة التي حدث عندها الخطأ وأردت العودة إليها.
</p>

<p>
	توفر جميع بيئات التطوير المتكاملة إمكانية التنقيح فيها إضافة إلى وجود منقحات مستقلة لأغلب لغات البرمجة، وتلك المزايا التي في المنقحات تجعل منها أدوات لا غنى عنها للمبرمجين، فما عليك مثلًا إذا شككت في وجود خطأ في الدالة <code>foo()‎</code> سوى أن تضع نقطة توقف عندها وتشغل الشيفرة وستقف عند تنفيذ هذه الدالة ثم تتولى أنت التحكم يدويًا في المنقح وتفحص المتغيرات سطرًا بسطر دون الحاجة لإضافة تعليمات طباعة.
</p>

<h2 id="-">
	تعلم لغات برمجية جديدة
</h2>

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

<p>
	نتحدث في هذا القسم عن كيفية تعلم لغات إضافية بعد إتقان اللغة الأولى إذ ستحتاج إلى ممارسة هذا النشاط طول مسيرتك المهنية، لكن بما أن تعلم اللغات مهارة في ذاته فكلما تعلمت لغات أكثر سهلت عليك عملية التعلم نفسها.
</p>

<p>
	وهناك ركنان أساسيان ينبغي اعتبارهما عند تعلم لغة جديدة لها نمط paradigm تعرفه مسبقًا، كأن تكون لغة إجرائية procedural أو كائنية التوجه object oriented أو وظيفية functional:
</p>

<ol>
	<li>
		تعلم القواعد syntax مثل كيفية كتابة تعليمات <code>if</code> و <code>while</code> وكيفي التصريح عن المتغيرات والدوال
	</li>
	<li>
		تعلم المكتبات القياسية، وهي الوظائف المدمجة التي تستطيع استخدامها كقراءة الملفات وكتابتها أو الطباعة على الشاشة أو الاتصال بخادم ويب
	</li>
</ol>

<p>
	يكون تعلم القواعد هو الجزء الأسهل في الغالب لأن معظم لغات البرمجة تبنى على قواعد بسيطة نسبيًا، لكن لا زال عليك تعلم الكثير من وظائف المكتبات القياسية وغيرها لكي تكون محترفًا، تمامًا كما أن معرفة الأفعال والأسماء والصفات وإعراب الجمل في اللغات البشرية لا يكفي لأن تكتب رواية أو شعرًا.
</p>

<h3 id="-">
	تعلم قواعد اللغة البرمجية
</h3>

<p>
	أفضل طريقة لتعلم قواعد اللغة هي اقتحام المجال مباشرة بأن تتابع درسًا تعليميًا وتكتب برنامج وإن كان لتوضيح المثال فقط، أي تتمرن بها على جانب ما من اللغة التي تتعلمها، على سبيل المثال، كيف تصيغ التعليمات الشرطية في لغة Rust؟ لم لا تكتب برنامجًا بسيطًا للتدرب على هذا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7461_34" style=""><span class="pln">fn main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="lit">1</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        println</span><span class="pun">!(</span><span class="str">"هناك خطأ فادح"</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        println</span><span class="pun">!(</span><span class="str">"هذا صحيح"</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	فتمرين بسيط كهذا يجعلك تتفكر إن كانت هذه الأقواس حول تعليمة <code>if</code> ضرورية، دعنا نتحقق:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7461_36" style=""><span class="pln">$ rustc foo</span><span class="pun">.</span><span class="pln">c
  warning</span><span class="pun">:</span><span class="pln"> unnecessary parentheses around </span><span class="pun">`</span><span class="kwd">if</span><span class="pun">`</span><span class="pln"> condition</span></pre>

<p>
	تبين أنها ليست ضرورية! هذا هو الهدف من التعلم بمثل هذه التمارين البسيطة، من المفيد أيضًا أن تبحث عن كيفية تطبيق المفاهيم التي عرفتها من لغتك الأولى في اللغة الجديدة التي تتعلمها، مثل الدالة الرئيسية والمتغيرات والتعليمات الشرطية وحلقات التكرار والأصناف Classes وغيرها.
</p>

<p>
	قد تجد نفسك تبحث عن كل صغيرة وكبيرة في البداية، تمامًا كما لو كنت تتعلم لغة بشرية جديدة وأردت الذهاب إلى المتجر، صحيح أنك تعلم ما تريد وكيف تقوله بلغتك، لكن ستحتاج إلى البحث عن الكلمات المرادفة في اللغة الجديدة إذا لم تكن تعرفها بعد، الجميل هنا أن قواعد لغات البرمجة أبسط كثيرًا من اللغات البشرية ويمكن إتقانها بسرعة نسبيًا.
</p>

<h3 id="-">
	تعلم المكتبات القياسية
</h3>

<p>
	تأتي المكتبة القياسية مدمجة في لغة البرمجة نفسها، وتحتوي على وظائف جاهزة للاستخدام لتوفير الوقت والجهد من ناحية ولتضمن وجودها عند أي شخص لديه هذه اللغة مثبتة على حاسوبه، لكن ينبغي أن تعلم محتويات هذه المكتبة كي تعرف ما تستطيع اللغة إنجازه فور تثبيتها ولا تضيع وقتك في إعادة اختراع العجلة دون داعي.
</p>

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

<p>
	يميل برايان -كاتب الدليل- إلى تعلم المكتبات تدريجيًا إذ يتعلم بالتفصيل ما يحتاجه لإنجاز مهمة محددة أما بقية الوظائف فيكفيه عرفة وجودها بما أنه تصفح وثائق المكتبة واللغة، لكن لا يتقنها وإنما يتعلم ما يكفيه أن يستطيع استخدامها فقط.
</p>

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

<h3 id="-">
	تعلم نموذج برمجي جديد
</h3>

<p>
	النموذج البرمجي programming paradigm هو طريقة تصف المشكلة وفقًا لنموذج بحيث يمكن الوصول إلى حل لها بسهولة، على سبيل المثال إذا كنت تملأ استمارة ضريبية فستجد نفسك تتبع سلسلة من الخطوات المرتبة، تكتب اسمك ثم دخلك، وإذا كان الدخل أكبر من قيمة معينة فستفعل كذا، وإلا فافعل كذا، فما هي إلا إجراءات تتبعها لنمذجة المشكلة في سلسلة من الخطوات التي يسهل حلها بها.
</p>

<p>
	لنفرض الآن أنك تحاكي عالم افتراضي ثلاثي الأبعاد، قد يكون لدينا في هذا العالم نوع من الكائنات يسمى غول orc يتجول في أرجاء هذا العالم، لكل واحد منها إحداثياته الخاصة ونقاط الحياة الخاصة به، لكنها جميعًا تتبع نفس السلوك عندما يقترب اللاعب منها، فهنا نستطيع نمذجة المشكلة كمجموعة من الكائنات المستقلة التي تشترك في سلوكيات معينة.
</p>

<p>
	وتوجد طريقتان مختلفتان لحل المشكلة، إما بنمذجتها كسلسلة خطوات أو ككائنات، ونسمي هذه النماذج المختلفة نماذج برمجية، فالنوع الأول -أي الخطوات المتسلسلة- نسميه البرمجة الإجرائية Procedural Programming أما النوع الثاني فنسميه البرمجة كائنية التوجه Object-Oriented Programming. صحيح أن هناك نماذج غيرها لكن أشهرها هي البرمجة الكائنية والإجرائية والوظيفية functional.
</p>

<p>
	لكن انتبه إلى أن تعلم نموذج برمجي جديد والانتقال إليه أصعب كثيرًا من تعلم لغة برمجية تتبع نفس النموذج، وبالتشبيه باللغات البشرية مرة أخرى ستجد أن الانتقال من الإسبانية إلى الإيطالية سهل نسبيًا لجذورهما اللاتينية، لكن تعلم اللغة الصينية مثلًا أمر مختلف كليًا إذ يتعين عليك تعلم تقنيات ومفاهيم قد لا تكون مدركًا لوجودها أصلًا.
</p>

<p>
	يذكر برايان -كاتب الدليل- أنه تعلم لغة Erlang وهي لغة وظيفية، مع ضعف مستواه في هذا النموذج، حيث لا تتغير قيمة متغير بعد تحديده أبدًا، وهذا كان خلاف ما تعود عليه من قبل، لكن الواقع يقول أن هذه اللغة قد بنيت عليها أنظمة ضخمة وناجحة، لذا تحتم عليه تغيير طريقة تفكيره في كيفية نمذجة المشكلات. النصيحة الأساسية هنا هي التعرض لأمثلة كثيرة لترى كيف تنفذ هذه اللغة المهام البسيطة، أي تحاول التمرن على برامج صغيرة وإن كانت بقصد التجربة كما شرحنا أول الفصل، وأن تحاول ابتكار تحديات متعلقة بهذا النموذج تم تحاول حلها، تستطيع طلب ذلك من أدوات الذكاء الاصطناعي كذلك، فهذا مفيد في بناء مهاراتك واكتشاف الثغرات في فهمك للنموذج.
</p>

<h2 id="-">
	استخدام الذكاء الاصطناعي
</h2>

<p>
	يُعد الذكاء الاصطناعي هذه الأيام الحدث الأبرز على الساحة التقنية إذ يستخدمه الجميع تقريبًا ولا تكاد تجد أحدًا لا يتحدث عنه أو يحاول إقحامه فيما يفعل، لكن يبدو ألا أحد يستطيع الإجابة على سؤال ماذا سيحدث في المستقبل كنتيجة لهذه الموجة الجديدة من التقنية، قد يحلو لمدراء شركات الذكاء الاصطناعي التصريح بأن الذكاء الاصطناعي في طريقه للسيطرة على العالم وحل كل المشكلات التي تعاني منها البشرية وستقل الحاجة إلى الأيدي العاملة عن السابق.
</p>

<p>
	لكن الغالب أن هذا لن يحدث حتى لو استطاع الذكاء الاصطناعي حقًا أن يحل مشاكلنا، إذ سيظل البشر يحاولون تخطي حدود الممكن بالنسبة لهم إلى مستويات تصعب على الذكاء الاصطناعي.
</p>

<p>
	ما أهمية مثل هذا الحديث لك كطالب تدرس علوم الحاسوب حاليًا؟ سنوضح في هذا الفصل كيفية استخدام الذكاء الاصطناعي كمعين لك في دراستك ثم كيفية استخدامه في بيئة العمل، وكذلك ما يجب تجنبه عند استخدامه.
</p>

<h3 id="-">
	ما يجب تجنبه مع الذكاء الاصطناعي
</h3>

<p>
	يذكر برايان -كاتب الدليل- أنه يحب دراسة كتاب Structure and Interpretation of Computer Programs لتطوير قدراته البرمجية وأنه يعطي نفسه ست ساعات لحل كل مشكلة في ذلك الكتاب، لاحظ أن المشكلات التي نتحدث عنها هنا ليست مشكلات حقيقية بل تدريبية مصطنعة، وحلولها متوفرة مجانًا على الإنترنت، فلماذا يقضي ست ساعات في حلها إذًا، ألا يبدو هذا مضيعة للوقت في حين كان يستطيع نسخ مستودع repo كتبه شخص آخر أو ينسخ الحل من مكان ما أو ربما يدفع لمبرمج كي يحل له هذه المشاكل؟
</p>

<p>
	لا شك أن الإجابة بديهية هنا إذ أن قضاء هذا الوقت في التفكير للوصول إلى حل المشكلة هو محل التعلم بعد المحاولات المضنية التي تجتهد فيها في الوصول إلى الحل، وبالمثل فطلبك من الذكاء الاصطناعي حل المشكلة التي لديك لن يطور مهاراتك أبدًا، فهو يشبه أنك طلبت من مبرمج غيرك أن يحل لك المشكلة بدلًا عنك، أو استخدامك لإنسان آلي يرفع الأثقال عنك في الصالة الرياضية، صحيح أنك أتممت التمارين بنجاح لكنك لم تستفد بثانية واحدة منها.
</p>

<p>
	قد يتحجج البعض أن بعض الأنشطة في التمارين لا يوجد مثلها في الحياة الواقعية، فما هو النشاط الحقيقي في الحياة الذي يتطلب منك رفع الأثقال طول اليوم أو بالطريقة التي تتمرن بها في الصالة الرياضية؟ لا يوجد شيء كهذا في الواقع، لكنها أدوات وأساليب تستخدمها لتحقيق هدف أسمى من مجرد تأدية النشاط، وهو أن يكون جسدك أقوى وأن تصير أصح بدنًا وأكفأ في ممارسة نشاطاتك اليومية لسنوات وسنوات، وهذا نفسه ما يحدث في المدرسة فما المشاكل والمسائل التي يطلب منك حلها إلا أثقال غير واقعية صممت لتستخرج أقصى ما عندك من طاقة لتصل حعلى القوة اللازمة لأداء الوظائف الحقيقية في سوق العمل.
</p>

<p>
	ولأن هذه المشكلات غير حقيقية فيستطيع الذكاء الاصطناعي حلها كلها بسهولة، فقد تدرب على أطنان من البيانات ليصير بهذا المستوى، لكن لا يخدعك هذا فتظن أنه يستطيع حل المشاكل الواقعية التي تقابلها في عملك، على الأقل إلى الآن، من يدري، فلو كان يستطيع حلها لما كانت أجور المطورين عالية، ذلك أن حل المشاكل التي تواجهك في العمل برمجيًا أمر ليس باليسير ولا يمكن التحايل عليه بكتابة سؤال لذكاء اصطناعي، وإلا لصارت أجور المطورين زهيدة لا تتعدى الحد الأدنى للرواتب، فشاهد هذه الفقرة أنك لن تصل إلى وظيفة براتب عالي إذا كان كل ما تفعله هو سؤال الذكاء الاصطناعي.
</p>

<p>
	من ناحية أخرى فهذا لا يعني ألا تتقن استخدام هذه الأدوات، فلا بأس من استخدامه بالطريقة الصحيحة التي تفيدك في دراستك وعملك ولا تؤثر على إدراكك ومهاراتك، فمثلًا لا تطلب منه تنفيذ المشروع المطلوب منك بالكامل، فلن تتعلم شيئًا حتى لو استطاع تنفيذه، فالهدف من المشروع أثناء الدراسة هو التمرين الذهني الذي تمارسه أثناء العمل عليه بنفسك.
</p>

<h3 id="-">
	الاستخدام الصحيح للذكاء الاصطناعي في الدراسة
</h3>

<p>
	قبل أن نشرح الكيفية المناسبة لاستخدام الذكاء الاصطناعي يجب أن نوضح أنه إذا كانت جامعتك أو معلمك يحظرون استخدام الذكاء الاصطناعي فالتزم بهذا وتجاهل ما نذكره هنا، أما على فرض أنك تتعلم ذاتيًا أو كان استخدام الذكاء الاصطناعي مسموحًا به فاتبع ما يلي.
</p>

<p>
	أولًا ينبغي أن تستخدمه كأنه مدرب خاص يعرف إلى حد كبير العديد من جوانب المهارة التي تتعلمها، لكنه قد يخطئ أو يقدم نصيحة سيئة من وقت لآخر، لذا يجب أن تنتبه إلى كل ما يصدر منه لتنتقي ما تستطيع تعلمه. إليك بعض الأمثلة التي تستطيع اللجوء فيها إلى الذكاء الاصطناعي:
</p>

<ul>
	<li>
		النقاط الصغيرة: جزء بسيط من المشروع وقفت عنده أو قاعدة معينة نسيتها
	</li>
	<li>
		الطرق النموذجية: الطريقة النموذجية لكتابة حلقة تكرارية تحذف عناصر مثلًا من مصفوفة
	</li>
	<li>
		استخدام الأدوات: وظيفة معامل معين operator أو كيفية استخدامه
	</li>
	<li>
		استكشاف اللغات والمكتبات: أسئلة حول مكتبات أو لغات برمجية تعرضت لها أو تريد العمل بها أو استخدامها
	</li>
</ul>

<p>
	مثل هذه الأسئلة الصغيرة أمثلة ممتازة على تطعيم عملية الدراسة بإجابات كنت ستقضي وقتًا أطول في التوصل إليها باستخدام محركات البحث العادية.
</p>

<p>
	كذلك يُستخدم الذكاء الاصطناعي في تقديم تلميحات عن الحل لمساعدتك في الوصول إليه، كأن تعطيه جزءًا من الشيفرة كدالة أو نحوها ثم تطلب تخبره بوجود خطأ في هذه الشيفرة، لكن تطلب منه أن يعطيك تلميحات فقط عن الإجابة أو كيفية التفكير في المشكلة لتجد الحل بنفسك.
</p>

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

<p>
	بقي أن نذكر الحالة الأخيرة التي يكون مقبولًا فيها استخدام الذكاء الاصطناعي، وهو حين يسمح لك المعلم بهذا إن كنت في دراسة منتظمة، فقد يريد تدريبك على ممارسة واقعية بدلًا من بيئة التدريب، وكلا النوعين لازمين لنجاحك في دراستك.
</p>

<h3 id="-">
	استخدام الذكاء الاصطناعي في العمل
</h3>

<p>
	يذكر برايان -كاتب الدليل- أن الذكاء الاصطناعي لم يكن موجودًا بالصورة المعروفة حاليًا حين كان يعمل في السوق البرمجي، لكنه مع هذا يستخدمه الآن لإنجاز مهام بسرعة أكبر بسبب مستوى خبرته الحالي.
</p>

<p>
	وقد ذكرنا أن المطور المبتدئ يعتمد على الاستنتاج المنطقي في حل المشكلات بينما يضيف المطور الخبير إلى هذا إمكانية تمييزه لأنماط المشكلات، وستكتسب كلما زادت خبرتك فهمًا أعمق للعناصر التي تتكون منها أي مشكلة حيث تميز الأجزاء التي تحتاج إليها فقط وتفكر منطقيًا في كيفية ترابطها أو تركيبها، هذا يعني أن المطور الخبير أفضل في مرحلتي الفهم والتخطيط -من المراحل الأربعة لحل المشكلات- حيث تُحسم معركة المشكلة البرمجية في هاتين المرحلتين.
</p>

<p>
	ويستطيع الخبير أن يستغل الذكاء الاصطناعي في كتابة شيفرة معينة لأحد أجزاء المشكلة، كأن يقول له مثلًا "أريد تصفية هذه النتائج بناء على تعبير نمطي regex معين" ثم يطلب منه برمجة هذا الجزء ويتأكد بعدها من صحة الشيفرة التي كتبها ويعدلها وفقًا لاحتياجه ثم ينتقل إلى حل مشكلة أخرى، بل قد يستخدمونه في التقنيات التي لا يتقنون العمل عليها ثم يعتمدون على خبرتهم لمعرفة متى يجب التوقف والعودة لتعلم المزيد عن التقنية، كأنها حاسة سادسة تولدت مع الخبرة للشيفرات التي قد تسبب مشاكل لاحقًا، وكذلك سيعرف متى يمكنه التقدم بحذر وجمع المزيد من المعلومات حول ما يعطيه الذكاء الاصطناعي له.
</p>

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

<h3 id="-">
	الذكاء الاصطناعي في سوق العمل
</h3>

<p>
	يذكر برايان أنه كان يظن أن شركة ياهو فكرتها غبية حين خرجت للنور أول مرة، لكنها حققت أرباحًا بمليارات الدولارات، وهو أمر يتكرر كثيرًا في السوق البرمجي كل فترة، أن هذه التقنية أو تلك ستكون ثورة جديدة تلغي ما سبقها وتقلل الحاجة إلى المطورين لأن كتابة البرمجيات ستكون أمرًا سهلًا على كل الناس، كما حدث في موجة No Code، لكن قد يحدث حقًا أن توجد طفرة لا تنطبق عليها القوانين السابقة كما نرى في حالة النماذج اللغوية وأدوات الذكاءالاصطناعي مثل ChatGPT.
</p>

<p>
	وصحيح أن شركات No Code كانت تبالغ في تصريحاتها لرفع قيمتها السوقية وتحقيق عوائد ضخمة للمستثمرين فيها، وبالمثل تفعل الآن شركات الذكاء الاصطناعي، فهو نمط يتكرر بغض النظر عما إذا كانوا سيحققون هذه العوائد أم لا، ورغم أن النماذج اللغوية تظهر خبرة برمجية استثنائية، لكن لا زال لها حدود منطقية.
</p>

<p>
	فإذا فرضنا أن لدينا أداة ذكاء اصطناعي خارقة إلى حد أنها تستطيع تنفيذ مطلوب مثل "صمم وابن شركة تحطم جميع منافسيّ وتجعلني أغنى شخص على الكوكب"، فهل تظن أن هذا سيحدث حقًا في الواقع؟ ألا ترى أنه إذا كان لديك وصول إلى هذه الأداة فكذلك كل شخص على كوكب الأرض؟ ستكون النتيجة الحتمية لهذا أن نعود جميعًا إلى نقطة الصفر حيث يكون الجميع متساوون.
</p>

<p>
	لكن البشر بطبعهم يحبون التميز، فستجد من يحاول تعديل الناتج الذي يخرج به الذكاء الاصطناعي ليختلف عن منافسه، وهكذا يستمر هذا التوجه ربما إلى ما لا نهاية، فربما تقل الوظائف الروتينية السهلة لأن الذكاء الاصطناعي يستطيع حلها دون الحاجة إلى فريق كبير من المهندسين، لكن ستظل المشكلات المبتكرة والجديدة تحتاج إلى الكثير من العمل البشري، والأهم من هذا أن وتيرة تنفيذ المهام نفسها ستكون أسرع باستخدام الطرق التي ذكرناها من قبل عن توظيف الذكاء الاصطناعي في الدراسة والعمل.
</p>

<p>
	وإذا عدنا بالزمن إلى بدايات البرمجة لاستقراء ما قد يحدث في المستقبل بناء على تاريخ المجال نفسه، فسنجد أن أغلب البرامج كانت تكتب بلغة التجميع Assembly وكانت تحتاج إلى معرفة متخصصة ضخمة لكتابة برامج ملأى بالأخطاء البرمجية، ثم انتشرت المصرفات compilers التي ألغت الحاجة إلى الكتابة بلغة التجميع واختفت وظائفها وحلت محلها طرق برمجة أسرع وأسهل، واستطاع البشر الانتقال إلى حل مشاكل أعقد وأصعب، فلا شك أن البشر سيواصلون تخطي سقف الممكن وقد يكون الذكاء الاصطناعي مفيدًا للغاية، لكن مع وجود بشري في كرسي القيادة.
</p>

<h2 id="-">
	خاتمة
</h2>

<p>
	تعرفنا في هذا الدليل على المنهج الذي ينبغي أن يتبعه الدارس لعلوم الحاسوب، والأسلوب الأمثل لحل المشكلات البرمجية، والسمات النفسية التي ينبغي أن تكون لدى الدارس لهذا المجال والعامل فيه، إضافة إلى توضيح حول استخدام الذكاء الاصطناعي في التعلم، ونأمل بعده أن يقتحم الطالب هذا المجال على بينة من المطلوب منه أثناء الدراسة أو العمل.
</p>

<p>
	ترجمة -بتصرف- <a href="https://beej.us/guide/bglcs/html/" rel="external nofollow">للدليل Beej's Guide to Learning Computer Science</a> لصاحبه Brian “Beej Jorgensen” Hall
</p>
]]></description><guid isPermaLink="false">2600</guid><pubDate>Sun, 22 Feb 2026 12:54:00 +0000</pubDate></item><item><title>&#x627;&#x644;&#x641;&#x631;&#x642; &#x628;&#x64A;&#x646; &#x62A;&#x639;&#x644;&#x645; &#x627;&#x644;&#x622;&#x644;&#x629; &#x648;&#x627;&#x644;&#x62A;&#x639;&#x644;&#x645; &#x627;&#x644;&#x639;&#x645;&#x64A;&#x642;</title><link>https://academy.hsoub.com/programming/artificial-intelligence/%D8%A7%D9%84%D9%81%D8%B1%D9%82-%D8%A8%D9%8A%D9%86-%D8%AA%D8%B9%D9%84%D9%85-%D8%A7%D9%84%D8%A2%D9%84%D8%A9-%D9%88%D8%A7%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A7%D9%84%D8%B9%D9%85%D9%8A%D9%82-r2599/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2026_03/ML-DL.png.b90b31e8cbc7546cf13b7b2931661313.png" /></p>
<p>
	نسمع العديد من المصطلحات التي يخلط فيما بينها ضمن مجال الذكاء الاصطناعي Artificial Intelligence منها مصطلح تعلم الآلة Machine learning والتعلم العميق Deep learning، واللذان يعدان أساسيان في هذا المجال.
</p>

<p>
	بشكل عام يهتم هذين المجالين بمعالجة البيانات لاستخراج المعلومات منها وأحدهما يشمل الآخر، لذا سنوضح في هذا المقال هذه المصطلحات وماذا يشمل كل منها وفي ماذا تستخدم حتى نفهم الفرق بينها، ويمكنك التعرف أكثر على المصطحات الأخرى في مجال الذكاء الاصطناعي من مقال <a href="https://academy.hsoub.com/programming/artificial-intelligence/%D8%A7%D9%84%D9%85%D9%81%D8%A7%D9%87%D9%8A%D9%85-%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A9-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A7%D9%84%D8%A2%D9%84%D8%A9-r1009" rel="">المفاهيم الأساسية لتعلم الآلة</a>.
</p>

<h2 id="-machine-learning">
	تعلم الآلة Machine learning
</h2>

<p>
	فرع من مجال الذكاء الاصطناعي يستخدم خوارزميات محددة لاستخراج الأنماط patterns والتوقعات من البيانات، ويختلف النظام الذي يستخدم تعلم الآلة عن البرمجة العادية الثابتة بتطوير نموذج الخوارمية وتحسينها تدريجيًا عبر التدريب والاختبار المتكرر للنتائج أثناء معالجة بيانات دون تدخل بشري عبر التعلم من تلك البيانات مما يحسّن دقة النتائج مع الوقت.
</p>

<p>
	يستخدم خوارزميات التصنيف والتنبؤ والتي تحوي على معاملات parameters يمكن ضبطها تلقائيًا أثناء عملية التدريب لتحسين دقتها، فمثلًا عند استخدام خوارزمية شجرة القرار Decision Trees لتصنيف البيانات يعاير النظام بنيتها تلقائيًا، فكلما تدرب على بيانات أكبر وأكثر تنوعًا تحسّنت دقتها في معالجة بيانات جديدة.
</p>

<p>
	من مزايا هذا النوع من التعليم قدرته على التعلم من كمية بيانات صغيرة نسبيًا، ولكنه يعتمد على وجود مزايا مستخرجة مسبقًا من البيانات ولا يمكنه استخراجها بنفسه أي يحتاج لتدخل بشري في هذه المرحلة، وبعدها فقط يمكنه التعلم من تلك المزايا واتخاذ قرارات أفضل مع الوقت.
</p>

<p>
	تطبيقاته كثيرة ومنتشرة بشكل كبير منها أنظمة الاقتراحات في منصات المحتوى كاقتراح فيديوهات للمستخدم قد يرغب بمشاهدتها أو اقتراح المنشورات في وسائل التواصل الاجتماعي، وصولًا إلى تصنيف المحتوى كتصنيف رسائل البريد الالكتروني لاستبعاد السيء منها أو تصنيف المراجعات النصية على المنتجات كإيجابية أو سلبية.
</p>

<p>
	يصنف تعلم الآلة إلى ثلاثة أنواع رئيسية تختلف بحسب نوع المشكلة التي يحلها، وطريقة تعامل النظام مع البيانات وشكلها وطريقة التعلم منها أثناء التدريب، وسنشرحها في الفقرات التالية.
</p>

<h3 id="-supervised-learning">
	التعلم الموجه Supervised Learning
</h3>

<p>
	النوع الأساسي والأكثر استخداماً في تعلم الآلة، حيث يتدرب فيه النظام على البيانات والإجابات الصحيحة المقابلة لها، فيحللها ويستنتج تلقائيًا العلاقة بينها ليصبح قادرًا بعد التدريب على التنبؤ بإجابات بيانات جديدة لم يرها من قبل، وتقسم تلك بيانات وإجاباتها إلى قسمين قسم كبير عادةً يكون 80% منها لتدريب النظام والباقي 20% تستخدم لاختبار دقة وكفاءة النظام.
</p>

<p>
	تنقسم الخوارزميات المستخدمة في التعلم الموجه لنوعين أساسيين بحسب نوع التنبؤات المطلوبة وهي:
</p>

<p>
	<strong>التصنيف Classification</strong> لتقسيم البيانات إلى فئات كتصنيف رسائل البريد الإلكتروني إلى جيدة أو سيئة، ومن الخوارزميات المستخدمة فيه الانحدار اللوجستي Logistic Regression للتصنيف الثنائي، وأشجار القرار Decision Trees لتصنيف القرارات ونتائجها المحتملة
</p>

<p>
	<strong>الانحدار Regression</strong> للتنبؤ بقيم رقمية، كسعر منتج معين أو درجة حرارة الطقس ومن الخوارزميات التي يستخدمها الانحدار الخطي Linear Regression والانحدار متعدد الحدود Polynomial Regression لاكتشاف العلاقة بين البيانات وتتوقع منها القيم للبيانات الجديدة
</p>

<h3 id="-unsupervised-learning">
	التعلم غير الموجه Unsupervised Learning
</h3>

<p>
	يتعلم فيه النظام من البيانات فقط ليستكشف منها الأنماط والبنى المخفية فيها دون تدخل بشري وتنقسم الخوارزميات المستخدمة فيه لثلاث أنواع رئيسية:
</p>

<p>
	<strong>خوارزميات التجميع clustering</strong> وفيها يتم توزيع البيانات المتشابهة في مجموعات، كتصنيف زبائن متجر إلكتروني بحسب المشتريات او الاهتمامات، ومنها خوارزمية تجميع K-Means وخوارزمية التجميع الهيكلية Hierarchical Clustering
</p>

<p>
	<strong>قاعدة الارتباط Association rule</strong> تستكشف العلاقات في البيانات وتستنتج منها قواعد شرطية تُعبّر عن وجود بيانات في حال وجود أخرى
</p>

<p>
	<strong>تقليل الأبعاد Dimensionality Reduction</strong> تهدف لتقليل عدد المزايا أو المتغيرات في البيانات مع الحفاظ قدر الإمكان على المعلومات الموجودة في البيانات ومن خوارزمياتها تحليل مكون المبدأ Principal Component Analysis و التضمين المحلي الخطي Locally Linear Embedding
</p>

<h3 id="-reinforcement-learning">
	التعلم المعزز Reinforcement Learning
</h3>

<p>
	يركز على تعليم وكيل Agent كيف يتخذ قرارات عبر التجربة والخطأ ليحصل على أكبر قدر ممكن من المكافئات من البيئة التي يتفاعل معها ليحقق بالنهاية هدف معين، ومن تطبيقاته نماذج التحكم بالروبوتات العادية والصناعية، ونماذج لعب الألعاب التي تتجاوز مهارتها البشر.
</p>

<p>
	ويقسم إلى نوعين هما <strong>التعزيز الإيجابي Positive Reinforcement</strong> والذي يكافئ الأفعال التي تؤدي لحدث ما و<strong>التعزيز السلبي Negative Reinforcement</strong> والذي يكافئ الأفعال التي تسبب تجنب حدث ما.
</p>

<h2 id="-deep-learning">
	التعلم العميق Deep learning
</h2>

<p>
	فرع متقدم من تعلم الآلة يعتمد على الشبكات العصبية Neural Networks متعددة الطبقات لاستخراج الأنماط والتوقعات من البيانات مباشرة، ويختلف عن تعلم الآلة التقليدي بقدرته على استخراج المزايا تلقائيًا عبر طبقات متتالية دون الحاجة لتدخل بشري، مع تحسين الأداء تدريجيًا عبر التدريب على كميات هائلة من البيانات.
</p>

<p>
	مثلًا عند استخدام شبكة عصبية التفافية Convolutional Neural Network للتعرف على الصور، تتعلم الطبقات الأولى كشف الحواف والأشكال البسيطة، ثم تتطور الطبقات العميقة لاكتشاف وجوه أو أجسام معقدة كلما زادت البيانات المعالجة، مما يحسن الدقة في التصنيف أو التنبؤ، أي يستطيع التعلم العميق استخراج المزايا بنفسه من أي نوع من البيانات.
</p>

<p>
	تُصنف طرق التعلم العميق ضمن الأنواع الثلاثة لتعلم الآلة التي ذكرناها سابقًا، ولكنها تتفوق بتنفيذها في التطبيقات المعقدة، مثل استخدامها لخوارزميات التصنيف Classification والانحدار Regression في التعلم الموجه، واستخدام التجميع Clustering في التعلم غير الموجه، وقدرته الممتازة في التفاعل ضمن البيئات الافتراضية في التعلم المعزز.
</p>

<p>
	من مزاياه قدرته على معالجة بيانات غير مهيكلة كالصور والنصوص بكفاءة عالية، ولكنه يتطلب كميات بيانات ضخمة وموارد معالجة قوية من كروت شاشة وذاكرة، وزمن تدريب طويل مما يجعله أقل كفاءة على بيانات صغيرة مقارنة بتعلم الآلة التقليدي.
</p>

<p>
	تشمل تطبيقاته التعرف على الوجوه والترجمة الآلية الفورية، وتشخيص الأمراض من الأشعة الطبية، والسيارات ذاتية القيادة، ومن التطبيقات التي شهرت هذا المجال وأبرزت قوته كان نموذج ألفا جو AlphaGo الذي استطاع بتطبيق التعلم المعزز التدرب على لعبة Go المعروفة بتعقيدها واستطاع هزيمة أبطال تلك اللعبة.
</p>

<h2 id="-">
	الفرق بين تعلم الآلة والتعلم العميق
</h2>

<p>
	التعلم العميق فرع ضمن مجال تعلم الآلة، وتكمن ميزة وفرق التعلم العميق في مرحلة استخراج المزايا Feature Extraction من البيانات قبل استخلاص النتائج المطلوبة منها، حيث يستطيع استخراج المزايا بنفسه ما يجعله أقوى في معالجة مجال أوسع من البيانات، بينما مجالات تعلم الآلة الأخرى تحتاج لمزايا مجهزة يدويًا مسبقًا بجانب البيانات لتتمكن من استخلاص النتائج المطلوبة منها.
</p>

<p style="text-align: center;">
	<img alt="Pasted-image-20260112152839.png" style="width: 400px; height: auto;" src="https://ana.hsoubcdn.com/apps/notes/files/ab124318-7e96-4a77-a962-2b8e5829121a/8ad9acf6-18cc-4f16-b1f6-98fae23c5de0/Pasted-image-20260112152839.png">
</p>

<p>
	هذا الفرق يجعل طرق تعلم الآلة التقليدية خفيفة وسريعة ولا تحتاج لموارد معالجة قوية ولا حتى لكمية كبيرة من البيانات، على عكس التعلم العميق الذي يتطلب موارد معالجة وكميات بيانات كبيرة على حساب قدرته على معالجة بيانات معقدة والوصول لنتائج لا يمكن لتعلم الآلة التقليدي الوصول إليها، والجدول التالي يوضح أهم الفروقات بين المجالين:
</p>

<table>
	<tbody>
		<tr>
			<td>
				 
			</td>
			<td>
				تعلم الآلة
			</td>
			<td>
				التعلم العميق
			</td>
		</tr>
		<tr>
			<td>
				البنية
			</td>
			<td>
				خوارزميات تتعلم من البيانات وتحسن أداءها مع الخبرة
			</td>
			<td>
				فرع من تعلم الآلة يعتمد على الشبكات العصبونية متعددة الطبقات
			</td>
		</tr>
		<tr>
			<td>
				كمية بيانات التدريب
			</td>
			<td>
				صغيرة إلى متوسطة
			</td>
			<td>
				بيانات ضخمة
			</td>
		</tr>
		<tr>
			<td>
				استخراج المزايا
			</td>
			<td>
				يدوي بتدخل بشري
			</td>
			<td>
				تلقائي يستخرجها مباشرة من البيانات
			</td>
		</tr>
		<tr>
			<td>
				وقت التدريب
			</td>
			<td>
				سريع
			</td>
			<td>
				بطيء
			</td>
		</tr>
		<tr>
			<td>
				قوة وكمية موارد المعالجة
			</td>
			<td>
				قليلة
			</td>
			<td>
				كبيرة
			</td>
		</tr>
		<tr>
			<td>
				الدقة
			</td>
			<td>
				تعتمد على جودة المزايا والخوارزمية المستخدمة
			</td>
			<td>
				عالية في حال وجود بيانات كافية
			</td>
		</tr>
	</tbody>
</table>

<h2 id="-">
	خلاصة
</h2>

<p>
	تعرفنا في هذا المقال عن مجالي تعلم الآلة والتعلم العميق والفرق بينهما إضافة لمتطلبات كل منها، هذا الفرق يوضح متى نختار كل منها للحصول على نتائج جيدة، وتذكر أن مجال تعلم الآلة أوسع ويشمل ضمنه مجال التعلم العميق.
</p>

<h2 id="-">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/artificial-intelligence/%D8%A7%D9%84%D8%B0%D9%83%D8%A7%D8%A1-%D8%A7%D9%84%D8%A7%D8%B5%D8%B7%D9%86%D8%A7%D8%B9%D9%8A" rel="">الذكاء الاصطناعي: دليلك الشامل</a>
	</li>
	<li>
		كتاب <a href="https://academy.hsoub.com/files/17-%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%B0%D9%83%D8%A7%D8%A1-%D8%A7%D9%84%D8%A7%D8%B5%D8%B7%D9%86%D8%A7%D8%B9%D9%8A-%D9%88%D8%AA%D8%B9%D9%84%D9%85-%D8%A7%D9%84%D8%A2%D9%84%D8%A9/" rel="">مدخل إلى الذكاء الاصطناعي وتعلم الآلة</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/artificial-intelligence/%D8%A7%D9%84%D9%85%D9%81%D8%A7%D9%87%D9%8A%D9%85-%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A9-%D9%84%D9%84%D8%B0%D9%83%D8%A7%D8%A1-%D8%A7%D9%84%D8%A7%D8%B5%D8%B7%D9%86%D8%A7%D8%B9%D9%8A-r2478" rel="">المفاهيم الأساسية للذكاء الاصطناعي</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/artificial-intelligence/%D9%83%D9%84-%D9%85%D8%A7-%D8%AA%D9%88%D8%AF-%D9%85%D8%B9%D8%B1%D9%81%D8%AA%D9%87-%D8%B9%D9%86-%D8%AF%D8%B1%D8%A7%D8%B3%D8%A9-%D8%A7%D9%84%D8%B0%D9%83%D8%A7%D8%A1-%D8%A7%D9%84%D8%A7%D8%B5%D8%B7%D9%86%D8%A7%D8%B9%D9%8A-r2362" rel="">كل ما تود معرفته عن دراسة الذكاء الاصطناعي</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2599</guid><pubDate>Tue, 20 Jan 2026 07:40:00 +0000</pubDate></item><item><title>&#x62A;&#x639;&#x631;&#x641; &#x639;&#x644;&#x649; &#x627;&#x644;&#x634;&#x628;&#x643;&#x627;&#x62A; &#x627;&#x644;&#x639;&#x635;&#x628;&#x64A;&#x629; &#x627;&#x644;&#x627;&#x644;&#x62A;&#x641;&#x627;&#x641;&#x64A;&#x629; CNNs</title><link>https://academy.hsoub.com/programming/artificial-intelligence/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-%D8%A7%D9%84%D8%B9%D8%B5%D8%A8%D9%8A%D8%A9-%D8%A7%D9%84%D8%A7%D9%84%D8%AA%D9%81%D8%A7%D9%81%D9%8A%D8%A9-cnns-r2597/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2026_03/CNNs(2).png.75da8efe0a11de7ec713ef0861cdb678.png" /></p>
<p>
	تُعدُّ الشبكات العصبية Neural Networks نموذجًا حاسوبيًا مستوحى من الدماغ البشري يحلل البيانات ويتعلم منها بعدة طرق، كالتعلم العميق الذي يمكنك الاطلاع عليه أكثر من مقال <a href="https://academy.hsoub.com/programming/artificial-intelligence/%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-%D9%84%D9%81%D9%87%D9%85-%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A7%D9%84%D8%B9%D9%85%D9%8A%D9%82-r1422/" rel="">دليل المبتدئين لفهم أساسيات التعلم العميق</a>، ومن فروعها الشبكات العصبية الالتفافية Convolutional Neural Networks - CNNs، المصممة لاستخراج المزايا والتعرف على الكائنات من البيانات، وتستخدم خصيصًا في مجال تصنيف الصور وتحليل محتوياتها.
</p>

<p>
	تُمثَّل بيانات الإدخال input data لمعظم الشبكات العصبية بمصفوفة matrix شكلها يعتمد على نوع البيانات، فمثلًا يمكن تمثيل الصور بمصفوفة أبعادها هي الطول حجمه يساوي طول الصورة، والعرض حجمه يساوي عرض الصورة، واللون لكل بكسل pixel في الصورة يتألف من ثلاث ألوان الأحمر والأخضر والأزرق RGB أي حجمه يساوي 3، فيمكن تمثيل الصورة بمصفوفة ثلاثية الأبعاد حجمها الطول×العرض×3.
</p>

<p>
	سنتعرف في هذا المقال على مكونات الشبكات العصبية الالتفافية وطريقة عمل كل منها وأهم التطبيقات العملية التي تستخدم فيها.
</p>

<h2 id="-">
	أنواع وترتيب طبقات الشبكات الالتفافية
</h2>

<p>
	يتميز هذا النوع من الشبكات العصبية بالأداء العالي في التعامل مع البيانات متعددة الأبعاد كالصور والصوتيات والتي تكون دخلًا لهذه الشبكة وتُمثّل على شكل مصفوفة بيانات الإدخال input data، ثم تمرر تلك المصفوفة إلى طبقات الشبكة وهي ثلاث أنواع رئيسية بالترتيب التالي بطبقة واحدة أو أكثر من كل نوع وهي:
</p>

<ol>
	<li>
		الطبقة الالتفافية Convolutional layer
	</li>
	<li>
		طبقة التجميع Pooling layer
	</li>
	<li>
		الطبقة المتصلة كليًا Fully connected layer
	</li>
</ol>

<p>
	تزداد قدرة الشبكة وتعقيد التعرف على مزايا في البيانات مع كل طبقة منها، فمثلًا عند تحليل صورة تُركز الطبقات الابتدائية على تحديد المزايا البسيطة كالألوان والحواف، ثم تُجمّع الطبقات التي تليها تلك المزايا وتستنتج منها مزايا أعقد كالأشكال، وصولًا لطبقات الشبكة النهائية التي تتعرف على الكائنات.
</p>

<h3 id="-convolutional-layer">
	الطبقة الالتفافية Convolutional layer
</h3>

<p>
	هي أساس الشبكات العصبية الالتفافية تحوّل مصفوفة البيانات المدخلة إليها إلى مصفوفة جديدة تعبّر عن المزايا الموجودة ضمنها، حيث تستخدم المُرشّح filter ويسمى أيضًا النواة kernal أو كاشف الميزة feature detector والذي يكون حجمه صغيرًا للالتفاف -لذا سُميت الطبقة بالالتفافية- على أجزاء من مصفوفة الدخل، وفي كل خطوة تُطبّق عملية الضرب النقطي dot product بين المرشح وجزء من مصفوفة الدخل لينتج عنها مصفوفة جديدة تدعى خارطة الميزة feature map أو خارطة التفعيل activation map أو الميزة المُلتفة convolved feature.
</p>

<p>
	تٌجمع بعد ذلك مصفوفات الخرائط الناتجة معًا ويطبق عليها تابع تفعيل مثل التابع ReLU لتسهيل التعامل معها لاحقًا وزيادة كفاءة الشبكة وتحويل قيمها إلى قيم غير خطية، وبذلك يكون خرج هذه الطبقة جاهزًا لمعالجته في الطبقة اللاحقة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="180850" href="https://academy.hsoub.com/uploads/monthly_2026_01/image.png.e5301874bdce815db3001e8469905121.png" rel=""><img alt="image.png" class="ipsImage ipsImage_thumbnailed" data-fileid="180850" data-ratio="50.14" data-unique="gr4lbvhb8" style="width: 700px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_01/image.thumb.png.ff7e4d6fd9f128dcc7e933b0baf4ad69.png"></a>
</p>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="180853" href="https://academy.hsoub.com/uploads/monthly_2026_01/image.png.e4ea1da256bb19c31b7841cc10ed34c3.png" rel=""><img alt="image.png" class="ipsImage ipsImage_thumbnailed" data-fileid="180853" data-ratio="100.00" data-unique="qde9kxty3" style="width: 300px; height: auto;" width="600" src="https://academy.hsoub.com/uploads/monthly_2026_01/image.thumb.png.8343cce29d34a163843399332d49c5f3.png"></a>
</p>

<h4 id="-filter-matrix">
	شكل مصفوفة المٌرشح Filter matrix
</h4>

<p>
	يمثل كل مرشح ميزة صغيرة على شكل مصفوفة أوزان weights تُعدّل خلال عملية التدريب لتصحيح عمل الشبكة وتصبح قادرة على استكشاف المزايا التي نبحث عنها، وشكل المصفوفة يماثل شكل مصفوفة بيانات الإدخال من حيث عدد الأبعاد، أما حجم كل بعد يعتمد على مدى قرب المزايا التي نبحث عنها من المعنى الذي يُعبّر عنه البعد المقابل في مصفوفة بيانات الإدخال.
</p>

<p>
	فمثلًا لتحليل صورة مُمثلة بمصفوفة ثلاثية الأبعاد يجب أن تكون المرشحات بنفس الشكل أي ثلاثية الأبعاد أيضًا، أما حجم كل بعد وبما أن طول وعرض الصورة لا يعبر مباشرة عن المزايا الموجودة ضمن الصورة، فحجم الأبعاد المقابلة لها ضمن المرشح تكون صغيرة مثلا بحجم 5×5 أو 3×3، أما بُعد اللون فهو يُعبر عن محتوى الصورة فيكون حجم البعد المقابل له ضمن المرشح مساويًا له أي 3، لتكون حجم المرشحات الممكنة 5×5×3 أو 3×3×3 أو أي قيم أخرى مماثلة.
</p>

<p>
	كلما زاد عدد المرشحات المستخدمة زادت قدرة الشبكة على استخراج مزايا معقدة أكثر، كما يؤثر طول الخطوة Stride التي يخطوها المرشح أثناء عملية المسح على شكل مصفوفة الميزة الناتجة، وقد لا يناسب أحيانًا حجم المرشح لمسح مصفوفة البيانات بشكل كامل عندها نلجأ لإحدى الطرق التالية:
</p>

<ul>
	<li>
		الحشو الصحيح Valid padding بتجاهل عمليات المسح النهائية التي لا تتناسب بالحجم
	</li>
	<li>
		الحشو المماثل Same padding بإضافة أصفار لحواف مصفوفة المرشح ليكون حجم مصفوفة الخرج مساويًا لحجم مصفوفة بيانات الإدخال
	</li>
	<li>
		الحشو الكلي Full padding بإضافة أصفار في حواف مصفوفة البيانات لزيادة حجم مصفوفة الخرج
	</li>
</ul>

<h3 id="-pooling-layer">
	طبقة التجميع Pooling layer
</h3>

<p>
	وظيفتها تقليل أبعاد وشكل المصفوفة الداخلة إليها، وطريقة عملها تشبه طبقة الالتفاف، بتمرير مرشح على المصفوفة المدخلة إليها.
</p>

<p>
	الفرق أن المرشح في هذه الطبقة لا يحوي أوزانًا بل هو دالة تجميع aggregation تُطبّق على أجزاء الدخل وتكتب نتائج ذلك المسح في مصفوفة الخرج الأقل بعدًا، وهناك نوعين من هذه الدوال تختلف في طريقة حساب النتيجة أثناء المسح:
</p>

<ul>
	<li>
		<strong>التجميع الأعظمي</strong> Max pooling يُختار فيه أعلى القيم
	</li>
	<li>
		<strong>التجميع الوسطي</strong> Average pooling يُحسب فيه وسطي القيم
	</li>
</ul>

<p>
	قد نتسائل لماذا نختزل في هذه الطبقة بعض المعلومات المستخرجة التي قد تكون مهمة من الطبقة السابقة، وذلك لعدة أسباب منها التبسيط وزيادة الأداء حيث سنتعامل مع بيانات أقل في الطبقات اللاحقة، وأيضًا تمنع هذه الطبقة الشبكة من الإفراط في التخصيص overfitting فيبقى عمل الشبكة يغطي مجال أكبر من بيانات الإدخال الغير مدرب عليها.
</p>

<h3 id="-fully-connected-layer">
	الطبقة المتصلة كليًا Fully connected layer
</h3>

<p>
	تتكون من طبقات ضمن كل منها أعداد مختلفة من العقد nodes، بحيث تتصل فيها العقد من كل طبقة مع كل العقد في الطبقة التي تليها، وتطبق في كل عقدة أوزان على مدخلات العقدة ثم تطبق على النتيجة دالة تفعيل عادة تكون من نوع softmax وينتج عنها احتمال من صفر إلى واحد، وتكرر هذه العملية في جميع طبقات العقد للوصول إلى الطبقة النهائية.
</p>

<p>
	تستكشف هذه الطبقة الأنماط من المزايا المستخرجة من الطبقات السابقة من الشبكة، وتعطي جوابًا نهائيًا وهو خرج الشبكة ككل.
</p>

<h2 id="-">
	تطبيقات الشبكات العصبية الالتفافية
</h2>

<p>
	من أهم استخدامات هذه الشبكة في مجال الذكاء الاصطناعي هي مهام الرؤية الحاسوبية لتحليل واستخراج المعلومات من الصور والفيديوهات وحتى الصوتيات.
</p>

<p>
	فمثلًا تستخدمها منصات التواصل الاجتماعي في تحليل الصور المنشورة فيها، والمتاجر الرقمية في مسح صور المنتجات وتصنيفها لاقتراحها على الزبائن، وفي المجال الطبي تٌستخدم في تحليل صور الأشعة وتحديد احتمالية وجود الأمراض، ومن أهم التطبيقات لهذه الشبكات هو في مجال النقل لتطوير سيارات ذاتية القيادة وتخفيف أخطاء السائقين لحماية الركاب.
</p>

<h2 id="-">
	خلاصة
</h2>

<p>
	تعتبر الشبكة العصبية الالتفافية من أهم أنواع الشبكات في مجال الذكاء الاصطناعي لتطبيقاتها المختلفة المفيدة، والمزايا والتطبيقات المختلفة التي تقدمها من التعرف على الكائنات إلى استخراج المزايا من البيانات من أنواع مختلفة.
</p>

<p>
	تعرفنا في هذا المقال على والطبقات المكونة لها وعمل كل منها، من طبقة الالتفاف إلى طبقة التجميع وصولًا للطبقة المتصلة كليًا، وعلى أهم التطبيقات التي تٌستخدم فيها تلك الشبكة.
</p>

<h2 id="-2">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/artificial-intelligence/%d8%a7%d9%84%d8%b0%d9%83%d8%a7%d8%a1-%d8%a7%d9%84%d8%a7%d8%b5%d8%b7%d9%86%d8%a7%d8%b9%d9%8a/" rel="">الذكاء الاصطناعي: دليلك الشامل</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/files/17-%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%B0%D9%83%D8%A7%D8%A1-%D8%A7%D9%84%D8%A7%D8%B5%D8%B7%D9%86%D8%A7%D8%B9%D9%8A-%D9%88%D8%AA%D8%B9%D9%84%D9%85-%D8%A7%D9%84%D8%A2%D9%84%D8%A9/" rel="">مدخل إلى الذكاء الاصطناعي وتعلم الآلة</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/artificial-intelligence/%d8%a7%d9%84%d9%85%d9%81%d8%a7%d9%87%d9%8a%d9%85-%d8%a7%d9%84%d8%a3%d8%b3%d8%a7%d8%b3%d9%8a%d8%a9-%d9%84%d9%84%d8%b0%d9%83%d8%a7%d8%a1-%d8%a7%d9%84%d8%a7%d8%b5%d8%b7%d9%86%d8%a7%d8%b9%d9%8a-r2478/" rel="">المفاهيم الأساسية للذكاء الاصطناعي</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/artificial-intelligence/%d9%83%d9%84-%d9%85%d8%a7-%d8%aa%d9%88%d8%af-%d9%85%d8%b9%d8%b1%d9%81%d8%aa%d9%87-%d8%b9%d9%86-%d8%af%d8%b1%d8%a7%d8%b3%d8%a9-%d8%a7%d9%84%d8%b0%d9%83%d8%a7%d8%a1-%d8%a7%d9%84%d8%a7%d8%b5%d8%b7%d9%86%d8%a7%d8%b9%d9%8a-r2362/" rel="">كل ما تود معرفته عن دراسة الذكاء الاصطناعي</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2597</guid><pubDate>Mon, 12 Jan 2026 11:02:00 +0000</pubDate></item><item><title>&#x636;&#x628;&#x637; &#x62D;&#x631;&#x643;&#x627;&#x62A; &#x627;&#x644;&#x634;&#x62E;&#x635;&#x64A;&#x629; &#x641;&#x64A; &#x645;&#x62D;&#x631;&#x643; &#x627;&#x644;&#x623;&#x644;&#x639;&#x627;&#x628; Godot</title><link>https://academy.hsoub.com/programming/game-development/%D8%B6%D8%A8%D8%B7-%D8%AD%D8%B1%D9%83%D8%A7%D8%AA-%D8%A7%D9%84%D8%B4%D8%AE%D8%B5%D9%8A%D8%A9-%D9%81%D9%8A-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-godot-r2594/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_05/002..png.e1f49a810610a382041b68a6c8a5b1b9.png" /></p>
<p>
	لنفترض أن لدينا شخصيةً ثلاثية الأبعاد من نوع صلب Rigged تم بناؤها ذاتيًا أو حملناها من مصدر خارجي ونريد إعداد وضبط حركاتها في جودو. سنستكشف في هذا المقال طريقة فعل ذلك، وسنستخدم نفس شخصية المغامر وبقية الأصول التي ذكرناها في <a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D8%B3%D8%AA%D9%8A%D8%B1%D8%A7%D8%AF-%D8%A7%D9%84%D8%A3%D8%B5%D9%88%D9%84-assets-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D9%81%D9%8A-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-godot-r2593/" rel="">المقال السابق</a> من <a href="https://academy.hsoub.com/tags/%D8%AF%D9%84%D9%8A%D9%84%20%D8%AC%D9%88%D8%AF%D9%88/" rel="">سلسلة دليل جودو</a>.
</p>

<h2 id="">
	إعداد الشخصية
</h2>

<p>
	اخترنا <a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%B9%D9%82%D8%AF-nodes-%D9%81%D9%8A-%D9%85%D8%AD%D8%B1%D9%83-%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot-r2490/" rel="">العقدة</a> <code>CharacterBody3D</code> لتمثّل شخصية المغامر، لهذا ينبغي أن يكون المشهد كالتالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172895" href="https://academy.hsoub.com/uploads/monthly_2025_05/01_knight_node.png.91b5bec850ced9e8de43853b9681839f.png" rel=""><img alt="01 knight node" class="ipsImage ipsImage_thumbnailed" data-fileid="172895" data-unique="9joeya557" src="https://academy.hsoub.com/uploads/monthly_2025_05/01_knight_node.png.91b5bec850ced9e8de43853b9681839f.png"> </a>
</p>

<p>
	<strong>ملاحظة</strong>: أغلقنا فرع العقدة <code>Rig</code> كي لا تطول القائمة.
</p>

<p>
	أول ما قد نلاحظه أن يدي المغامر ممتلئتين، فقد زُوّد الفنان المغامر بكل الأسلحة والدروع ووضعها في مكانها واتجاهها الصحيح. بإمكاننا طبعًا التنقل في قائمة العقد وإخفاء ما نريده.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172903" href="https://academy.hsoub.com/uploads/monthly_2025_05/02_3dcharacter_default_pose.png.108d96018f1b4de698df3ab2d2e5dbf2.png" rel=""><img alt="02 3dcharacter default pose" class="ipsImage ipsImage_thumbnailed" data-fileid="172903" data-unique="jnp98c9aq" src="https://academy.hsoub.com/uploads/monthly_2025_05/02_3dcharacter_default_pose.png.108d96018f1b4de698df3ab2d2e5dbf2.png"> </a>
</p>

<h3 id="animationtree">
	استخدام العقدة AnimationTree
</h3>

<p>
	من الصعب التعامل مع كل الحركات المتوفرة للشخصية من خلال الشيفرة. لذا من المهم التفكير مثلًا بعدد العبارات الشرطية <code>if</code> التي نحتاجها كي نحدد الحركة المناسبة التي يجب تنفيذها في التوقيت المطلوب، وذلك اعتمادًا على ما يفعله اللاعب. لن يكون الأمر صعبًا بالطبع عند وجود عدة حركات فقط، لكن سرعان ما ستخرج الأمور عن السيطرة، ولن تكون هناك جدوى فعلية منها.
</p>

<p>
	علينا التفكير أيضًا في الحالة التي يقف فيها اللاعب دون حراك؛ إذ لا بد عندها من تنفيذ حركة الوقوف دون فعل شيء Idle. وعندما يضغط اللاعب مفتاح الحركة إلى الأمام، ستنفذ الشخصية حركة المشي Walking.
</p>

<p>
	ستبدو هذه التغيرات السريعة في الحركة مزعجة، ويُفضّل أن تكون هذه الحركات المتعاقبة متمازجةً ببعضها لتعطي انتقالًا أكثر سلاسة. ولحل مشاكل الحركة المعقدة تلك، يجب استخدام عقدة <code>AnimationTree</code>، وهي عقدة مصممة للتحكم بالعقدة <code>AnimationPlayer</code> وتقدم وظائفًا تتحكم بالانتقال بين الحركات المختلفة وتمزجها معًا.
</p>

<p>
	سنضيف العقدة <code>AnimationTree</code> إلى المشهد ، ثم نضبط الخاصية <code>Tree Root</code> في الفاحص Inspector على القيمة <code>AnimationNodeStateMachine</code>، ثم نلحق العقدة <code>AnimationPlayer</code> بهذه العقدة من خلال الخاصية <code>Anim Player</code>، ونفعّل بعدها الخاصية <code>Active</code>
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172897" href="https://academy.hsoub.com/uploads/monthly_2025_05/03_animation_tree.png.f33b6cfd711ec181be890b42b7c0e94a.png" rel=""><img alt="03 animation tree" class="ipsImage ipsImage_thumbnailed" data-fileid="172897" data-unique="810p5pr9o" src="https://academy.hsoub.com/uploads/monthly_2025_05/03_animation_tree.png.f33b6cfd711ec181be890b42b7c0e94a.png"> </a>
</p>

<p>
	<strong>ملاحظة</strong>: قد نلاحظ أننا لن تتمكن من تشغيل الحركات التي تضمها العقدة <code>AnimationPlayer</code> عندما تكون الخاصية <code>Active</code> للعقدة <code>AnimationTree</code> فعّالة، لذا إن أردنا تعديل أي شيء أو اختبار الحركات، فعلينا إلغاء تفعيل هذه الخاصية مؤقتًا.
</p>

<h3 id="idlewalkrun">
	حلقة الحركات التوقف Idle والمشي Walk والجري Run
</h3>

<p>
	هنالك الكثير من الحركات التي سنجدها في هذه النماذج، لكننا سنركز في مثالنا على حلقة حركات التوقف Idle والمشي Walk والجري Run، مع القفز والهجوم؛ فإن أردنا إضافة حركات أخرى، سنكرر ببساطة ما سنفعله مع الحركة التي نريد.
</p>

<p>
	سنبحث عن الحركات Idle و Running_A و Walking_Backwards و Running_Strafe_Left / Running_Strafe_Right ضمن العقدة <code>AnimationPlayer</code>، وسنتأكد من أن هذه الحركات ستعمل على شكل حلقة.
</p>

<p>
	يمكننا اختبار ذلك من خلال تشغيل الحركة عبرالزر ▶، وفي حال كانت هناك أي حركة غير معدّة لتكرار نفسها، سنعيد استيراد الشخصية بعد ضبط الحركات على وضع التكرار.
</p>

<p>
	سنختار العقدة <code>AnimationTree</code> وسنرى أن هناك لوحة جديدة قد فُتحت أسفل العارض.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172901" href="https://academy.hsoub.com/uploads/monthly_2025_05/04_animtree_empty.png.b0cb4d5c708f13589970151a6b32e480.png" rel=""><img alt="04 animtree empty" class="ipsImage ipsImage_thumbnailed" data-fileid="172901" data-unique="r4ncikefj" src="https://academy.hsoub.com/uploads/monthly_2025_05/04_animtree_empty.thumb.png.7e2063a88604fe1ef7988c19ad56aeb2.png"> </a>
</p>

<p>
	وكمثال عن طريقة العمل، سننقر بالزر الأيمن للفأرة في أي نقطة فارغة من اللوحة، ونضغط على خيار <strong>أضف حركة Add Animation</strong> ثم نختار Idle. نكرر العملية ونضيف أيضًا الحركة 1H_Melee_Attack_Chop.
</p>

<p>
	سنضغط الآن على أيقونة ربط العقد Connect Nodes، ثم نرسم اتصالًا بين الزر <code>start</code> و الحركة <code>Idle</code>، وسرى كيف تُنفّذ الحركة Idle مباشرةً.
</p>

<p>
	نريد الآن الانتقال من التوقف إلى الهجوم ثم العودة إلى حركة التوقف بعد انتهاء حركة الهجوم. ولهذا، سنرسم اتصالين إضافيين من وإلى حركة الهجوم. لن يعمل ذلك كما هو مطلوب، لكنه سينتقل بين الحركتين بسرعة، لأن كلا الاتصالين ضُبطا للانتقال الفوري.
</p>

<p>
	لتغير شروط الانتقال، سننتقل إلى وضع الاختيار، وذلك بالنقر على الأيقونة المخصصة، ثم النقر على أحد الاتصالين، وستظهر في نافذة الفاحص خاصيات الاتصال. نحتاج إلى ضبط الخاصية <code>Advanced&gt;Mode</code> على <code>Enabled</code> في الاتصال من الحركة Idle، ويعني ذلك أن هذا الانتقال سيُنفذ فقط عندما نقرر ذلك وليس آليًا. سنلاحظ بعد ذلك أن لون الأيقونة على خط الاتصال سيتغير.
</p>

<p>
	سنضبط الخاصية <code>Switch</code> للاتصال الثاني إلى الحركة Idle على <code>AtEnd</code>، وتبقى قيمة الخاصية <code>Advanced&gt;Mode</code> على <code>Auto</code>. عند النقر الآن على الزر ▶ في عقدة الهجوم، ستعمل حركة الهجوم، ثم تعود إلى حركة التوقف عندما تنتهي.
</p>

<p>
	يوضح هذا المثال البسيط طريقة إعداد حركات مختلفة وضبط الانتقالات بين هذه الحركات.
</p>

<p>
	سنحتاج لاحقًا بطبيعة الحال إلى أكثر من ذلك، لهذا سنحذف العقد الموجودة في لوحة شجرة التحريك باستخدام أيقونة الحذف، ودعنا نحضّر فضاء المزج blendspace.
</p>

<h3 id="blendspace">
	فضاء المزج blendspace
</h3>

<p>
	ننقر بالزر اليميني للفأرة على أي نقطة فارغة من لوحة شجرة الحركات ثم نختار <code>BlendSpace2D</code>، وننقر بعد ذلك على الأيقونة التي تظهر، مع تغيير اسمها إلى <code>IWR</code>. نضيف بعد ذلك اتصالًا من <code>Start</code> إلى <code>IWR</code> كي يبدأ فضاء المزج بالعمل تلقائيًا.
</p>

<p>
	ننقر بعد ذلك على أيقونة القلم في الفضاء <code>IWR</code> لتظهر لنا النافذة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172899" href="https://academy.hsoub.com/uploads/monthly_2025_05/05_blendspce_edit.png.1f4db7c602fa44b7cd240c988d4e504b.png" rel=""><img alt="05 blendspce edit" class="ipsImage ipsImage_thumbnailed" data-fileid="172899" data-unique="hoppwds1u" src="https://academy.hsoub.com/uploads/monthly_2025_05/05_blendspce_edit.thumb.png.121632ff314da1ec98e875d21d45ee77.png"> </a>
</p>

<p>
	يمثل هذا الفضاء ثنائي البعد شعاع الحركة الأفقية للشخصية، فعندما تقف الشخصية ساكنة، تكون قيمة الشعاع <code>(0,0)</code>. ننقر الآن على زر إنشاء نقاط Create Points، ثم ننقر على منتصف الشبكة لإضافة الحركة <code>idle</code>.
</p>

<p>
	نضيف الحركة Running_A في المنتصف والأعلى، والحركة Walking_Backwards في المنتصف والأسفل الحركة، ثم نضيف في النهايتين الأفقيتين الحركتين Running_Strafe_Left و Running_Strafe_Right.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172902" href="https://academy.hsoub.com/uploads/monthly_2025_05/06_building_blendspace.png.9374d100022cfc44dc6993a22f72fa75.png" rel=""><img alt="06 building blendspace" class="ipsImage ipsImage_thumbnailed" data-fileid="172902" data-unique="y8tbyhkb1" src="https://academy.hsoub.com/uploads/monthly_2025_05/06_building_blendspace.thumb.png.7ac1da5137132b5aa53851f7c06b8912.png"> </a>
</p>

<p>
	يمكننا الآن النقر على أيقونة تحديد موضع الخلط في الفراغ، ثم النقر ضمن الشبكة والسحب لرؤية الانتقال السلس بين الحركات الأربعة والتوقف.
</p>

<p>
	بعد الانتهاء من تجربة عملية المزج، سننقر على الجذر Root لتعود إلى لوحة شجرة الحركات.
</p>

<h3 id="statemachine">
	ضبط آلة الحالة State machine
</h3>

<p>
	يمكن عدّ حلقة الحركات <code>IWR</code> على أنها قلب شجرة الحركات، حيث ستقضي الشخصية معظم الوقت في تنفيذ تلك الحركات، و تتفرع بقية الحركات عنها كما فعلنا قبل قليل مع حركة الهجوم.
</p>

<p>
	تعرض لقطة الشاشة التالية مجموعة من الحركات التي ترتبط بالحلقة <code>IWR</code>، ومن المهم هنا تذكر أن خاصيات الانتقال تُضبط بنفس الأسلوب الذي اتبعناه في المثال السابق.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172896" href="https://academy.hsoub.com/uploads/monthly_2025_05/07_multi_anim.png.9a54a651e81cdf5e130a5815dd860374.png" rel=""><img alt="07 multi anim" class="ipsImage ipsImage_thumbnailed" data-fileid="172896" data-unique="h6lzj20a4" src="https://academy.hsoub.com/uploads/monthly_2025_05/07_multi_anim.thumb.png.be58e1fa6ca0ab5d5e22859c3d83c4d6.png"> </a>
</p>

<p>
	يمكننا أيضًا النقر على الحركة لتغيير اسمها، وذلك لأن بعض الأسماء طويلة؛ أما الحركة التي تختلف عن غيرها فهي حركة القفز، إذ تقسم هذه الحركة إلى ثلاثة أجزاء هي البداية Start والعودة land التي تُنفّذ عندما تبدأ الشخصية بالقفز وعندما تنهيه، بينما يمثل الجزء الثالث التوقف Idle حركة وصل تُنفّذ طالما أن الشخصية لا تزال في الجو، خاصةً إن انتقلت أثناء القفز مسافة طويلة مثلًا.
</p>

<p>
	سنضيف الآن حركات القفز الثلاث كالتالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172900" href="https://academy.hsoub.com/uploads/monthly_2025_05/08_adding_jump_loop.png.ea4888c1c7b82fee238424ab6e178227.png" rel=""><img alt="08 adding jump loop" class="ipsImage ipsImage_thumbnailed" data-fileid="172900" data-unique="9j4lulpcq" src="https://academy.hsoub.com/uploads/monthly_2025_05/08_adding_jump_loop.thumb.png.1fd2dc9a541171efe4a8fffe3605662a.png"> </a>
</p>

<p>
	وبما أننا نريد الانتقال مباشرةً من الحلقة <code>IWR</code> إلى الحركة <code>Jump_Idle</code> عند السقوط عن حافة، فعند النقر على زر القفز ستُنفّذ الحركة <code>Jump_Start</code>. وإضافة إلى ذلك، أبقينا الانتقال من <code>Jump_Start</code> إلى <code>Jump_Idle</code> على القيمة <code>Auto</code>. لكن بدل تغيير الخاصية <code>Advanced&gt;Mode</code> إلى <code>Enabled</code>، أضفنا شرطًا من خلال ضبط قيمة الخاصية <code>Condition</code> على <code>jumping</code>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172898" href="https://academy.hsoub.com/uploads/monthly_2025_05/09_jumping_condition.png.0e45844f0ffc0de13c45110a17e6c74a.png" rel=""><img alt="09 jumping condition" class="ipsImage ipsImage_thumbnailed" data-fileid="172898" data-unique="9dfymv7tq" src="https://academy.hsoub.com/uploads/monthly_2025_05/09_jumping_condition.png.0e45844f0ffc0de13c45110a17e6c74a.png"> </a>
</p>

<p>
	وبشكل مشابه، نضع شرطًا على تنفيذ الانتقال بين <code>Jump_Idle</code> و <code>Jump_Land</code> هو <code>grounded</code>؛ وسنتمكن من ضبط هذه الشروط لاحقًا من خلال الشيفرة لتفعيل عملية الانتقال.
</p>

<p>
	قد نلاحظ أخيرًا إن أمعنا النظر أن الانتقال بين الحركتين <code>Jump_Land</code> و <code>IWR</code> لا تبدو سلسلة، لأن آخر إطار من الأولى لا يتوافق تمامًا مع أول إطار من الثانية. يمكن أن نتدارك هذا الأمر باختيار الانتقال بينهما وضبط قيمة الخاصية <code>Xfade Time</code> على <code>0.1</code> وسنرى نتيجةً مرضية.
</p>

<h2 id="-1">
	خلاصة
</h2>

<p>
	ضبطنا في هذا المقال حركات شخصية ثلاثية الأبعاد وأصبحت جاهزةً للاستخدام؛ وباستخدام العقدة <code>AnimationTree</code> أصبح اختيار الانتقال بين الحركات أكثر سهولةً في شيفرة الحركة.
</p>

<p>
	ترجمة -وبتصرف- للمقال: <a href="https://kidscancode.org/godot_recipes/4.x/3d/assets/character_animation/index.html" rel="external nofollow">Character Animation</a>.
</p>

<h2 id="-2">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/game-development/%D8%A7%D8%B3%D8%AA%D9%8A%D8%B1%D8%A7%D8%AF-%D8%A7%D9%84%D8%A3%D8%B5%D9%88%D9%84-assets-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D9%81%D9%8A-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-godot-r2593/" rel="">استيراد الأصول Assets ثلاثية الأبعاد في محرك الألعاب Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%85%D9%86%D8%B7%D9%82%D8%A9-%D8%A7%D9%84%D9%84%D8%B9%D8%A8-%D9%84%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D9%88%D8%AF%D9%88-r2444/" rel="">إعداد منطقة اللعب للعبة ثلاثية الأبعاد باستخدام جودو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B4%D8%AE%D8%B5%D9%8A%D8%A7%D8%AA-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-godot-r2508/" rel="">إنشاء شخصيات ثلاثية الأبعاد في جودو Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%AD%D8%B1%D9%8A%D9%83-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D9%8B%D8%A7-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AD%D8%B1%D9%83-%D8%AC%D9%88%D8%AF%D9%88-r2450/" rel="">تحريك اللاعب برمجيًا في لعبة ثلاثية الأبعاد باستخدام محرك جودو</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2594</guid><pubDate>Mon, 25 Aug 2025 16:01:02 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x64A;&#x631;&#x627;&#x62F; &#x627;&#x644;&#x623;&#x635;&#x648;&#x644; Assets &#x62B;&#x644;&#x627;&#x62B;&#x64A;&#x629; &#x627;&#x644;&#x623;&#x628;&#x639;&#x627;&#x62F; &#x641;&#x64A; &#x645;&#x62D;&#x631;&#x643; &#x627;&#x644;&#x623;&#x644;&#x639;&#x627;&#x628; Godot</title><link>https://academy.hsoub.com/programming/game-development/%D8%A7%D8%B3%D8%AA%D9%8A%D8%B1%D8%A7%D8%AF-%D8%A7%D9%84%D8%A3%D8%B5%D9%88%D9%84-assets-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D9%81%D9%8A-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-godot-r2593/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_05/001.Assets.png.fdd36f46a890f7f30f0faedfcbb59b2d.png" /></p>
<p>
	نقدم في سلسلة المقالات التالية طرائق لاستيراد أصول الألعاب ثلاثية الألعاب والتعامل معها بما في ذلك النماذج Models والرسوم المتحركة Animations ومواد البناء Materials. سنستخدم في مثالنا الذي نبنيه لتوضيح الفكرة بعض الأصول المتاحة على موقع <a href="https://kaylousberg.itch.io/" rel="external nofollow">Kay Lousberg</a>:
</p>

<ul>
	<li>
		<a href="https://kaylousberg.itch.io/kaykit-adventurers" rel="external nofollow">حزمة شخصيات المغامرين Adventurers</a>
	</li>
	<li>
		<a href="https://kaylousberg.itch.io/kaykit-dungeon-remastered" rel="external nofollow">حزمة Dungeon</a>
	</li>
</ul>

<p>
	عند استخدام حزم الأصول التي ذكرناها ببداية المقال، سنلاحظ أن هناك عدة نسخ من الأصول وبتنسيقات مختلفة مثل OBJ و FBX و GLTF، وأنه توجد ملفات إضافية مثل الأمثلة وملفات نقوش منفصلة في حال أردت تعديل أي شيء. وبالطبع لن نحتاج إلى كل ذلك، فالتنسيق GLTF هو الصيغة المفضلة للاستيراد في جودو. إذًا كل ما علينا فعله هو سحب المجلد <code>gltf</code> أو الملفات <code>gltf.</code> أو <code>glb.</code> وهي الصيغة الثنائية لنفس الملف، إلى مجلد مشروعك.
</p>

<p>
	سنختار المجلد gltf من حزمة Dungeon، والمجلد characters من حزمة Adventurers، ونسحبهما إلى مجلد مشروعنا
</p>

<p>
	<strong>ملاحظة</strong>: هنالك الكثير من الملفات في حزمة Dungeon وقد يستغرق جودو بعض الوقت في قراءتها.
</p>

<h2 id="">
	استيراد شخصية
</h2>

<p>
	سنختار الملف Knight.glb من المجلد characters&gt;gltf ضمن نافذة نظام الملفات FileSystem، ثم ننقر على نافذة استيراد Import في الأعلى إلى جوار نافذة المشهد Scene.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172892" href="https://academy.hsoub.com/uploads/monthly_2025_05/01_import_knight_char.png.5204fca0885ac174ce3bc0a69d91ca5a.png" rel=""><img alt="01 import knight char" class="ipsImage ipsImage_thumbnailed" data-fileid="172892" data-unique="629lotz5g" src="https://academy.hsoub.com/uploads/monthly_2025_05/01_import_knight_char.thumb.png.857dec40d30beacf131cb945f49801ea.png"> </a>
</p>

<p>
	سنجد ضمن النافذة بعض خيارات الاستيراد الأساسية، لكن بإمكاننا تفصيل العملية. سننقر على زر إعدادات متقدمة Advanced، وستظهر لنا نافذة جديدة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172894" href="https://academy.hsoub.com/uploads/monthly_2025_05/02_advanced_import.png.8882112081c7ee77bd1b03994378911f.png" rel=""><img alt="02 advanced import" class="ipsImage ipsImage_thumbnailed" data-fileid="172894" data-unique="czklz5qmn" style="width: 700px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_05/02_advanced_import.thumb.png.266f204595f6e310ca5f8a7bf339b035.png"> </a>
</p>

<p>
	سنرى في اليمين (أو اليسار في النسخة الإنجليزية) البيانات التي يضمها مشهد GLTF بما في ذلك النقوش textures والحركات Animations؛ وسنلاحظ كيف اُلحقت كل الأسلحة بالشخصية، إضافةً إلى قائمة مطولة بالحركات التي يمكن للشخصية تنفيذها.
</p>

<p>
	تظهر الشخصية في وسط النافذة، إضافةً إلى مجموعة من الخيارات إلى اليسار (اليمين في النسخة الإنجليزية) التي تساعدنا على تعديل ضبط العنصر الذي نختاره من القائمة اليمينية.
</p>

<p>
	وطالما أننا سنكتب شيفرة اللاعب باعتباره <a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%B9%D9%82%D8%AF-nodes-%D9%81%D9%8A-%D9%85%D8%AD%D8%B1%D9%83-%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot-r2490/" rel="">عقدةً</a> من النوع <code>CharacterBody3D</code>، فبإمكاننا تحديد نوع العقدة من خلال النقر على مشهد scene، ثم الانتقال إلى القائمة اليمينة والنقر إلى جوار الخاصية <code>Root Type</code> ثم اختيار العقدة <code>CharacterBody3D</code> من النافذة التي ظهرت.
</p>

<h3 id="animations">
	الحركات Animations
</h3>

<p>
	سننتقل إلى الأسفل في نافذة الإعدادات المتقدمة نحو شجرة الحركات Animations، وسنجد الكثير منها. سنحتاج في مثالنا إلى تنفيذ بعض الحركات مرةً واحدةً فقط مثل الهجوم، في حين سننفذ بعضًا آخر في حلقات مثل المشي walk والتوقف دون فعل Idle والجري Running. ولتنفيذ أي حركة مستمرة بشكل حلقة، سنننقر عليها ثم نضبط الخاصية <code>Loop Mode</code> على <code>Linear</code>. سنكرر هذا الأمر على جميع حركات المشي Walking والجري Running والتوقف Idle المختلفة. وعند الانتهاء من ذلك سننقر على زر إعادة الاستيراد Reimport في أسفل النافذة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172890" href="https://academy.hsoub.com/uploads/monthly_2025_05/03_anim_loop.png.c481e4dccfe33e4384810decc6755292.png" rel=""><img alt="03 anim loop" class="ipsImage ipsImage_thumbnailed" data-fileid="172890" data-unique="86ubn8ynf" src="https://academy.hsoub.com/uploads/monthly_2025_05/03_anim_loop.png.c481e4dccfe33e4384810decc6755292.png"> </a>
</p>

<p>
	<strong>ملاحظة</strong>: إن كنا نصمم شخصياتنا بأنفسنا، نستطيع تجاوز عملية التعيين اليدوي لتكرار الحركة، والانتقال مباشرةً إلى تنفيذ الأمر تلقائيًا من خلال إضافة اللاحقة <code>loop-</code> إلى اسم الحركة. وللمزيد من التوضيح يمكن مراجعة <a href="https://docs.godotengine.org/en/stable/tutorials/assets_pipeline/importing_scenes.html#import-hints" rel="external nofollow">توثيق جودو</a>.
</p>

<p>
	سننقر الآن على الملف knight.glb بالزر اليمين للفأرة ونختار الأمر مشهد مورّث جديد New Inherited Scene. سنرى أن هناك عقدة جديدة ظهرت في نافذة المشهد تضم كل النماذج والحركات <code>AnimationPlayer</code> التي نستطيع تجريبها ضمن نافذة التحريك أسفل نافذة العرض.
</p>

<h3 id="-1">
	استيراد عناصر البيئة المحيطة
</h3>

<p>
	يمكنننا استيراد عناصر البيئة المحيطة بنفس الأسلوب. لنستخدم كمثال عملية استيراد حائط من حزمة dungeon. ونظرًا لوجود الكثير من الملفات في الحزمة، سنكتب wall في مرشح البحث، لنجد المشهد المطلوب الآتي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172893" href="https://academy.hsoub.com/uploads/monthly_2025_05/04_filtering_wall.png.c87fd2bf06bcc3259d5fe8d65f62b78a.png" rel=""><img alt="04 filtering wall" class="ipsImage ipsImage_thumbnailed" data-fileid="172893" data-unique="96tfvin6t" src="https://academy.hsoub.com/uploads/monthly_2025_05/04_filtering_wall.png.c87fd2bf06bcc3259d5fe8d65f62b78a.png"> </a>
</p>

<p>
	نريد أن يكون الجدار صلبًا، وسيكون إنشاء جسم من النوع <code>StaticBody3D</code> بأنفسنا مضنيًا مع جميع أشكال التصادم collision shapes التي يجب تعيينها لكل عنصر من الجسم. لكن يستطيع جودو تنفيذ الأمر تلقائيًا عند استيراد العنصر.
</p>

<p>
	سننتقل إلى نافذة الإعدادات المتقدمة للمشهد المستورد، ثم نختار العقدة wall من النوع Mesh أيقونتها شبكة حمراء، ثم نفعّل الخيار فيزياء Physics وننتقل إلى الخاصية <code>Physics&gt;Shape Type</code> ونضبطها على <code>Simple Convex</code>.
</p>

<p>
	يمثل هذا الخيار الشكل المحدب الذي يغلف الجدار الذي سيساعد في اكتشاف تصادم أي جسم مع الجدار.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172891" href="https://academy.hsoub.com/uploads/monthly_2025_05/05_shape_type.png.9f61609b990478fc0729d4c3246d766a.png" rel=""><img alt="05 shape type" class="ipsImage ipsImage_thumbnailed" data-fileid="172891" data-unique="bdysbtrdf" style="width: 700px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_05/05_shape_type.thumb.png.869c95511dc59b3f58e6b9b17c7fdd12.png"> </a>
</p>

<p>
	سننقر الآن على خيار إعادة الاستيراد Reimport، وهكذا سيُنشئ جودو عقدةً من النوع <code>StaticBody3D</code> عند استخدام هذا العنصر في اللعبة.
</p>

<p>
	<strong>ملاحظة</strong>: كما أشرنا سابقًا، هناك طريقة استيراد لتعيين أشكال التصادم تلقائيًا. لذا سنضيف في مشروع Blender اللاحقة <code>col-</code> مما يساعد البرنامج الذي يستورد الملف في تنفيذ العملية تلقائيًا. ويمكن مراجعة <a href="https://docs.godotengine.org/en/stable/tutorials/assets_pipeline/importing_scenes.html#import-hints" rel="external nofollow">توثيق جودو</a>.
</p>

<h3 id="-2">
	أتمتة الاستيراد
</h3>

<p>
	على الرغم من أن إضافة تلميحات إلى أسماء الملفات المستوردة هي الطريقة المفضلة في أتمتة عملية الاستيراد، خاصةً عندما نصمم بأنفسنا الشخصيات والعناصر. لكنه أمر لا يمكن تنفيذه عند استيراد هذه الملفات من حزم جاهزة كالتي نستخدمها في مثالنا.
</p>

<p>
	بإمكاننا أيضًا كتابة سكريبت استيراد يُنفّذ على كل عقدة مستوردة من نوع معين، فنستطيع مثلًا أتمتة عملية إنشاء جسم التصادم التي شرحناها في الفقرة السابقة، حيث يتنقل السكريبت التالي خلال جميع عقد الكائن الذي استوردناه ويُنشئ جسم تصادم لكل عقدة من النوع <code>MeshInstance3D</code>
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1597_9" style=""><span class="lit">@tool</span><span class="pln">
extends </span><span class="typ">EditorScenePostImport</span><span class="pln">

func _post_import</span><span class="pun">(</span><span class="pln">scene</span><span class="pun">):</span><span class="pln">
    iterate</span><span class="pun">(</span><span class="pln">scene</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> scene

func iterate</span><span class="pun">(</span><span class="pln">node</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> node </span><span class="pun">!=</span><span class="pln"> null</span><span class="pun">:</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> node </span><span class="kwd">is</span><span class="pln"> </span><span class="typ">MeshInstance3D</span><span class="pun">:</span><span class="pln">
        node</span><span class="pun">.</span><span class="pln">create_trimesh_collision</span><span class="pun">()</span><span class="pln">
      </span><span class="kwd">for</span><span class="pln"> child </span><span class="kwd">in</span><span class="pln"> node</span><span class="pun">.</span><span class="pln">get_children</span><span class="pun">():</span><span class="pln">
        iterate</span><span class="pun">(</span><span class="pln">child</span><span class="pun">)</span></pre>

<p>
	ننتقل في النافذة استيراد Import إلى الخاصية <code>Import Script</code> ثم نختار السكريبت السابق بعد حفظه في مكان مناسب. وهكذا سيُنشئ جودو أشكال تصادم لكل عقدة من النوع <code>MeshInstance3D</code> عند النقر على إعادة الاستيراد Reimport.
</p>

<h2 id="-3">
	خلاصة
</h2>

<p>
	تعلمنا في هذا المقال طريقة استيراد أصول ثلاثية أبعاد جاهزة إلى <a href="https://academy.hsoub.com/programming/game-development/%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot/" rel="">محرك الألعاب جود</a>و وضبط بعض الخيارات المهمة لعملية الاستيراد وكتابة سكريبت لأتمتتها.
</p>

<p>
	ترجمة -وبتصرف- للمقالين: <a href="https://kidscancode.org/godot_recipes/4.x/3d/assets/index.html" rel="external nofollow">Assets</a> و <a href="https://kidscancode.org/godot_recipes/4.x/3d/assets/importing_assets/index.html" rel="external nofollow">Importing Assets</a>
</p>

<h2 id="-4">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/game-development/%D8%A8%D8%AF%D8%A1-%D9%88%D8%A5%D9%86%D9%87%D8%A7%D8%A1-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D9%81%D9%8A-%D9%85%D8%AD%D8%B1%D9%83-%D8%AC%D9%88%D8%AF%D9%88-godot-r2592/" rel="">بدء وإنهاء الألعاب في محرك جودو Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%85%D9%86%D8%B7%D9%82%D8%A9-%D8%A7%D9%84%D9%84%D8%B9%D8%A8-%D9%84%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D9%88%D8%AF%D9%88-r2444/" rel="">إعداد منطقة اللعب للعبة ثلاثية الأبعاد باستخدام جودو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B4%D8%AE%D8%B5%D9%8A%D8%A7%D8%AA-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D9%81%D9%8A-%D8%AC%D9%88%D8%AF%D9%88-godot-r2508/" rel="">إنشاء شخصيات ثلاثية الأبعاد في جودو Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%AD%D8%B1%D9%8A%D9%83-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D9%8B%D8%A7-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%84%D8%A7%D8%AB%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AD%D8%B1%D9%83-%D8%AC%D9%88%D8%AF%D9%88-r2450/" rel="">تحريك اللاعب برمجيًا في لعبة ثلاثية الأبعاد باستخدام محرك جودو</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2593</guid><pubDate>Mon, 18 Aug 2025 16:04:01 +0000</pubDate></item><item><title>&#x623;&#x647;&#x645; &#x627;&#x644;&#x62A;&#x645;&#x627;&#x631;&#x64A;&#x646; &#x627;&#x644;&#x639;&#x645;&#x644;&#x64A;&#x629; &#x627;&#x644;&#x62A;&#x64A; &#x642;&#x62F; &#x62A;&#x637;&#x644;&#x628; &#x641;&#x64A; &#x627;&#x644;&#x645;&#x642;&#x627;&#x628;&#x644;&#x627;&#x62A; &#x644;&#x62A;&#x648;&#x638;&#x64A;&#x641; &#x645;&#x637;&#x648;&#x631; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646;</title><link>https://academy.hsoub.com/programming/python/%D8%A3%D9%87%D9%85-%D8%A7%D9%84%D8%AA%D9%85%D8%A7%D8%B1%D9%8A%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D9%8A-%D9%82%D8%AF-%D8%AA%D8%B7%D9%84%D8%A8-%D9%81%D9%8A-%D8%A7%D9%84%D9%85%D9%82%D8%A7%D8%A8%D9%84%D8%A7%D8%AA-%D9%84%D8%AA%D9%88%D8%B8%D9%8A%D9%81-%D9%85%D8%B7%D9%88%D8%B1-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2596/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_05/-----.png.74bd27198dc93753c1aadd109be8286d.png" /></p>
<p>
	شرحنا في المقال السابق أهم الأسئلة النظرية التي قد تُطرح على المتقدمين لوظيفة مطور بايثون خلال مقابلة التوظيف، وسنوضح في هذا المقال أهم الأسئلة والتمارين العملية التي قد تُطلب منك فوريًا في مقابلات التوظيف بوظيفة مطور بايثون.
</p>

<p>
	قد تُطرح في المقابلات التقنية بعض الأسئلة التي تتطلب تحسين أو تصحيح شيفرة برمجية، سنذكر أهمها في ما يلي.
</p>

<h2 id="">
	التمرين الأول: تحسين الشيفرة
</h2>

<p>
	قد يحتوي التمرين على أسئلة من نوع:
</p>

<ol>
	<li>
		اشرح وظيفة الدالة <code>process_numbers</code> في المثال التالي وما هو الخرج؟
	</li>
	<li>
		ما هي العملية التي يمكن التخلي عنها دون أن يؤثر ذلك في الخرج؟
	</li>
</ol>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6765_6" style=""><span class="kwd">def</span><span class="pln"> process_numbers</span><span class="pun">(</span><span class="pln">numbers</span><span class="pun">):</span><span class="pln">
    unique_numbers </span><span class="pun">=</span><span class="pln"> list</span><span class="pun">(</span><span class="pln">set</span><span class="pun">(</span><span class="pln">numbers</span><span class="pun">))</span><span class="pln">
    unique_numbers</span><span class="pun">.</span><span class="pln">sort</span><span class="pun">()</span><span class="pln">
    unique_tuple </span><span class="pun">=</span><span class="pln"> tuple</span><span class="pun">(</span><span class="pln">unique_numbers</span><span class="pun">)</span><span class="pln">
    total </span><span class="pun">=</span><span class="pln"> sum</span><span class="pun">(</span><span class="pln">unique_tuple</span><span class="pun">)</span><span class="pln">
    average </span><span class="pun">=</span><span class="pln"> total </span><span class="pun">/</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">unique_tuple</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> unique_tuple </span><span class="kwd">else</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">[</span><span class="pln">num </span><span class="kwd">for</span><span class="pln"> num </span><span class="kwd">in</span><span class="pln"> unique_tuple </span><span class="kwd">if</span><span class="pln"> num </span><span class="pun">&gt;</span><span class="pln"> average</span><span class="pun">]</span><span class="pln">

</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">process_numbers</span><span class="pun">([</span><span class="lit">7</span><span class="pun">,</span><span class="lit">1</span><span class="pun">,</span><span class="lit">3</span><span class="pun">,</span><span class="lit">4</span><span class="pun">,</span><span class="lit">2</span><span class="pun">,</span><span class="lit">3</span><span class="pun">,</span><span class="lit">5</span><span class="pun">,</span><span class="lit">6</span><span class="pun">,</span><span class="lit">4</span><span class="pun">]))</span></pre>

<p>
	وتكون الإجابة على النحو الآتي:
</p>

<ul>
	<li>
		<strong>الجواب 1</strong>: تقبل الدالة السابقة وسيطًا على شكل قائمة list مكونة من أعداد ثم تختار العناصر الفريدة منها وتوجِد متوسطها الحسابي، ثم تعيد قائمةً تضم الأعداد الفريدة الأكبر تمامًا من المتوسط. وسيكون الخرج <code>[7, 6, 5]</code>
	</li>
	<li>
		<strong>الجواب 2</strong>: عملية تحويل القائمة List إلى قائمة Tuple في السطر الرابع
	</li>
</ul>

<h2 id="-1">
	التمرين الثاني: استخدام مكتبات بايثون مقارنة بالتنفيذ اليدوي لخوارزمية
</h2>

<p>
	قد يحتوي التمرين على أسئلة من نوع:
</p>

<ol>
	<li>
		ما وظيفة الشيفرة في هذا التمرين؟
	</li>
	<li>
		هل يمكنك تنفيذ نفس الوظيفة باستخدام مكتبات جاهزة؟
	</li>
	<li>
		اكتب شيفرةً تقارن بين زمني تنفيذ الطريقتين السابقتين، ما الذي تلاحظه مع الازدياد الكبير لحجم قائمة البيانات؟
	</li>
</ol>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6765_8" style=""><span class="kwd">import</span><span class="pln"> random

random_data </span><span class="pun">=</span><span class="pln"> set</span><span class="pun">(</span><span class="pln">random</span><span class="pun">.</span><span class="pln">sample</span><span class="pun">(</span><span class="pln">range</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10000</span><span class="pun">),</span><span class="pln"> </span><span class="lit">1000</span><span class="pun">))</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> calculate_median</span><span class="pun">(</span><span class="pln">data</span><span class="pun">):</span><span class="pln">
   data </span><span class="pun">=</span><span class="pln"> list</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln">
    data</span><span class="pun">.</span><span class="pln">sort</span><span class="pun">()</span><span class="pln">
    n </span><span class="pun">=</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln">
    mid </span><span class="pun">=</span><span class="pln"> n </span><span class="pun">//</span><span class="pln"> </span><span class="lit">2</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> n </span><span class="pun">%</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">1</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> data</span><span class="pun">[</span><span class="pln">mid</span><span class="pun">]</span><span class="pln">
    </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">(</span><span class="pln">data</span><span class="pun">[</span><span class="pln">mid </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> data</span><span class="pun">[</span><span class="pln">mid</span><span class="pun">])</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="lit">2</span></pre>

<p>
	وتكون الإجابة كالآتي:
</p>

<ul>
	<li>
		<strong>الجواب الأول:</strong> تعطي الشيفرة السابقة طريقة حساب وسيط Median لعينة من الأرقام بطريقة يدوية
	</li>
	<li>
		<strong>الجواب الثاني</strong>: نعم، باستخدام المكتبة <code>statistics</code> أو <code>scipy</code>
	</li>
	<li>
		<strong>الجواب الثالث</strong>: باستخدام المكتبة <code>time</code>
	</li>
</ul>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6765_10" style=""><span class="kwd">import</span><span class="pln"> random
</span><span class="kwd">import</span><span class="pln"> time
</span><span class="kwd">import</span><span class="pln"> statistics

random_data </span><span class="pun">=</span><span class="pln"> set</span><span class="pun">(</span><span class="pln">random</span><span class="pun">.</span><span class="pln">sample</span><span class="pun">(</span><span class="pln">range</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10000</span><span class="pun">),</span><span class="pln"> </span><span class="lit">1000</span><span class="pun">))</span><span class="pln">

</span><span class="com"># تنفيذ يدوي للخوارزمية</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> calculate_median</span><span class="pun">(</span><span class="pln">data</span><span class="pun">):</span><span class="pln">
   data </span><span class="pun">=</span><span class="pln"> list</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln">
    data</span><span class="pun">.</span><span class="pln">sort</span><span class="pun">()</span><span class="pln">
    n </span><span class="pun">=</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln">
    mid </span><span class="pun">=</span><span class="pln"> n </span><span class="pun">//</span><span class="pln"> </span><span class="lit">2</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> n </span><span class="pun">%</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">1</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> data</span><span class="pun">[</span><span class="pln">mid</span><span class="pun">]</span><span class="pln">
    </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">(</span><span class="pln">data</span><span class="pun">[</span><span class="pln">mid </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> data</span><span class="pun">[</span><span class="pln">mid</span><span class="pun">])</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="lit">2</span><span class="pln">

</span><span class="com"># Statistics استخدام المكتبة</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> median_using_statistics</span><span class="pun">(</span><span class="pln">data</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> statistics</span><span class="pun">.</span><span class="pln">median</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln">

</span><span class="com"># تقدير وقت التنفيذ</span><span class="pln">
start_manual </span><span class="pun">=</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">time</span><span class="pun">()</span><span class="pln">
calculate_median</span><span class="pun">(</span><span class="pln">random_data</span><span class="pun">)</span><span class="pln">
end_manual </span><span class="pun">=</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">time</span><span class="pun">()</span><span class="pln">

start_Statistics </span><span class="pun">=</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">time</span><span class="pun">()</span><span class="pln">
median_using_statistics</span><span class="pun">(</span><span class="pln">random_data</span><span class="pun">)</span><span class="pln">
end_Statistics </span><span class="pun">=</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">time</span><span class="pun">()</span><span class="pln">

</span><span class="com"># طباعة النتائج</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="str">"Manual Sort-Based Time:"</span><span class="pun">,</span><span class="pln"> end_manual </span><span class="pun">-</span><span class="pln"> start_manual</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="str">"Statistics-Based Time:"</span><span class="pun">,</span><span class="pln"> end_Statistics </span><span class="pun">-</span><span class="pln"> start_Statistics</span><span class="pun">)</span></pre>

<p>
	يمكن ملاحظة أنه مع ازدياد حجم البيانات، يصبح وقت التنفيذ أكبر بكثير في حال الطريقة اليدوية، لأن المكتبات تكون محققة باستخدام خوارزميات أسرع وأمثل وتعمل بطريقة أكثر كفاءة، مما يجعل وقت تنفيذها أقل أو أكثر استقرارًا مقارنةً بالأسلوب اليدوي.
</p>

<h2 id="numpy">
	التمرين الثالث: استخدام المكتبة <code>numpy</code>
</h2>

<p>
	قد يحتوي التمرين على أسئلة من نوع:
</p>

<ol>
	<li>
		أوجد الخطأ في الشيفرة التالية -لا بد من امتلاك خلفية باستخدام المكتبة <code>numpy</code>
	</li>
	<li>
		فسّر الخطأ
	</li>
	<li>
		صحح الخطأ، واكتب الشيفرة الصحيحة
	</li>
</ol>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6765_12" style=""><span class="kwd">import</span><span class="pln"> numpy </span><span class="kwd">as</span><span class="pln"> np
</span><span class="kwd">def</span><span class="pln"> transform_array</span><span class="pun">(</span><span class="pln">arr</span><span class="pun">):</span><span class="pln">
    arr</span><span class="pun">[</span><span class="pln">arr </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">nan  </span><span class="com"># NaN استبدل القيم السالبة بقيمة من النوع </span><span class="pln">
    mean_value </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">nanmean</span><span class="pun">(</span><span class="pln">arr</span><span class="pun">)</span><span class="pln">  </span><span class="com"># NaN احسب المتوسط مهملًا القيم </span><span class="pln">
    arr </span><span class="pun">=</span><span class="pln"> arr </span><span class="pun">/</span><span class="pln"> mean_value  </span><span class="com"># سوّي عناصرالمصفوفة بالقسمة على المتوسط</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> arr
data </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">array</span><span class="pun">([</span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">7</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">])</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">transform_array</span><span class="pun">(</span><span class="pln">data</span><span class="pun">))</span></pre>

<p>
	يكون الجواب على النحو الآتي:
</p>

<ul>
	<li>
		<strong>الجواب الأول</strong>: الخطأ في السطر الثالث
	</li>
	<li>
		<strong>الجواب الثاني</strong>: لا تدعم <code>numpy</code> تخزين القيم <code>NaN</code> ضمن المصفوفات الصحيحة
	</li>
	<li>
		<strong>الجواب الثالث</strong>: علينا تحويل عناصر المصفوفة صراحة إلى النوع <a href="https://wiki.hsoub.com/Python/float" rel="external">float</a> قبل استبدال الأعداد السالبة
	</li>
</ul>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6765_14" style=""><span class="kwd">import</span><span class="pln"> numpy </span><span class="kwd">as</span><span class="pln"> np
</span><span class="kwd">def</span><span class="pln"> transform_array</span><span class="pun">(</span><span class="pln">arr</span><span class="pun">):</span><span class="pln">
    arr </span><span class="pun">=</span><span class="pln"> arr</span><span class="pun">.</span><span class="pln">astype</span><span class="pun">(</span><span class="pln">float</span><span class="pun">)</span><span class="pln">  </span><span class="com"># float حوّل عناصر المصفوفة إلى النوع </span><span class="pln">
    arr</span><span class="pun">[</span><span class="pln">arr </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">nan  
    </span><span class="pun">.</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">
data </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">array</span><span class="pun">([</span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">7</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">])</span><span class="pln"> 
</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">transform_array</span><span class="pun">(</span><span class="pln">data</span><span class="pun">))</span></pre>

<h2 id="panda">
	التمرين الرابع: استخدام المكتبة <code>pandas</code>
</h2>

<p>
	قد يحتوي التمرين على أسئلة من نوع:
</p>

<ol>
	<li>
		أوجد الخطأ في الشيفرة التالية التي تستخدم المكتبة <code>pandas</code>
	</li>
	<li>
		فسّر الخطأ
	</li>
	<li>
		صحح الخطأ الموجود بالشيفرة، واكتب الشيفرة الصحيحة
	</li>
</ol>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6765_16" style=""><span class="kwd">import</span><span class="pln"> pandas </span><span class="kwd">as</span><span class="pln"> pd
data </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="str">'A'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">4</span><span class="pun">],</span><span class="pln"> </span><span class="str">'B'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="lit">6</span><span class="pun">,</span><span class="pln"> </span><span class="lit">7</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">]}</span><span class="pln">
df </span><span class="pun">=</span><span class="pln"> pd</span><span class="pun">.</span><span class="typ">DataFrame</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln">
df</span><span class="pun">[</span><span class="str">'C'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> df</span><span class="pun">.</span><span class="pln">A </span><span class="pun">*</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="com"># C أنشئ عمود جديد </span><span class="pln">
filtered_df </span><span class="pun">=</span><span class="pln"> df</span><span class="pun">[</span><span class="pln">df</span><span class="pun">.</span><span class="pln">C </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">5</span><span class="pun">]</span><span class="pln"> </span><span class="com"># أكبر من 5 C احذف الصف عندما تكون القيمة في </span><span class="pln">
filtered_df</span><span class="pun">.</span><span class="pln">reset_index</span><span class="pun">()</span><span class="pln"> </span><span class="com"># أعد ضبط فهرس إطار البيانات</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">filtered_df</span><span class="pun">)</span></pre>

<p>
	ويكون الجواب على النحو الآتي:
</p>

<ul>
	<li>
		<strong>الجواب الأول</strong>: الخطأ في السطر السادس
	</li>
	<li>
		<strong>الجواب الثاني</strong>: لن تتغير الفهرسة بعد حذف الصفوف ولن تُطبق على <code>filtered_df</code>
	</li>
	<li>
		<strong>الجواب الثالث</strong>: علينا استدعاء التابع <code>()reset_index</code> باستخدام <code>inplace=true</code> على النحو:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6765_18" style=""><span class="pln">filtered_df</span><span class="pun">.</span><span class="pln">reset_index</span><span class="pun">(</span><span class="pln">drop</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">,</span><span class="pln"> inplace</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span></pre>

<h4 id="-2">
	التمرين الخامس: الاستيثاق والثغرات الأمنية
</h4>

<p>
	تحاول الشيفرة التالية الاستيثاق باستخدام JWT، لكنها تعاني من ثغرة أمنية خطيرة والمطلوب:
</p>

<ol>
	<li>
		حدد الثغرة الأمنية في هذه الشيفرة
	</li>
	<li>
		اقترح حلًا لها
	</li>
</ol>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6765_20" style=""><span class="kwd">import</span><span class="pln"> jwt
</span><span class="kwd">import</span><span class="pln"> datetime

SECRET_KEY </span><span class="pun">=</span><span class="pln"> </span><span class="str">"supersecretkey"</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> generate_token</span><span class="pun">(</span><span class="pln">username</span><span class="pun">):</span><span class="pln">
    payload </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">"user"</span><span class="pun">:</span><span class="pln"> username</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"exp"</span><span class="pun">:</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">.</span><span class="pln">utcnow</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">timedelta</span><span class="pun">(</span><span class="pln">minutes</span><span class="pun">=</span><span class="lit">30</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> jwt</span><span class="pun">.</span><span class="pln">encode</span><span class="pun">(</span><span class="pln">payload</span><span class="pun">,</span><span class="pln"> SECRET_KEY</span><span class="pun">,</span><span class="pln"> algorithm</span><span class="pun">=</span><span class="str">"HS256"</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> verify_token</span><span class="pun">(</span><span class="pln">token</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
        decoded </span><span class="pun">=</span><span class="pln"> jwt</span><span class="pun">.</span><span class="pln">decode</span><span class="pun">(</span><span class="pln">token</span><span class="pun">,</span><span class="pln"> SECRET_KEY</span><span class="pun">,</span><span class="pln"> algorithms</span><span class="pun">=</span><span class="str">"HS256"</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> decoded</span><span class="pun">[</span><span class="str">"user"</span><span class="pun">]</span><span class="pln">
    </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">Exception</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> e</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">None</span></pre>

<p>
	وللإجابة عن هذه التساؤلات، سيكون <strong>الجواب الأول</strong> أن هذه الشيفرة معرضة لهجوم خلط الخوارزميات Algorithm Confusion Attack، نظرًا لإمكانية تعديل المخترق على ترويسة الطلب ببساطة ويلغي استخدام خوارزمية <code>HS256</code> إلى <code>none</code> وبالتالي يتسلل دون استيثاق
</p>

<p>
	أما الحل السريع وهو <strong>جواب السؤال الثاني</strong>، فيتمثل في إجبار استخدام خوارزمية ضمن الدالة <code>verify_token</code> والتقاط الأخطاء الناتجة. فيما يلي الشيفرة المعدلة لهذه الدالة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6765_22" style=""><span class="kwd">def</span><span class="pln"> verify_token</span><span class="pun">(</span><span class="pln">token</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
        </span><span class="com"># صرّح عن خوارزمية التشفير المتوقعة من المفتاح</span><span class="pln">
        decoded </span><span class="pun">=</span><span class="pln"> jwt</span><span class="pun">.</span><span class="pln">decode</span><span class="pun">(</span><span class="pln">token</span><span class="pun">,</span><span class="pln"> SECRET_KEY</span><span class="pun">,</span><span class="pln"> algorithms</span><span class="pun">=[</span><span class="str">"HS256"</span><span class="pun">])</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> decoded</span><span class="pun">[</span><span class="str">"user"</span><span class="pun">]</span><span class="pln">
    </span><span class="kwd">except</span><span class="pln"> jwt</span><span class="pun">.</span><span class="typ">ExpiredSignatureError</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"Token expired!"</span><span class="pln">
    </span><span class="kwd">except</span><span class="pln"> jwt</span><span class="pun">.</span><span class="typ">InvalidTokenError</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"Invalid token!"</span></pre>

<h2 id="-3">
	نصائح للاستعداد لمقابلة العمل
</h2>

<p>
	من أجل الاستعداد الجيد لمقابلة العمل كمطور بايثون، لا بد من التأكد من اعتماد النصائح الآتية:
</p>

<h3 id="-4">
	فهم ما نتعلمه
</h3>

<p>
	من المهم استيعاب الغاية من كل قاعدة ومبدأ وتقنية وتدوين الملاحظات حول الاستخدام الأمثل الذي وجدناه لها. يحتاج أي مطور بايثون إلى سؤال نفسه: لماذا تقدم بايثون مثلًا عدة بنىً مختلفة لتجميع البيانات مثل <code>list</code> و <code>tuple</code> و <code>range</code>؟ وما الغاية من كل منها؟ وأين يكمن الاستخدام الأمثل لها؟ هذه التساؤلات، ستساعد كل متقدم للوظيفة على تهيئة نفسه للإجابة بدقة واختصار على الأسئلة المطروحة عليه.
</p>

<h3 id="-5">
	التحليل والمقارنة
</h3>

<p>
	لا بد من التحليل والمقارنة سواء باستخدام تعليمات مختلفة أو خوارزمية مختلفة، خاصة عند موااجهة مقاربات مختلفة لحل المشكلة نفسها. علينا أن نفهم كل نهج بدقة وندوّن ملاحظاتنا عليه، ثم نضيف رأينا الخاص ونشرح لأنفسنا ما يجري وما هو الاستخدام الأمثل الذي وجدناه.
</p>

<h3 id="-6">
	تطبيق أفضل الممارسات
</h3>

<p>
	كل منا حر في صياغة الكود البرمجي الخاص به بالطريقة التي يجدها مناسبة، لكن لنتخيل أن يفعل ذلك كل عضو من أعضاء فريق العمل؟ ستكون الامور فوضوية ومربكة لذا علينا أن نعود نفسنا على تطبيق أفضل الممارسات التي يُنصح بها أثناء بناء المشاريع، إذ سيسهّل هذا الأمر الاندماج لاحقًا ضمن أي فريق عمل خاصة في الشركات التي تأخذ تطبيق أفضل الممارسات في كتابة وتنسيق وصيانة الشيفرة على محمل الجد دائمًا. يمكن قراءة مقال <a href="https://academy.hsoub.com/programming/python/%D9%83%D9%8A%D9%81-%D8%AA%D9%83%D8%AA%D8%A8-%D9%83%D9%88%D8%AF-%D8%A3%D9%86%D9%8A%D9%82-%D9%88%D8%B3%D9%87%D9%84-%D8%A7%D9%84%D9%82%D8%B1%D8%A7%D8%A1%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-coding-style-r2513/" rel="">كيف تكتب كود أنيق وسهل القراءة باستخدام لغة بايثون</a> للمساعدة بذلك.
</p>

<h3 id="-7">
	التركيز على المهارات المطلوبة في العمل
</h3>

<p>
	لا أحد منا كامل، فقد نمتلك بعضًا من المهارات المطلوبة وينقصنا بعضها، لكن من المهم أن لا نشعر بالإحباط، وأن نحاول تعزيز معارفنا وخبراتنا بالاطلاع على مراجع مفيدة والمراجع كثيرة ومتنوعة، ويمكن هنا الاستفادة من <a href="https://academy.hsoub.com/programming/python/" rel="">مقالات أكاديمية حسوب</a> حول بايثون، إلى جانب <a href="https://academy.hsoub.com/files/c5-programming/" rel="">الكتب البرمجية</a> التي تساعد على التعلم بطريقة منهجية منظمة.
</p>

<h2 id="-8">
	ختامًا
</h2>

<p>
	بهذا نكون قد قدمنا في هذا المقال ما ينبغي على المتقدم لوظيفة مطور بايثون أن يحضّره ويتذكر تفاصيله قبل الوصول إلى مقابلة العمل، ثم عرضنا بعضًا من الأسئلة التي قد تُطرح عليه أثناء المقابلة.
</p>

<h2 id="-9">
	المراجع
</h2>

<ul>
	<li>
		<a href="https://wiki.hsoub.com/Python" rel="external">توثيق بايثون باللغة العربية</a> على موقع موسوعة حسوب 
	</li>
	<li>
		<a href="https://academy.hsoub.com/files/15-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86/" rel="">كتاب البرمجة بلغة بايثون من إعداد أكاديمية حسوب</a>
	</li>
	<li>
		<a href="https://www.javatpoint.com/advance-concepts-of-python-for-python-developer" rel="external nofollow">Advance Concepts of Python for Python Developer</a> 
	</li>
	<li>
		<a href="https://pwskills.com/blog/advanced-python-tutorials/" rel="external nofollow">Advanced Python Tutorials: Dive into Complex Concepts</a>
	</li>
	<li>
		<a href="https://www.testgorilla.com/blog/advanced-python-interview-questions/" rel="external nofollow">( advanced Python interview questions (and answers</a>
	</li>
	<li>
		<a href="https://github.com/Tanu-N-Prabhu/Python/blob/master/Python%20Coding%20Interview%20Prep/Python%20Coding%20Interview%20Questions%20(Beginner%20to%20Advanced).md" rel="external nofollow">Python Coding Interview Questions (Beginner to Advanced)</a>
	</li>
</ul>

<h2 id="-10">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/%D8%A3%D9%87%D9%85-%D8%A7%D9%84%D8%A3%D8%B3%D8%A6%D9%84%D8%A9-%D8%A7%D9%84%D9%86%D8%B8%D8%B1%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D9%8A-%D9%82%D8%AF-%D8%AA%D8%B7%D8%B1%D8%AD-%D9%81%D9%8A-%D8%A7%D9%84%D9%85%D9%82%D8%A7%D8%A8%D9%84%D8%A7%D8%AA-%D9%84%D8%AA%D9%88%D8%B8%D9%8A%D9%81-%D9%85%D8%B7%D9%88%D8%B1-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2595/" rel="">أهم الأسئلة النظرية التي قد تطرح في المقابلات لتوظيف مطور بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D8%A3%D9%87%D9%85-%D8%A3%D8%B3%D8%A6%D9%84%D8%A9-%D8%A7%D9%84%D9%85%D9%82%D8%A7%D8%A8%D9%84%D8%A7%D8%AA-%D9%84%D8%AA%D9%88%D8%B8%D9%8A%D9%81-%D9%85%D8%B7%D9%88%D8%B1-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A3%D9%85%D8%A7%D9%85%D9%8A%D8%A9-r2569/" rel="">أهم أسئلة المقابلات لتوظيف مطور واجهة أمامية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/entrepreneurship/tips/%D9%83%D9%8A%D9%81-%D8%AA%D8%AD%D8%B6%D8%B1-%D9%84%D9%85%D9%82%D8%A7%D8%A8%D9%84%D8%A9-%D8%B9%D9%85%D9%84-%D9%86%D8%A7%D8%AC%D8%AD%D8%A9%D8%9F-r1036/" rel="">كيف تحضر لمقابلة عمل ناجحة؟</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/advanced/%D9%83%D9%8A%D9%81-%D8%AA%D8%AD%D8%B6%D8%B1-%D9%84%D8%A3%D8%B3%D8%A6%D9%84%D8%A9-%D9%85%D9%82%D8%A7%D8%A8%D9%84%D8%A9-%D8%B9%D9%85%D9%84-%D9%85%D9%87%D9%86%D8%AF%D8%B3-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A7%D8%AA-r751/" rel="">كيف تحضر لأسئلة مقابلة عمل مهندس البرمجيات</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2596</guid><pubDate>Wed, 13 Aug 2025 16:02:01 +0000</pubDate></item><item><title>&#x628;&#x62F;&#x621; &#x648;&#x625;&#x646;&#x647;&#x627;&#x621; &#x627;&#x644;&#x623;&#x644;&#x639;&#x627;&#x628; &#x641;&#x64A; &#x645;&#x62D;&#x631;&#x643; &#x62C;&#x648;&#x62F;&#x648; Godot</title><link>https://academy.hsoub.com/programming/game-development/%D8%A8%D8%AF%D8%A1-%D9%88%D8%A5%D9%86%D9%87%D8%A7%D8%A1-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D9%81%D9%8A-%D9%85%D8%AD%D8%B1%D9%83-%D8%AC%D9%88%D8%AF%D9%88-godot-r2592/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_05/04..png.9e9675c3ff474556e25bc89f824f39e2.png" /></p>
<p>
	سنتعرف في هذا المقال وهو جزء من <a href="https://academy.hsoub.com/tags/%D8%AF%D9%84%D9%8A%D9%84%20%D8%AC%D9%88%D8%AF%D9%88/" rel="">سلسلة دليل جودو</a>، على الخطوة الأخيرة في بناء لعبة سفينة الفضاء ثنائية الأبعاد، وهي كيفية إضافة زر لبدء اللعبة وإعلان نهاية اللعبة Game Over وهو ما سنتعلمه في مقال اليوم.
</p>

<h2 id="">
	إطلاق اللعبة
</h2>

<p>
	عندما نشغّل اللعبة حاليًا من داخل <a href="https://academy.hsoub.com/programming/game-development/%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot/" rel="">محرك الألعاب جودو</a>، سيبدأ اللعب مباشرةً، ولهذا سنضيف زرًا كي لا تبدأ اللعبة إلا بعد النقر عليه. لتحقيق ذلك، سنضع زر البدء في مكان مركزي في واجهة اللعبة ونمكّن اللاعب من النقر عليه لبدء اللعبة.
</p>

<p>
	يمكن تحقيق ذلك باستخدام عقدة <code>CenterContainer</code> و<code>CanvasLayer</code>، وضبط إعدادات التموضع Layout لضمان أن الزر سيكون في مركز الشاشة ويستجيب جيدًا على مختلف أحجام الشاشات.
</p>

<p>
	سنضيف <a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%B9%D9%82%D8%AF-nodes-%D9%81%D9%8A-%D9%85%D8%AD%D8%B1%D9%83-%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot-r2490/" rel="">عقدةً</a> من النوع <code>CenterContainer</code> إلى المشهد الرئيسي وكابن للعقدة <code>CanvasLayer</code>، ونضبط الخاصية <code>Layout&gt;Anchor Preset&gt;Layout Mode</code> لتكون قيمتها على كامل المستطيل Full Rect.
</p>

<p>
	سنضيف بعد ذلك عقدة ابن من النوع <code>TextureButton</code> ونسمّيها Start، ثم نضع الصورة <code>START (48 x 8).png</code> في الخاصية <code>Textures&gt;Normal</code> لهذا الزر.
</p>

<p>
	سنضيف مرجعًا إلى الزر في أعلى سكريبت المشهد الرئيسي كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1977_8" style=""><span class="lit">@onready</span><span class="pln"> var start_button </span><span class="pun">=</span><span class="pln"> $CanvasLayer</span><span class="pun">/</span><span class="typ">CenterContainer</span><span class="pun">/</span><span class="typ">Start</span></pre>

<p>
	والآن سنصل الإشارة <code>pressed</code> الخاصة بالزر إلى العقدة <code>Main</code> ثم نضيف الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1977_10" style=""><span class="pln">func _on_start_pressed</span><span class="pun">():</span><span class="pln">
    start_button</span><span class="pun">.</span><span class="pln">hide</span><span class="pun">()</span><span class="pln">
    new_game</span><span class="pun">()</span></pre>

<p>
	تعالج الدالة <code>()new_game</code> عملية إطلاق اللعبة، ولهذا سنعدّل مضمون الدالة <code>()ready_</code> كي تعرض زر البدء بدلًا من نشر الأعداء:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1977_12" style=""><span class="pln">func _ready</span><span class="pun">():</span><span class="pln">
    start_button</span><span class="pun">.</span><span class="pln">show</span><span class="pun">()</span><span class="pln">
</span><span class="com">#    spawn_enemies()</span></pre>

<p>
	سنضيف الآن الدالة <code>()new_game</code> إلى السكريبت الرئيسي على النحو الآتي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1977_14" style=""><span class="pln">func new_game</span><span class="pun">():</span><span class="pln">
    score </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
    $CanvasLayer</span><span class="pun">/</span><span class="pln">UI</span><span class="pun">.</span><span class="pln">update_score</span><span class="pun">(</span><span class="pln">score</span><span class="pun">)</span><span class="pln">
    $Player</span><span class="pun">.</span><span class="pln">start</span><span class="pun">()</span><span class="pln">
    spawn_enemies</span><span class="pun">()</span></pre>

<p>
	يجب أن يظهر زر البداية الآن عند تشغيل اللعبة، وسيؤدي الضغط عليه إلى انطلاقها
</p>

<h2 id="-1">
	إنهاء اللعبة
</h2>

<p>
	من أجل إنهاء اللعبة، ومنحها الشكل النهائي بالعبارة الشهيرة Game Over، سنحتاج إلى إضافة عقدة ابن من النوع <code>TextureRect</code> إلى العقدة <code>CenterContainer</code>، وسنسميها GameOver.
</p>

<p>
	سنستخدم الصورة <code>GAME_OVER (72 x 8).png</code> كخلفية لها، وستتداخل هذه العقدة مع عقدة زر البداية، لكن لن يؤثر ذلك على اللعبة لأن كلًا منهما سيظهر في توقيت مختلف.
</p>

<p>
	سنضيف الآن مرجعًا آخر إلى أعلى السكريبت الرئيسي كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1977_16" style=""><span class="lit">@onready</span><span class="pln"> var game_over </span><span class="pun">=</span><span class="pln"> $CanvasLayer</span><span class="pun">/</span><span class="typ">CenterContainer</span><span class="pun">/</span><span class="typ">GameOver</span></pre>

<p>
	نضيف بعد ذلك الدالة <code>()game_over.hide</code> إلى الدالة <code>()ready_</code>، ثم نصل إشارة اللاعب <code>died</code> إلى <code>Main</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1977_18" style=""><span class="pln">func _on_player_died</span><span class="pun">():</span><span class="pln">
    get_tree</span><span class="pun">().</span><span class="pln">call_group</span><span class="pun">(</span><span class="str">"enemies"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"queue_free"</span><span class="pun">)</span><span class="pln">
    game_over</span><span class="pun">.</span><span class="pln">show</span><span class="pun">()</span><span class="pln">
    await get_tree</span><span class="pun">().</span><span class="pln">create_timer</span><span class="pun">(</span><span class="lit">2</span><span class="pun">).</span><span class="pln">timeout
    game_over</span><span class="pun">.</span><span class="pln">hide</span><span class="pun">()</span><span class="pln">
    start_button</span><span class="pun">.</span><span class="pln">show</span><span class="pun">()</span></pre>

<p>
	وهنا ستعرض الشيفرة السابقة عبارة Game Over لمدة ثانيتين، ثم تعود باللاعب إلى زر البداية كي يتمكن من إعادة اللعبة.
</p>

<h2 id="-2">
	الخلاصة
</h2>

<p>
	بهذا نكون قد أنهينا بناء لعبة بسيطة ثنائية الأبعاد عبر محرك الألعاب جودو، وإن أردت الاطلاع على المزيد من الأفكار والتقنيات في استخدام محرّك الألعاب جودو، وكيفية تسخيرها في تحويل أفكارك إلى ألعاب احترافية ننصحك بالاطلاع على <a href="https://academy.hsoub.com/learn/game-development/" rel="">دورة تطوير الألعاب باستخدام محرك جودو</a> التي تقدمها <a href="https://academy.hsoub.com" rel="external">أكاديمية حسوب</a> التي تنطلق بك من الصفر وحتى الاحتراف من خلال دروس فيديو أعدها وقدمها مطورون خبراء في هذا المجال وباللغة العربية.
</p>

<p>
	وإن كنت تشعر أنك فهمت التقنيات التي ناقشناها في هذه السلسلة، فأنت مستعد لتطوير اللعبة أكثر. لهذا، حاول إضافة ميزة واحدة جديدة إلى اللعبة، ونقترح عليك بعضًا منها:
</p>

<ul>
	<li>
		<strong>إضافة أنواع مختلفة من الأعداء</strong>: ستجد أيقونات مختلفة للأعداء استفد من ذلك
	</li>
	<li>
		<strong>مجموعات من الأعداء</strong>: أضف مجموعة أخرى من الأعداء عندما تنتهي من المجموعة الأولى
	</li>
	<li>
		<strong>كبير الأعداء</strong>: جرب أن تضيف عدوًا أكبر حجماً من الأعداء العاديين
	</li>
	<li>
		<strong>معززات القوة</strong>: جرب عرض جوائز تعزز قوة المركبة الفضائية، وفكر كيف ستجمعها
	</li>
	<li>
		<strong>إعادة شحن الدرع</strong>: أضف معززات لتدعيم الدرع الذي يحمي السفينة الفضائية
	</li>
	<li>
		<strong>تطوير القذائف</strong>: أضف أنواعًا أخرى من القذائف
	</li>
	<li>
		<strong>إضافة مؤثرات صوتية وأصوات</strong>: يمكنك إضافة تأثيرات صوتية إلى اللعبة لجعلها أكثر جاذبية
	</li>
</ul>

<p>
	ترجمة -وبتصرف للمقالين: <a href="https://kidscancode.org/godot_recipes/4.x/games/first_2d/first_2d_10/index.html-" rel="external nofollow">Starting and Ending the Game</a> و <a href="https://kidscancode.org/godot_recipes/4.x/games/first_2d/first_2d_end/index.html" rel="external nofollow">Wrapping up</a>.
</p>

<h2 id="-3">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/game-development/%D8%A8%D9%86%D8%A7%D8%A1-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D9%88%D8%B9%D8%A7%D8%B1%D8%B6-%D8%A7%D9%84%D9%86%D8%AA%D9%8A%D8%AC%D8%A9-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-%D8%A7%D9%84%D8%B3%D9%81%D9%8A%D9%86%D8%A9-%D8%A7%D9%84%D9%81%D8%B6%D8%A7%D8%A6%D9%8A%D8%A9-r2591/" rel="">بناء واجهة المستخدم وعارض النتيجة في لعبة السفينة الفضائية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D8%B4%D9%87%D8%AF-%D8%A5%D8%B7%D9%84%D8%A7%D9%82-%D8%A7%D9%84%D8%B9%D8%AF%D9%88-%D9%84%D9%84%D9%86%D8%B4%D8%B1-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-%D8%A7%D9%84%D8%B3%D9%81%D9%8A%D9%86%D8%A9-%D8%A7%D9%84%D9%81%D8%B6%D8%A7%D8%A6%D9%8A%D8%A9-r2590/" rel="">إنشاء مشهد إطلاق العدو للنشر في لعبة السفينة الفضائية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A7%D9%84%D9%85%D8%B4%D9%87%D8%AF-%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A-%D9%84%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%86%D8%A7%D8%A6%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D8%B9%D8%A8%D8%B1-%D9%85%D8%AD%D8%B1%D9%83-godot-r2589/" rel="">إنشاء المشهد الرئيسي للعبة ثنائية الأبعاد عبر محرك Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%B5%D9%85%D9%8A%D9%85-%D9%85%D8%B4%D9%87%D8%AF-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%86%D8%A7%D8%A6%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-godot-r2586/" rel="">تصميم مشهد اللاعب في لعبة ثنائية الأبعاد باستخدام Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%B9%D9%82%D8%AF-nodes-%D9%81%D9%8A-%D9%85%D8%AD%D8%B1%D9%83-%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot-r2490/" rel="">تعرف على العقد Nodes في محرك ألعاب جودو Godot</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2592</guid><pubDate>Mon, 11 Aug 2025 16:08:01 +0000</pubDate></item><item><title>&#x628;&#x646;&#x627;&#x621; &#x648;&#x627;&#x62C;&#x647;&#x629; &#x627;&#x644;&#x645;&#x633;&#x62A;&#x62E;&#x62F;&#x645; &#x648;&#x639;&#x627;&#x631;&#x636; &#x627;&#x644;&#x646;&#x62A;&#x64A;&#x62C;&#x629; &#x641;&#x64A; &#x644;&#x639;&#x628;&#x629; &#x627;&#x644;&#x633;&#x641;&#x64A;&#x646;&#x629; &#x627;&#x644;&#x641;&#x636;&#x627;&#x626;&#x64A;&#x629;</title><link>https://academy.hsoub.com/programming/game-development/%D8%A8%D9%86%D8%A7%D8%A1-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D9%88%D8%B9%D8%A7%D8%B1%D8%B6-%D8%A7%D9%84%D9%86%D8%AA%D9%8A%D8%AC%D8%A9-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-%D8%A7%D9%84%D8%B3%D9%81%D9%8A%D9%86%D8%A9-%D8%A7%D9%84%D9%81%D8%B6%D8%A7%D8%A6%D9%8A%D8%A9-r2591/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_05/03..png.b3d51e733fc3e92c22c377d480b28011.png" /></p>
<p>
	سنشرح في هذا المقال وهو جزء من <a href="https://academy.hsoub.com/tags/%D8%AF%D9%84%D9%8A%D9%84%20%D8%AC%D9%88%D8%AF%D9%88/" rel="">سلسلة دليل جودو</a>، كيفية بناء واجهة المستخدم وعارض النتيجة في الألعاب ثنائية الأبعاد عبر محرك Godot.
</p>

<p>
	آخر الأقسام الرئيسية التي علينا بناؤها في لعبتنا ثنائية الأبعاد هي واجهة المستخدم User Interface؛ إذ سنحتاج إلى طريقة لعرض نتيجة اللاعب وغيرها من المعلومات. ولتنفيذ الأمر، سنستخدم عقدةً مختلفة من النوع <code>Control</code> التي يزودنا بها محرك الألعاب جودو لبناء واجهات المستخدم.
</p>

<h2 id="">
	مشهد واجهة المستخدم
</h2>

<p>
	سنبدأ المشهد <a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%B9%D9%82%D8%AF-nodes-%D9%81%D9%8A-%D9%85%D8%AD%D8%B1%D9%83-%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot-r2490/" rel="">بعقدة</a> من النوع <code>MarginContainer</code> ونسميها <code>UI</code>، حيث تضمن الحاوية <code>MarginContainer</code> ألا يقترب العقد الأبناء كثيرًا من الحافة من خلال إضافة هوامش حولها.
</p>

<p>
	سننقر الآن على العقدة <code>UI</code> وننتقل إلى نافذة الفاحص Inspector، ثم نضبط قيم الهوامش الأربعة على 10 ضمن الخاصية <code>Theme Overrides&gt;Constants</code>.
</p>

<p>
	ننتقل بعدها إلى نافذة العرض الثنائي وننقر على أيقونة تجهيزات المراسي Anchor Preset وأيقونة المرساة Anchor، ثم نختار موقع الحاوية ليكون في أعلى الشاشة وعلى اتساعها بالعرض بالأعلى Top Wide.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172880" href="https://academy.hsoub.com/uploads/monthly_2025_05/01_top_wide.png.b1ffc8f7053ec0a1755e580c090aecb6.png" rel=""><img alt="01 top wide" class="ipsImage ipsImage_thumbnailed" data-fileid="172880" data-unique="4c4tnxxhd" src="https://academy.hsoub.com/uploads/monthly_2025_05/01_top_wide.png.b1ffc8f7053ec0a1755e580c090aecb6.png"> </a>
</p>

<p>
	نضيف تاليًا عقدةً من النوع <code>HBoxContainer</code>، وهي نوع من الحاويات التي تنظم الأبناء أفقيًا. سنضيف ضمن هذه العقدة عقدة ابن من النوع <code>TextureProgressBar</code>، وهي شريط تقدم يمثل وضع درع المركبة الفضائية، وسنسمي هذه العقدة ShieldBar.
</p>

<p>
	لا تضم مجموعة الصور التي نزّلناها في مشروعنا أية صورة مناسبة لشريط التقدم، لذلك سننزلّ الصورتين التاليتين ونحفظهما في مجلد اللعبة. ضع صورة الخلفية الخضراء <a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172881" href="https://academy.hsoub.com/uploads/monthly_2025_05/02_bar_prog.png.6959a736cf737f2d7be6b092d27c8407.png" rel=""><img alt="02 bar prog" class="ipsImage ipsImage_thumbnailed" data-fileid="172881" data-unique="lb4qfmm2p" src="https://academy.hsoub.com/uploads/monthly_2025_05/02_bar_prog.png.6959a736cf737f2d7be6b092d27c8407.png"></a> كقيمة للخاصية <code>Texture&gt;Progress</code>، والصورة البيضاء <a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172877" href="https://academy.hsoub.com/uploads/monthly_2025_05/03_bar_under.png.d8efb899c11487ad7f41aba809257d97.png" rel=""><img alt="03 bar under" class="ipsImage ipsImage_thumbnailed" data-fileid="172877" data-unique="on08gkvmp" src="https://academy.hsoub.com/uploads/monthly_2025_05/03_bar_under.png.d8efb899c11487ad7f41aba809257d97.png"></a> كقيمة للخاصية <code>Texture&gt;Under</code>.<br type="_moz">
	 
</p>

<p>
	سنلاحظ مباشرة أن الشريط صغير جدًا، لذلك سنغير قيمة الخاصية <code>Minimum Custom Size</code> لتصبح <code>(80,60)</code> وسنرى أن المستطيل البرتقالي قد كبر.
</p>

<p>
	وكما هو واضح، لن يكون تمدد الصورة جميلًا، وقد يبدو سيئًا أيضًا، لهذا سنفعّل الخيار <code>Range&gt;Nine Patch Stretch</code> ونضبط بعدها قيم الخاصية <code>Stretch Margin</code> الأربعة التي ستظهر على <code>3</code>.
</p>

<p>
	من المفترض أن نرى الآن شريطًا طويلًا فارغًا. ولمعرفة كيف سيبدو عندما يبدأ بالامتلاء، سنغير قيمة الخاصية <code>Range&gt;Value</code> إلى أي قيمة بين 0 و 100.
</p>

<p style="text-align: center;">
	<img alt="04_level_bar.png" class="ipsImage ipsImage_thumbnailed" data-fileid="172882" data-ratio="39.97" data-unique="0s2lvwhtn" width="593" src="https://academy.hsoub.com/uploads/monthly_2025_05/04_level_bar.png.01d4c21773907fc1a585ab99bc3df46f.png">
</p>

<p>
	سنعرض إلى اليسار نتيجة اللاعب، وقد نستخدم عقدةً من النوع <code>Label</code> وخط مناسب. وبالطبع لن تكون الخطوط التي يقدمها نظام التشغيل مناسبةً للعبة، ولهذا سنستخدم أيقونات الأرقام الموجودة في مجلد الصور، وذلك بعد معالجتها عن طريق الشيفرة لعرضها بالشكل الصحيح.
</p>

<h2 id="-1">
	إنشاء عداد للنتيجة
</h2>

<p>
	سننشئ مشهدًا جديدًا أساسه عقدة من النوع <code>HBoxContainer</code> ونسيمها <code>ScoreCounter</code>. سنضبط هذه العقدة ليكون مركز ارتكاز العقدة بالعرض بالأعلى Top Wide، أي أن العقدة ستمتد أفقيًا وتتموضع عند الجزء العلوي من المشهد، ومحاذاتها نهاية الإنكماش End، أي ستكون محاذاة العقدة على الطرف الأيمن أو نهاية المساحة المتاحة باستخدام الزر المجاورة لأيقونة المرساة.
</p>

<p>
	سنضبط أيضًا الخاصية <code>Theme Overrides&gt;Constants&gt;Separation</code> على القيمة <code>0</code>، وهنا علينا تفعيل الخيار إلى جانب الخاصية.
</p>

<p>
	سنضيف الآن مجموعةً من العقد النصية من النوع <code>TextureRect</code> ضمن الحاوية الأفقية لعرض الأرقام، وسنضيف عقدةً واحدة أولًا ثم نضاعف العدد.
</p>

<p>
	سنسمي العقدة النصية <code>Digit0</code> ثم ننتقل إلى الفاحص، فالخاصية <code>Texture</code>، ونختار إنشاء واحدة جديدة New AtlasTexture. سننقر بعد ذلك على هذا الخيار لتظهر لنا نافذة أسفلها الخاصية <code>Atlas</code>. نسحب الآن الصورة <code>Number_font (8 x 8).png</code> إلى هذه الخاصية ثم نضبط قيم الخاصية <code>Region</code> على <code>(32, 8, 8, <span class="ipsEmoji">😎</span></code>. بعد ذلك ننقر مجددًا على الخاصية <code>Texture</code> لتغلق النافذة، ثم اضبط قيمة الخاصية <code>Stretch Mode</code> على <code>Keep Aspect Centered</code>.
</p>

<p>
	سنعود الآن إلى نافذة المشهد ونختار العقدة <code>Digit0</code>، ثم نضغط على المفتاحين <code>Ctrl+D</code> سبع مرات لإنشاء سبع عقد أخرى مماثلة للأولى من ناحية الشكل والخاصيات. حيث ستبدو نافذة العرض بعد هذه الخطوات كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172878" href="https://academy.hsoub.com/uploads/monthly_2025_05/05_digits_nodes.png.f088e3a0f639d498177989e992d72f48.png" rel=""><img alt="05 digits nodes" class="ipsImage ipsImage_thumbnailed" data-fileid="172878" data-unique="ruup32vsh" src="https://academy.hsoub.com/uploads/monthly_2025_05/05_digits_nodes.thumb.png.b10b79cfdd2f968280b4edb9a129f906.png"> </a>
</p>

<p>
	على الرغم من إنشاء ثمانية عقد <code>TextureRect</code> للأرقام، إلا أن قيمة الخاصية <code>Texture</code> تبقى نفسها، وهذه مشكلة، فعند تغيير الخاصية <code>Region</code> لعرض الأرقام المختلفة ستعرض كل العقد صورة الرقم نفسه.
</p>

<p>
	يعود السبب في ذلك إلى أن كائنات الموارد مثل العقدة <code>Texture</code> ستحمّل في الذاكرة مرةً واحدة ثم تتشاركها العقدة المختلفة. وهذا أمر فعّال جدًا لأنه لا يهدر الذاكرة بتحميل نفس النسخة من المورد عدة مرات. إذًا، علينا تخصيص جزء من المورد لكل عقدة حتى تكون الصورة المعروضة في كل عقدة فريدة، وهنا سننقر في كل عقدة على السهم المجاور للخاصية <code>AtlasTexture</code> ثم نضغط على خيار اجعله فريدًا Make Unique.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172883" href="https://academy.hsoub.com/uploads/monthly_2025_05/06_make_unique.png.a0fa299ce244cacee0f795579dc964e3.png" rel=""><img alt="06 make unique" class="ipsImage ipsImage_thumbnailed" data-fileid="172883" data-unique="rqc6dlrr9" src="https://academy.hsoub.com/uploads/monthly_2025_05/06_make_unique.png.a0fa299ce244cacee0f795579dc964e3.png"> </a>
</p>

<p>
	نضيف الآن سكريبت إلى العقدة <code>ScoreCounter</code>، ونختار فيها المنطقة الصحيحة <code>Region</code> من الصورة لكل رقم نريد عرضه:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4068_10" style=""><span class="pln">extends </span><span class="typ">HBoxContainer</span><span class="pln">

var digit_coords </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="lit">1</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Vector2</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">),</span><span class="pln">
    </span><span class="lit">2</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Vector2</span><span class="pun">(</span><span class="lit">8</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">),</span><span class="pln">
    </span><span class="lit">3</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Vector2</span><span class="pun">(</span><span class="lit">16</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">),</span><span class="pln">
    </span><span class="lit">4</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Vector2</span><span class="pun">(</span><span class="lit">24</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">),</span><span class="pln">
    </span><span class="lit">5</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Vector2</span><span class="pun">(</span><span class="lit">32</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">),</span><span class="pln">
    </span><span class="lit">6</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Vector2</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">),</span><span class="pln">
    </span><span class="lit">7</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Vector2</span><span class="pun">(</span><span class="lit">8</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">),</span><span class="pln">
    </span><span class="lit">8</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Vector2</span><span class="pun">(</span><span class="lit">16</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">),</span><span class="pln">
    </span><span class="lit">9</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Vector2</span><span class="pun">(</span><span class="lit">24</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">),</span><span class="pln">
    </span><span class="lit">0</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Vector2</span><span class="pun">(</span><span class="lit">32</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func display_digits</span><span class="pun">(</span><span class="pln">n</span><span class="pun">):</span><span class="pln">
    var s </span><span class="pun">=</span><span class="pln"> </span><span class="str">"%08d"</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> n
    </span><span class="kwd">for</span><span class="pln"> i </span><span class="kwd">in</span><span class="pln"> </span><span class="lit">8</span><span class="pun">:</span><span class="pln">
      get_child</span><span class="pun">(</span><span class="pln">i</span><span class="pun">).</span><span class="pln">texture</span><span class="pun">.</span><span class="pln">region </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Rect2</span><span class="pun">(</span><span class="pln">digit_coords</span><span class="pun">[</span><span class="pln">int</span><span class="pun">(</span><span class="pln">s</span><span class="pun">[</span><span class="pln">i</span><span class="pun">])],</span><span class="pln">
          </span><span class="typ">Vector2</span><span class="pun">(</span><span class="lit">8</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">))</span></pre>

<p>
	تبدأ الشيفرة بتشكيل قائمة من الإحداثيات التي تمثل أماكن من الصورة يحدد كل منها مكان وجود رقم، ثم تنسّق الدالة <code>()display_digits</code> العدد المطلوب ليكون من 8 أرقام، بحيث لو كان أمامنا الرقم 285 مثلًا، فسيكتب بالشكل 00000258. بعد ذلك، سنضع الرقم المناسب في كل منزلة من العدد السابق اعتمادًا على مصفوفة الإحداثيات.
</p>

<h2 id="ui">
	إضافة سكريبت واجهة المستخدم UI
</h2>

<p>
	نعود الآن إلى المشهد ui ثم نضيف المشهد <code>ScoreCounter</code> إلى العقدة <code>HBoxContainer</code>. نضيف بعد ذلك السكريبت التالي إلى العقدة UI:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4068_12" style=""><span class="pln">extends </span><span class="typ">MarginContainer</span><span class="pln">

</span><span class="lit">@onready</span><span class="pln"> var shield_bar </span><span class="pun">=</span><span class="pln"> $HBoxContainer</span><span class="pun">/</span><span class="typ">ShieldBar</span><span class="pln">
</span><span class="lit">@onready</span><span class="pln"> var score_counter </span><span class="pun">=</span><span class="pln"> $HBoxContainer</span><span class="pun">/</span><span class="typ">ScoreCounter</span><span class="pln">

func update_score</span><span class="pun">(</span><span class="pln">value</span><span class="pun">):</span><span class="pln">
    score_counter</span><span class="pun">.</span><span class="pln">display_digits</span><span class="pun">(</span><span class="pln">value</span><span class="pun">)</span><span class="pln">


func update_shield</span><span class="pun">(</span><span class="pln">max_value</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">):</span><span class="pln">
    shield_bar</span><span class="pun">.</span><span class="pln">max_value </span><span class="pun">=</span><span class="pln"> max_value
    shield_bar</span><span class="pun">.</span><span class="pln">value </span><span class="pun">=</span><span class="pln"> value</span></pre>

<p>
	سنستدعي هاتين الدالتين في المشهد الرئيسي Main في كل مرة نحتاج فيها إلى تغيير النتيجة أو قوة درع السفينة.
</p>

<h2 id="uimain">
	إضافة المشهد UI إلى المشهد الرئيسي Main
</h2>

<p>
	سنضيف الآن عقدةً من النوع <code>CanvasLayer</code> إلى المشهد الرئيسي Main، ثم منسخ إليها المشهد UI كعقدة ابن. ستُنشئ العقدة <code>CanvasLayer</code> طبقة رسم جديدة، ولهذا ستُرسم واجهة المستخدم فوق جميع مكونات اللعبة؛ ولحل هذه المشكلة سنغيّر التابع التالي في السكريبت <code>main.gd</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4068_14" style=""><span class="pln">func _on_enemy_died</span><span class="pun">(</span><span class="pln">value</span><span class="pun">):</span><span class="pln">
    score </span><span class="pun">+=</span><span class="pln"> value
    $CanvasLayer</span><span class="pun">/</span><span class="pln">UI</span><span class="pun">.</span><span class="pln">update_score</span><span class="pun">(</span><span class="pln">score</span><span class="pun">)</span></pre>

<h2 id="-2">
	درع اللاعب
</h2>

<p>
	يمكننا إضافة الدرع إلى سكريبت اللاعب، عبر إضافة الأسطر التالية إلى الملف <code>player.gd</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4068_16" style=""><span class="pln">signal died
signal shield_changed

</span><span class="lit">@export</span><span class="pln"> var max_shield </span><span class="pun">=</span><span class="pln"> </span><span class="lit">10</span><span class="pln">
var shield </span><span class="pun">=</span><span class="pln"> max_shield</span><span class="pun">:</span><span class="pln">
    set </span><span class="pun">=</span><span class="pln"> set_shield</span></pre>

<p>
	سنستدعي الدالة <code>()set_shield</code> من خلال عملية الإسناد <code>= set</code> في كل مرة يُضبط فيها المتغير <code>shield</code> الذي يمثل قيمة درع المركبة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4068_18" style=""><span class="pln">func set_shield</span><span class="pun">(</span><span class="pln">value</span><span class="pun">):</span><span class="pln">
    shield </span><span class="pun">=</span><span class="pln"> min</span><span class="pun">(</span><span class="pln">max_shield</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">)</span><span class="pln">
    shield_changed</span><span class="pun">.</span><span class="pln">emit</span><span class="pun">(</span><span class="pln">max_shield</span><span class="pun">,</span><span class="pln"> shield</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> shield </span><span class="pun">&lt;=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">:</span><span class="pln">
      hide</span><span class="pun">()</span><span class="pln">
      died</span><span class="pun">.</span><span class="pln">emit</span><span class="pun">()</span></pre>

<p>
	نستطيع أيضًا وصل إشارة المركبة <code>area_entered</code> كي نلتقط اصطدام العدو بالمركبة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4068_20" style=""><span class="pln">func _on_area_entered</span><span class="pun">(</span><span class="pln">area</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> area</span><span class="pun">.</span><span class="pln">is_in_group</span><span class="pun">(</span><span class="str">"enemies"</span><span class="pun">):</span><span class="pln">
      area</span><span class="pun">.</span><span class="pln">explode</span><span class="pun">()</span><span class="pln">
      shield </span><span class="pun">-=</span><span class="pln"> max_shield </span><span class="pun">/</span><span class="pln"> </span><span class="lit">2</span></pre>

<p>
	لنضف الآن بعض الضرر إلى درع المركبة عندما تُصاب في سكريبت قذائف العدو <code>enemy_bullet.gd</code>، وذلك على النحو الآتي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4068_22" style=""><span class="pln">func _on_area_entered</span><span class="pun">(</span><span class="pln">area</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> area</span><span class="pun">.</span><span class="pln">name </span><span class="pun">==</span><span class="pln"> </span><span class="str">"Player"</span><span class="pun">:</span><span class="pln">
      queue_free</span><span class="pun">()</span><span class="pln">
      area</span><span class="pun">.</span><span class="pln">shield </span><span class="pun">-=</span><span class="pln"> </span><span class="lit">1</span></pre>

<p>
	في الأخير، علينا وصل إشارة اللاعب <code>shield_changed</code> إلى الدالة التي تحدّث شريط الدرع في واجهة المستخدم، ولهذا سننتقل إلى الفاحص بعد اختيار عقدة اللاعب <code>Player</code> في المشهد الرئيسي. سننقر بعد ذلك في نافذة العقدة Node نقرًا مزدوجًا على الإشارة، وذلك لفتح نافذة توصل إشارة إلى دالة Connect a Signal.
</p>

<p>
	سنختار بعد ذلك العقدة <code>UI</code> ثم نكتب <code>update_shield</code> في صندوق الدالة المتلقية Receiver Method.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172879" href="https://academy.hsoub.com/uploads/monthly_2025_05/07_connect_signal.png.447424e2785278d9b70f869a007fe392.png" rel=""><img alt="07 connect signal" class="ipsImage ipsImage_thumbnailed" data-fileid="172879" data-unique="a7lmhcomt" style="width: 300px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_05/07_connect_signal.png.447424e2785278d9b70f869a007fe392.png"> </a>
</p>

<p>
	يمكننا الآن تشغيل اللعبة والتأكد من أن طاقة الدرع تنخفض عندما تصيبه قذيفة أو مركبة معادية.
</p>

<h2 id="-3">
	ختامًا
</h2>

<p>
	بهذا نكون قد تعرفنا على كيفية بناء واجهة المستخدم وعارض النتيجة في الألعاب ثنائية الأبعاد عبر <a href="https://academy.hsoub.com/programming/game-development/%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot/" rel="">محرك الألعاب جودو</a>، وسنتعرف على كيفية تطوير اللعبة أكثر بالمقالات الموالية. ترجمة -وبتصرف- للمقال: <a href="https://kidscancode.org/godot_recipes/4.x/games/first_2d/first_2d_09/index.html" rel="external nofollow">UI and Score</a>.
</p>

<h2 id="-4">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/game-development/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D8%B4%D9%87%D8%AF-%D8%A5%D8%B7%D9%84%D8%A7%D9%82-%D8%A7%D9%84%D8%B9%D8%AF%D9%88-%D9%84%D9%84%D9%86%D8%B4%D8%B1-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-%D8%A7%D9%84%D8%B3%D9%81%D9%8A%D9%86%D8%A9-%D8%A7%D9%84%D9%81%D8%B6%D8%A7%D8%A6%D9%8A%D8%A9-r2590/" rel="">إنشاء مشهد إطلاق العدو للنشر في لعبة السفينة الفضائية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A7%D9%84%D9%85%D8%B4%D9%87%D8%AF-%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A-%D9%84%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%86%D8%A7%D8%A6%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D8%B9%D8%A8%D8%B1-%D9%85%D8%AD%D8%B1%D9%83-godot-r2589/" rel="">إنشاء المشهد الرئيسي للعبة ثنائية الأبعاد عبر محرك Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%B5%D9%85%D9%8A%D9%85-%D9%85%D8%B4%D9%87%D8%AF-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%86%D8%A7%D8%A6%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-godot-r2586/" rel="">تصميم مشهد اللاعب في لعبة ثنائية الأبعاد باستخدام Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%B9%D9%82%D8%AF-nodes-%D9%81%D9%8A-%D9%85%D8%AD%D8%B1%D9%83-%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot-r2490/" rel="">تعرف على العقد Nodes في محرك ألعاب جودو Godot</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2591</guid><pubDate>Fri, 08 Aug 2025 16:00:01 +0000</pubDate></item><item><title>&#x623;&#x647;&#x645; &#x627;&#x644;&#x623;&#x633;&#x626;&#x644;&#x629; &#x627;&#x644;&#x646;&#x638;&#x631;&#x64A;&#x629; &#x627;&#x644;&#x62A;&#x64A; &#x642;&#x62F; &#x62A;&#x637;&#x631;&#x62D; &#x641;&#x64A; &#x627;&#x644;&#x645;&#x642;&#x627;&#x628;&#x644;&#x627;&#x62A; &#x644;&#x62A;&#x648;&#x638;&#x64A;&#x641; &#x645;&#x637;&#x648;&#x631; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646;</title><link>https://academy.hsoub.com/programming/python/%D8%A3%D9%87%D9%85-%D8%A7%D9%84%D8%A3%D8%B3%D8%A6%D9%84%D8%A9-%D8%A7%D9%84%D9%86%D8%B8%D8%B1%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D9%8A-%D9%82%D8%AF-%D8%AA%D8%B7%D8%B1%D8%AD-%D9%81%D9%8A-%D8%A7%D9%84%D9%85%D9%82%D8%A7%D8%A8%D9%84%D8%A7%D8%AA-%D9%84%D8%AA%D9%88%D8%B8%D9%8A%D9%81-%D9%85%D8%B7%D9%88%D8%B1-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2595/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_05/-----.png.c44adb6a7305d89ee1e9a2df378c78bf.png" /></p>
<p>
	تُعَد لغة بايثون من أكثر لغات البرمجة قوةً وانتشارًا، فهي تدخل في معظم البنى الرقمية العصرية. ونظرًا لتنوع المسارات الوظيفية التي قد يوفرها احتراف البرمجة بلغة بايثون، سيركز المطوّر غالبًا على إتقان أساسيات مسار أو مسارين ثم يبحث عن فرصة عمل كمتدرب. ويَعُدّ أغلب المطوروين هذه الخطوة على أنها الخطوة الأصعب في المسيرة المهنية، ويعاني الكثيرون في اجتيازها.
</p>

<p>
	لهذا السبب، نوجه مقالنا إلى مطوري بايثون الذين يستعدون للدخول إلى سوق العمل. ونناقش فيه أهم النقاط التي قد يُسألون عنها في مقابلات العمل، سواءً لناحية استيعابهم مفاهيم اللغة الأساسية أو لتحديد إمكانياتهم التقنية وقدرتهم على إيجاد الحلول.
</p>

<p>
	سنتحدث فيما يلي عن بعض الأسئلة التي تُطرح على مطوري بايثون في مقابلات العمل، وقد قدمناها وفق أقسام تغطي أساسيات اللغة، ثم مكتبات بايثون، وأطر العمل التي تعتمد على بايثون.
</p>

<h2>
	أسئلة عامة
</h2>

<p>
	فيما يلي أبرز الأسئلة التي قد تطرح على المتقدمين لوظيفة مطوري بايثون في مقابلات التوظيف.
</p>

<h3 id="-1">
	لماذا اخترت العمل مع بايثون؟ ما مميزاتها
</h3>

<p>
	للإجابة على هذا السؤال، لا بد من توضيح أن <a href="https://academy.hsoub.com/programming/python/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1815/" rel="">بايثون</a> هي لغة واسعة الانتشار تعتمد عليها كبرى الشركات العالمية، ومطلوبة بشدة في سوق العمل، نظرًا لإمكانياتها الكبيرة. ويعزز ذلك الكمّ الهائل من المكتبات التي تغطي تقريبًا جميع الاحتياجات في العالم الرقمي مثل تحليل البيانات وتعلم الآلة والذكاء الصنعي وتطبيقات الويب.
</p>

<p>
	أما ما <a href="https://academy.hsoub.com/programming/python/%D9%85%D9%85%D9%8A%D8%B2%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86/" rel="">يميز بايثون</a> فبدايةً هي سهولة الصياغة اللغوية، فهي قريبة من الإنكليزية، إلى جانب وجود فكرة الإزاحة لتميز الكتل عن بعضها، مما يجعلها ذلك أسهل من ناحية الفهم والقراءة؛ إلى جانب أن قدرة بايثون على تحديد نوع المتغير دون التصريح عنه يُعَد فعالًا جدًا ومريحًا؛ أما الشيء الأهم، فهو وجود كم هائل من المكتبات التي تغطي معظم احتياجاتي كمطور دون إعادة اختراع العجلة من جديد، إضافةً إلى إمكانية استخدام اللغة لتطوير أي شيء تقريبًا دون الحاجة إلى الاستعانة بلغات أخرى.
</p>

<h3 id="dynamicallytyped">
	ما المقصود بأن لغة بايثون ديناميكية النمط dynamically typed
</h3>

<p>
	بايثون لغة ديناميكية في تحديد نوع المتغير، فهي تحدد تلقائيًا نوع المتغير بمجرد إسناد قيمة له. فإن أسندنا له قيمةً صحيحة، فسيكون المتحول صحيحًا، ونصيًا إن أسندنا إليه نصًا، بالتالي لا حاجة للتصريح عن نوع المتغير.
</p>

<h3 id="-2">
	كيف نحدد نوع متغير ما إن احتجنا إلى ذلك
</h3>

<p>
	لا يمكن تحديد نوع المتغير عند التصريح عنه؛ إذ يرمي عندها المفسّر Interpreter خطأً. ولحل الأمر نستخدم التابع <code>()type</code> لتحديد نوع المتغير والتعليمة <code>is</code> لمقارنة نوعه مع الأنواع التي تدعمها لغة بايثون وتكون نتيجة العملية نتيجة منطقيةً بوليانية (صحيح أو خاطئ)، وعندها يُنفذ الشرط المتعلق بالنوع أو لا يُنفّذ.
</p>

<h3 id="listtuple">
	قارن بين <code>list</code> و <code>tuple</code> في بايثون، ووضّح ذلك من خلال مثال
</h3>

<p>
	يقدم كلا النوعين أسلوبًا لترتيب البيانات وفق تسلسل محدد لتخزينها، مثل عرض قائمة بالمقاسات المتاحة لسلعة ما؛ أما وجه الاختلاف بينهما، فهو قابلية التعديل Immutability؛ فالقوائم من النوع <code><a href="https://wiki.hsoub.com/Python/list" rel="external">list</a></code> ديناميكية، ويمكن تعديل عناصرها وإضافة أو حذف عناصر بعد إنشائها وفي أي وقت؛ في حين لا يمكن تعديل أي شيء بالقوائم من النوع <code><a href="https://wiki.hsoub.com/Python/tuple" rel="external">tuple</a></code> بعد إنشائها.
</p>

<p>
	مع ذلك لكل نوع استخدامه المناسب، حيث نستخدم القائمة <code>list</code> مثلًا لتخزين الاسم المستعار لمستخدم وعمره ومكان إقامته وبريده الإلكتروني، فقد يرغب المستخدم بتغيير أي منها؛ في حين نستخدم القائمة <code>tuple</code> لتخزين أيام الأسبوع مثلًا فهي لن تتغير.
</p>

<h3 id="positional">
	كيف نمرر إلى دالة أربعة وسطاء موضعيين Positional دون أن تصرح عن أربع معاملات
</h3>

<p>
	نصرح في هذه الحالة عن الدالة بالطريقة التقليدية ثم نسمي معاملًا واحدًا فقط يبدأ بالرمز <code>*</code> كالتالي:
</p>

<pre class="ipsCode">def func(*args):
</pre>

<p>
	بهذا الشكل يمكن أن نمرر للدالة أي عدد من الوسطاء، وsنصل إلى أي وسيط من خلال دليل المعامل <code>args</code> مثل <code>args[2]</code> لاستخدام قيمة الوسيط الثالث.
</p>

<h3 id="-3">
	متى يحافظ وسيط ممر إلى دالة على قيمته بعد أن تحدّثها الدالة
</h3>

<p>
	تمرر بايثون الوسطاء إلى الدوال على شكل مرجع إلى كائن، لهذا نحن أمام سلوكين في هذه الحالة:
</p>

<ul>
	<li>
		الوسيط القابل للتعديل Mutable مثل القوائم <code>list</code> سيحتفظ المتغير عندها بالتعديلات التي أجرتها الدالة
	</li>
	<li>
		الوسيط غير القابل للتعديل مثل المتغيرات الصحيحة <a href="https://wiki.hsoub.com/Python#%D8%A7%D9%84%D8%A3%D8%B9%D8%AF%D8%A7%D8%AF_%D8%A7%D9%84%D8%B5%D8%AD%D9%8A%D8%AD%D8%A9_(int)" rel="external">int</a> والنصوص <a href="https://wiki.hsoub.com/Python#%D8%A7%D9%84%D8%B3%D9%84%D8%A7%D8%B3%D9%84_%D8%A7%D9%84%D9%86%D8%B5%D9%8A%D8%A9_(str)" rel="external">str</a> لن تتغير عندها قيمة المتغير خارج الدالة
	</li>
</ul>

<h3 id="decorators">
	ما هي المزخرفات Decorators في بايثون؟
</h3>

<p>
	<a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%85%D8%B2%D8%AE%D8%B1%D9%81%D8%A7%D8%AA-decorators-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r303/" rel="">المزخرفات Decorators</a> تقنيًا هي دوال تقبل دالة أخرى كوسيط لها وتعمل على زيادة وظائف هذه الدالة أو تغيير وظيفتها وإعادة هذه الدالة بشكلها المحسّن دون الحاجة إلى تغيير الشيفرة الأصلية لها.
</p>

<h3 id="classobjectinstance">
	ما الفرق بين الصنف Class والكائن Object والنسخة instance في بايثون؟
</h3>

<p>
	تُعَد هذه المفاهيم أساسيةً في <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%83%D8%A7%D8%A6%D9%86%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D9%88%D8%AC%D9%87-object-oriented-programming-%D9%88%D8%A7%D9%84%D8%A3%D8%B5%D9%86%D8%A7%D9%81-classes-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2154/" rel="">البرمجة كائنية التوجه <abbr title="Object-Oriented Programming | البرمجة كائنية التوجه"><abbr title="Object-Oriented Programming | البرمجة كائنية التوجه">OOP</abbr></abbr></a>، فالصنف في بايثون هو مخطط أو هيكل للكائن، تُعرَّف فيه المتغيرات والدوال التي تصف خصائص الكائن وطريقة عمله أو الوظائف التي يقوم بها؛ أما الكائن، فهو كيان فعلي حقيقي تأخذ حيزًا من الذاكرة بٌنيت على أساس مخطط الصنف. والنسخة هي مصطلح يُستخدم لوصف الكائن بعد إنشائه، خاصةً عند وجود أكثر من كائن ومن نفس الصنف.
</p>

<h3 id="__init____new____call__">
	ما عمل الدوال الخاصة  <code>__init__</code> و <code>__new__</code> و <code>__call__</code> في بايثون؟
</h3>

<p>
	تسمى هذه الدوال الخاصة باسم الدوال السحرية Magic Methods، حيث تتحكم بايثون من خلالها بسلوك الكائنات Objects بطرق خاصة، وتستخدم الدوال المعنية على النحو الآتي:
</p>

<ul>
	<li>
		تُستخدم الدالة <code>__new__</code> للتحكم في عملية إنشاء كائن من الصنف، وتُستدعى قبل <code>__init__</code> تُفيد في حالات خاصة مثل حالة نمط التصميم <a href="https://wiki.hsoub.com/Design_Patterns/singleton" rel="external">singleton</a> أو Metaclass
	</li>
	<li>
		تُستخدم الدالة <code>__init__</code> لتهيئة خصائص الكائن Object عند إنشائه، وتُستدعى مباشرة بعد <code>__new__</code>
	</li>
	<li>
		تُستخدم الدالة <code>__call__</code> لجعل الكائن قابلًا للاستدعاء كأنه دالة، بحيث يمكن تنفيذ كود عند استدعاء هذا الكائن مباشرةً
	</li>
</ul>

<h3 id="metaclass">
	وضح مفهوم الصنف Metaclass في بايثون
</h3>

<p>
	Metaclass هو صنف خاص في بايثون مهمته تعريف سلوك الأصناف، حيث يتحكم بإنشاء وتعديل وتحديد سلوك هذه الأصناف قبل بناء كائنات منها. ينتُج عن Metaclass في العادة أصناف أخرى، فبينما تُعطي الأصناف Classes كائنات Objects، يعطينا الصنف Metacalss أصنافًا مخصصةً أو يغير خصائص وتوابع صنف ديناميكيًا.
</p>

<h3 id="singleton">
	ما هو نمط المفردة Singleton في بايثون وأين يُستخدم
</h3>

<p>
	نمط المفردة <a href="https://wiki.hsoub.com/Design_Patterns/singleton" rel="external">singleton</a> هو نموذج في تصميم الأصناف يفرض على الصنف عدم إنتاج أكثر من نسخة منه، وهكذا سيعيد الصنف نفس الكائن عند استدعائه مرةً أخرى، أو عند إنشاء نسخة أخرى عنه. ولهذا النموذج أهميته في حالات استخدام معينة مثل بناء كائنات تسجيل الدخول إلى تطبيق مثلًا أو الحاجة لوجود كائن واحد فقط يؤمن اتصالًا مع قاعدة بيانات.
</p>

<h3 id="overriding">
	اشرح مفهوم التجاوز Overriding في بايثون
</h3>

<p>
	يرتبط هذا المفهوم بالبرمجة كائنية التوجه في بايثون وبالوراثة تحديدًا، فلو كان لدينا صنف ابن Class يرث من صنف آخر أب وعرّفنا تابع Method في الصنف الابن بنفس البصمة Signature للدالة الموجودة في الصنف الأب، فإن دالة الابن ستتجاوز وتلغي وتستبدل دالة الأب تلقائيًا. تدعم بايثون التجاوز، وتقدم التابع <code>super</code> إن أردنا استدعاء تابع العنصر الأب الأصلي ضمن الابن.
</p>

<h3 id="iterators">
	ماهي المكررات Iterators في بايثون
</h3>

<p>
	المكررات <a href="https://wiki.hsoub.com/Python/iterators" rel="external">Iterators</a> في بايثون هي آلية تسمح لنا بالتنقل عبر مجموعة من العناصر مثل القوائم <a href="https://wiki.hsoub.com/Python/list" rel="external"><code>list</code></a> أو <a href="https://wiki.hsoub.com/Python/tuples" rel="external">الصفوف</a> أو حتى <a href="https://wiki.hsoub.com/Python/str" rel="external"> السلاسل النصية </a> تدريجيًا. وبدون الحاجة إلى حفظ جميع العناصر في الذاكرة مرةً واحدة.
</p>

<p>
	يمكننا تحويل أي مجموعة قابلة للتكرار إلى مكرر باستخدام التابع <code>()iter</code>الذي يعيد لنا مكررًا يمكن استخدامه للتنقل عبر العناصر، فلكي نتقل من عنصر إلى عنصر آخر في المكرر، سنستخدم الدالة <code>()next</code>، وفي حال انتهت العناصر سنحصل على استنثاء من نوع <code>StopIteration</code> لنعرف أننا وصلنا لنهاية عناصر المكرر. فيما يلي مثال لمكرر سلسلة نصية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3304_22" style=""><span class="pln">text </span><span class="pun">=</span><span class="pln"> </span><span class="str">"Ali"</span><span class="pln">

</span><span class="com"># تحويل النص إلى مكرر</span><span class="pln">
text_iterator </span><span class="pun">=</span><span class="pln"> iter</span><span class="pun">(</span><span class="pln">text</span><span class="pun">)</span><span class="pln">

</span><span class="com"># التنقل عبر الأحرف باستخدام </span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">next</span><span class="pun">(</span><span class="pln">text_iterator</span><span class="pun">))</span><span class="pln">  </span><span class="com"># Output: A</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">next</span><span class="pun">(</span><span class="pln">text_iterator</span><span class="pun">))</span><span class="pln">  </span><span class="com"># Output: l</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">next</span><span class="pun">(</span><span class="pln">text_iterator</span><span class="pun">))</span><span class="pln">  </span><span class="com"># Output: i</span><span class="pln">

</span><span class="com"># محاولة الوصول إلى حرف غير موجود</span><span class="pln">
</span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">next</span><span class="pun">(</span><span class="pln">text_iterator</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">except</span><span class="pln"> </span><span class="typ">StopIteration</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"وصلنا إلى نهاية المكرر"</span><span class="pun">)</span></pre>

<h3 id="with">
	حدد استخدامات الكلمة المفتاحية <code>with</code> في بايثون مع ذكر مثال حول ذلك
</h3>

<p>
	تُستخدم <code>with</code> غالبًا لإدارة الموارد التي يعمل عليها التطبيق كالملفات وقواعد البيانات والاتصالات الشبكية، فمن خلالها سنضمن حيازة المورد واستخدامه بأمان، وإغلاقه تلقائيًا بعد الانتهاء منه حتى في حال وقوع أخطاء؛ فعندما نريد فتح ملف مثلًا، سنستخدم التعليمة <code>open</code> لفتح الملف، ولا بد من إغلاق الملف يدويًا عند الانتهاء باستخدام <code>close</code>، لكن عند استخدام <code>with</code> قبل <code>open</code>، فلا حاجة عندها لإغلاق الملف وسيُغلق تلقائيًا.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3304_24" style=""><span class="kwd">with</span><span class="pln"> open</span><span class="pun">(</span><span class="str">"file.txt"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"r"</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> file</span><span class="pun">:</span><span class="pln">
    content </span><span class="pun">=</span><span class="pln"> file</span><span class="pun">.</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">content</span><span class="pun">)</span></pre>

<h3 id="threadprocess">
	ما الفرق بين الخيط Thread والعملية Process
</h3>

<p>
	الخيط هو آلية لتنفيذ مجموعة تعليمات متزامنة مع مجموعة تعليمات أخرى لكن باستخدام نفس الموارد، كمساحة الذاكرة والملفات مثلًا.
</p>

<p>
	توحي الخيوط بمعالجة التعليمات على التوازي، لكن ما يجري في الواقع هو الانتقال السريع بين خيوط المعالجة، كما تتبادل الخيوط بيانات فيما بينها لتنسيق ماركة الموارد؛ أما العملية Process فهي آلية مشابهة لكن لكل عملية معزولة ولها مواردها الخاصة ولا تتشارك العمليات الموارد والبيانات فيما بينها إلا إن طُلب منها صراحة فعل ذلك بآليات تواصل خاصة.
</p>

<h3 id="gil">
	ما هي فكرة GIL في بايثون، وما تأثيراتها على تنفيذ الشيفرة
</h3>

<p>
	تعدّ GIL أو مايعرف بالقفل العام للمفسر Global Interpreter Lock آليةً مهمة في بايثون تمنع أكثر من خيط معالجة Thread من تنفيذ شيفرة بايثون في نفس اللحظة ضمن نفس العملية. بمعنى آخر، حتى لو كانت لدينا عدة خيوط تعمل معًا، فإن GIL يسمح فقط لخيط واحد بتنفيذ تعليمات بشيفرة البايت Bytecode في كل وقت.
</p>

<p>
	يؤثر وجود GIL بوضوح على أداء البرامج التي تعتمد على المعالجة المكثفة للمعالج CPU-bound، مثل معالجة الصور وتدريب نماذج تعلم الآلة والعمليات الحسابية الثقيلة، وذلك لأنه يمنع الاستفادة الكاملة من جميع أنوية المعالج المتاحة، مما يؤدي إلى أداء أقل من المتوقع.
</p>

<p>
	مع ذلك، يمكن التغلب على هذه المشكلة باستخدام تقنية المعالجة المتعددة Multi-Processing بدلًا من الخيوط، بحيث تعمل كل عملية مستقلة بدون مشاركة GIL؛ كما يكمننا استخدام توزيعات بايثون لا تحتوي على GIL مثل Jython أو IronPython. ومن الحلول المتاحة أيضًا الاعتماد على مكتبات مكتوبة بلغة C أو لغات أخرى نتفادى قيد GIL، مثل NumPy وتنسرفلو <a href="https://academy.hsoub.com/programming/artificial-intelligence/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%85%D9%86%D8%B5%D8%A9-%D8%AA%D9%86%D8%B3%D8%B1%D9%81%D9%84%D9%88-tensorflow-%D9%84%D9%84%D8%B0%D9%83%D8%A7%D8%A1-%D8%A7%D9%84%D8%A7%D8%B5%D8%B7%D9%86%D8%A7%D8%B9%D9%8A-r2491/" rel="">TensorFlo</a>.
</p>

<h3 id="pythoncpythonjython">
	هل هناك فرق بين Python و CPython و Jython
</h3>

<p>
	بالطبع، فبايثون Python هي مجموعة من المواصفات التي تحدد صياغة اللغة، بينما CPython و Jython هما طريقتان لتطبيق وتنفيذ هذه المواصفات؛ حيث أن CPython هي تنفيذ بايثون الرسمي بلغة C، أي أنها كُتبت باستخدام لغة C وتُصرّف إلى شيفرة البايت bytecode قبل أن ينفذها مفسّر مبني بلغة C؛ بينما Jython هي تنفيذ لبايثون بلغة جافا، فقد كُتبت باستخدام جافا وتُنفّذ باستخدام <a href="https://academy.hsoub.com/programming/java/%D8%A2%D9%84%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A7%D9%84%D8%A7%D9%81%D8%AA%D8%B1%D8%A7%D8%B6%D9%8A%D8%A9-java-virtual-machine-r964/" rel="">آلة جافا الافتراضية JVM</a> وتدعم كل مكتبات جافا.
</p>

<h2>
	أسئلة حول المكتبات Libraries
</h2>

<p>
	فيما يلي أبرز الأسئلة التي قد تطرح على المتقدمين لوظيفة مطوري بايثون في مقابلات التوظيف حول مكتبات بايثون.
</p>

<h3 id="ososenviron">
	ما هي وظيفة المكتبة المضمّنة <code>OS</code>؟ وما عمل <code>os.environ</code>
</h3>

<p>
	تقدم هذه المكتبة وظائف للتفاعل مع نظام التشغيل، بما في ذلك إدارة الملفات ومعالجة العمليات وإدارة استدعاء وظائف نظام التشغيل؛ أما <code>os.environ</code>، فهو كائن شبيه <a href="https://academy.hsoub.com/programming/python/%D9%81%D9%87%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%85%D9%8A%D8%B3-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-r743/" rel="">بالقواميس</a> يُستخدم للعمل مع متغيرات البيئة في حالات عديدة مثل ضبط الإعدادات والتعامل مع البيانات الحساسة بأمان.
</p>

<h3 id="asyncio">
	ماهي وظيفة المكتبة <code>asyncio</code> ومتى تُستخدم
</h3>

<p>
	تُستخدم مكتبة <code>asyncio</code> في بايثون لتنفيذ عدة مهام في نفس الوقت، لاسيما في حال كانت هذه المهام تنتظر بيانات معينةم مثل انتظار رد من قاعدة بيانات أو من الإنترنت؛ فبدلًا من إيقاف البرنامج حتى ترد القاعدة أو السيرفر وحدوق حجب أو تعليق لعملية التنفيذ، تسمح <code>asyncio</code> للبرنامج بمواصلة عمله على مهام أخرى في نفس الوقت دون الحاجة لإنشاء خيوط Threads جديدة أو عمليات متعددة Multiprocessing.
</p>

<p>
	يتم استخدام هذه الوظيفة عندما تكون لدينا عمليات مقيدة بالمدخلات والمخرجات I/O bound، أي عمليات تتوقف على انتظار بيانات، مثل إرسال طلبات لشبكة، أو قراءة ملفات، أو عند الحاجة لتنفيذ عدة أشياء بنفس الوقت على التزامن بدون استهلاك موارد كثيرة، وبكفاءة أعلى من الخيوط Threads، فهي تعد أفضل من الخيوط في بعض الحالات لأن بايثون يعتمد تقنية GIL التي تمنع تشغيل خيوط كثيرة بفعالية عالية، وبهذا تجنبنا <code>asyncio</code> هذه المشكلة، فتكون أسرع وأخف على الذاكرة.
</p>

<h3 id="multithreadingmultiprocessing">
	ما هي المكتبات التي تدعم تقنيتي Multi-Threading و Multi-Processing في بايثون
</h3>

<p>
	تقدم بايثون المكتبة <code>threading</code> لدعم إنشاء خيوط المعالجة والتحكم بها. وتستخدم في العمليات المقيدة بأجهزة الدخل والخرج كتلك التي تنتظر الكتابة إلى وسيط أو القراءة منه؛ بينما تستخدم المكتبة <code>multiprocessing</code> لدعم العمليات المستقلة التي تقيدها الحمولة العالية للمعالج وعلى التوازي، مثل العمليات الحسابية المعقدة.
</p>

<p id="copydeepcopycopy">
	ما الفرق بين التابعين<code>()copy</code> و <code>()deepcopy</code> في المكتبة <code>copy</code>
</p>

<p>
	توفر المكتبة copy في بايثون طريقتين لنسخ الكائنات، هما:
</p>

<ol>
	<li>
		النسخ السطحي Shallow copy باستخدام الدالة <code>()copy</code>
	</li>
	<li>
		النسخ العميق Deep copy باستخدام الدالة <code>()deepcopy</code>
	</li>
</ol>

<p>
	في حالة النسخ السطحي ننسخ الكائن الخارجي فقط، لكن تبقى العناصر الداخلية للكائن مشتركةً بين النسخة الأصلية والنسخة الجديدة؛ بمعنى أن التغييرات التي نجريها على العناصر الداخلية ستؤثر على الأصل أيضًا؛ أما في حالة النسخ العميقة، فإننا ننسخ الكائن وكل عناصره الداخلية بالكامل، بحيث تكون النسخة مستقلةً تمامًا، وأي تعديل على النسخة لا يؤثر أبدًا على الأصل.
</p>

<p>
	على سبيل المثال لو عرفنا كائن قائمة <code>original</code> يحتوي على قائمتين داخلية، ونسخنا منه نسختين سطحية shallow_copy وعميقة deep_copy كما في المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3304_26" style=""><span class="kwd">import</span><span class="pln"> copy

</span><span class="com"># قائمة بداخلها قوائم </span><span class="pln">
original </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[[</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">],</span><span class="pln"> </span><span class="pun">[</span><span class="lit">4</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="lit">6</span><span class="pun">]]</span><span class="pln">

</span><span class="com"># نسخ سطحي</span><span class="pln">
shallow_copy </span><span class="pun">=</span><span class="pln"> copy</span><span class="pun">.</span><span class="pln">copy</span><span class="pun">(</span><span class="pln">original</span><span class="pun">)</span><span class="pln">

</span><span class="com"># نسخ عميق</span><span class="pln">
deep_copy </span><span class="pun">=</span><span class="pln"> copy</span><span class="pun">.</span><span class="pln">deepcopy</span><span class="pun">(</span><span class="pln">original</span><span class="pun">)</span><span class="pln">

</span><span class="com"># نعدل على أحد العناصر الداخلية في القائمة الأصلية</span><span class="pln">
original</span><span class="pun">[</span><span class="lit">0</span><span class="pun">][</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'X'</span><span class="pln">

</span><span class="com"># نطبع النتائج</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="str">"القائمة الأصلية:"</span><span class="pun">,</span><span class="pln"> original</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="str">"النسخة السطحية:"</span><span class="pun">,</span><span class="pln"> shallow_copy</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="str">"النسخة العميقة:"</span><span class="pun">,</span><span class="pln"> deep_copy</span><span class="pun">)</span></pre>

<p>
	فستتعدل النسخة السطحية مع هذا التغيير وتبقى النسخة العميقة على حالها كما يوضح الخرج التالي:
</p>

<pre class="ipsCode">القائمة الأصلية: [['X', 2, 3], [4, 5, 6]]
النسخة السطحية: [['X', 2, 3], [4, 5, 6]]
النسخة العميقة: [[1, 2, 3], [4, 5, 6]]
</pre>

<h3 id="-4">
	ما أشهر المكتبات المستخدمة للعمل مع المصفوفات والحسابات الرياضية في بايثون
</h3>

<p>
	توفر بايثون العديد من المكتبات القوية التي تساعدنا للعمل مع المصفوفات وإجراء الحسابات الرياضية المتقدمة وأشهرها:
</p>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D9%85%D9%81%D8%A7%D9%87%D9%8A%D9%85-%D9%85%D8%AA%D9%82%D8%AF%D9%85%D8%A9-%D8%AD%D9%88%D9%84-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-numpy-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1809/" rel="">Numpy</a>: تدعم المصفوفات متعددة الأبعاد، وتقدم مجموعةً كبيرة من الدوال الرياضية للعمل معها
	</li>
	<li>
		<code>SciPy</code>: مبنية على أساس <code>Numpy</code> وتقدم وظائف علمية وتقنية أكبر مثل التكامل والاستيفاء
	</li>
	<li>
		<code>Pandas</code>: مكتبة قوية لمعالجة وتحليل البيانات تقدم هياكل بيانات مثل إطار البيانات DataFrame لتسهيل التعامل مع الجداول والبيانات الضخمة
	</li>
	<li>
		<code>SymPy</code>: مكتبة للرياضيات الرمزية تتيح إجراء عمليات جبرية مثل حل المعادلات والتفاضل والتكامل
	</li>
</ul>

<h3 id="-5">
	ما أشهر مكتبات التعامل مع الرسوميات في بايثون
</h3>

<p>
	يمكن ذكر المكتبات الآتية:
</p>

<ul>
	<li>
		<code>Matplotlib</code>: لرسم صور ثنائية البعد ساكنة أو تفاعلية، وتقديم تمثيل رسومي متحرك
	</li>
	<li>
		<code>Pillow</code>: مكتبة لمعالجة الصور، تسمح بفتحها وتعديلها وحفظها
	</li>
	<li>
		<code>Plotly</code>: مكتبة قوية لإنشاء رسوميات تفاعلية ثلاثية الأبعاد وواجهات عرض ديناميكية تصلح للويب والتطبيقات التفاعلية
	</li>
</ul>

<h3 id="-7">
	ما المكتبات التي يشيع استخدامها في تطبيقات الرؤية الحاسوبية
</h3>

<p>
	أبرز هذه المكتبات هي:
</p>

<ul>
	<li>
		<code>OpenCV</code>: مكتبة بايثون قوية لإنجاز مهام الرؤية الحاسوبية، مثل معالجة الصور وتطبيق فلاتر مختلفة عليها وتحويلها بين أنظمة لونية مختلفة، كما تفيد في تحليل الفيديو واكتشاف الأشياء Object Detection
	</li>
	<li>
		<code>SimpleCV</code>: هي مكتبة سهلة الاستخدام للمبتدئين في الرؤية الحاسوبية، تتيح الوصول لأدوات معالجة الصور والكاميرا بسرعة وسهولة
	</li>
</ul>

<h3 id="-8">
	ما أشهر مكتبات بايثون لبرمجة الأنظمة المدمجة
</h3>

<p>
	يمكن الإشارة إلى مكتبتي:
</p>

<ul>
	<li>
		<code>MicroPython</code>: تتبع أسلوب خاص في بناء Python3 لبرمجة المتحكمات الصغرية Microcontroller وتجهيزات الأنظمة المدمجة مثل الحساسات
	</li>
	<li>
		<code>PySerial</code>: تقدم وظائف للتخاطب مع واجهات التخاطب التسلسلية مثل UART باستخدام بايثون
	</li>
</ul>

<h2>
	أسئلة حول اطر العمل Frameworks
</h2>

<p>
	فيما يلي أبرز الأسئلة التي قد تطرح على المتقدمين لوظيفة مطوري بايثون في مقابلات التوظيف حول اُطر العمل Frameworks
</p>

<h3 id="django">
	ما هو إطار العمل Django وما هي أهم ميزاته
</h3>

<p>
	يُعَد إطار جانغو <a href="https://academy.hsoub.com/programming/python/django/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-r2041/" rel="">Django</a> أحد أشهر أطر العمل الشاملة لتطوير تطبيقات الويب يوفر الكثير من الأدوات الجاهزة المضمّنة لتنفيذ الكثير من المهام، مثل إدارة المستخدمين، والتحقق من الصلاحيات، ونظام إدارة المحتوى، والتعامل مع قواعد البيانات؛ ويستخدم أساسًا لبناء التطبيقات المتوسطة إلى كبيرة.
</p>

<h3 id="flask">
	ماهو إطار العمل Flask وما هي أهم ميزاته؟
</h3>

<p>
	يُعَد فلاسك <a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r2201/" rel="">Flask</a> إطار خفيف ومرن يسمح بتطوير تطبيقات ويب بمرونة عالية، لكنه لا يتضمن الكثير من الأدوات الجاهزة مثل جانغو Django، ويتطلب خطوات إعداد وتهيئة أكثر منه، وهو يصلح لبناء تطبيقات ويب صغيرة إلى متوسطة، كما يستخدم في تقديم خدمات ويب مصغّرة Microservices بالإضافة إلى بناء واجهات برمجية.
</p>

<h3 id="fastapi">
	ماهو إطار العمل FastAPI وما هي أهم ميزاته؟
</h3>

<p>
	يُعَد FastAPI إطار عمل حديثًا وخفيف الوزن لبناء واجهات برمجة التطبيقات APIs سريعة وقوية وعالية الأداء باستخدام لغة بايثون.؛ هو يعتمد كثيرًا على ميزتين قويتين في بايثون هما:
</p>

<ul>
	<li>
		تحديد نوع البيانات المتوقعة للمتغيرات Type Hints
	</li>
	<li>
		البرمجة غير المتزامنة Asynchronous Programming
	</li>
</ul>

<h3 id="-6">
	ما هي اُطر العمل التي يشيع استخدامها في بناء الشبكات العصبونية وتعلم الآلة
</h3>

<p>
	يمكن ذكر الآتي:
</p>

<ul>
	<li>
		<code>TensorFlow</code>: إطار عمل يُستخدم لبناء وتدريب الشبكات العصبونية
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/artificial-intelligence/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%A8%D8%A7%D9%8A-%D8%AA%D9%88%D8%B1%D8%B4-pytorch-%D9%88%D8%A3%D9%87%D9%85%D9%8A%D8%AA%D9%87-%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D8%B0%D9%83%D8%A7%D8%A1-%D8%A7%D9%84%D8%A7%D8%B5%D8%B7%D9%86%D8%A7%D8%B9%D9%8A-r2311/" rel="">PyTorch</a>: إطار عمل مفتوح المصدر يقدم أدوات لبناء وتدريب الشبكات العصبونية
	</li>
</ul>

<h2 id="-9">
	ختامًا
</h2>

<p>
	بهذا نكون قد تعرفنا على أهم الأسئلة النظرية التي قد تطرح على المتقدم إلى وظيفة مطور بلغة بايثون، وسنواصل ففي مقالنا التالي توضيح أهم الأسئلة والتمارين العملية التي قد تطلب في المقابلات لتوظيف مطور بايثون، حتى تكون مستعدًا من كل النواحي لمقابلة العمل.
</p>

<h2 id="-10">
	المراجع
</h2>

<ul>
	<li>
		<a href="https://wiki.hsoub.com/Python" rel="external">توثيق بايثون باللغة العربية</a> على موقع موسوعة حسوب
	</li>
	<li>
		<a href="https://academy.hsoub.com/files/15-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86/" rel="">كتاب البرمجة بلغة بايثون من إعداد أكاديمية حسوب</a>
	</li>
	<li>
		<a href="https://www.javatpoint.com/advance-concepts-of-python-for-python-developer" rel="external nofollow">Advance Concepts of Python for Python Developer</a> 
	</li>
	<li>
		<a href="https://pwskills.com/blog/advanced-python-tutorials/" rel="external nofollow">Advanced Python Tutorials: Dive into Complex Concepts</a> <a href="https://www.testgorilla.com/blog/advanced-python-interview-questions/" rel="external nofollow">( advanced Python interview questions (and answers</a>
	</li>
	<li>
		<a href="https://github.com/Tanu-N-Prabhu/Python/blob/master/Python%20Coding%20Interview%20Prep/Python%20Coding%20Interview%20Questions%20(Beginner%20to%20Advanced).md" rel="external nofollow">Python Coding Interview Questions (Beginner to Advanced)</a>
	</li>
</ul>

<h2 id="-11">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D8%A3%D9%87%D9%85-%D8%A3%D8%B3%D8%A6%D9%84%D8%A9-%D8%A7%D9%84%D9%85%D9%82%D8%A7%D8%A8%D9%84%D8%A7%D8%AA-%D9%84%D8%AA%D9%88%D8%B8%D9%8A%D9%81-%D9%85%D8%B7%D9%88%D8%B1-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A3%D9%85%D8%A7%D9%85%D9%8A%D8%A9-r2569/" rel="">أهم أسئلة المقابلات لتوظيف مطور واجهة أمامية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/entrepreneurship/tips/%D9%83%D9%8A%D9%81-%D8%AA%D8%AD%D8%B6%D8%B1-%D9%84%D9%85%D9%82%D8%A7%D8%A8%D9%84%D8%A9-%D8%B9%D9%85%D9%84-%D9%86%D8%A7%D8%AC%D8%AD%D8%A9%D8%9F-r1036/" rel="">كيف تحضر لمقابلة عمل ناجحة؟</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/advanced/%D9%83%D9%8A%D9%81-%D8%AA%D8%AD%D8%B6%D8%B1-%D9%84%D8%A3%D8%B3%D8%A6%D9%84%D8%A9-%D9%85%D9%82%D8%A7%D8%A8%D9%84%D8%A9-%D8%B9%D9%85%D9%84-%D9%85%D9%87%D9%86%D8%AF%D8%B3-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A7%D8%AA-r751/" rel="">كيف تحضر لأسئلة مقابلة عمل مهندس البرمجيات</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2595</guid><pubDate>Wed, 06 Aug 2025 16:08:02 +0000</pubDate></item><item><title>&#x625;&#x646;&#x634;&#x627;&#x621; &#x645;&#x634;&#x647;&#x62F; &#x625;&#x637;&#x644;&#x627;&#x642; &#x627;&#x644;&#x639;&#x62F;&#x648; &#x644;&#x644;&#x646;&#x634;&#x631; &#x641;&#x64A; &#x644;&#x639;&#x628;&#x629; &#x627;&#x644;&#x633;&#x641;&#x64A;&#x646;&#x629; &#x627;&#x644;&#x641;&#x636;&#x627;&#x626;&#x64A;&#x629;</title><link>https://academy.hsoub.com/programming/game-development/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D8%B4%D9%87%D8%AF-%D8%A5%D8%B7%D9%84%D8%A7%D9%82-%D8%A7%D9%84%D8%B9%D8%AF%D9%88-%D9%84%D9%84%D9%86%D8%B4%D8%B1-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-%D8%A7%D9%84%D8%B3%D9%81%D9%8A%D9%86%D8%A9-%D8%A7%D9%84%D9%81%D8%B6%D8%A7%D8%A6%D9%8A%D8%A9-r2590/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_05/02..png.1a52028ee0a918ac9990cd9f0a327789.png" /></p>
<p>
	بعد أن بنينا شخصية لاعب قادر على إطلاق النار على العدو في لعبتنا ثنائية الأبعاد ضمن محرك الألعاب جودو، لا بد من إنشاء عدو في اللعبة ونبرمج طريقة حركته في اللعبة وطريقة إضافة آلية إطلاق النار إليه ليهاجم اللاعب، وهذا ما سنتعلمه في مقالنا الذي هو جزء من سلسلة <a href="https://academy.hsoub.com/tags/%D8%AF%D9%84%D9%8A%D9%84%20%D8%AC%D9%88%D8%AF%D9%88/" rel="">دليل جودو</a>.
</p>

<h2 id="">
	بناء المشهد
</h2>

<p>
	سنستخدم العقدة <code>Area2D</code> كأساس لبناء المشهد لأننا بحاجة إلى اكتشاف تداخل كائن العدو مع اللاعب أو قذائفه، كما سنحتاج إلى عدة عقد نلخصها كالتالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8399_8" style=""><span class="typ">Enemy</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Area2D</span><span class="pln">
   </span><span class="typ">Sprite2D</span><span class="pln">
   </span><span class="typ">CollisionShape2D</span><span class="pln">
   </span><span class="typ">AnimationPlayer</span><span class="pln">
  </span><span class="typ">MoveTimer</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Timer</span><span class="pln">
  </span><span class="typ">ShootTimer</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Timer</span></pre>

<p>
	نختار العقدة Enemy ثم ننقر على النافذة <a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%B9%D9%82%D8%AF-nodes-%D9%81%D9%8A-%D9%85%D8%AD%D8%B1%D9%83-%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot-r2490/" rel="">عقدة Node</a> إلى جوار الفاحص inspector وننقر بعدها على مجموعات Groups، ثم على أيقونة <code>+</code> لإضافة مجموعة جديدة سنسميها <strong>enemies</strong>.
</p>

<p>
	<strong>ملاحظة</strong>: من المهم هنا تذكر أن السكريبت في مشهد القذائف يبحث عن كائنات من مجموعة الأعداء enemies.
</p>

<p>
	سننتقل بعد ذلك إلى العقدة <code>Sprite2D</code> ثم نضيف الصورة <code>Bon_Bon (16 x 16).png</code> إلى الخاصية <code>Texture</code> ونضبط قيمة الخاصية <code>Animation&gt;Hframes</code> على <code>4</code>.
</p>

<p>
	<strong>ملاحظة</strong>: تمثل الصورة <code>Bon_Bon (16 x 16).png</code> مجموعة من الإطارات الرسومية التي تشكل الحركة فكل إطار يمثل وضعية أو جزءًا من هذه الحركة، وعند ضبط Hframes إلى 4 معناه أننا نخبر جودو بأن الصورة مقسمة أفقيًا إلى 4 إطارات، مما يسمح له بالتعامل مع كل إطار على حدة فإذا كانت الصورة بحجم 16 × 16 بكسل فعند تقسيمها إلى 4 إطارات أفقية سيكون كل إطار بعرض 4 بكسل وارتفاع 16 بكسل ويمكن عرض كل إطار بشكل منفصل لتكوين الحركة.
</p>

<p>
	ننقر الآن على العقدة <code>CollisonShape2D</code> ثم ننتقل إلى الخاصية <code>Shape</code> ونختار شكل التصادم <code>RectangleShape2D</code> ونضبط حجمه ليناسب أبعاد الأيقونة؛ ثم ننتقل إلى أيقونتي المؤقتات ونفعّل الخاصية <code>One Shot</code> لكل مؤقت.
</p>

<p>
	ننقر على العقدة <code>AnimationPlayer</code> وننتقل إلى لوحة التحريك Animation Panel، وننشئ رسمًا متحركًا باسم bounce ونضبطه ليكرر نفسه looping وأن يبدأ العمل تلقائيًا عند تشغيل اللعبة autoplay بالنقر على الأزرار الموافقة.
</p>

<p>
	بعد هذه المرحلة، سنضبط الخيار انطباق أو محاذاة Snap في أسفل اللوحة على القيمة <code>0.05</code>.
</p>

<p>
	سننشئ الآن مسارات تحريك لتتبع الخاصيتين <code>Textrure</code> و <code>Hframes</code> للشخصية وهي بحالتنا العقدة <code>Sprite2D</code>، وذلك بالنقر على أيقونة المفتاح إلى جانب كل منهما. وسنحتاج إلى مفاتيح مرجعية على هذه المسارات لاحقًا عندما نضيف رسمًا متحركًا جديدًا باسم explode يستخدم قيمًا مختلفة لهاتين الخاصيتين.
</p>

<p>
	سنضيف الآن مسارًا تحريكيًا للخاصية <code>Frame</code> بالنقر بدايةً على المفتاح إلى جانب الخاصية، ونبدأ بعدها بإنشاء مفتاح مرجعي يتغير عنده إطار الشخصية وفق الترتيب التالي 2&gt;1&gt;0&gt;3&gt;0، بحيث يفصل بين كل مفتاحين 0.1 ثانية ماعدا آخر إطارين سيكونان متداخلين.
</p>

<p>
	لتنفيذ العملية، سنحرك المقبض الأزرق على الخط الزمني مسافة 0.1 ثانية ونضع قيمة الإطار المطلوب في مربع الخاصية Frame، ثم ننقر على زر المفتاح وهكذا. وسينتج عن تحريك الرسم بهذا الشكل أيقونة تنمو وتعود إلى حجمها الطبيعي في النهاية. وسيبدو شكل لوحة التحريك كالتالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="172875" href="https://academy.hsoub.com/uploads/monthly_2025_05/01_bounce_animation.png.9a88f049bd11d45dc0fd12896033953f.png" rel=""><img alt="01 bounce animation" class="ipsImage ipsImage_thumbnailed" data-fileid="172875" data-unique="fk004rwjx" src="https://academy.hsoub.com/uploads/monthly_2025_05/01_bounce_animation.png.9a88f049bd11d45dc0fd12896033953f.png"> </a>
</p>

<p>
	ننقر على زر التشغيل في لوحة التحريك لرؤية نتيجة العمل، وبإمكاننا طبعًا تعديل الحركة حسب رغبتنا.
</p>

<p>
	نضيف الآن رسمًا متحركًا جديدًا باسم explode ونضبط مدته على 0.4 ثانية، كما نستبدل صورة الشخصية بالصورة <code>Explosion (16 x 16).png</code> وننشئ مسارات للخاصيات <code>Frame</code> و <code>Texture</code> و <code>Hframes</code>.
</p>

<p>
	سنعدّل قيمة الخاصية <code>Hframes</code> إلى <code>5</code>، ثم كما فعلنا سابقًا، ونضع مفتاحين مرجعيين في المسار الأول عند اللحظة 0، وتكون قيمة الإطار 0 والثانية عند اللحظة 0.4، كما ستكون قيمة الإطار 4.
</p>

<p>
	عند الانتهاء من كل ذكل، سنشغل الرسم المتحرك لرؤية النتيجة.
</p>

<h2 id="-1">
	سكريبت شخصية العدو
</h2>

<p>
	ينتشر <a href="https://academy.hsoub.com/programming/game-development/%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%B9%D8%AF%D9%88-%D9%88%D8%AD%D9%8A%D9%88%D8%A7%D9%86-%D8%A3%D9%84%D9%8A%D9%81-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-godot-r2584/" rel="">الأعداء</a> أعلى شاشة اللعبة على شكل شبكة، حيث ستقترب شبكة الأعداء من اللاعب بعد فترة زمنية محددة، ثم يعود إلى الأعلى ما لم يُدمر منها، كما سيطلق الأعداء النار على اللاعب دوريًا.
</p>

<p>
	سنضيف سكريبتًا إلى عقدة العدو ونعرّف المتغيرات التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8399_11" style=""><span class="pln">extends </span><span class="typ">Area2D</span><span class="pln">

var start_pos </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Vector2</span><span class="pun">.</span><span class="pln">ZERO
var speed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pln">

</span><span class="lit">@onready</span><span class="pln"> var screensize </span><span class="pun">=</span><span class="pln"> get_viewport_rect</span><span class="pun">().</span><span class="pln">size</span></pre>

<p>
	يتتبع المتغير <code>start_pos</code> موقع انطلاق الأعداء كي يعودوا إلى موقعهم الأصلي بعد الحركة. سنضبط قيمة المتغير عندما ننشر الأعداء في الشبكة، وسنستدعي الدالة <code>()start</code> الخاصة بهم:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8399_13" style=""><span class="pln">func start</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">):</span><span class="pln">
  speed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
  position </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Vector2</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">.</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="pln">pos</span><span class="pun">.</span><span class="pln">y</span><span class="pun">)</span><span class="pln">
  start_pos </span><span class="pun">=</span><span class="pln"> pos
  await get_tree</span><span class="pun">().</span><span class="pln">create_timer</span><span class="pun">(</span><span class="pln">randf_range</span><span class="pun">(</span><span class="lit">0.25</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.55</span><span class="pun">)).</span><span class="pln">timeout
  var tween </span><span class="pun">=</span><span class="pln"> create_tween</span><span class="pun">().</span><span class="pln">set_trans</span><span class="pun">(</span><span class="typ">Tween</span><span class="pun">.</span><span class="pln">TRANS_BACK</span><span class="pun">)</span><span class="pln">
  tween</span><span class="pun">.</span><span class="pln">tween_property</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> </span><span class="str">"position:y"</span><span class="pun">,</span><span class="pln"> start_pos</span><span class="pun">.</span><span class="pln">y</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1.4</span><span class="pun">)</span><span class="pln">
  await tween</span><span class="pun">.</span><span class="pln">finished
  $MoveTimer</span><span class="pun">.</span><span class="pln">wait_time </span><span class="pun">=</span><span class="pln"> randf_range</span><span class="pun">(</span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="lit">20</span><span class="pun">)</span><span class="pln">
  $MoveTimer</span><span class="pun">.</span><span class="pln">start</span><span class="pun">()</span><span class="pln">
  $ShootTimer</span><span class="pun">.</span><span class="pln">wait_time </span><span class="pun">=</span><span class="pln"> randf_range</span><span class="pun">(</span><span class="lit">4</span><span class="pun">,</span><span class="pln"> </span><span class="lit">20</span><span class="pun">)</span><span class="pln">
  $ShootTimer</span><span class="pun">.</span><span class="pln">start</span><span class="pun">()</span></pre>

<p>
	سنستدعي الدالة السابقة عند نشر الأعداء، ونمرر إليها شعاع موضع يمثل مكان ظهور العدو على الشاشة، وسنلاحظ عندها أننا ننشر العدو فوق الحافة العلوية للشاشة. سنعطي <code>y</code> قيمة سالبة كي نتمكن من تحريك العدو عندما يدخل إلى الشاشة باستخدام مولد إطارات بينية Tween، ونضبط أيضًا قيمتي المؤقتين عشوائيًا كي لا يتحرك العدو ويطلق النار في نفس الوقت، وبعد ذلك سنصل إشارة <code>timeout</code> الخاصة بكل مؤقت.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8399_15" style=""><span class="pln">func _on_timer_timeout</span><span class="pun">():</span><span class="pln">
  speed </span><span class="pun">=</span><span class="pln"> randf_range</span><span class="pun">(</span><span class="lit">75</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">)</span><span class="pln">

func _on_shoot_timer_timeout</span><span class="pun">():</span><span class="pln">
  $ShootTimer</span><span class="pun">.</span><span class="pln">wait_time </span><span class="pun">=</span><span class="pln"> randf_range</span><span class="pun">(</span><span class="lit">4</span><span class="pun">,</span><span class="pln"> </span><span class="lit">20</span><span class="pun">)</span><span class="pln">
  $ShootTimer</span><span class="pun">.</span><span class="pln">start</span><span class="pun">()</span></pre>

<p>
	يمكن هنا التحرك عند انتهاء المؤقت من عد الفترة الزمنية التي يُضبط عليها، لكننا لم ننشئ قذائف الأعداء بعد، وهذا ما سنتعامل معه لاحقًا.
</p>

<p>
	وبما أننا سنغير سرعة حركة الأعداء من خلال المتغير <code>speed</code>، فمعنى ذلك، أن بإمكاننا التحرك عبر كتابة الشيفرة التالية في دالة المعالجة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8399_17" style=""><span class="pln">func _process</span><span class="pun">(</span><span class="pln">delta</span><span class="pun">):</span><span class="pln">
  position</span><span class="pun">.</span><span class="pln">y </span><span class="pun">+=</span><span class="pln"> speed </span><span class="pun">*</span><span class="pln"> delta
  </span><span class="kwd">if</span><span class="pln"> position</span><span class="pun">.</span><span class="pln">y </span><span class="pun">&gt;</span><span class="pln"> screensize</span><span class="pun">.</span><span class="pln">y </span><span class="pun">+</span><span class="pln"> </span><span class="lit">32</span><span class="pun">:</span><span class="pln">
    start</span><span class="pun">(</span><span class="pln">start_pos</span><span class="pun">)</span></pre>

<p>
	إن لم تكن قيمة المتغير <code>speed</code> هي 0، فسنرى العدو يتحرك على الشاشة، وعندما يصل إلى الأسفل يعود من الأعلى مجددًا.
</p>

<p>
	لقد كتبنا الدالة التي تعالج انفجار الأعداء عندما تصطدم بها قذيفة اللاعب الدالة <code>()explode</code> عندما بنينا مشهد القذيفة، لهذا سنضيف نفس الدالة هنا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8399_19" style=""><span class="pln">func explode</span><span class="pun">():</span><span class="pln">
  speed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
  $AnimationPlayer</span><span class="pun">.</span><span class="pln">play</span><span class="pun">(</span><span class="str">"explode"</span><span class="pun">)</span><span class="pln">
  set_deferred</span><span class="pun">(</span><span class="str">"monitoring"</span><span class="pun">,</span><span class="pln"> false</span><span class="pun">)</span><span class="pln">
  died</span><span class="pun">.</span><span class="pln">emit</span><span class="pun">(</span><span class="lit">5</span><span class="pun">)</span><span class="pln">
  await $AnimationPlayer</span><span class="pun">.</span><span class="pln">animation_finished
  queue_free</span><span class="pun">()</span></pre>

<p>
	سنوقف في هذه الدالة حركة العدو، ثم نشغّل الرسم المتحرك الخاص بعملية الانفجار ونحذف بعدها العدو في نهاية الدالة. وتضمن الدالة <code>()set_deferred</code> إيقاف الخاصية <code>monitoring</code> في كائن العدو كي لا تصيبه قذيفة أخرى أثناء انفجاره، وعندها سيصيب القذيفة التي تنفجر ويفجرها مجددًا ويستدعي الدالة التي تنفذ عملية التفجير مجددًا، وهكذا.
</p>

<p>
	سنضيف الآن الإشارة <code>died</code> إلى أعلى السكريبت:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8399_21" style=""><span class="pln">signal died</span></pre>

<p>
	وسنستخدم هذه الإشارة لإبلاغ المشهد الرئيسي أن اللاعب حقق بعض النقاط.
</p>

<h2 id="-2">
	نشر العدو
</h2>

<p>
	سنعود الآن إلى المشهد الرئيسي Main لإضافة بعض الأعداء إلى اللعبة، ولهذا سنضيف سكريبتًا إلى المشهد الرئيسي ونبدأ بتحميل مشهد العدو برمجيًا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8399_23" style=""><span class="pln">extends </span><span class="typ">Node2D</span><span class="pln">

var enemy </span><span class="pun">=</span><span class="pln"> preload</span><span class="pun">(</span><span class="str">"res://enemy.tscn"</span><span class="pun">)</span><span class="pln">
var score </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span></pre>

<p>
	لن يبدأ نشر العدو في اللعبة قبل النقر على زر البداية. وطالما أننا لم نرتّب هذا الأمر بعد، فسننشره مباشرة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8399_25" style=""><span class="pln">func _ready</span><span class="pun">():</span><span class="pln">
  spawn_enemies</span><span class="pun">()</span><span class="pln">

func spawn_enemies</span><span class="pun">():</span><span class="pln">
  </span><span class="kwd">for</span><span class="pln"> x </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">9</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> y </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">3</span><span class="pun">):</span><span class="pln">
      var e </span><span class="pun">=</span><span class="pln"> enemy</span><span class="pun">.</span><span class="pln">instantiate</span><span class="pun">()</span><span class="pln">
      var pos </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Vector2</span><span class="pun">(</span><span class="pln">x </span><span class="pun">*</span><span class="pln"> </span><span class="pun">(</span><span class="lit">16</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">8</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">24</span><span class="pun">,</span><span class="pln"> </span><span class="lit">16</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="lit">4</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> y </span><span class="pun">*</span><span class="pln"> </span><span class="lit">16</span><span class="pun">)</span><span class="pln">
      add_child</span><span class="pun">(</span><span class="pln">e</span><span class="pun">)</span><span class="pln">
      e</span><span class="pun">.</span><span class="pln">start</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">)</span><span class="pln">
      e</span><span class="pun">.</span><span class="pln">died</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="pln">_on_enemy_died</span><span class="pun">)</span></pre>

<p>
	تنشر الشيفرة السابقة 27 عدوًا ضمن شبكة في النصف الأعلى من الشاشة، ولا بد من التأكد من وصل الإشارة <code>died</code> لكل عدو، ولهذا علينا إضافة الدالة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8399_27" style=""><span class="pln">func _on_enemy_died</span><span class="pun">(</span><span class="pln">value</span><span class="pun">):</span><span class="pln">
  score </span><span class="pun">+=</span><span class="pln"> value</span></pre>

<p>
	سنشغل الآن المشهد، وسنرى مجموعةً من الأعداء أعلى الشاشة يسقطون دوريًا نحو الأسفل.
</p>

<h2 id="-3">
	إطلاق النار من المركبات المعادية
</h2>

<p>
	طالما أن مركبات العدو ستتمكن من إطلاق النار، فسنمنحها شيئًا لتطلق النار عليه.
</p>

<h3 id="-4">
	مشهد قذائف العدو
</h3>

<p>
	سننشئ مشهدًا باسم EnemyBullet مشابهًا من ناحية التكوين لمشهد قذائف اللاعب. لن نكرر خطوات الإنشاء هنا طبعًا، لهذا يمكن العودة إلى مشهد قذائف اللاعب <code>bullet</code> في حال مصادفة مشكلة ما؛ إذ سيكون الفرق الوحيد هو الصورة المستخدمة للقذيفة في هذا المشهد، وهي الصورة <code>Enemy_projectile (16 x 16).png</code>، وسيختلف السكريبت هنا قليلًا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8399_29" style=""><span class="pln">extends </span><span class="typ">Area2D</span><span class="pln">

</span><span class="lit">@export</span><span class="pln"> var speed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">150</span><span class="pln">

func start</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">):</span><span class="pln">
  position </span><span class="pun">=</span><span class="pln"> pos

func _process</span><span class="pun">(</span><span class="pln">delta</span><span class="pun">):</span><span class="pln">
  position</span><span class="pun">.</span><span class="pln">y </span><span class="pun">+=</span><span class="pln"> speed </span><span class="pun">*</span><span class="pln"> delta</span></pre>

<p>
	نصل الإشارتين <code>screen_exited</code> و <code>area_entered</code> العائدتين للعقدتين <code>VisibleOnScreenNotifier2D</code> و <code>Area2D</code> على التوالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8399_31" style=""><span class="pln">func _on_visible_on_screen_notifier_2d_screen_exited</span><span class="pun">():</span><span class="pln">
  queue_free</span><span class="pun">()</span><span class="pln">

func _on_area_entered</span><span class="pun">(</span><span class="pln">area</span><span class="pun">):</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> area</span><span class="pun">.</span><span class="pln">name </span><span class="pun">==</span><span class="pln"> </span><span class="str">"Player"</span><span class="pun">:</span><span class="pln">
    queue_free</span><span class="pun">()</span></pre>

<p>
	<strong>ملاحظة</strong>: تُطلق الإشارة <code>screen_exited</code> عندما يخرج الكائن من الشاشة، وتُستخدم لحذفه أو إعادة ضبطه وتطلق الإشارة <code>area_entered</code> عندما يدخل جسم إلى منطقة محددة وهي تُستخدم للكشف عن الاصطدامات أو التفاعلات.
</p>

<p>
	وكما نلاحظ، نحن هنا نلتقط اصطدام القذيفة المعادية باللاعب، لكننا لم نحصل على النتيجة المطلوبة حتى اللحظة. سنعود إلى ذلك بعد إضافة طريقة تصف تضرر سفينة اللاعب عند إصابته.
</p>

<h3 id="-5">
	إضافة آلية إطلاق النار إلى العدو
</h3>

<p>
	سنحمّل مشهد قذيفة العدو في أعلى سكريبت العدو:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8399_33" style=""><span class="pln">var bullet_scene </span><span class="pun">=</span><span class="pln"> preload</span><span class="pun">(</span><span class="str">"res://enemy_bullet.tscn"</span><span class="pun">)</span></pre>

<p>
	ونعدّل بعد ذلك دالة إطلاق النار إلى النحو الآتي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8399_35" style=""><span class="pln">func _on_shoot_timer_timeout</span><span class="pun">():</span><span class="pln">
  var b </span><span class="pun">=</span><span class="pln"> bullet_scene</span><span class="pun">.</span><span class="pln">instantiate</span><span class="pun">()</span><span class="pln">
  get_tree</span><span class="pun">().</span><span class="pln">root</span><span class="pun">.</span><span class="pln">add_child</span><span class="pun">(</span><span class="pln">b</span><span class="pun">)</span><span class="pln">
  b</span><span class="pun">.</span><span class="pln">start</span><span class="pun">(</span><span class="pln">position</span><span class="pun">)</span><span class="pln">
  $ShootTimer</span><span class="pun">.</span><span class="pln">wait_time </span><span class="pun">=</span><span class="pln"> randf_range</span><span class="pun">(</span><span class="lit">4</span><span class="pun">,</span><span class="pln"> </span><span class="lit">20</span><span class="pun">)</span><span class="pln">
  $ShootTimer</span><span class="pun">.</span><span class="pln">start</span><span class="pun">()</span></pre>

<p>
	سنشغّل الآن المشهد Main، وسنرى بعض قذائف العدو تهطل إلى الأسفل بين الفينة والأخرى.
</p>

<h2 id="-6">
	ختامًا
</h2>

<p>
	بهذا نكون قد أتممنا إعداد مشهد العدو الذي يطلق النار في لعبتنا ثنائية الأبعاد، وسنتعرف على المزيد لتطوير لعبتنا في المقالات القادمة.
</p>

<p>
	ترجمة -وبتصرف- للمقالين: <a href="https://kidscancode.org/godot_recipes/4.x/games/first_2d/first_2d_07/index.html" rel="external nofollow">Enymies</a> و <a href="https://kidscancode.org/godot_recipes/4.x/games/first_2d/first_2d_08/index.html" rel="external nofollow">Enemy Shooting</a>.
</p>

<h2 id="-7">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/game-development/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A7%D9%84%D9%85%D8%B4%D9%87%D8%AF-%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A-%D9%84%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%86%D8%A7%D8%A6%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D8%B9%D8%A8%D8%B1-%D9%85%D8%AD%D8%B1%D9%83-godot-r2589/" rel="">إنشاء المشهد الرئيسي للعبة ثنائية الأبعاد عبر محرك Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A8%D9%86%D8%A7%D8%A1-%D9%85%D8%B4%D9%87%D8%AF-%D8%A7%D9%84%D9%82%D8%B0%D9%8A%D9%81%D8%A9-%D9%88%D8%A5%D8%B7%D9%84%D8%A7%D9%82-%D8%A7%D9%84%D9%86%D8%A7%D8%B1-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-%D8%B3%D9%81%D9%8A%D9%86%D8%A9-%D8%A7%D9%84%D9%81%D8%B6%D8%A7%D8%A1-r2588/" rel="">بناء مشهد القذيفة وإطلاق النار في لعبة سفينة الفضاء</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%B5%D9%85%D9%8A%D9%85-%D9%85%D8%B4%D9%87%D8%AF-%D8%A7%D9%84%D9%84%D8%A7%D8%B9%D8%A8-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-%D8%AB%D9%86%D8%A7%D8%A6%D9%8A%D8%A9-%D8%A7%D9%84%D8%A3%D8%A8%D8%B9%D8%A7%D8%AF-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-godot-r2586/" rel="">تصميم مشهد اللاعب في لعبة ثنائية الأبعاد باستخدام Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%B9%D8%AF%D9%88-%D9%88%D8%AD%D9%8A%D9%88%D8%A7%D9%86-%D8%A3%D9%84%D9%8A%D9%81-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-godot-r2584/" rel="">برمجة عدو وحيوان أليف في لعبة Godot</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%B9%D9%82%D8%AF-nodes-%D9%81%D9%8A-%D9%85%D8%AD%D8%B1%D9%83-%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-%D8%AC%D9%88%D8%AF%D9%88-godot-r2490/" rel="">تعرف على العقد Nodes في محرك ألعاب جودو Godot</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2590</guid><pubDate>Mon, 04 Aug 2025 16:03:02 +0000</pubDate></item></channel></rss>
