السابقالفهرسالتالي

الفصل 16

GridWorld: الجزء الثالث

 

إذا لم تحل التمارين في الفصلين 5 و10، عليك حلهم قبل قراءة هذا الفصل. للتذكير فقط، يمكنك العثور على وثائق أصناف GridWorld على http://thinklikecs.webs.com/resources/javadoc/gridworld/.

يعرض الجزء الثالث من دليل الطالب لبرنامج GridWorld الأصناف المكونة للبرنامج والتفاعلات فيما بينها. هذه الأصناف هي مثال عن التصميم كائني التوجه وستمنحنا الفرصة للتحدث عن مشاكل التصميم كائني التوجه.

قبل قراءة دليل الطالب، توجد بعض الأشياء التي يجب أن تعرفها أولاً.

16.1 ArrayList

يستخدم البرنامج java.util.ArrayList، وهو كائن يشبه المصفوفات. بل هو مجموعة (collection)، ما يعني أنه كائن يحتوي على كائنات أخرى. توفر Java مجموعات أخرى بقدرات متفاوتة، لكن لاستخدام GridWorld سنحتاج إلى ArrayList فقط.

لترى مثالاً، نزل http://thinklikecs.webs.com/resources/code/BlueBug.java و http://thinklikecs.webs.com/resources/code/BlueBugRunner.java. الحشرة الزرقاء (BlueBug) هي حشرة تتحرك عشوائياً وتبحث عن الصخور. إذا حثرت على صخرة، ستلونها بالأزرق.

إليك كيفية عملها. عند استدعاء act، تعطي الحشرة الزرقاء موقعها ومرجعاً للشبكة:

Location loc = getLocation();
Grid grid = getGrid();
النوع بين قوسي الزاوية (<>) هو معامل نوع (type parameter) يحدد محتويات الشبكة (grid). بكلمات أخرى، grid ليست مجرد كائن من نوع Grid، بل هي Grid تحتوي على Actors.

الخطوة التالية هي معرفة جيران الموقع الحالي. توفر Grid عملية تفعل ذلك:

ArrayList neighbors = grid.getNeighbors(loc); 
القيمة المعادة من getNeighbors هي ArrayList من Actors. تعيد عملية size طول ArrayList، وتختار get عنصراً. لذا يمكننا طباعة الجيران كما يلي:
for (int i=0; i < neighbors.size(); i++) {
   Actor actor = neighbors.get(i);
   System.out.println(actor);
}
إن المرور على ArrayList هو عملية شائعة لدرجة وجود تعليمة خاصة بها. لذا يمكننا أن نكتب:
for (Actor actor: neighbors) {
   System.out.println(actor);
}
نعلم أن الجيران هم Actors، لكننا لا نعرف نوعهم: قد يكونوا من النوع Bug، أو Rock، الخ. حتى نميز الصخور، سنستعمل عامل instanceof، الذي يتحقق من انتماء كائن ما إلى صنف معين.
for (Actor actor: neighbors) {
   if (actor instanceof Rock) {
      actor.setColor(Color.blue);
   }
}
حتى نتمكن من تشغيل كل هذا علينا استيراد الأصناف التي استخدمناها:
import info.gridworld.actor.Actor;
import info.gridworld.actor.Bug;
import info.gridworld.actor.Rock;
import info.gridworld.grid.Grid;
import info.gridworld.grid.Location;
import java.awt.Color;
import java.util.ArrayList;

تمرين 16.1

ابدأ مع نسخة من BlueBug.java، اكتب تعريف صنف لنوع جديد من الحشرات التي تبحث عن الأزهار وتأكلها. يمكنك "أكل" الأزهار باستدعاء removeSelfFromGrid عليها.

16.2 الواجهات

يستخدم GridWorld واجهات (Java interfaces) Java، لذلك أريد أن أشرح ما هي هذه الواجهات. تعني كلمة "interface" أشياء مختلفة بحسب موقعها من الكلام، لكنها في Java تشير إلى ميزة للغة البرمجة: الواجهة هي تعريف صنف لا تملك عملياته أجسام (bodies).

في تعريف الصنف العادي، يوجد لكل عملية نموذج أولي (prototype) وجسم (body) (انظر القسم 8.5). يدعى النموذج الأولي أيضاً بالتوصيف (specification) لأنه يصف اسم العملية، ومعاملاتها ونوع إرجاعها؛ يدعى الجسم بالتطبيق (implementation) لأنه يطبق (implement) التوصيف.

في واجهات Java لا تملك العمليات أجسام، لذا فهي تصف العمليات بدون أن تطبّـقها.

مثلاً، java.awt.Shape هي واجهة فيها نماذج أولية للعمليات contains، وintersects، والعديد من العمليات الأخرى. يوفر الصنف java.awt.Rectangle تطبيقات لهذه العمليات، لذلك نقول

"Rectangle implements Shape". هذا هو السطر الأول من تعريف الصنف Rectangle:

public class Rectangle extends Rectangle2D implements Shape, Serializable
يرث الصنف Rectangle العمليات من الصنف Rectangle2D ويوفر تطبيقات للعمليات الموجودة في Shape وSerializable. في برنامج GridWorld يطبق صنف Location واجهة java.lang.Comparable بتوفيره العملية compareTo، المشابهة لعملية compareCards في القسم 13.5.

يعرّف GridWorld أيضاً واجهة جديدة، اسمها Grid، التي توصّف العمليات التي يجب على الشبكات (grids) توفيرها. كما يحتوي البرنامج على تطبيقين لها، BoundedGrid وUnboundedGrid.

بقي أن نعقب على الاختصار API الذي يمثل الكلمات "Application programming Interface" – "واجهة برمجة التطبيقات". API هي مجموعة العمليات المتوفرة لمبرمجي التطبيقات حتى يستخدموها. انظر http://en.wikipedia.org/wiki/Application_programming_interface.

16.3 public وprivate

أتذكر عندما قلت في الفصل 1 أنني سأشرح سبب وجود كلمة public قبل العملية main؟ أخيراً، آن الأوان لذلك.

تعني كلمة public إمكانية استدعاء العملية من الأصناف الأخرى. الكلمة البديلة لهذه هي private، التي تعني أن استدعاء العملية ممكن فقط داخل الصنف الذي عرّفت فيه.

يمكن أن تكون متغيرات الحالة عامة (public) أو خاصة (private) أيضاً: متغير الحالة الخاص لا يمكن الوصول إليه إلا داخل الصنف الذي عرّف فيه.

السبب الرئيسي لجعل العمليات ومتغيرات الحالة خاصة هو الحد من التعاملات بين الأصناف المختلفة في سبيل التحكم بالتعقيد.

مثلاً، يبقي صنف Location متغيرات حالاته خاصة. لديه عمليات وصول (accessor methods) مثل getRow وgetCol، لكنه لا يوفر أية عمليات للتعديل على متغيرات حالاته (his instance variables). بالتالي، كائنات Location غير قابلة للتحوير، أي أننا نستطيع مشاركتها بدون أن نقلق بخصوص أشياء غير متوقعة تنتج عن تعدد الأسماء (aliasing).

إن تخصيص العمليات يساعد على تبسيط واجهة برمجة التطبيقات API. غالباً ما تتضمن الأصناف عمليات مساعِدة تُستخدَم لتنفيذ عمليات أخرى، لكن جعل هذه الأخيرة جزءاً من الAPI سيكون غير ضروري ومسبباً للأخطاء.

العمليات ومتغيرات الحالة الخاصة هما ميزتان لغويتان تساعد المبرمجين على ضمان تغليف البيانات (data encapsulation)، الذي يعني عزل الكائنات الموجودة في أحد الأصناف عن الأصناف الأخرى.

تمرين 16.2

أصبحت تعرف الآن ما يكفي لقراءة الجزء 3 من دليل الطالب لبرنامج GridWorld وحل التمرينات الموجودة فيه.

16.4 لعبة الحياة

اخترع الرياضي (عالم رياضيات) جون كُنواي "لعبة الحياة" – "Game of Life"، التي يصفها بأنها "لعبة بدون لاعبين" - "zero-player game" بسبب عدم الحاجة إلى لاعبين ليختاروا استراتيجيات أو يصنعوا قرارات. بعد تجهيز الشروط الابتدائية، تجلس وتشاهد اللعبة تلعب لوحدها. لكن تبين أن ذلك أكثر تشويقاً مما يبدو؛ يمكنك القراءة عنها على http://en.wikipedia.org/wiki/Conways_Game_of_Life.

إن الغرض من هذا التمرين هو تنفيذ لعبة الحياة باستخدام GridWorld. ستكون الشبكة رقعة اللعبة (game board)، وستمثل الصخور القطع.

تجرى اللعبة في أدوار، أو خطوات زمنية (time steps). في بداية الخطوة الزمنية، كل صخرة إما أن تكون "على قيد الحياة" أو "ميتة". على الشاشة، سيمثـِل لون الصخرة حالتها.

تعتمد حالة كل صخرة على حالة جيرانها. لكل صخرة 8 جيران، عدا الصخور الموجودة على حافة الشبكة. ها هي القواعد:

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

معظم الأوضاع الابتدائية البسيطة إما أن تنقرض فيها الصخور سريعاً أو تصل إلى وضع مستقر. لكن توجد بعض الشروط الابتدائية التي تظهِر تعقيداً مميزاً. أحدها هو r-pentomino: يبدأ مع 5 صخور فقط، يعمل 1103 خطوة زمنية وينتهي في وضع مستقر مع 116 صخرة حية (انظر http://www.conwaylife.com/wiki/R-pentomino).

الأقسام التالية هي اقتراحاتي لتنفيذ لعبة الحياة (GoF) في GridWorld. يمكنك تنزيل الحل من http://thinklikecs.webs.com/resources/code/LifeRunner.java و http://thinklikecs.webs.com/resources/code/LifeRock.java.

16.5 LifeRunner

اصنع نسخة من BugRunner.java باسم LifeRunner.java وأضف العمليات ذات النماذج الأولية التالية:

/**
 * Makes a Game of Life grid with an r-pentomino.
 */
public static void makeLifeWorld(int rows, int cols)

/**
 * Fills the grid with LifeRocks.
 */
public static void makeRocks(ActorWorld world)
يجب أن تصنع makeLifeWorld شبكة عناصر (Actors) بالإضافة إلى ActorWorld، ثم تستدعي makeRocks، التي ستضع LifeRock في كل موقع من الشبكة.

16.6 LifeRock

اصنع نسخة من BoxBug.java باسم LifeRock.java. يجب أن يوسع LifeRock الصنف Rock. أضف عملية act لا تنفذ أي شيء. يجب أن تكون قادراً الآن على تشغيل البرنامج وأن ترى شبكة ممتلئة بالصخور.

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

/**
 * Returns true if the Rock is alive.
 */
public boolean isAlive()

/**
 * Makes the Rock alive.
 */
public void setAlive()

/**
 * Makes the Rock dead.
 */
public void setDead()
اكتب عملية بناء تستدعي setDead وتأكد أن جميع الصخور ميتة.

16.7 التحديثات المتزامنة

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

في سبيل إجراء التحديثات في وقت متزامن، أقترح أن تكتب عملية act بطورين: خلال الطور الأول، تحسب كافة الصخور جيرانها وتسجل النتائج؛ وفي الطور الثاني، تحدّث كافة الصخور حالتها.

هكذا بدت عملية act التي كتبتها أنا:

/**
 * Check what phase we're in and calls the appropriate method.
 * Moves to the next phase.
 */
   public void act() {
   if (phase == 1) {
      numNeighbors = countLiveNeighbors();
      phase = 2;
   } else {
      updateStatus();
      phase = 1;
   }
}
phase وnumNeighbors متغيرات حالة. وها هي النماذج الأولية للعمليات countLiveNeighbors وupdateStatus:
/**
 * Counts the number of live neighbors.
 */
public int countLiveNeighbors()

/**
 * Updates the status of the Rock (live or dead) based on the number
 * of neighbors.
 */
public void updateStatus()
ابدأ بكتابة نسخة بسيطة من updateStatus تغير الصخور الحية إلى ميتة وبالعكس. الآن شغل البرنامج وتأكد أن الصخور تغير ألوانها. كل خطوتين في World تكافئ خطوة زمنية واحدة في لعبة الحياة.

املأ الآن أجسام countLiveNeighbors وupdateStatus وفقاً للقواعد وانظر لترى إذا كان النظام سيعمل كما هو متوقع.

16.8 الشروط الابتدائية

لتغيير الشروط الابتدائية، يمكننا استخدام قائمة GridWorld المنبثقة لتغيير حالة الصخور بباستدعاء setAlive. أو يمكنك كتابة عمليات تؤتمت هذه العملية.

في LifeRunner، أضف عملية باسم makeRow تنشئ وضعاً أولياً فيه n صخرة على قيد الحياة في صف في منتصف الشبكة. ماذا يحدث عند استخدام قيم مختلفة للمتغير n؟

أضف عملية باسم makePentomino تصنع شكل r-pentomino في منتصف الشبكة. يجب أن يبدو الوضع الابتدائي كهذا:

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

موقع لعبة الحياة على الإنترنت يصف أوضاعاً ابتدائية أخرى تعطي نتائج مثيرة (http://www.conwaylife.com). اختر واحداً يعجبك ونفذه.

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

تمرين 16.3

إذا نفذت لعبة الحياة، فأنت جاهز للجزء 4 من دليل الطالب لبرنامج GridWorld. اقرأه وحل تمارينه.

تهانينا، لقد انتهيت!