‏إظهار الرسائل ذات التسميات tutorials. إظهار كافة الرسائل
‏إظهار الرسائل ذات التسميات tutorials. إظهار كافة الرسائل

الأحد، 16 ديسمبر 2012

رسم الصيغ الرياضية بلغة كلمات (الجزء الثاني)

الهدف من المشروع هو برنامج يرسم الصيغ الرياضية، مثلاً يقول له المستخدمم س^2 + 5 فيرسم البرنامج شكل الـparabola المعروف.
  • في الجزء الأول تحدثنا عن الإعراب، الدالة التي تأخذ نصاً مثل 12+13 وتعود بالمصفوفة ["+"، 12، 13] التي تمثل ما يسمى بشجرة الإعراب parse tree.
لو كنت قد قرأت الجزء الأول فأنت إذاً جاهز الآن لنختتم هذه السلسلة.

حساب قيمة التعبير

حسناً. لدينا الآن شجرة إعراب مثل 100 أو ["+"، س، 12]، ماذا نفعل بها؟ نريد دالة اسمها تقييم تأخذ الشجرة وتعود بالقيمة التي تعبر عنها.
  • لو كانت "الشجرة" في صورة قيمة عددية، نعود بتلك القيمة.
  • لو كانت الشجرة هي المتغير س (كقيمة نصية)، فإننا لا نستطيع أن نفعل شيئاً بأنفسنا، لذلك سوف نقدم عاملاً إضافياً parameter للدالة تقييم به قيمة س المطلوبة.
  • لو كانت الشجرة في صورة مصفوفة مثل ["+"، أ، ب] سوف نقوم بالآتي:
    • ناد الدالة تقييم مع الفرع الأيمن أ لحساب قيمته
    • ناد الدالة أيضاً مع الفرع الأيسر ب
    • نفذ العملية الحسابية وارجع بالنتيجة.
كل هذا يعطينا الكود التالية، الدالة تأخذ الشجرة "ت" وقيمة "س" المحددة من البرنامج، وتحسب قيمة الصيغة الرياضية:
دالة تقييم (ت، س):
    إذا ت ~ ["+"، ؟أ، ؟ب]:
        ارجع ب: تقييم (أ، س)+ تقييم (ب، س)
    وإلا إذا ت ~ ["-"، ؟أ، ؟ب]:
        ارجع ب: تقييم (أ، س)- تقييم (ب، س)
    وإلا إذا ت ~ ["×"، ؟أ، ؟ب]:
        ارجع ب: تقييم (أ، س)× تقييم (ب، س)
    وإلا إذا ت ~ ["÷"، ؟أ، ؟ب]:
        مقام = تقييم (ب، س)
        إذا مقام <> 0 :
            ارجع ب: تقييم (أ، س)÷ مقام 
        وإلا :
            اطبع "خطأ: قسمة على صفر" 
            ارجع ب: 0 
        تم 
    وإلا إذا ت ~ ["^"، ؟أ، ؟ب]:
        ارجع ب: أس (تقييم (أ، س)، تقييم (ب، س))
    وإلا إذا ت ~ "س" :
        ارجع ب: س 
    وإلا :
          -- لو وصلنا إلى هنا فقيمة ت هي اصلا عدد
         ارجع ب: ت 
    تم 
نهاية

لاحظ كيف استخدمنا مرة أخرى عملية المطابقة ~ لتسهيل الموضوع. الآن نحن جاهزون للرسم. أولاً نكتب إجراءاً مساعداً يرسم المحاور س، ص
إجراء ارسم.المحاور ():
    ارسم.خط (400، 0)- (400، 599)
    ارسم.خط (0، 300)- (799، 300)
نهاية

الآن سوف نكتب الدالة التي ترسم فعلاً. خطتنا كالآتي:
الدالة سوف تأخذ عاملين: من.س و إلى.س،  لكي نستطيع أن نقول مثلاً ارسم س^2 من س=-4 إلى س=4
سوف تقوم الدالة بالآتي:
  • كرر بحيث يبدأ المتغير أ بالقيمة من.س وينتهي بالقيمة إلى.س، وكل مرة نضيف قيمة صغيرة إلى أ:
    • احسب ص عن طريق استدعاء تقييم لحساب قيمة الصيغة الرياضية، واعطها قيمة المتغير س تساوي العداد أ
    • سوف نرسم خطاً صغيراً بين كل نقطتين:
      • لو كان هذا أول تكرار، فلا يوجد لدينا سوى نقطة واحدة. خزن قيمة أ في المتغير أ.قديم وقيمة ص في المتغير ص.قديم
      • لو لم يكن أول تكرار، فنحن لدينا النقطة (أ، ص) والنقطة (أ.قديم، ص.قديم). سنرسم خطاً بينهما
هذا يعطينا الكود :
إجراء ارسم.الدالة (التعبير، من.س، إلى.س):
    أ = من.س 
    أ.قديم = لاشيء 
    ص.قديم = لاشيء 
    مقياس.س = 20 
    مقياس.ص = 15 
    
    كرر مادام أ <= إلى.س :
        ص = تقييم (التعبير، أ)
        إذا ليس أ.قديم = لاشيء :
            س1 = - أ × مقياس.س + 400 
            ص1 = - ص × مقياس.ص + 300 
            س2 = - أ.قديم × مقياس.س + 400 
            ص2 = - ص.قديم × مقياس.ص + 300 
            ارسم.خط (س1، ص1)- (س2، ص2)، 4 
        تم 
        أ.قديم = أ 
        ص.قديم = ص 
        أ = أ + 0.1 
        انتظر (30)
    تابع 
نهاية

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

لا يبقى إلا ان نربط كل المكونات ببعضها!

اقرأ "الصيغة؟"، الصيغة 
اقرأ "من؟"، # أ 
اقرأ "إلى؟"، # ب 

الصيغة = تبديل (الصيغة، " "، "")
الشجرة = تعبير (الصيغة)
ارسم.المحاور ()
ارسم.الدالة (الشجرة، أ، ب)

ماذا فعلنا؟
  • قرأنا الصيغة من المستخدم (وقيم س التي سنبدأ وننتهي بها)
  • حذفنا المسافات من الصيغة
  • أعربناها لنحصل على شجرة الإعراب بواسطة الدالة تعبير
  • رسمنا المحاور
  • نادينا ارسم.الدالة وأعطيناها الشجرة وقيم من.س وإلى.س
  • ارسم.الدالة سوف تنادي تقييم باستمرار لكي تحسب قيمة الصيغة عند كل قيمة لـ س
  • ....وترسم الخطوط المطلوبة!

كم سطراً كتبنا؟ للأسف تجاوزنا حاجز المائة قليلاً: 109. لكن لو حذفنا السطور الفارغة بين تعريفات الدوال...الخ سيكون العدد أقل من المائة.

أقل من مائة سطر، تقوم بما قد يحتاج مئات الأسطر في لغات أخرى...

يمكنك عزيزي القاريء أن تجرب عمل نسخة متقدمة من البرنامج تضيف إمكانية استخدام الدوال مثل جتا، جا، أو تضيف الثابتين ط (π) و هـ (e) أوتقدم بعض المساعدة للمستخدم. لمَ لا تجرب؟ :)

رسم الصيغ الرياضية بلغة كلمات

نريد برنامجاً يأخذ صيغة رياضية ويرسمها!


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

الجزء الأول: كيف نفهم الصيغة الرياضية؟

يوجد في علوم الحاسب ما يسمى parsing (الإعراب). لو أعربنا التعبير الحسابي س^2+5 فسيكون لنا النتيجة التالية:

هذه الرسمة اسمها "شجرة الإعراب" أو parse tree، وهي تشبه الشجرة لأن لها جذراً وفروعاً. كيف نعبر عن هذه الشجرة بقيمة في لغة كلمات؟ هناك طرق كثيرة: يمكن أن نستخدم الفصائل والكائنات، أو المصفوفات، أو القواميس...فلنستخدم طريقة بسيطة: سوف تكون كل شجرة عبارة من مصفوفة من ثلاثة عناصر: العملية الحسابية نفسها، والقيمتان التي تجري عليهما العملية. مثلاً إن قمنا بإعراب التعبير "4+5" سوف نحصل على المصفوفة ["+"، 4، 5]. لو أعربنا القيمة النصية "(4+5)×2" سوف نحصل على ["×"، ["+"، 4، 5]، 2]. أي أن الشجرة هي عملية ضرب بين ["+"، 4، 5] وبين 2.

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

لاحظ أن الإعراب لا يعود دائماً بمصفوفات..لو أعربنا مثلاً التعبير 12 فإن "الشجرة" ما هي إلا العدد 12 نفسه.

رائع. الآن علينا أن نفكر: مم تتكون الصيغ الرياضية؟ سوف نسمي الصيغة في صورتها النهائية "تعبير حسابي"، وهذا التعبير مكون من حد واحد أو أكثر. ما الذي يفصل بين الحدود في التعبير الواحد؟ علامات الجمع والطرح. أي أن التعبير يكون في صيغة حد1 + حد2 - حد3....الخ، أو مكون من حد واحد.


كيف نعبر عن هذا رياضياً؟ هناك طريقة اسمها Parse Expression Grammar أو PEG تسمح لنا أن نكتب القاعدة بهذه الطريقة:

تعبير = حد "+" تعبير       (هذه قاعدة 1#)
أو       حد "-" تعبير       (قاعدة 2#)
أو       حد                  (قاعدة 3#)

ما معنى هذا الوصف؟ معناه أن التعبير يمكن أن يكون حداً واحداً أو مجموعة من الحدود يفصل بينها + و -. مثلاً يمكن أن نبدأ بالحد "2×3". نحن نعرف أن هذا تعبير من قاعدة #3 من التعريف. ماذا عن 1 + 2×3؟ نعرف أن هذا أيضاً تعبير (من قاعدة #1 التي تقول أن حد + تعبير = تعبير، وبالتالي سمحت لنا بإضافة حد في المقدمة). ماذا عن 5 - 1 + 2×3؟ هذا أيضاً تعبير من جزء 2# من التعريف


لو شعرت أن الأمر صعب فلا تقلق: الموضوع ببساطة هو recursion مثلما تعلمت في البرمجة. المسألة هي أن التعبير يمكن أن يكون حداً أو يكون حد + حد + حد ....، أو حد + حد - حد - حد ....الخ.

ماذا عن الحدود نفسها؟ الحد هو "أس" واحد أو أكثر تفصل بينها علامات  ×، ÷

هذه كلها حدود:

5^2 × 6
6 ÷ 3
18
6^س

كيف نكتبها بطريقة PEG؟ هكذا:

حد = أس "×" حد
أو     أس "÷" حد
أو أس

وهي كما ترى نفس طريقة التعبيرات. لاحظ أن قيمة عادية مثل "18" نعتبرها أس رغم غياب علامة ^، مثلما اعتبرنا أن 12 هو تعبير رغم غياب علامة + أو -

ماذا عن الأسس؟ هي ببساطة مجموعة من واحد أو أكثر من التعبيرات الأولية يفصل بينها علامة ^. كل هذه أسس:

س^2
2^س
س^2^3
س
12

والقاعدة (كما خمنت) هي:

أس = أولي "^" أس
أو     أولي

ولكن ما هو التعبير الأولي؟؟؟ إنه رقم، أو المتغير س، أو تعبير بين أقواس:

أولي = "س"
أو     رقم
أو  "(" تعبير ")"

لم يبق ما نعرّفه سوى الأرقام نفسها. الرقم هو خانة واحدة أو أكثر (الخانة هي الرمز 0، أو 1، أو 2...إلى 9).

رقم = خانة رقم
أو خانة
خانة = من "0" إلى "9"

الرقم هو خانات لا يفصل بينها شيء. لهذا لم نقل مثلاً رقم = خانة + رقم بل قلنا مباشرةً رقم = خانة رقم....

ولكن ما فائدة كل هذا؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟؟

فائدة كل هذا أن لغة كلمات بها محرك إعراب جاهز. لو قدمت له قواعد في صورة PEG سوف يقدم لك إمكانية إعراب أي نص يتماشى مع هذه القواعد. مثلما نعرّف في كلمات دالة أو إجراء أو فصيلة، يمكننا أيضاً أن نعرّف قواعداً. انظر للكود الآتية:
قواعد تعبير :
    تعبير = حد "+" تعبير 
    أو حد "-" تعبير 
    أو حد 

    حد = أس "×" حد 
    أو أس "÷" حد 
    أو أس 

    أس = أولي "^" أس 
    أو أولي 


    أولي = رقم 
    أو "س" 
    أو "(" تعبير ")" 

    رقم = خانة رقم
    أو خانة 

    خانة = من "0" إلى "9" 
نهاية 
 

اطبع تعبير ("1+2+3")

في السطر الأخير نجرب القواعد الجديدة. سوف يطبع مفسر كلمات لك القيمة......لاشيء :(

لماذا؟ في الواقع سوف تقوم كلمات بإعراب التعبير، لكننا لم نطلب منها أن تعطينا شجرة إعراب، لذلك سوف تعود لنا بالقيمة لاشيء. هيا نغير ذلك الموقف! فلنبدأ بشيء بسيط كالأرقام:
رقم = خانة:أ رقم:ب => أ + ب 
أو خانة 

خانة = من "0" إلى "9"

لاحظ شيئين:
في السطر الأول قلنا خانة:أ رقم:ب ، هذه طريقتنا في إعطاء أسماء للمكونات التي نعربها.
أننا قلنا في أخر السطر => أ + ب ، وهذه طريقتنا في أن نعود بقيم أثناء الإعراب

يمكن أن نقرأ السطر كله كالآتي: الرقم مكون من خانة هي أ، ورقم أصغر هو ب، وحين نجدهما نعود بالقيمة أ + ب.

لاحظ أن أ و ب هما نصان، فلو أعربنا الرقم "123" فسيكون أ = "1" و ب يساوي "23" (الأرقام تقرأ من اليسار) وبهذا يكون الرقم كله هو أ + ب = "123"

ماذا عن السطر الثاني الذي يقول أو خانة؟ نحن لم نخبره بأي قيمة يعود، لكن لو رأى مفسر كلمات قاعدة ما هي الا استدعاء قاعدة أخرى (نحن لم نفعل سوى استدعاء "خانة") فسيعود بنفس القيمة الآتية من القاعدة المستدعاه. كأني بالضبط قلت أو خانة:أ => أ

ماذا عن السطر خانة = من "0" إلى "9"؟ حين تكون القاعدة ما هي إلا قيمة نصية فالقيمة العائدة منها هي نفس القيمة النصية.

إذاً قد عرفنا ماذا يرجع من رقم. ماذا عن شيء مثل تعبير؟

تعبير = حد:أ "+" تعبير:ب => ["+"، أ، ب]
أو حد:أ "-" تعبير:ب => ["-"، أ، ب]
أو حد:أ => أ

باللغة البشرية:
التعبير هو حد اسمه أ، ثم علامة "+"، ثم تعبير أصغر اسمه ب ، وفي تلك الحالة ارجع بالمصفوفة ["+"، أ، ب]
أو هو حد اسمه أ، ثم علامة "-"، ثم تعبير أصغر اسمه ب، وهنا ارجع بالمصفوفة ["-"، أ، ب]
أو هو مجرد حد اسمه أ، وفي تلك الحالة ارجع بذلك الحد.

سهل، أليس كذلك؟ بنفس الطريقة نعدل "حد" و "أس" و"أولي" ليكون لدينا القواعد كلها:

قواعد تعبير :
    تعبير = حد : أ "+" تعبير : ب => ["+"، أ، ب]
    أو حد : أ "-" تعبير : ب => ["-"، أ، ب]
    أو حد : أ => أ 

    حد = أس:أ "×" حد:ب => ["×"، أ، ب]
    أو أس:أ "÷" حد:ب => ["÷"، أ، ب]
    أو أس:أ => أ 

    أس = عامل:أ "^" أس:ب => ["^"، أ، ب]
    أو عامل 

    عامل = رقم:أ => كعدد (أ)
    أو "س" 
    أو "(" تعبير : أ ")" => أ 

    رقم = خانة : أ رقم : ب => أ + ب 
    أو خانة 

    خانة = من "0" إلى "9" 
نهاية 

لاحظ أننا في هذا الجزء التالي قد حولنا الرقم من قيمة نصية إلى قيمة عددية:

عامل = رقم:أ => كعدد (أ) 
 
قد يبدو الأمر صعباً، لكنه أسهل بكثير من كتابة برنامج يأخذ أي صيغة رياضية ويفهمها بطريقة يدوية. القواعد كلها أخذت 30 سطراً لكنها وفرت عشرات أو مئات السطور المطلوبة لعمل مثل هذا البرنامج في لغات أخرى.

سوف تصنع لنا كلمات دالة اسمها "تعبير" (لأننا قد سمينا القواعد "تعبير")، وهذه الدالة إن أعطيناها نصاً فستعطينا شجرة إعراب. جرب أن تضيف هذا السطر في آخر البرنامج:
اطبع تعبير("12+13×14")

سوف يظهر المخرج التالي:

["+"، 12، ["×"، 13، 14]]


في الواقع الكود السابقة ليست دقيقة لأن العمليات الرياضية + - ÷ × بها خاصية left associativity. أي أن تعبير مثل 1-2-5 المفروض أن يكون (1-2)-5، لكن برنامجنا يعتبره 1-(2-5)، أي أنه يعود بالشجرة ["-"، 1، ["-"، 2، 5] بدلاً من الشجرة الصحيحة وهي ["-"، ["-"، 1، 2]، 5]

سبب المشكلة هو أنه كان المفروض في القواعد أن نقول:
تعبير = تعبير + حد
ولكننا قلنا
تعبير = حد + تعبير

للأسف لا يمكن استخدام الطريقة الصحيحة في هذا الإصدار من كلمات بسبب مشكلة اسمها left recursion (هناك طرق لحل تلك المشكلة لكنها لم تطبق في كلمات بعد). لذلك علينا الحذر: البرنامج سيعطي نتائجاً خاطئة في المدخلات أ-ب-ج أو أ÷ب÷ج، ولكن يمكننا استخدام الأقواس في تلك الحالات.

على العموم قد أنهينا جزء إعراب الصيغ الرياضية، ومازال لدينا 70 سطراً أيضاً!

الجمعة، 16 نوفمبر 2012

Solving an ACM problem with Kalimat (part 2)

  • هذا المقال تكملة للجزء الأول الذي تجده هنا، لكن في الواقع الجزء الثاني يتضمن كل المعلومات المطلوبة ولا تحتاج لقراءة الجزء الأول لتفهمه.
  • مسألة الـACM نفسها هنا
 ما المطلوب من المسألة؟ كتابة برنامج به وصف لمجموعة من المبان، وحذف الخطوط المتقاطعة بحيث يظهر فقط المحيط العام لتلك المبان، كما توضح الصورة:


كيف نحل هذه المسألة؟ من حسن الحظ أن شرح السؤال نفسه يلمح لنا بالحل!
The skyline vector should represent the "path" taken, for example, by a bug starting at the minimum x-coordinate and traveling horizontally and vertically over all the lines that define the skyline.

يقول لنا البرنامج أن الحل يصف تحركات حشرة تتحرك عبر صف المبان. تعال نتخيل أن هذه الحشرة هي نملة (بدلاً من البقة المذكورة في المسألة). الحركة في الصور التالية من اليسار لليمين:

في البداية ستسير النملة أفقياً حتى تصطدم بجدار بمبنى:
فإن وجدت المبنى ستبدأ في التحرك رأسياً لتتسلقه


إن وجدت نفسها عند سفح مبنى من جديد، فستبدأ في تسلقه، وإن وجدت نفسها عند هضبة فستنزل

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

وكيف تنتقل بين الحالات؟

إن كانت واقفة على الأرض:
  • تسير إلى سفح أول مبنى أمامها

إن كانت واقفة على سطح مبنى:

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


إن كانت عند سفح:
  • تصعد لأعلى المبنى، ثم تكون حالتها "على سطح مبنى"
إن كانت عند هضبة:
  • إن كان هناك سطح مبنى يعوق هبوطها، تنزل إلى سطح ذلك المبنى ثم تصبح حالتها "على سطح مبنى"
  • إن لم يكن هناك عائق تنزل إلى الأرض (ص = 0) وتصبح حالتها "على الأرض"



الملخص:

لم يبق سوى كتابة الكود التي تمثل هذا الرسم، ثم نستطيع جميعنا أن نعود إلى البيت ونأكل الساندوتشات التي أعدتها أمهاتنا :)


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

الآن إلى الكوووود!!


كما نعلم، مجموعة البيانات المدخلة هي مصفوفة من ثلاثيات، مثل [[12، 10، 20]، [15، 8، 22]، [20، 12، 30]]، وكل ثلاثية تعبر عن مبنى [الحافة اليسرى، الارتفاع، الحافة اليمنى]. فلنبدأ بشيء بسيط: دوال قصيرة لاستخراج بيانات كل مبنى مما يجعل البرنامج أسهل في القراءة:
دالة يسار ( مبنى ) :
    ارجع ب: مبنى [ 1 ] 
نهاية 

دالة سطح ( مبنى ) :
    ارجع ب: مبنى [ 2 ] 
نهاية 

دالة يمين ( مبنى ) :
    ارجع ب: مبنى [ 3 ] 
نهاية

الآن نرى الإجراء الأساسي لحل المسألة:
بداية.البحث مشترك 

إجراء إجابة ( صف.المباني ) :
    بداية.البحث = 1 
    على.الأرض ( 0 ، 0 ، صف.المباني ) 
نهاية 

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

ماذا أخذ الإجراء على.الأرض؟ أخذ قيم س، ص، ومصفوفة المباني.

جميل جداً. الآن ننظر للإجراء نفسه:

إجراء على.الأرض(س ، ص ، المباني) :
 
 لكل أ من بداية.البحث إلى عدد(المباني) :
        إذا يسار(المباني [أ]) > س :
            س = يسار(المباني[أ]) 
            اطبع س 
            حدث.بداية.البحث(س، المباني) 
            وكل إلى عند.سفح(س، ص، المباني[أ] ، المباني) 
        تم 
    تابع 
         -- إن وصل التنفيذ هنا فمعنى ذلك
         -- أن النملة على الأرض ولا يوجد
         -- مبان على يمينها: أي أن الرحلة
         -- قد انتهت
 نهاية

الإجراء مكون من جزئين: الجزء الأول (بين لكل...تابع) يبحث عن أول مبنى على يمين النملة، فإن وجده:
- يغير قيمة "س" ليحرك النملة عند سفح المبنى، ويطبع القيمة الجديدة كما هو مطلوب في المخرجات
- ينادي حدث.بداية.البحث(س، المباني) لكي يبحث عن أول مبنى على يمين س الجديدة ويجعل رقم هذا المبنى هو بداية.البحث
- ينفذ السطر وكل إلى عند.سفح (____ ) ويعطيه بعض القيم، مما يجعل البرنامج الآن في حالة جديدة.

ما معنى وكل إلى؟ انها تجعل السطر من "استدعاء إجراء" إلى "توكيل لإجراء". ما الفرق بينهما؟ في حالة التوكيل فإنه قبل استدعاء عند.سفح(...) سوف يتم أولاً حذف الstack frame الخاصة بـ على.الأرض( ) من الذاكرة، وهذا  طبيعي لأن هذا الجزء من البرنامج لا نحتاج أن نرجع إليه.

الآن نرى عند.سفح:
إجراء عند.سفح ( س ، ص ، مبنى ، المباني ) :

    ص = سطح ( مبنى ) 
    اطبع ص 
    وكل إلى عند.سطح ( س ، ص ، مبنى ، المباني ) 
نهاية 

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

سهلة دي مش كدة؟

إجراء عند.سطح(س، ص، مبنى، المباني) :

 لكل أ من بداية.البحث إلى عدد(المباني) :
        م = المباني [أ] 
         -- لو وجدنا مبنى خارجاً تماماً عن حدود المبنى الحالي،
         -- فكل المباني التي تليه هي الأخرى خارجة عن الحدود
         -- وبالتالي لا داع للاستمرار في البحث
        إذا يسار(م) > يمين(مبنى) : اذهب إلى نهاية.البحث 
 
         -- هل م أعلى من المبنى الذي تقف عليه النملة؟
         إذا سطح(م) > سطح(مبنى) :
            -- في هذه الحالة فهو عقبة. هيا نتحرك إلى سفحه
             س = يسار (م) 
            اطبع س 
            حدث.بداية.البحث(س ، المباني) 
            وكل إلى عند.سفح(س، ص، م، المباني)           
        تم 
    تابع 
    علامة نهاية.البحث 
    -- إن وصلنا هنا ولم نجد شيئاً، نسير إلى نهاية المبنى الحالي
     س = يمين(مبنى) 
    اطبع س 
    حدث.بداية.البحث(س، المباني) 
    وكل إلى عند.هضبة( س ، ص ، مبنى ، المباني) 
نهاية

لا تنزعج! الكود أبسط مما يبدو عليها. هي ببساطة تقوم بالآتي:
1- نفذ لكل مبنى م في المباني التي على يمين النملة:
  • لو كان يسار م أكبر من يمين المبنى الذي نقف عليه، إذا فكل المباني بعيدة ولا توجد عقبات. سوف نذهب للخطوة 2
  • لو كان الأمر غير هذا، فقد وجدنا مبنى يقطع مبنانا! لو كان أعلى من مبنانا أيضاً فيجب أن نصل إلى سفحه. حرك س إلى سفح المبنى، واطبعها، وانتقل لحالة عند.سفح
2- لو انتهت الحلقة ولم نجد عقبة، إذاً فنحن على سطح مبنى بلا عقبات. هيا نسير إلى نهاية السطح (نغير س ونطبعها)، ثم ننتقل إلى حالة عند.هضبة

لو كنت لاتزال تجد صعوبة، انظر إلى الصور مرة أخرى.

الآن نصل إلى عند.هضبة:
إجراء عند.هضبة(س، ص، مبنى، المباني) :

 أعلى.عقبة = لاشيء 
    لكل أ من 1 إلى عدد(المباني) :
        م = المباني[أ] 
        إذا م <> مبنى وأيضا يمين(م) > س وأيضا يسار(م) < س :
             -- لقد وجدنا مبنى يقطع مبنانا
             -- وهو بالتأكيد ليس أعلى منه لأنه لو كان أعلى
             -- لكنا عليه بدلا من المبنى الحالي
 
             -- لو كنا مازلنا في بداية البحث، نعطي
             -- قيمة مبدئية للمتغير أعلى.عقبة
            إذا أعلى.عقبة = لاشيء :
                أعلى.عقبة = م 
            تم 
             
             -- سوف نختار أعلى مبنى فيهم لنهبط إليه
            إذا سطح(م) > سطح(أعلى.عقبة):
                أعلى.عقبة = م
            تم 
        تم 
    تابع 
    إذا أعلى.عقبة = لاشيء :
        ص = 0 
        اطبع ص 
        وكل إلى على.الأرض(س، ص، المباني) 
    وإلا :
        ص = سطح(أعلى.عقبة) 
        اطبع ص 
        وكل إلى عند.سطح(س، ص، أعلى.عقبة، المباني) 
    تم 
نهاية

مرة أخرى: الأمر أبسط مما يبدو عليه. الدالة جزءان:

الجزء الأول بين "لكل...تابع" يبحث عن أعلى مبنى يحتوي النقطة س التي تقف فيها النملة (يمينه أكبر من س ويساره اقل منها). لأن هذا الشرط ينطبق على المبني الحالي الذي تقف النملة عليه؛ لابد أن نضع جزءاً من الشرط يتأكد أن م هو مبنى مختلف عن المبنى الحالي.

الجزء الأول بين "لكل...تابع" له دور آخر: من بين كل المباني التي تحتوي س، فإنه يختار أعلاها. هذا يتم عن طريق ثلاثة أجزاء:
  • خارج الـloop، يجعل قيمة المتغير أعلى.عقبة تساوي لاشيء.
  • داخل الـloop، يعطي هذا المتغير قيمة أولية هي أول مبنى يصلح يجده، وذلك عن طريق الشرط إذا أعلى.عقبة = لاشيء :... هذا الجزء يتم تنفيذه مرة واحدة فقط.
  • داخل الـloop أيضاً، يتم كل مرة مقارنة م بأعلى عقبة، لحساب قيمة max في المتغير أعلى.عقبة بالطريقة المعروفة.
الجزء الثاني: لو كانت قيمة المتغير أعلى.عقبة تساوي لاشيء، فمعنى هذا أنه لا توجد عقبات، فتنزل النملة على الأرض (تغير ص وتغير حالتها).

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

أخيراً هذا هو الإجراء حدث.بداية.البحث
إجراء حدث.بداية.البحث (س، المباني) :
    
    لكل أ من بداية.البحث إلى عدد( المباني ) :
        إذا يسار (المباني [أ]) > س :
            بداية.البحث = أ 
            اذهب إلى النهاية 
        تم 
    تابع 
    علامة النهاية 
نهاية

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

هل يعمل البرنامج؟ القيم المطبوعة هي نفس النتيجة المكتوبة في موقع المسألة (ولكني لم أختبره على مدخلات أخرى)
يمكنك أن تختبره أنت إن أردت! الكود كاملةً تجدها هنا:
-- هذا البرنامج حل لمسألة Skyline problem من مسائل الـACM
 -- تجد المسألة هنا: http://uva.onlinejudge.org/external/1/105.html
 
-- اختبارات()
 حل.المسألة ( ) 

إجراء حل.المسألة ( ) :
    صف.المباني = [ ] 
    ملف.المدخلات = افتح.ملف ( "input.txt" ) 
    كرر مادام ليس ملف.المدخلات : منته ( ) :
        س = ملف.المدخلات : اقرأ.سطر ( ) 
        الثلاثية = تفصيص ( س ، " " ) 
        الثلاثية [ 1 ] = كعدد ( الثلاثية [ 1 ] ) 
        الثلاثية [ 2 ] = كعدد ( الثلاثية [ 2 ] ) 
        الثلاثية [ 3 ] = كعدد ( الثلاثية [ 3 ] ) 
        صف.المباني = صف.المباني + [ الثلاثية ] 
    تابع 
    اغلق.ملف ( ملف.المدخلات ) 
    إجابة ( صف.المباني ) 
نهاية 


بداية.البحث مشترك 

إجراء إجابة ( صف.المباني ) :
    بداية.البحث = 1 
    على.الأرض ( 0 ، 0 ، صف.المباني ) 
نهاية 


بداية.البحث مشترك 
إجراء على.الأرض ( س ، ص ، المباني ) :

 لكل أ من بداية.البحث إلى عدد ( المباني ) :
        إذا يسار ( المباني [ أ ] ) > س :
            س = يسار ( المباني [ أ ] ) 
            اطبع س 
            حدث.بداية.البحث ( س ، المباني ) 
            وكل إلى عند.سفح ( س ، ص ، المباني [ أ ] ، المباني ) 
        تم 
    تابع 
         -- إن وصل التنفيذ هنا فمعنى ذلك
         -- أن النملة على الأرض ولا يوجد
         -- مبان على يمينها: أي أن الرحلة
         -- قد انتهت
 
نهاية 
إجراء عند.سفح ( س ، ص ، مبنى ، المباني ) :

 ص = سطح ( مبنى ) 
    اطبع ص 
    وكل إلى عند.سطح ( س ، ص ، مبنى ، المباني ) 
نهاية 

إجراء عند.سطح ( س ، ص ، مبنى ، المباني ) :
      --  اطبع "سطح"
 لكل أ من بداية.البحث إلى عدد ( المباني ) :
        م = المباني [ أ ] 
         -- لو وجدنا مبنى خارجاً تماماً عن حدود المبنى الحالي،
         -- فكل المباني التي تليه هي الأخرى خارجة عن الحدود
         -- لاحظ أن الأمر التالي غير مختوم بكلمة "تم" بل
         -- الأمر كله سطر واحد
         إذا يسار ( م ) > يمين ( مبنى ) : اذهب إلى نهاية.البحث 
        
         -- هل م أعلى من المبنى الذي تقف عليه النملة؟
         إذا سطح ( م ) > سطح ( مبنى ) :
             -- في هذه الحالة فهو عقبة. هيا نتحرك إلى سفحه
             س = يسار ( م ) 
             اطبع س 
             حدث.بداية.البحث ( س ، المباني ) 
            وكل إلى عند.سفح ( س ، ص ، م ، المباني )        
         تم 
    تابع 
    علامة نهاية.البحث 
     -- إن وصلنا هنا ولم نجد شيئاً، نسير إلى نهاية المبنى الحالي
      س = يمين ( مبنى ) 
    اطبع س 
    حدث.بداية.البحث ( س ، المباني ) 
    وكل إلى عند.هضبة ( س ، ص ، مبنى ، المباني ) 
نهاية 

إجراء عند.هضبة ( س ، ص ، مبنى ، المباني ) :

    أعلى.عقبة = لاشيء 
    لكل أ من 1 إلى عدد ( المباني ) :
        م = المباني [ أ ] 
        إذا م <> مبنى وأيضا يمين ( م ) > س وأيضا يسار ( م ) < س :
             -- لقد وجدنا مبنى مختلف يقطع مبنانا
             -- وهو بالتأكيد ليس أعلى منه لأنه لو كان أعلى
             -- لكنا عليه بدلا من المبنى الحالي
 
             -- لو كنا مازلنا في بداية البحث، نعطي
             -- قيمة مبدئية للمتغير أعلى.عقبة
             إذا أعلى.عقبة = لاشيء :
                أعلى.عقبة = م 
            تم 
            عقبة2 = م 
             -- سوف نختار أعلى مبنى فيهم لنهبط إليه
            إذا سطح ( عقبة2 ) > سطح ( أعلى.عقبة ) :
                أعلى.عقبة = عقبة2 
            تم 
        تم 
    تابع 
    إذا أعلى.عقبة = لاشيء :
        ص = 0 
        اطبع ص 
        وكل إلى على.الأرض ( س ، ص ، المباني ) 
    وإلا :
        ص = سطح ( أعلى.عقبة ) 
        اطبع ص 
        وكل إلى عند.سطح ( س ، ص ، أعلى.عقبة ، المباني ) 
    تم 
نهاية 

إجراء حدث.بداية.البحث ( س ، المباني ) :
    
    لكل أ من بداية.البحث إلى عدد ( المباني ) :
        إذا يسار ( المباني [ أ ] ) > س :
            بداية.البحث = أ 
            اذهب إلى النهاية 
        تم 
    تابع 
    علامة النهاية 
نهاية 


دالة يسار ( مبنى ) :
    ارجع ب: مبنى [ 1 ] 
نهاية 

دالة سطح ( مبنى ) :
    ارجع ب: مبنى [ 2 ] 
نهاية 

دالة يمين ( مبنى ) :
    ارجع ب: مبنى [ 3 ] 
نهاية

الخميس، 15 نوفمبر 2012

Solving an ACM problem with Kalimat

هذه هي المسألة التي يتحدث عنها المقال، اقرأها جيداً ثم تعال وأكمل القراءة :)

شكل مبدئي للحل
 
البرنامج يقرأ ملفاً به عدد من السطور، كل سطر هو مجموعة من الأرقام تفصل بينها مسافات، وكل ثلاثة أرقام متتالية تعبر عن مبنى في المدن التي تتعامل معها المسألة. إذاً فلنبدأ بقراءة ملف المدخلات وتقسيم كل سطر إلى ثلاثيات
إجراء حل.المسألة() :
    صف.المباني = [ ] 
    ملف.المدخلات = افتح.ملف("input.txt") 
    كرر مادام ليس ملف.المدخلات : منته() :
        س = ملف.المدخلات : اقرأ.سطر() 
        الثلاثية = تفصيص(س ، " ") 
        صف.المباني = صف.المباني + [الثلاثية] 
    تابع 
    اغلق.ملف(ملف.المدخلات) 
    اطبع إجابة(صف.المباني) 
نهاية

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

دالة إجابة(صف.المباني) :
    -- مؤقتا  سوف نرجع بالمدخلات كما هي
    -- للتأكد أن الخطة العامة صحيحة
    ارجع ب: صف.المباني 
نهاية

عظيم! يمكننا الآن تشغيل البرنامج (بعد التأكد من وجود ملف input.txt به مثال للمدخلات الصحيحة كما في صفحة المسألة).

في الواقع حين جربت تشغيل البرنامج وجدت خطأ...ليس في البرنامج بل في كلمات نفسها! وجدت المفسر يقول لي "استدعاء الإجراء أو الدالة بعدد غير مناسب من القيم المرسلة" وقصده على التعبير ملف.المدخلات: منته( )  ، يبدو أنه كان يتوقع عدداً أكبر من العوامل.

هل ينفع هذا؟ يبدو أن كلام المشككين صحيح: العرب لا يستطيعون عمل لغات برمجة. لماذا لا يرسل أحدكم رسالة بريدية غاضبة لمصمم اللغة يؤنبه على إهماله؟ :(

القصد، قمت بتصحيح الخطأ وإعادة تنفيذ البرنامج:




حسناً! نحن نقرأ المدخلات بصورة صحيحة، ويبقى أن نحل المسألة. هل سنقدر على هذا؟ وسؤال آخر: هل سيعمل الحل بسرعة؟ لاحظ أن كلمات أبطأ بكثير من لغة مثل سي++. هذه لغة مصممة للتعليم والرسم بالكمبيوتر والألعاب، وليس لـ"طحن الأرقام". أنا لم أحل المسألة بعد، لذلك فأنا لا أعرف فعلاً إجابة السؤال. سأبدأ الآن في حلها..

اضغط هنا لتقرأ الجزء الثاني

الخميس، 7 يوليو 2011

قوائم متسلسلة في كلمات

تريد أن تحل مسألة ACM بكلمات؟ سوف تحتاج بضعة أشياء، منها القوائم المتسلسلة Linked lists. هيا نبدأ في عملها - أول ما نحتاجه هو فصيلة class يعبر عن حلقة في السلسلة. ما يكافيء node في لغات البرمجة الأجنبية :)
فصيلة حلقة :
له بيان
له سابق، تالي
نهاية
هنا لا نعرف methods في فصيلتنا، فقط نعرف مجالات للبيانات data fields.
بالمرة هيا نعرف فصيلة القائمة:
فصيلة قائمة :
له أول ، آخر
نهاية
قبل أن نتابع علينا أن نعرف بعض المعلومات عن الكائنات في لغة كلمات:
  • لإنشاء كائن object من فصيلة معينة نكتب اسم الفصيلة يليه كلمة جديد مثل م = موظف جديد
  • للوصول إلى field نكتب اسمه قبل الكائن، أي أن س مركز النقطة في كلمات يكافيء point.center.x في اللغات التقليدية
  • الثابت لاشيء يعادل null في اللغات المماثلة للJava أو #C.
الآن نريد أن نضيف إمكانيات الإضافة والمسح من القائمة. هنا ستكون الفصيلة قائمة أكثر من مجرد حاوٍ للبيانات، سوف نضيف إليها methods. قبل أن نضيفها يجب أولاً أن نعرف كيف نستدعيها!

تخيل أن فصيلة القائمة لديها method اسمها اضف(...)، سوف نستدعيها هكذا:

ق : اضف(12)

هذا يكافيء lst.add(12) //add to list في اللغات الأخرى. وهو في كلمات اسمه إرسال رسالة إلى الكائن، والكود التي ستتفذ ستكون استجابة لهذه الرسالة. مثل ال++C لابد أن نعرف الاستجابة على جزئين: نعلن عنها داخل الفصيلة ونكتب تفاصيلها خارج الفصيلة.

أولاً الإعلان:
فصيلة قائمة :
له أول ، آخر
-- هنا أعلننا عن الاستجابة
يستجيب ل: اضف ( عنصر )
نهاية
ثانياً التفصيل:
استجابة قائمة ق ل: اضف ( عنصر ) :
-- سوف نكتب التنفيذ هنا
نهاية
هنا نعرف method جديدة عن طريق كلمة استجابة. لابد أن نعطي اسماً للكائن متلقي الرسالة هو هنا ق (في اللغات التقليدية يكون الاسم اوتوماتيكيا this ، لكن هنا لابد من إعطاء اسم).

لاحظ كيف أن الكلام يبدو طبيعياً جداً... استجابة قائمة ق لـ"أضف عنصر" هو افعل كذا كذا..نهاية. هذا لأن كلمات لغة برمجة تستمد جمالها من جمال اللغة العربية™

الآن يمككنا أن نكتب الكود أخيراً:
استجابة قائمة ق ل: اضف ( العنصر ) :
ح = حلقة جديد
بيان ح = العنصر

إذا أول ق = لاشيء :
أول ق = ح
        آخر ق = ح
    وإلا :
تالي آخر ق = ح
        سابق ح = آخر ق
        آخر ق = ح
    تم
نهاية
أنظروا! استطيع كتابة كود الإضافة إلى قائمة!! سنترك كود المسح كتمرين للطالب :)

كلمة استجابة في كلمات، مثل كلمة إجراء، تعبر عن void function, void method. ماذا لو أردنا عمل شيء يعود بقيمة؟ هنا بدلاً من استخدام كلمة استجابة سوف نستخدم كلمة رد.
فصيلة قائمة :
له أول ، آخر
يستجيب ل: اضف ( عنصر )
-- هنا عرفنا رداً جديداً
يرد على عددهم ( )
نهاية
ثم نكتب تنفيذ الرد:
رد قائمة ق على عددهم ( ) :
إذا أول ق = لاشيء :
ارجع ب: 0
    وإلا :
النتيجة = 0
       أ = أول ق
طالما ليس أ = لاشيء :
النتيجة = النتيجة + 1
           أ = تالي أ
       تابع
ارجع ب: النتيجة
    تم
نهاية
الآن..نستطيع اختبار البرنامج:
الوجبات = قائمة جديد
الوجبات : اضف ( "شاورمة" )
الوجبات : اضف ( "مجبوس" )
الوجبات : اضف ( "فول وطعمية" )

اطبع الوجبات : عددهم ( )
قد يهم محبي النحو سبب تسميتنا للرد بإسم عددهم ؛ هذا يجعل التعبير كله يأخذ صورة بدل الجزء من الكل/بدل الاشتمال في اللغة العربية. هل تذكر في المدرسة "أعجبني الخروف صوفه"؟ نحن هنا نقول "اطبع الوجبات عددهم" :)

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

فصيلة قائمة :
له أول ، آخر
يستجيب ل: اضف ( عنصر )
يرد على عددهم ( )
نهاية

رد قائمة ق على عددهم ( ) :
إذا أول ق = لاشيء :
ارجع ب: 0
وإلا :
النتيجة = 0
أ = أول ق
طالما ليس أ = لاشيء :
النتيجة = النتيجة + 1
أ = تالي أ
تابع
ارجع ب: النتيجة
تم
نهاية

استجابة قائمة ق ل: اضف ( العنصر ) :
ح = حلقة جديد
بيان ح = العنصر

إذا أول ق = لاشيء :
أول ق = ح
آخر ق = ح
وإلا :
تالي آخر ق = ح
سابق ح = آخر ق
آخر ق = ح
تم
نهاية

الوجبات = قائمة جديد
الوجبات : اضف ( "شاورمة" )
الوجبات : اضف ( "مجبوس" )
الوجبات : اضف ( "فول وطعمية" )

اطبع الوجبات : عددهم ( )

الخميس، 23 يونيو 2011

الكرات المتحركة - مثال بكلمات

نريد أن نبدأ بتحريك كرة واحدة، ثم بعد ذلك نتصرف في باقي الكرات.
إجراء حرك.الكرة ( طيف ، س ، ص ، السرعة ) :
-- سوف نكتب كود هنا
نهاية
(اعتبر الإجراء مثل void function).
هذا الإجراء يأخذ اربع عوامل:
  • طيف هو الطيف الذي يعبر عن رسمة الكرة؛ الطيف شكل متحرك يرسم على الشاشة (sprite) وسنعرف الآن كيف نتعامل معه
  • س، ص هو المكان الذي نريد أن يبدأ فيه رسم الكرة
  • السرعة هو مصفوفة (array) من عنصرين يعبر عن متجه vector فيه سرعة الكرة في الاتجاهين السيني والصادي
نبدأ في تفاصيل الإجراء:
إجراء حرك.الكرة ( طيف ، س ، ص ، السرعة ) :
    كرر :
        ارسم.طيف طيف في ( س ، ص )
        س = س + السرعة [ 1 ]
        ص = ص + السرعة [ 2 ]
        انتظر(15)
    تابع
نهاية
الأمر هنا بسيط: الأمر ارسم.طيف/في يأخذ طيفاً ويرسمة في نقطة محددة هي هنا نقطة البداية، ثم نغير هذه النقطة كل مرة حسب متجه السرعة، بأن نضيف السرعة في الاتجاه السيني إلى س ونفس الشيء في ص. لاحظ أن المصفوفات في كلمات تبدأ من الواحد.

نكرر هذه العملية باستمرار مع انتظار بسيط - 15 ميلليثانية - بين رسمة والأخرى

المشكلة هنا هي أن الكرة ستخرج من الشاشة بعد قليل وتختفي، نحن نريدها أن "تصطدم" بالشاشة ومن ثم تغير اتجاهها. خطتنا بسيطة:
  • إذا اصطدمت الكرة بقمة الشاشة أو قاعها، نقلب إشارة السرعة في الاتجاه الصادي فنحيلها من موجبة لسالبة أو بالعكس، وبذلك تنزل الكرة مثلاً إن كانت تصعد.
  • إذا اصطدمت بيمين الشاشة أو يسارها نغير إشارة المكون السيني للسرعة
  • في الحالتين إن كانت الكرة تكاد أن تُرسم خارج الشاشة نعيد مكانها لحافة الشاشة
كيف ينعكس ذلك على الكود؟ تعال نرى مثلاً ماذا يحدث لو تجاوزت الكرة الحافة اليمنى للشاشة.
إذا س < 0 :
    س = 0
    السرعة [ 1 ] = - السرعة [ 1 ]
تم
الحافة اليمنى هي النقطة س=صفر، فأن كانت س أقل من الصفر فمعنى هذا أن الكرة توشك أن تخرج، فنعيدها مرة أخرى إلى الحافة ونعكس إشارة المكون الأول للسرعة. لاحظ أن نظام الإحداثيات يساير الكتابة العربية (س تزداد من اليمين لليسار).

ماذا يحدث لو تجاوزت الكرة يسار الشاشة؟ هذا:
إذا س + عرض.الطيف ( طيف ) > 799 :
    س = 799 - عرض.الطيف ( طيف )
    السرعة [ 1 ] = - السرعة [ 1 ]
تم
شاشة الرسم في كلمات عرضها 800 نقطة وارتفاعها 600، لذلك النقطة في أقصى اليسار هي النقطة 799 لأن العد يبدأ من الصفر.

إذا تجاوزت حافة الكرة اليسرى (المحددة بقيمة س + عرض الكرة) هذا الحد نعيد الحافة اليسرى للنقطة الأخيرة. لاحظ أن التخصيص
س = 799 - عرض.الطيف ( طيف )
معناه اجعل س + عرض الطيف = 799، أي اجعل الحافة اليسرى بهذه القيمة.
سنفعل نفس الشيء مع قيم ص ليكون الإجراء الكامل كالآتي:
إجراء حرك.الكرة ( طيف ، س ، ص ، السرعة ) :
    كرر :
        ارسم.طيف طيف في ( س ، ص )
        س = س + السرعة [ 1 ]
        ص = ص + السرعة [ 2 ]
        إذا س < 0 :
            س = 0
            السرعة [ 1 ] = - السرعة [ 1 ]
        وإلا إذا س + عرض.الطيف ( طيف ) > 799 :
        س = 799 - عرض.الطيف ( طيف )
            السرعة [ 1 ] = - السرعة [ 1 ]
        تم
        إذا ص < 0 :
            ص = 0
            السرعة [ 2 ] = - السرعة [ 2 ]
        وإلا إذا ص + ارتفاع.الطيف ( طيف ) > 599 :
            ص = 599 - ارتفاع.الطيف ( طيف )
            السرعة [ 2 ] = - السرعة [ 2 ]
        تم
        انتظر ( 15 )
    تابع
نهاية
عظيم!! لقد أنهينا الإجراء. لم يتبق سوى استدعاؤه:
ط = حمل.طيف ( "ball1.bmp" )
س = عشوائي ( 800 )
ص = عشوائي ( 600 )
السرعة.س = عشوائي ( 20 ) + 1
السرعة.ص = عشوائي ( 20 ) + 1
السرعة = [ السرعة.س ، السرعة.ص ]
حرك.الكرة ( ط ، س ، ص ، السرعة )
هنا قمنا باستخدام الدالة الجاهزة حمل.طيف لنقوم بتحميل صورة للكرة وتكوين طيف منها، ثم ولدنا بعض القيمة العشوائية لمكان الكرة وسرعتها وقمنا بتمريرهم إلى الإجراء. لاحظ كيف يمكن عمل مصفوفة من عنصرين بالتعبير [ أ، ب] كما فعلنا للمتغير السرعة

يمكننا الآن أن ننفذ البرنامج:
للأسف لقطة الشاشة لا تبين روعة الكرة وهي تجري..كان ينبغي أن أضع فيديو لهذا المثال على اليوتيوب :]

الآن نريد أن نجعل البرنامج يتحرك فيه كرات كثيرة...هذا في الواقع أسهل مما نتخيل بسبب إمكانات البرمجة المتوازية في لغة كلمات. الأمر شغل يأخذ الصيغة التالية:
شغل <استدعاء إجراء>
ومعناه "استدع هذا الإجراء بحيث يعمل على التوازي مع باقي البرنامج وأي إجراءات أخرى". كل ما علينا الآن هو تشغيل عدة نسخ من الإجراء على التوازي ليكون لدينا كرات كثيرة!

(ملاحظة: تنفيذ الإجراءات في الواقع لن يكون متوازياً، بل ستقوم الآلة الافتراضية virtual machine الخاصة بكلمات بالانتقال بسرعة بين إجراء والآخر ليبدو الأمر كأنه على التوازي. في المستقبل ننوي تغيير الآلة الافتراضية لتحقيق توازٍ حقيقي).

سيبدو تنفيذ الإجراء الآن كالآتي، لنحرك عشر كرات عشوائياً:
لكل أ من 1 إلى 10 :
    ط = حمل.طيف ( "ball1.bmp" )
    س = عشوائي ( 800 )
    ص = عشوائي ( 600 )
    السرعة.س = عشوائي ( 20 ) + 1
    السرعة.ص = عشوائي ( 20 ) + 1
    السرعة = [ السرعة.س ، السرعة.ص ]
    شغل حرك.الكرة ( ط ، س ، ص ، السرعة ) 
تابع

جميل، لكن الكرات كلها من نفس اللون، نريد أن يكون الأمر متنوعاً قليلاً..نرى الآن البرنامج في صورته النهائية:
ملفات.الأطياف=["ball4.bmp"،"ball3.bmp"،"ball2.bmp"،"ball1.bmp"]

لكل أ من 1 إلى 10 :
    ط = حمل.طيف ( ملفات.الأطياف [ عشوائي ( 4 ) + 1 ] )
    س = عشوائي ( 800 )
    ص = عشوائي ( 600 )
    السرعة.س = عشوائي ( 20 ) + 1
    السرعة.ص = عشوائي ( 20 ) + 1
    السرعة = [ السرعة.س ، السرعة.ص ]
    شغل حرك.الكرة ( ط ، س ، ص ، السرعة )
تابع

إجراء حرك.الكرة ( طيف ، س ، ص ، السرعة ) :
    كرر :
    ارسم.طيف طيف في ( س ، ص )
        س = س + السرعة [ 1 ]
        ص = ص + السرعة [ 2 ]
        إذا س < 0 :
            س = 0
            السرعة [ 1 ] = - السرعة [ 1 ]
        وإلا إذا س + عرض.الطيف ( طيف ) > 799 :
        س = 799 - عرض.الطيف ( طيف )
            السرعة [ 1 ] = - السرعة [ 1 ]
        تم
        إذا ص < 0 :
            ص = 0
            السرعة [ 2 ] = - السرعة [ 2 ]
        وإلا إذا ص + ارتفاع.الطيف ( طيف ) > 599 :
            ص = 599 - ارتفاع.الطيف ( طيف )
            السرعة [ 2 ] = - السرعة [ 2 ]
        تم
        انتظر ( 15 )
    تابع
نهاية
لتحميل المثال كاملاً:
اضغط هذا الرابط

لتحميل لغة كلمات:
http://code.google.com/p/kalimat/downloads
أو من الموقع الرسمي http://www.kalimat-lang.com

الجمعة، 17 يونيو 2011

آلة حاسبة بلغة كلمات


لا تحتاج خبرة سابقة بالبرمجة بلغة كلمات لتتابع هذا المثال (لكن تحتاج لأن تعرف البرمجة بشكل عام). إذا أردت أن تجربه عملياً فيمكنك تحميل أحدث نسخة من اللغة من http://code.google.com/p/kalimat/downloads


هام: هذا المثال يحتاج نسخة يونيو 2011 أو أحدث ليعمل. لو كنت قد حملت نسخة قديمة من كلمات فستحتاج أن تحمل نسخة جديدة.

الآن هيا نبدأ!

رسم الأزرار على الشاشة
زر1 = زر جديد
زر1 : حدد.المكان ( 10 ، 10 )
زر1 : حدد.الحجم ( 30 ، 30 )
زر1 : حدد.النص ( "1" )
ن = النافذة.الحالية ( )
ن : اضف ( زر1 )

ستؤدي الكود السابقة لعرض زر على الشاشة. لاحظ ان علامة النقطتين ' : ' معناها method call، مثل علامة النقطة في لغات البرمجة المعروفة. الأمر جديد هنا هو نفسه operator new في اللغات الأخرى.

نحن سنرسم أزراراً كثيرة في البرنامج، لذلك هيا نكتب دالة (function) توفر علينا عناء الكتابة المتكررة:
دالة صنع.زر(س ، ص ، العرض ، الارتفاع ، النص ، النافذة):
   ز = زر جديد
   ز : حدد.المكان ( س ، ص )
   ز : حدد.الحجم ( العرض ، الارتفاع )
   ز : حدد.النص ( النص )
   النافذة : اضف ( ز )
   ارجع ب: ز
نهاية
لاحظ هنا أن العوامل (parameters) لا تأخذ تعريف للأنواع، بل فقط تأخذ اسماً، وذلك لأن اللغة dynamically typed مثل لغات البايثون مثلاً أو الجافاسكريبت. أيضاً لاحظ أن النقطة هي جزء من اسم الدالة، أي ان اسمها صنع.زر هكذا كلمة واحدة، النقطة في كلمات هي جزء من رموز المعرفات (identifier characters) ولا دور لها في معنى البرنامج.

الآن وقد صار لدينا دالة تسهل وضع الأزرار يمكننا أن نرسم شكل الآلة الحاسبة، نبدأ أولاً بالأزرار ثم نضيف لاحقاً 'شاشة العرض' التي ستظهر عليها النتيجة:
ن = النافذة.الحالية ( )
زر1 = صنع.زر (40 ، 130 ، 30 ، 30 ، "1" ، ن)
زر2 = صنع.زر (70 ، 130 ، 30 ، 30 ، "2" ، ن)
زر3 = صنع.زر (100، 130 ، 30 ، 30 ، "3" ، ن)
زر4 = صنع.زر (40 ، 100 ، 30 ، 30 ، "4" ، ن)
زر5 = صنع.زر (70 ، 100 ، 30 ، 30 ، "5" ، ن)
زر6 = صنع.زر (100، 100 ، 30 ، 30 ، "6" ، ن)
زر7 = صنع.زر (40 ، 70  ، 30 ، 30 ، "7" ، ن)
زر8 = صنع.زر (70 ، 70  ، 30 ، 30 ، "8" ، ن)
زر9 = صنع.زر (100 ، 70 ، 30 ، 30 ، "9" ، ن)
زر0 = صنع.زر (70 ، 160 ، 30 ، 30 ، "0" ، ن)

زر.الجمع = صنع.زر (10 ، 70  ، 30 ، 30 ، "+" ، ن)
زر.الطرح = صنع.زر (10 ، 100 ، 30 ، 30 ، "-" ، ن)
زر.الضرب = صنع.زر (10 ، 130 ، 30 ، 30 ، "×" ، ن)
زر.القسمة = صنع.زر(10 ، 160 ، 30 ، 30 ، "÷" ، ن )
زر.يساوي = صنع.زر (40 ، 160 ، 30 ، 30 ، "=" ، ن)
زر.الفاصلة.العشرية = صنع.زر(100، 160، 30، 30، "."، ن)

والآن حان دور شاشة العرض:
الشاشة = سطر.نصي جديد
الشاشة : حدد.المكان ( 10 ، 30 )
الشاشة : حدد.الحجم ( 120 ، 30 )
الشاشة : حدد.النص ( "" )
ن : اضف ( الشاشة )

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

الآن يمكننا تجربة البرنامج:

شكله ظريف، لكن هناك مشكلتان: النافذة حجمها الافتراضي أكبر من اللازم، كما أن عنوانها "تنفيذ البرنامج" عام أكثر مما ينبغي...من السهل تغيير ذلك على أية حال. أضف هذه السطور لآخر البرنامج:

ن : حدد.الحجم ( 150 ، 210 )
ن : حدد.العنوان ( "الحاسبة" )


هذا أفضل بكثير!

ادخال المستخدم للأرقام

الآن بعد عمل "شكل" البرنامج حان وقت اضافة "المضمون". أول شيء نريده هو عرض الأرقام حين يدخلها المستخدم. الأمر نلخصه في القواعد الآتية:
1- إذا ضغط المستخدم زر رقمي [0..9]، اضف الرقم المناظر للزر إلى شاشة العرض
2- إذا ضغط المستخدم زر الفاصلة العشرية اضفه لشاشة العرض إذا كانت هذه أول مرة يضغط فيها، وإلا لا تفعل شيئاً.

قبل أن نبدأ بتنفيذ كل ذلك علينا أن نأخذ جانباً ونعرف كيف نتعامل مع الحوادث (events) مثل ضغط زر او اختيار شيء من قائمة أو ما شابه. يحدث ذلك في كلمات عن طريق مفهوم القنوات.

اضف هذا الجزء لنهاية البرنامج:
كرر :
   تسلم إشارة من ضغط  زر1
   الشاشة : الحق.نص ( "1" )
تابع
هناك قناة في فصيلة الأزرار اسمها ضغط. القناة هي كائن يمكن عن طريقه لجزء من البرنامج (مثل الزر) أن يرسل إشارة لجزء آخر (مثل السطور السابقة) عندما يريد ابلاغه بحدث معين.

الأمر
تسلم إشارة من ضغط زر1
سوف يؤدي لأن ينتظر البرنامج حتى يرسل الزر الإشارة المطلوبة قبل المتابعة. سوف تأتي تلك الإشارة حين يضغط المستخدم زر ادخال الواحد1، في تلك الحالة سيضيف البرنامج الرمز "1" لشاشة العرض ويعود إلى حالة الانتظار في حلقة لا نهائية.

لو قمت بتجربة البرنامج الآن ستجد أنه يضيف "1" إلى الشاشة كلما ضغطت الزر المطلوب.

(دور القنوات أكثر بكثير مما قيل هنا، لكن لن نتوسع في الحديث عنها في هذا المثال المبدئي)

كيف نجعله يضيف باقي الأرقام؟ المشكلة أن هذه الكود لن تنفع:
كرر :
   تسلم إشارة من ضغط زر1
   الشاشة : الحق.نص ( "1" )

   تسلم إشارة من ضغط زر2
   الشاشة : الحق.نص ( "2" )
تابع
لماذا لن تنفع؟ لأننا هنا نأمره أن يتسلم الإشارات بالترتيب، أي أننا نقول للبرنامج "تسلم اشارة من ضغط زر1، ثم تسلم إشارة من زر2". هذا سيعمل فقط لو أراد المستخدم أن يدخل شيئاً مثل "12" أو "1212"، "121212"...الخ :(

ما الحل إذاً؟ لابد من طريقة نقول له أنه هناك أكثر من قناة وعليك استلام اشارة من أي واحدة منها حين تأتي. هذا هو دور الأمر تخير
كرر :
    تخير :
    تسلم إشارة من ضغط زر1 :
       الشاشة : الحق.نص ( "1" )
    أو تسلم إشارة من ضغط زر2 :
       الشاشة : الحق.نص ( "2" )
    تم
تابع
هنا سوف ينتظر البرنامج أي من الإشارتين، ضغط زر1 أو ضغط زر2، فإن جاءت الإشارة سينفذ جزء الكود المرتبط بها (أي يضيف 1 أو 2 إلى الشاشة حسب الإشارة القادمة) ثم يتابع في حلقة لا تنتهي.

من السهل تعميم ذلك لندخل الأرقام كلها:
كرر :
    تخير :
    تسلم إشارة من ضغط زر1 :
       الشاشة : الحق.نص ( "1" )
    أو تسلم إشارة من ضغط زر2 :
       الشاشة : الحق.نص ( "2" )
    أو تسلم إشارة من ضغط زر3 :
       الشاشة : الحق.نص ( "3" )
    أو تسلم إشارة من ضغط زر4 :
       الشاشة : الحق.نص ( "4" )
    أو تسلم إشارة من ضغط زر5 :
       الشاشة : الحق.نص ( "5" )
    أو تسلم إشارة من ضغط زر6 :
       الشاشة : الحق.نص ( "6" )
    أو تسلم إشارة من ضغط زر7 :
       الشاشة : الحق.نص ( "7" )
    أو تسلم إشارة من ضغط زر8 :
       الشاشة : الحق.نص ( "8" )
    أو تسلم إشارة من ضغط زر9 :
       الشاشة : الحق.نص ( "9" )
    أو تسلم إشارة من ضغط زر0 :
       الشاشة : الحق.نص ( "0" )
    تم
تابع
الكود طويلة لكن كلها تشبه بعضها؛ لا تعقيد كبير فيها. هناك طرق لجعلها أكثر تنظيماً لكن صورتها الحالية بسيطة، ونحن نريده مثالاً بسيطاً في الوقت الحالي...

الفاصلة العشرية

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

كرر :
    تخير :
    تسلم إشارة من ضغط زر.الفاصلة.العشرية :
        إذا كتبنا.فاصلة.عشرية = خطأ :
            الشاشة : الحق.نص ( "." )
            كتبنا.فاصلة.عشرية = صحيح
        تم
    أو تسلم إشارة من ضغط زر1 :
        الشاشة : الحق.نص ( "1" )
    أو تسلم إشارة من ضغط زر2 :
        الشاشة : الحق.نص ( "2" )
    ........وهكذا وهكذا الخ الخ
    تم
تابع

العمليات

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

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

ستأخذ الكود الصورة التالية:
كتبنا.فاصلة.عشرية = خطأ
العملية = ""
العدد.الأول = لاشيء
 
كرر :
    تخير :
    تسلم إشارة من ضغط زر.الجمع :
        العملية = "+"
        العدد.الأول = الشاشة : نصه ( )
        العدد.الأول = كعدد ( العدد.الأول )
        كتبنا.فاصلة.عشرية = خطأ
        الشاشة : حدد.النص ( "" )

    أو تسلم إشارة من ضغط زر.يساوي :
        العدد.الثاني = الشاشة : نصه ( )
        العدد.الثاني = كعدد ( العدد.الثاني )
        إذا العملية = "+" :
            النتيجة = العدد.الأول + العدد.الثاني
            النتيجة = كنص ( النتيجة )
            الشاشة : حدد.النص ( النتيجة )
            العملية = ""
        تم
    أو تسلم إشارة من ضغط زر.الفاصلة.العشرية :
        ....وهكذا الكلام السابق الذي نعرفه....
    تم
تابع
تعال نأخذ كل جزء على حدىً.. أولاً زر الجمع نفسه:
...
تسلم إشارة من ضغط زر.الجمع :
    العملية = "+"
    العدد.الأول = الشاشة : نصه ( )
    العدد.الأول = كعدد ( العدد.الأول )
    كتبنا.فاصلة.عشرية = خطأ
    الشاشة : حدد.النص ( "" )
...
هذا الجزء يقوم بالآتي عند ضغط زر الجمع:
1- يحدد العملية بأنها "+"
2- يأخذ النص المكتوب على الشاشة، يحوله لعدد، ويخزنه في المتغير العدد.الأول لأننا سنحتاجه بعد ذلك لإجراء عملية الجمع
3- يفرغ الشاشة استعداداً لإدخال رقم جديد
4- يسمح لنا بإدخال فاصلة عشرية (إن أردنا) أثناء إدخال الرقم الثاني وذلك بأن يجعل قيمة كتبنا.فاصلة.عشرية تساوي خطأ

الجزء الآخر عند ضغط زر 'يساوي'
....
أو تسلم إشارة من ضغط زر.يساوي :
    العدد.الثاني = الشاشة : نصه ( )
    العدد.الثاني = كعدد ( العدد.الثاني )
    إذا العملية = "+" :
        النتيجة = العدد.الأول + العدد.الثاني
        النتيجة = كنص ( النتيجة )
        الشاشة : حدد.النص ( النتيجة )
        العملية = ""
    تم
....
هذا بدوره يقوم بالآتي:
1- يأخذ العدد الموجود على الشاشة ويخزنه في المتغير العدد.الثاني
2- يتأكد أن العملية عملية جمع (سيكون هذا مهماً حين نضيف باقي العمليات) ويحسب النتيجة المطلوبة
3- يعرض النتيجة على الشاشة بعد تحويلها إلى صورة نصية
4- يعيد العملية إلى القيمة غير المعرفة "" إلى أن نختار عملية جديدة

هناك فقط مشكلة هي أن البرنامج سيعمل بطريقة صحيحة مرة واحدة فقط: لو أدخلت 12 ثم + ثم 13 ثم = فستجد النتيجة 25، لكن إن ضغطت بعدها 1 فستجد مكتوب على الشاشة 251

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

أيضاً تصرف آلتنا الحاسبة ليس مثل الآلة العادية. نحن حين نضغط 12 ثم + سوف نجد الشاشة قد مسحت، بينما في الحاسبة العادية سوف نضغط 12 ثم + ولا يتم مسح الشاشة إلا عند بدء إدخال العدد الثاني.

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

لكي نحل المشكلة نحتاج لمتغير جديد اسمه تفريغ، معناه "هل علينا أن نفرغ الشاشة حين يضغط المستخدم على زر عددي في المرة القادمة"؟

سوف نحتاج أيضاً أن نعدّل البرنامج قليلاً (التعديلات بالخط الثقيل ولونها أحمر):
كتبنا.فاصلة.عشرية = خطأ
العملية = ""
العدد.الأول = لاشيء
تفريغ = خطأ 
 
كرر :
    تخير :
    تسلم إشارة من ضغط زر.الجمع :
        العملية = "+"
        العدد.الأول = الشاشة : نصه ( )
        العدد.الأول = كعدد ( العدد.الأول )
        كتبنا.فاصلة.عشرية = خطأ
        تفريغ = صحيح 
    أو تسلم إشارة من ضغط زر.يساوي :
        العدد.الثاني = الشاشة : نصه ( )
        العدد.الثاني = كعدد ( العدد.الثاني )

        إذا العملية = "+" :
            النتيجة = العدد.الأول + العدد.الثاني
            النتيجة = كنص ( النتيجة )
            الشاشة : حدد.النص ( النتيجة )
            العملية = ""
            تفريغ = صحيح 
        تم
    أو تسلم إشارة من ضغط زر.الفاصلة.العشرية :
        إذا كتبنا.فاصلة.عشرية = خطأ :
            الشاشة : الحق.نص ( "." )
            كتبنا.فاصلة.عشرية = صحيح
        تم
    أو تسلم إشارة من ضغط زر1 :
        إذا تفريغ = صحيح :
            الشاشة : حدد.النص ( "" ) 
            تفريغ = خطأ 
        تم
        الشاشة : الحق.نص ( "1" )
    أو تسلم إشارة من ضغط زر2 :
       ...باقي الأرقام مثلها مثل 1
    تم
تابع
الموضوع بسيط جداً:
1- في نهاية ضغط زر الجمع أو زر يساوي نقوم بتحديد قيمة تفريغ لتكون صحيح.
2- في أزرار الأرقام نتأكد من قيمة ذلك المتغير، فإن كانت صحيح نقوم بمسح الشاشة قبل عرض الرقم المكافيء للزر

يبقى شيء واحد هو تعميم العمليات لتشمل الطرح والقسمة والضرب. سوف نعرض هنا البرنامج كاملاً بعد إضافة هذا الجزء:
دالة صنع.زر(س، ص، العرض، الارتفاع، النص، النافذة):
    ز = زر جديد
    ز : حدد.المكان ( س ، ص )
    ز : حدد.الحجم ( العرض ، الارتفاع )
    ز : حدد.النص ( النص )
    النافذة : اضف ( ز )
    ارجع ب: ز
نهاية

ن = النافذة.الحالية ( )
زر1 = صنع.زر (40 ، 130 ، 30 ، 30 ، "1" ، ن)
زر2 = صنع.زر (70 ، 130 ، 30 ، 30 ، "2" ، ن)
زر3 = صنع.زر (00 ، 130 ، 30 ، 30 ، "3" ، ن)
زر4 = صنع.زر (40 ، 100 ، 30 ، 30 ، "4" ، ن)
زر5 = صنع.زر (70 ، 100 ، 30 ، 30 ، "5" ، ن)
زر6 = صنع.زر (100، 100 ، 30 ، 30 ، "6" ، ن)
زر7 = صنع.زر (40 ، 70  ، 30 ، 30 ، "7" ، ن)
زر8 = صنع.زر (70 ، 70  ، 30 ، 30 ، "8" ، ن)
زر9 = صنع.زر (100، 70  ، 30 ، 30 ، "9" ، ن)
زر0 = صنع.زر (70 ، 160 ، 30 ، 30 ، "0" ، ن)

زر.الجمع = صنع.زر (10،  70 ، 30 ، 30 ، "+" ، ن)
زر.الطرح = صنع.زر (10، 100 ، 30 ، 30 ، "-" ، ن)
زر.الضرب = صنع.زر (10، 130 ، 30 ، 30 ، "×" ، ن)
زر.القسمة = صنع.زر(10، 160 ، 30 ، 30 ، "÷" ، ن)
زر.يساوي = صنع.زر (40، 160 ، 30 ، 30 ، "=" ، ن)

زر.الفاصلة.العشرية = صنع.زر(100، 160، 30، 30، "."، ن)

الشاشة = سطر.نصي جديد
الشاشة : حدد.المكان ( 10 ، 30 )
الشاشة : حدد.الحجم ( 120 ، 30 )
الشاشة : حدد.النص ( "" )
ن : اضف ( الشاشة )

ن : حدد.الحجم ( 150 ، 210 )
ن : حدد.العنوان ( "الحاسبة" )
كتبنا.فاصلة.عشرية = خطأ
العملية = ""
العدد.الأول = لاشيء
تفريغ = خطأ

كرر :
    تخير :
    تسلم إشارة من ضغط زر.الجمع :
        العملية = "+"
        العدد.الأول = الشاشة : نصه ( )
        العدد.الأول = كعدد ( العدد.الأول )
        كتبنا.فاصلة.عشرية = خطأ
        تفريغ = صحيح
    أو تسلم إشارة من ضغط زر.الطرح :
        العملية = "-"
        العدد.الأول = الشاشة : نصه ( )
        العدد.الأول = كعدد ( العدد.الأول )
        كتبنا.فاصلة.عشرية = خطأ
        تفريغ = صحيح
    أو تسلم إشارة من ضغط زر.الضرب :
        العملية = "×"
        العدد.الأول = الشاشة : نصه ( )
        العدد.الأول = كعدد ( العدد.الأول )
        كتبنا.فاصلة.عشرية = خطأ
        تفريغ = صحيح
    أو تسلم إشارة من ضغط زر.القسمة :
        العملية = "÷"
        العدد.الأول = الشاشة : نصه ( )
        العدد.الأول = كعدد ( العدد.الأول )
        كتبنا.فاصلة.عشرية = خطأ
        تفريغ = صحيح
    أو تسلم إشارة من ضغط زر.يساوي :
        العدد.الثاني = الشاشة : نصه ( )
        العدد.الثاني = كعدد ( العدد.الثاني )
        النتيجة = لاشيء
        إذا العملية = "+" :
            النتيجة = العدد.الأول + العدد.الثاني
        وإلا إذا العملية = "-" :
            النتيجة = العدد.الأول - العدد.الثاني
        وإلا إذا العملية = "×" :
            النتيجة = العدد.الأول × العدد.الثاني
        وإلا إذا العملية = "÷" :
            النتيجة = العدد.الأول ÷ العدد.الثاني
        تم

        إذا ليس النتيجة = لاشيء :
            النتيجة = كنص ( النتيجة )
            الشاشة : حدد.النص ( النتيجة )
            العملية = ""
            تفريغ = صحيح
        تم
    أو تسلم إشارة من ضغط زر.الفاصلة.العشرية :
        إذا كتبنا.فاصلة.عشرية = خطأ :
            الشاشة : الحق.نص (".")
            كتبنا.فاصلة.عشرية = صحيح
        تم
--------
    أو تسلم إشارة من ضغط زر1 :
        إذا تفريغ :
            الشاشة : حدد.النص ("")
            تفريغ = خطأ
        تم
        الشاشة : الحق.نص ("1")
--------
    أو تسلم إشارة من ضغط زر2 :
        إذا تفريغ :
            الشاشة : حدد.النص ("")
            تفريغ = خطأ
        تم
        الشاشة : الحق.نص ("2")
--------
    أو تسلم إشارة من ضغط زر3 :
        إذا تفريغ :
            الشاشة : حدد.النص ("")
            تفريغ = خطأ
        تم
        الشاشة : الحق.نص ("3")
--------
    أو تسلم إشارة من ضغط زر4 :
        إذا تفريغ :
            الشاشة : حدد.النص ("")
            تفريغ = خطأ
        تم
        الشاشة : الحق.نص ("4")
--------
    أو تسلم إشارة من ضغط زر5 :
        إذا تفريغ :
            الشاشة : حدد.النص ("")
            تفريغ = خطأ
        تم
        الشاشة : الحق.نص ("5")
--------
    أو تسلم إشارة من ضغط زر6 :
        إذا تفريغ :
            الشاشة : حدد.النص ("")
            تفريغ = خطأ
        تم
        الشاشة : الحق.نص ("6")
--------
    أو تسلم إشارة من ضغط زر7 :
        إذا تفريغ :
            الشاشة : حدد.النص ("")
            تفريغ = خطأ
        تم
        الشاشة : الحق.نص ("7")
--------
    أو تسلم إشارة من ضغط زر8 :
        إذا تفريغ :
            الشاشة : حدد.النص ("")
            تفريغ = خطأ
        تم
        الشاشة : الحق.نص ("8")
--------
    أو تسلم إشارة من ضغط زر9 :
        إذا تفريغ :
            الشاشة : حدد.النص ("")
            تفريغ = خطأ
        تم
        الشاشة : الحق.نص ("9")
--------
    أو تسلم إشارة من ضغط زر0 :
        إذا تفريغ :
            الشاشة : حدد.النص ("")
            تفريغ = خطأ
        تم
        الشاشة : الحق.نص ("0")
    تم
تابع
يبدو البرنامج طويلاً، لكن تذكر أنه كله أجزاء متكررة: الكود الخاصة بالعمليات مكررة أربع مرات والجزء الخاص بالأرقام عشر مرات! هناك بالتأكيد طرق لاختصار هذه الكود كما قلت، لكن حاليا...إنها تعمل :)


مازال هناك بعض الإمكانات التي يمكن إضافتها:
  • السماح بإدخال سلسلة مثل 12 / + / 13 / + / 15 بحيث تظهر نتيجة "12+13" عند ضغط علامة زائد الثانية قبل استمرار العمليات
  • إجراء العمليات بالترتيب الرياضي الصحيح، يعني لو أدخل المستخدم 7+5×2 تكون النتيجة 17 وليس 24
  • تدارك أخطاء المستخدم المحتملة
لكن هذه الإمكانيات خارج نطاق هذا المثال التوضيحي على أية حال. يمكنك أن تكتبها أنت إن أردت ^^