الأربعة تربح

السلام عليكم

يبدو لي أنكم مللتم من الأخ عبده
كما يبدو لي أنكم مللتم من المقدمات
على كل وكما أقولها لكم دائماً لا يعنيني راحتكم في شيء
بل ربما أكون أكثر سعادة لو نغصت حياتكم
لماذا؟
ببساطة أحب لقب علاء الشرير
لماذا؟
لأن عكس الشرير هو الطيب
والطيب في زمننا هذا له مكافئ آخر ألا وهو …
وتجد أطناناً من الناس تحاول أن تستغلك
والكل يقول لك أنت طيب و so cute
بالطبع ستكون طيب وجميل وكل الصفات الجيدة ماداموا يستغلونك
على كل أنا أريد التغيير ولا شيء غير تغيير
أما ما الذي سأكلمكم عنه اليوم
أظنكم لاحظتم اسم الموضوع

اسم اللعبة: لعبة الأربعة تربح

إصدارة اللعبة: v1.00

قصة اللعبة:
في البداية كان الأخ بيشوي العظيم من منتديات الفريق العربي يحاول برمجة اللعبة
بالطبع لا أذكر الكثير عن الموضوع
يهيألي أنه مضى عليه سنة على أقل تقدير
طبعاً وبما أني مشرف هناك فقد تطوعت لمساعدته
في النهاية وصلت إلى أني برمجت وأعدت برمجة اللعبة كاملة
وتوقف الأخ بيشوي عن إكمال اللعبة
أعتقد لإنشغاله في امتحانات الجامعة
على العموم أنا لم أتوقف
أنهيت النسخة v1.00 beta لكنها لم ترى النور
بعدها دخلت في دوامة حزمة المحرك
فترة طويلة نسيت موضوع هذه اللعبة تماماً
بعد أن أنهيت الإصدارة v0.8 من المحرك
أتتني فكرت أن أستخدم المحرك في تحريك لعبة الأربعة تربح
على كل أنهيت العمل عليها منذ فترة طويلة
لكنها بقيت تقبع في الظلمات السحيقة لجهازي
لا أدري ما الذي ذكرني فيها
لكني تذكرتها ولا أدري هل هو من حسن حظكم أو من سوء حظكم :)

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

اللعبة من الناحية التقنية:
اللعبة تتكون من مجموعة من الفئات
1- الفئة TheFourthWinGame
وهي تمثل الأساس في بناء اللعبة كلها أو ما يعرف بطبقة Logic
حيث تحتوي على مصفوفتين ثنائيتي الأبعاد وتمثلان:
أ- مصفوفة اسمها exist وتخبرنا إذا كان المربع قد تم وضع كرة فيه من قبل
ب- مصفوفة اسمها playerIfExist وتحتوي على اللاعب الذي وضع الكرة في المربع
واضح أن المصفوفة الثانية لا معنى لها بدون المصفوفة الأولى
بمعنى آخر أنا لا أبحث من وضع الكرة في المربع في حال عدم وجود كرة أصلاً

تحتوي الفئة على مجموعة من الدوال وهي تنقسم إلى قسمين:
أ-دالة findFirstEmptyCellInColumn تساعدك في GUI لمعرفة المكان الذي سوف ترسم فيه الحلقةالجديدة
إذا كان العمود ممتلئ سترجع هذه الدالة -1
ب-دوال الفحص وهي على النحو التالي
* دالة testFair وهي مسؤولة عن معرفة التعادل حين يحصل
* دالة testWin وهي مسؤولة عن معرفة الفوز حين يحصل
وتعتمد هذه الدالة على مجموعة دوال داخلية private
والدوال هي:
-testColumnWin هذه الدالة مسؤولة عن فحص الفوز على مستوى العمود
-testRowWin هذه الدالة مسؤولة عن فحص الفوز على مستوى الصف
-testMainQuarterWin هذه الدالة مسؤولة عن فحص الفوز على مستوى القطر الرئيسي
(القطر الرئيسي هو القطر الذي تزيد فيه قيمة الإحداثي السيني مع قيمة الإحداثي الصادي)
بمعنى النقطة الأولى (0،1) النقطة التي تليها (1,2) وهكذا
-testSeconderyQuarterWin هذه الدالة مسؤولة عن فحص الفوز على مستوى القطر الفرعي
(القطر الفرعي هو القطر الذي تقل فيه قيمة الإحداثي السيني وتزيد فيه قيمة الإحداثي الصادي)
بمعنى النقطة الأولى (3،0) النقطة التي تليها (2،1) وهكذا
ملاحظة: يجب فحص الفوز قبل فحص التعادل لأن الدالة الخاصة بالتعادل تفحص إذا كانت الأعمدة ملئا فقط
وهناك بعض الحالات التي تكون فيها الأعمدة ملئا مع هذا يكون هناك فائز
وهو ما أسميه الفائز في اللحظة الأخيرة
(لم أسميه فلسفة لكن هذه النقطة بالذات كانت موجودة كـ bug ولكني انتبهت لها في المراجعات الأخيرة قبل نشر اللعبة)
-أعتقد أن هذه النقطة خرق صريح للعمل كطبقات لكني أعتقد أن عملية فحص الفائز عملية مكلفة لذا لا أريد أن أقوم بها مرتين-

هناك مجموعة أخرى من الدوال المساعدة من نوع خذ وهات
دعوكم منها

2- الفئة PlayPanel
هذه الفئة من اسمها تقوم بتنظيم عمليات العرض للعبة
كما أنها تحتوي على تنظيم الأحداث الخاصة بالفأرة والخاصة بحزمة المحرك
على كل لن أشرح الكثير هنا
أريد فقط أن أسلط الضوء على عملية التحريك للكرات
لماذا عملية التحريك فقط لأن باقي الفئة يتعامل مع swing ويهيألي أنه موضوع قتلناه دراسة وتمحيصاً
لماذا عملية التحريك وقد تكلمنا بما فيه الكفاية عن المحرك
لأن الكثير من الإخوة لام علي عدم كتابة documentaition
ولكي أكون صادق معكم ومع نفسي التوثيق شيء أنا سيء فيه
لذا أحاول أن أغطي هذه النقطة بالكثير من الأمثلة مع شرحها
لعل وعسى أن أغطي موضوع التوثيق
عملية التحريك تتلخص في الدالة moveOval الموجودة في الحدث mouse click
هذه الدالة تأخذ نقطة نهاية الحركة على صعيد الإحداثي الصادي
لماذا الإحداثي الصادي لأن الحركة وكما لاحظتم في اللعبة حركة عمودية
الدالة تبدأ بالسطرين التاليين

PointToPointAnimation animation=new PointToPointAnimation(ballPanel.getX(), line_y, ballPanel.getX(), end,step, time);
AnimationList list=animation.excute();

هذه الفئة تأخذ إحداثي النقطة التي سيبدأ منها الكائن المتحرك الحركة والنقطة التي سينتهي إليها
وتقوم بتحويل المسافة بين النقطتين هذا إلى مجموعة من الحركات AnimationList
واضح أن أول أربعة مدخلات على صانع الكائنات الخاص بهذه الفئة هم إحداثيات النقطتين
بالنسبة لآخر مدخلين يمثلان المسافة (step) التي يقطعها الكائن المتحرك في وحدة الزمن (time)
السطرين في الشيفرة السابقة لا يعتبران من عملية التحريك
يمكننا النظر إليها كأدوات مساعدة للحزمة الرئيسية
بالنسبة للتحريك الفعلي فهو في الجزء التالي

if (ani == null) {
	obj = new AnimationObject(new Point(oval_x + 10, line_y),
			ballPanel,list, false);
	obj.addAnimationListner(new MyAnimationListener());
	ani = new AnimationEngine(PlayPanel.this);
	ani.addAnimationObject(obj);
	Thread animationThread = new Thread(ani);
	animationThread.start();
} else {
	obj.setList(list);
}

في البداية نجد أني فحصت حالة المتغير ani
المتغير ani هو كائن من فئة المحرك
وأسلوب الفحص هذا يسمى تأجيل إعطاء القيم الابتدائية للكائنات lazy intialization
معنى أن يكون ani بـ null هو أن المحرك يعمل لأول مرة
ومعنى هذا أنه يتوجب علينا أن نقوم بإنشاء المحرك وكائن متحرك
في حال كون الكائن ani موجود مسبقاً
فلا حاجة لإنشاء الكائنات السابقة وإنما فقط نحتاج إلى تغيير قائمة الحركات الخاصة بالكائن
حزمة المحرك تتعامل مع تغيير قائمة الحركات على أساس أنها إضافة مجموعة من الحركات
بالتالي يتم تفعيل المحرك ليقوم بتنفيذ هذه الحركات

طيب لنتكلم عن ما هو بداخل if
في البداية سأتكلم عن الكائن المتحرك obj

obj = new AnimationObject(new Point(oval_x + 10, line_y),
		ballPanel,list, false);

يتم إنشاء الكائن المتحرك عن طري صانع كائنات يحتوي على المدخلات التالية بالترتيب
-نقطة تواجد بدائية للكائن المتحرك
-كائن من الفئة Component أو أحد أبنائها
كما قلت مسبقاً الكائن المتحرك هو عبارة عن مكون Component يتحرك على حاوية Container
وظيفة المحرك أن يقوم بتحريك مجموعة من الكائنات المتحركة في آن واحد
ويصدر أحداث مختلفة تمكن المبرمج من تنفيذ أي شيفرة في أي وقت من عمل المحرك
-قائمة الحركات AnimationList
وهذه القائمة قد تكون خالية وقد تكون ملئا وقد تكون null
هذه القائمة تمثل خريطة الحركة الخاصة بالكائن المتحرك
هذه الخريطة توصف بالضبط آلية حركة المحرك
تتكون هذه الخريطة من حركة Animation أو أكثر
الحركة الواحدة تحتوي على وصف يخبر المحرك عن تفاصيل الحركة
هذا الوصف يتكون من
أ-نوع الحركة وينقسم إلى ثلاث أنواع أفقية ، عمودية و قطرية
ب-مقدار الحركة بالبكسيل
ج-المدة الزمنية لهذه الحركة
نلاحظ أننا لم نتعامل مع الفئة حركة Animation أبداً
بالطبع نحن مخطئون وإلا ما كنا رأينا شيئاً يتحرك
فعلياً قامت الأخت PointToPointAnimation بهذا الأمر بدلاً منا
-أخيراً قيمة منطقية boolean هذه القائمة هي جواب السؤال التالي
هل تريد تكرار الحركة بشكل لا متناهي؟
في حالتنا أنا وضعتها false بمعنى لا أريد
دعونا نتكلم عن السطر التالي

obj.addAnimationListner(new MyAnimationListener());

في حزمة المحرك لكي تستفيد من الأحداث الصادرة عن الحزمة
يجب أن يكون لدي مستمع listener يستمع لهذه الأحداث ويتفاعل معها
ما فائدة المستمع؟
المستمع يعمل على تلقي الأحداث من حزمة المحرك
ويقوم بتنفيذ شيفرة يحددها المبرمج
من الشيفرة السابقة نلاحظ أني ضفت كائن من المستمع MyAnimationListener
وهو مستمع قمت ببنائه كفئة داخلية inner class في الفئة PlayPanel
دعونا نلقي نظرة سريعة على الفئة MyAnimationListner
وهي المستمع الخاص بنا في هذه اللعبة
ببساطة أنا كمبرمج للعبة أريد عندما تنتهي حركة الكرة في العمود
أن يتم فحص حالة اللعبة
هل هناك فائز؟
هل امتلأت اللعبة؟ (كل الأعمدة ممتلئة بالكرات)
بحيث أخرج رسالة للمستخدم تقول مبروك فزت
أو تعادلت وهكذا
(لا تلقوا بالاً للرسائل الموجودة فهي للدعابة فقط
أو ممكن أن تقولوا هي رسالة إلى أستاذ وصديق عزيز علي)
على كل لاحظوا أنا أقول عندما تنتهي حركة الكرة
وحركة الكرة تنتهي عندما تنتهي قائمة الحركات الخاصة بها
حزمة المحرك تصدر أحداثاً خاصة بالكائن المتحرك منها
beforeAnimationList و afterAnimationList و afterAnimation و beforeAnimation
واضح أننا سنستخدم الحدث afterAnimationList
على كل تحدثت كثيراً عن الأحداث في موضوع AAnimation package
لا أريد أن أكرر نفس الكلام مرة أخرى
لكن لا مانع لدي لو كان لديكم أسئلة أن أجيبها

نأتي إلى المقطع التالي من الشيفرة

ani = new AnimationEngine(PlayPanel.this);
ani.addAnimationObject(obj);

AnimationEngine فئة المحرك هي الفئة الأساسية في عملية التحريك
تقوم هذه الفئة بترتيب جدولة الكائنات المتحركة وإصدار الأحداث
تستطيع القول أنها هي الفئة الأساسية في الحزمة كلها
تعمدت أن لا أجعلها Thread مباشرة وإنما Runnable
تسألوني لماذا؟
أجيب ببساطة لا أريد أن أحد (بضم الحاء وتشديد الدال) المستخدم بأي شيء
ربما المستخدم لا يريد أن يستخدم Thread ربما يريد استخدام ThreadPool مثلاً
ربما يظهر شيء جديد غير Thread في المستقبل
صحيح ربما أنا أهذي لكنه احتمال ممكن جداً
على كل صانع الكائنات الخاص بها يأخذ مدخل واحد ألا وهو كائن من الفئة حاوية Container أو أحد أبنائها
ويقوم بتحريك المكونات components على هذه الحاوية
أخيراً وبما أن AnimationEngine ليست Thread فأنا محتاج إلى واحد
وهذا ما قمت به في المقطع التالي

Thread animationThread = new Thread(ani);
animationThread.start();

تحميل اللعبة:
1- تحميل الإصدارة الأخيرة من حزمة المحرك كشيفرة مصدرية
AAnimation_src (64)
2- تحميل الإصدارة الأولى من لعبة الأربعة تربح كشيفرة مصدرية
TheFourthwin_src (56)
3- تحميل اللعبة كملف تنفيذي
FourthWin_bin (73)
لتشغيل اللعب فقط اضغط على ملف شغل الملف RUN.BAT

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

في النهاية لا يسعني إلا أن أشكر الإخوة:
1- بيشوي من منتديات الفريق العربي (بيشوي المشاكس اسم جيد يا عم بيشوي) فكما أخبرتكم هو صاحب الفكرة أصلاً
لم آخذ رأيه في موضوع النشر لأني برمجت كامل اللعبة
كما أنها لعبة مشهورة أصلاً فليست فكرته
لكني أعترف بأنه السبب في إكمالي البرمجة
لذا لو طلب مني إزالة المقال كله سأفعل
2- الشمري من منتديات الفريق العربي عبد الله متحمس جداً لبرمجة الألعاب
يعمل على محرك ألعاب أظن يسميه المحرك الذري كنت أريد المشاركة في هذا المحرك
لكن العمل يلتهم جل وقتي
على فكرة هو صاحب فكرة الفئة PointToPointAnimation
وقد اقترح علي أفكار كثيرة بالطبع هناك من نفذ وهناك من ينتظر
لكني لا يسعني إلا أن أقف احتراماً لعبدالله
3- عصام من منتديات الفريق العربي (herch)
ماذا لا تعرفون عصام؟
عصام الذي جعلني أكتب كيف تبني نظام أحداث خاص بحزمتك إطلالة على الأحداث في حزمة المحرك
لا بد أنكم تمزحون
على كل عصام هو الذي اقترح علي أن أكتب عن حزمة المحرك أكثر
وأنا أخذت كلامه على محمل الجد
المشكلة يا عصام أن برمجة الألعاب ليست بالأمر الهين
واللعبة الواحدة مهما بدت لك بسيطة إلا أنها تأخذ وقتاً لا بأس به في عملية البرمجة
لكني أحاول

أخيراً معلومات عن المبرمج:
م.علاء محمد خليل الصالحي
خريج هندسة حاسوب
الجامعة الإسلامية بغزة
أعمل في وحدة البرمجة في الجامعة الإسلامية

تحياتي

Tags: , , ,

17 Responses to “الأربعة تربح”

  1. Speed_Of_Light قال:

    لعبة جميلة ! أذكر أنها كانت من أوائل لعبي عندما كنت صغيراً …
    وكان لونها أزرق ! تماماً كالبرنامج !
    المهم … هذا أول مرة أطلع فيها على استخدام حزمة المحرك :)
    هل لاحظت أنه التحريك ليس ناعماً smooth ؟
    أي أنه يمكنك ملاحظة الدائرة تمسح من الأعلى للأسفل -بسرعة- وهي تتحرك !
    لا تقل لي هذا “البطئ” من الجافا ! لأني لا أظنه كذلك !
    وليس البطئ من جهازي أيضاً :D

  2. admin قال:

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

    تحياتي

  3. Speed_Of_Light قال:

    لم أطلع عن كثب على حزمة المحرك، لكني شبه متأكد من سبب المشكلة …
    وهي ببساطة “استدعاء متكرر لكود إعادة الرسم”
    أي أنه يتم إعادة رسم الصورة/الشكل عدد من المرات عند كل انتقال بدل مرة واحدة
    كيف تتأكد من ذلك ؟! (إذا كان لديك وقت طبعاً :))
    جرب أن تعمل Break Point على الطريقة/الدالة التي تقوم بإعادة الرسم ثم قم بتنقيح البرنامج وذلك بتحريك “شكل ما” حركة واحدة فقط
    لاحظ الاستدعاء المتكرر للطريقة/الدالة (إن وجد)

  4. admin قال:

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

    تحياتي

  5. Speed_Of_Light قال:

    نعم صحيح
    هذه بعض الروابط الهامة التي تفسر لماذا الحركة ليست ناعمة … مع حلول للمشكلة
    http://today.java.net/pub/a/today/2006/02/23/smooth-moves-solutions.html
    http://weblogs.java.net/blog/chet/archive/2006/02/make_your_anima.html

  6. admin قال:

    شكراً لك أخ حسام
    سأطلع على الروابط وأرد عليك في أقرب وقت

    تحياتي

  7. Mohammed Lubbad قال:

    شرح سلس وواضح سأقوم بتجربتها والتعليق

  8. admin قال:

    في انتظار التعليق أخ محمد

  9. Mohammed Lubbad قال:

    لعبة جميلة ورائعة لاحظت أيضا أن الحركة أنعم بشكل بسيط على نظام mac أكثر من ويندوز
    هناك الكثير من التوثيق في الكود ولكن للأسف xCode ( برنامج التطوير في mac ) لا يدعم العربية للتوثيق

    لم أهوى استخدام الجافا من قبل لذلك ليس لدي ما أضيفه. أتمنى لك التوفيق

  10. admin قال:

    موضوع الحركة كان فيه مشكلة فقط في FPS عدد الفريمات في الثانية
    غريب هذا الموضوع
    كنت أظن أن mac يقدم دعم جيد للعربية

    ياخسارة كنت أريد أن أسمع رأيك في الشيفرة

    تحياتي

  11. abuouf قال:

    كيف حالك اخي
    للاسف لا احب الالعاب ولا برمجتها ولكن دخلت لاسلم عليك :)

  12. admin قال:

    منور أخي أبو عوف
    لنا مدة لا نراك هنا أتمنى أن تكون بخير
    بالمناسبة أنا لا أحب الألعاب
    لكن برمجتها تستهويني وبشدة
    لأنها من أعقد أنواع البرمجة :)
    جرب وأؤكد لك أنك لن تندم

    تحياتي

  13. hello قال:

    这是什么语阿

  14. admin قال:

    welcome in my blog I hope to get fun
    I were use java to program it

    BW,
    Alaa

  15. bshara قال:

    اريد لعبة على الموبايل

  16. admin قال:

    أعتذر منك يا أختي

    تحياتي

Leave a Reply