اذهب إلى المحتوى

تطبيق عملي: بناء لعبة ورق في جافا


رضوى العربي

سنتناول خلال هذا القسم عدة أمثلة للتصميم كائني التوجه (object-oriented). اخترنا أن تَكُون تلك الأمثلة بسيطة بما فيه الكفاية لتَسمَح لنا بتصميم أصناف قابلة لإعادة الاِستخدَام على نحو معقول. تحديدًا، سنُصمّم لعبة ورق تَستخدِم مجموعة ورق اللعب القياسية (deck) والمعروفة باسم "مجموعة ورق لعب البوكر".

تصميم الأصناف

يمكننا توصيف لعبة الورق كالتالي: "كأيّ لعبة ورق عادية، سيَحصُل كل لاعب على أكثر من ورقة لعب. بدايةً ستُخلَط (shuffle) مجموعة ورق اللعب (deck). بعد ذلك، ستُسحَب ورقة واحدة بكل مرة من مجموعة ورق اللعب (deck)، ثُمَّ تُوزَّع إلى يد (hand) أحد اللاعبين. قد يُستبعَد بعض ورق اللعب (cards) من يد أحد اللاعبين، وقد يُضاف ورق جديد. يَعتمِد فوز لاعب معين بالمباراة أو خسارته لها على كُلًا من قيم (values) (آص، ٢، ٣، ..، ملك)، ورموز (suits) ("بستوني spades"، "ديناري diamonds"، "قلب hearts"، "سباتي clubs") ورق اللعب الذي تَسلَّمه".

إذا بحثنا عن الأسماء المذكورة بالتوصيف (specification) السابق، فسنَجِدْ التالي: لعبة، لاعب، يد (hand)، ورقة لعب (card)، مجموعة ورق اللعب (deck)، قيمة (value)، رمز (suit). يُعدّ كُلًا من الاسمين "قيمة" و "رمز" مُجرَّد قيم بسيطة، ويُمكِن تمثيلها كمُتْغيِّرات نُسخ (instance variables) بالصنف المُمثِل لورقة اللعب Card. يُمكِننا تمثيل الأسماء الخمسة الآخرى بواسطة أصناف (classes)، ولكننا سنَقْتصِر فقط على تلك الأسماء الأكثر قابلية لإعادة الاِستخدَام: ورقة لعب (card)، يد (hand)، مجموعة ورق اللعب (deck). أما إذا بحثنا عن الأفعال المذكورة بالتوصيف، فسنَجِدْ التالي: "خلط (shuffle) مجموعة ورق اللعب" و "توزيع ورقة لعب من مجموعة ورق اللعب". سنَستخدِم تابعي النُسخ (instance methods)‏ shuffle()‎ و dealCard()‎ لتمثيل كُلًا منهما ضِمْن الصَنْف Deck. بالإضافة إلى ذلك، هنالك أيضًا: "إضافة ورقة لعب إلى يد اللاعب" و "استبعاد ورقة لعب من يد اللاعب". سنَستخدِم تابعي النُسخ (instance methods)‏ addCard()‎ و removeCard()‎ لتمثيل كُلًا منهما ضِمْن الصنف Hand. أخيرًا، ورق اللعب هو كيان سلبي نوعًا ما، ولكن ينبغي على الأقل أن نَتَمكَّن من تَحْديد كُلًا من قيمته (value) ورمزه (suit). سنكتشف المزيد من توابع النُسخ الضرورية لاحقًا.

أولًا، سنبدأ بتصميم الصنف Deck تفصيليًا. عندما نُنشِئ مجموعة ورق لعب (deck) لأول مرة، فإنها ستَحتوِي على ٥٢ ورقة لعب (card) مُرتَّبة معياريًا. لإنشاء مجموعة ورق لعب (deck) جديدة، سيَحتاج الصَنْف Deck إلى بَانِي (constructor) بدون أي مُعامِلات (parameters)؛ لأن أي مجموعة ورق لعب جديدة دائمًا ما تَكُون هي نفسها. سيَتَضمَّن الصَنْف Deck تابع النُسخة (instance method)‏ shuffle()‎ المسئول عن ترتيب مجموعة ورق اللعب عشوائيًا، كما سيَتَضمَّن تابع النُسخة dealCard()‎ المسئول عن جَلْب ورقة اللعب (card) التالية من مجموعة ورق اللعب (deck). هذا التابع هو عبارة عن دالة (function) تُعيد قيمة من النوع Card؛ لأن المُستدعِي يحتاج إلى مَعرِفة ورقة اللعب (card) المُوزَّعة. في المقابل، لن يَستقبِل ذلك التابع أي مُعامِلات (parameters)؛ فليس هناك أيّ معلومات ينبغي تمريرها إلى مجموعة ورق اللعب (deck) عند سَحْب ورقة لعب (card)، فأنت فقط تُوزِّع ورقة اللعب التالية أيًا كانت. لكن ماذا سيَحدُث عند استدعاء التابع dealCard()‎ بينما لم يَعُدْ هناك أي ورق لعب إضافي بمجموعة ورق اللعب؟ في العموم، تُعدّ محاولة توزيع ورقة لعب من مجموعة ورق لعب فارغة بمثابة خطأ، لذا يُمكِن للصَنْف عندها أن يُبلِّغ عن اعتراض (exception). ولكن كيف سيَتَمكَّن البرنامج من مَعرِفة ما إذا كانت مجموعة ورق اللعب فارغة أم لا؟ قد يحتفظ البرنامج بعدد ورق اللعب المُستخدَم إلى الآن، ولكن ينبغي لمجموعة ورق اللعب نفسها أن تَكُون على دراية بتلك المعلومة، ولهذا سنُضيف تابع النسخة cardsLeft()‎ ليُعيد عدد ورق اللعب المُتبقِّي بمجموعة ورق اللعب، مما سيُمكِّن البرنامج أيضًا من سؤال مجموعة ورق اللعب عن العدد المُتبقِّي. اُنظر التوصيف الكامل لجميع البرامج الفرعية (subroutines) بالصنف Deck:

     /**
      * Constructor.  Create an unshuffled deck of cards.
      */
     public Deck()

     /**
      * Put all the used cards back into the deck,
      * and shuffle it into a random order.
      */
     public void shuffle()

     /**
      * As cards are dealt from the deck, the number of 
      * cards left decreases.  This function returns the 
      * number of cards that are still left in the deck.
      */
     public int cardsLeft()

     /**
      * Deals one card from the deck and returns it.
      * @throws IllegalStateException if no more cards are left.
      */
     public Card dealCard()

يَتَضمَّن التوصيف كل ما تحتاج إلى مَعرِفته لكي تَتَمكَّن من اِستخدَام الصَنْف Deck، ولكنه لا يُخبرنا عن كيفية تّنْفيذ الصنف، فبالنهاية هذا ليس تمرينًا على كتابة الشيفرة (coding) وإنما على التصميم (design). يمكنك أيضًا الإطلاع على الشيفرة المصدرية للصنف بالملف Deck.java إذا شئت. ستلاحظ أن الصنف يحتوي على متغير نسخة (instance variable) عبارة عن مصفوفة من النوع Cards. ربما لن تفهم بعض النقاط بالتنفيذ (implementation) الداخلي للصنف، ولكن ما يزال بإمكانك استخدامه بالبرامج الخاصة بك كصندوق أسود (black box).

بالمثل، يُمكِننا تحليل الصَنْف Hand. عندما نُنشِئ يد (hand) لأول مرة، فإنها لا تَحتوِي على أي ورق لعب (card). سيَتَضمَّن الصَنْف Hand تابع النُسخة (instance method)‏ addCard()‎ المسئول عن إضافة ورقة لعب إلى اليد. يَستقبِل ذلك التابع مُعامِلًا (parameter) من النوع Card لتَخْصِيص ورقة اللعب (card) المضافة. سيَتَضمَّن الصنف أيضًا تابع النُسخة removeCard()‎ المسئول عن استبعاد ورقة لعب من اليد. بالمثل، يَستقبِل ذلك التابع مُعامِلًا (parameter) من النوع Card لتَخْْصِيص ورقة اللعب (card) المُستبعدَة. والآن، لنطرح السؤال التالي: عند استبعاد ورقة لعب معينة من يد، هل ينبغي أن نُخصِّص ورقة اللعب ذاتها كأن نقول "استبعد ورقة الآص البستوني" أم عَبْر مَوضِعها باليد كأن نقول "استبعد ورقة اللعب الثالثة باليد"؟ يُمكِننا في الواقع السماح بالخيارين، فكما تَعلَم، يُمكِن لأيّ صنف أن يَتَضمَّن تابعين (methods) بنفس الاسم بشَّرْط أن يَكُون لديهما أعداد مختلفة أو أنواع مختلفة من المُعامِلات. ولهذا، سيَتَضمَّن الصَنْف نسختين من تابع النسخة removeCard()‎. يَستقبِل الأول مُعامِلًا (parameter) من النوع Card؛ لتَخْصِيص ورقة اللعب ذاتها المطلوب استبعادها، بينما يَستقبِل الآخر مُعامِلا (parameter) من النوع int؛ لتَخْصِيص مَوضِع ورقة اللعب المطلوب استبعادها. قد تَحتوِي اليد على عدد مُتْغيِّر من ورق اللعب، لذا سنُضيف تابع النُسخة getCardCount()‎ ليُعيد عدد ورق اللعب الموجود باليد. وأخيرًا، يُمكِننا أيضًا أن نضيف توابع نسخ (instance methods) آخرى لترتيب ورق اللعب باليد؛ حيث يُفضِّل كثير من اللاعبين ترتيب ورق اللعب بأيديهم بحيث يَتجَاوَر ورق اللعب من نفس القيمة. اُنظر التوصيف الكامل للصَنْف Hand القابل لإعادة الاستخدام:

    /**
     * Constructor. Create a Hand object that is initially empty.
     */
    public Hand()

    /**
     * Discard all cards from the hand, making the hand empty.
     */
    public void clear()

    /**
     * Add the card c to the hand.  c should be non-null.
     * @throws NullPointerException if c is null.
     */
    public void addCard(Card c)

    /**
     * If the specified card is in the hand, it is removed.
     */
    public void removeCard(Card c)

    /**
     * Remove the card in the specified position from the
     * hand.  Cards are numbered counting from zero.
     * @throws IllegalArgumentException if the specified 
     *    position does not exist in the hand.
     */
    public void removeCard(int position)

    /**
     * Return the number of cards in the hand.
     */
    public int getCardCount()

    /**
     * Get the card from the hand in given position, where 
     * positions are numbered starting from 0.
     * @throws IllegalArgumentException if the specified 
     *    position does not exist in the hand.
     */
    public Card getCard(int position)

    /**
     * Sorts the cards in the hand so that cards of the same 
     * suit are grouped together, and within a suit the cards 
     * are sorted by value.
     */
    public void sortBySuit()

    /**
     * Sorts the cards in the hand so that cards are sorted into
     * order of increasing value.  Cards with the same value 
     * are sorted by suit. Note that aces are considered
     * to have the lowest value.
     */
    public void sortByValue()

يمكنك الإطلاع على الشيفرة المصدرية للصَنْف بالملف Hand.java. لاحِظ أنك لن تَتَمكَّن من فهم بعض الأشياء ضِمْن تَّنْفيذ (implementation) الصَنْف، ولكنها لن تمنعك من اِستخدَام الصَنْف بمشروعاتك.

الصنف Card

سنَفْحَص تصميم الصَنْف Card وتَّنْفيذه (implementation) تفصيليًا. يَحتوِي الصنف على بَانِي (constructor) يَستقبِل كُلًا من قيمة (value) ورقة اللعبة المُنشئة ورمزها (suit). نستطيع تمثيل الرموز الأربعة باستخدام الأعداد الصحيحة ٠ و ١ و ٢ و ٣، ولكن لصعوبة تَذكُّر الرمز الذي يُمثله كل عدد منها، عَرَّفنا أربعة ثوابت مُسماة (named constants) بالصَنْف Card لتمثيل الاحتمالات الأربعة، فمثلًا، يُمثِل Card.SPADES الرمز "بستوني spades". صَرَّحنا عن تلك الثوابت لتَكُون من النوع int، وباِستخدَام المُبدِّلات public final static. كان من المُمكن أن نَستخدِم أنواع التعداد (enumerated type)، ولكن سنكتفي بالاعتماد على الثوابت من النوع العددي الصحيح. أما قيم ورق اللعب المُحتملة فهي الأعداد من ١ وصولًا إلى ١٣، بحيث تُمثِل الأعداد ١ و ١١ و ١٢ و ١٣ كُلًا من "الآص" و "الرجل أو الشبّ" و "الملكة أو البنت" و "الملك أو الشايب" على الترتيب. بالمثل، عَرَّفنا بعض الثوابت المُسماة (named constants) لتمثيل كُلًا من ورقة الآص، وورق اللعب المُصور. ستَجِدْ أيضًا أننا قد أضفنا ورقة "الجوكر".

بمُجرَّد معرفة كُلًا من قيمة (value) ورقة اللعب، ورمزها (suit)، نستطيع استدعاء البَانِي (constructor) لإنشاء كائن من النوع Card، كالتالي:

card1 = new Card( Card.ACE, Card.SPADES );  // أنشئ ورقة آص بستوني
card2 = new Card( 10, Card.DIAMONDS );   // أنشئ ورقة عشرة ديناري
// ‫يُسمَح بذلك طالما كانت s و v تعبيرات من النوع العددي الصحيح
card3 = new Card( v, s );  

يحتاج أي كائن من الصَنْف Card إلى مُتْغيِّرات نُسخ (instance variables) لتمثيل كُلًا من قيمة ورقة اللعب ورمزها، لذلك أضفنا مُتْغيِّرات النسخ suit و value للصَنْف. بدايةً، صَرَّحنا عنها باِستخدَام المُبدِّل private حتى يَستحِيل تَعْديلها من خارج الصَنْف، وفي المقابل، أضفنا تابعي الجَلْب getSuit()‎ و getValue()‎ لكي تَتَمكَّن الشيفرة خارج الصَنْف من قراءة قيمة ورقة اللعب ورمزها. يَعنِي ذلك أن قيم تلك المُتْغيِّرات لن تتغير نهائيًا بَعْد تهيئتها المبدئية بالباني (constructor)، ولهذا كان من المنطقي التَّصْريح عنها باستخدام المُبدِّل final. يُمكِنك التَّصْريح عن أيّ مُتْغيِّر نُسخة (instance variable) عمومًا باِستخدَام المُبدِّل final، بشَّرْط إِسْناد قيمة إليه إما بتَعْليمَة التَّصْريح (declaration) أو بكل البواني (constructor) المُعرَّفة بذلك الصَنْف. تعدّ كائنات ذلك الصَنْف كائنات ثابتة أو غَيْر قابلة للتعديل (immutable)؛ لأن جميع مُتْغيِّرات النسخ المُعرَّفة ضِمْنها قد صُرِّح عنها باِستخدَام المُبدِّل final.

أضفنا أيضًا بعض التوابع (methods) الآخرى إلى الصَنْف لطباعة كائناته (objects) بصيغة مقروءة، فمثلًا، بدلًا من طباعة العدد ٢ المُستخدَم لتمثيل الرمز "ديناري diamonds" ضِمْن الصَنْف، يُفضَّل طباعة الكلمة "Diamonds". ولأن عملية طباعة رمز ورقة اللعب شيئًا يُرجَح أن نحتاج إليه بالكثير من البرامج الآخرى، كان من المعقول إضافته للصَنْف، ولهذا أضفنا توابع النُسخ getValueAsString()‎ و getSuitAsString()‎ بحيث تُعيد التمثيل النصي (string representations) لكُلًا من قيمة ورقة اللعب ورمزها على الترتيب. بالإضافة إلى ذلك، عَرَّفنا تابع النسخة toString()‎ ليُعيد سِلسِلة نصية تَتَضمَّن كُلًا من قيمة ورقة اللعب ورمزها، مثل "ملكة القلوب". يُستدعَى هذا التابع (method) تلقائيًا لتَحْوِيل كائن من النوع Card إلى النوع String أينما اِستخدَمناه ضِمْن سياق يحتاج إلى سِلسِلة نصية، مثلًا عند ضمه (concatenate) إلى سِلسِلة نصية باِستخدَام العَامِل +. أي أن التَعْليمَة التالية:

System.out.println( "Your card is the " + card );

تُكافئ تمامًا التَعْليمَة:

System.out.println( "Your card is the " + card.toString() );

إذا كانت ورقة اللعب هي "ملكة القلوب"، فستَطبَع التَعْليمَتان السابقتان نفس السِلسِلة النصية "Your card is the Queen of Hearts".

اُنظر شيفرة الصَنْف Card بالكامل، كما أنها موجودة بالملف Card.java. يُعدّ هذا الصنف عامًا بما فيه الكفاية بما يُتيِح إعادة اِستخدَامه، أيّ أن العمل الذي بذلناه أثناء كُلًا من التصميم (designing)، وكتابة الشيفرة، والاختبار (testing) سيُؤتي ثماره على المدى الطويل.

public class Card {

   public final static int SPADES = 0;   
   public final static int HEARTS = 1;
   public final static int DIAMONDS = 2;
   public final static int CLUBS = 3;
   public final static int JOKER = 4;

   public final static int ACE = 1;      
   public final static int JACK = 11;    
   public final static int QUEEN = 12;   
   public final static int KING = 13;

   /**
    * This card's suit, one of the constants SPADES, HEARTS, DIAMONDS,
    * CLUBS, or JOKER.  The suit cannot be changed after the card is
    * constructed.
    */
   private final int suit; 

   /**
    * The card's value.  For a normal card, this is one of the values
    * 1 through 13, with 1 representing ACE.  For a JOKER, the value
    * can be anything.  The value cannot be changed after the card
    * is constructed.
    */
   private final int value;

   /**
    * Creates a Joker, with 1 as the associated value.  (Note that
    * "new Card()" is equivalent to "new Card(1,Card.JOKER)".)
    */
   public Card() {
      suit = JOKER;
      value = 1;
   }

   /**
    * Creates a card with a specified suit and value.
    * @param theValue the value of the new card.  For a regular card (non-joker),
    * the value must be in the range 1 through 13, with 1 representing an Ace.
    * You can use the constants Card.ACE, Card.JACK, Card.QUEEN, and Card.KING.  
    * For a Joker, the value can be anything.
    * @param theSuit the suit of the new card.  This must be one of the values
    * Card.SPADES, Card.HEARTS, Card.DIAMONDS, Card.CLUBS, or Card.JOKER.
    * @throws IllegalArgumentException if the parameter values are not in the
    * permissible ranges
    */
   public Card(int theValue, int theSuit) {
      if (theSuit != SPADES && theSuit != HEARTS && theSuit != DIAMONDS && 
            theSuit != CLUBS && theSuit != JOKER)
         throw new IllegalArgumentException("Illegal playing card suit");
      if (theSuit != JOKER && (theValue < 1 || theValue > 13))
         throw new IllegalArgumentException("Illegal playing card value");
      value = theValue;
      suit = theSuit;
   }

   /**
    * Returns the suit of this card.
    * @returns the suit, which is one of the constants Card.SPADES, 
    * Card.HEARTS, Card.DIAMONDS, Card.CLUBS, or Card.JOKER
    */
   public int getSuit() {
      return suit;
   }

   /**
    * Returns the value of this card.
    * @return the value, which is one of the numbers 1 through 13, inclusive for
    * a regular card, and which can be any value for a Joker.
    */
   public int getValue() {
      return value;
   }

   /**
    * Returns a String representation of the card's suit.
    * @return one of the strings "Spades", "Hearts", "Diamonds", "Clubs"
    * or "Joker".
    */
   public String getSuitAsString() {
      switch ( suit ) {
      case SPADES:   return "Spades";
      case HEARTS:   return "Hearts";
      case DIAMONDS: return "Diamonds";
      case CLUBS:    return "Clubs";
      default:       return "Joker";
      }
   }

   /**
    * Returns a String representation of the card's value.
    * @return for a regular card, one of the strings "Ace", "2",
    * "3", ..., "10", "Jack", "Queen", or "King".  For a Joker, the 
    * string is always numerical.
    */
   public String getValueAsString() {
      if (suit == JOKER)
         return "" + value;
      else {
         switch ( value ) {
         case 1:   return "Ace";
         case 2:   return "2";
         case 3:   return "3";
         case 4:   return "4";
         case 5:   return "5";
         case 6:   return "6";
         case 7:   return "7";
         case 8:   return "8";
         case 9:   return "9";
         case 10:  return "10";
         case 11:  return "Jack";
         case 12:  return "Queen";
         default:  return "King";
         }
      }
   }

   /**
    * Returns a string representation of this card, including both
    * its suit and its value (except that for a Joker with value 1,
    * the return value is just "Joker").  Sample return values
    * are: "Queen of Hearts", "10 of Diamonds", "Ace of Spades",
    * "Joker", "Joker #2"
    */
   public String toString() {
      if (suit == JOKER) {
         if (value == 1)
            return "Joker";
         else
            return "Joker #" + value;
      }
      else
         return getValueAsString() + " of " + getSuitAsString();
   }


} // end class Card

لعبة ورق بسيطة

أخيرًا، تَستعرِض الشيفرة التالية برنامجًا كاملًا يَستخدِم الصَنْفين Card و Deck. يَسمَح البرنامج للمُستخدِم بلعب لعبة ورق بسيطة اسمها هو HighLow. تُخلَط مجموعة ورق اللعب (deck)، وتُسحَب ورقة لعب واحدة، ليراها المُستخدِم. بَعْد ذلك، عليه أن يَتوقَّع ما إذا كانت ورقة اللعب التالية ستَكُون أكبر أو أقل من ورقة اللعب الحالية. إذا كان تَوقُّع المُستخدِم صحيحًا، تَحلّ ورقة اللعب التالية مَحلّ ورقة اللعب الحالية، ثم يَتوقَّع المُستخدِم مرة آخرى، ويستمر الأمر إلى أن يَتوقَّع المُستخدِم تَوقُّعًا خاطئًا. مجموع النقاط التي تَحصَّل عليها المُستخدِم تُساوِي عدد التوقُّعات الصحيحة.

يَتَضمَّن البرنامج تابعًا ساكنًا (static method). تُوكَل مُهِمّة لعب مباراة واحدة فقط من لعبة الورق HighLow لذلك التابع. يَسمَح البرنامج main()‎ للمُستخدِم بلعب عدة مباريات، وبالنهاية، يُخبره بمتوسط النقاط التي تَحصَّل عليها خلال جميع المباريات.

لن نَمُرّ عَبْر مراحل تطوير الخوارزمية (algorithm) المُستخدَمة بالبرنامج، لكن ينبغي عليك أن تقرأها بحرص، وأن تتأكَّد من فِهم طريقة عملها. يُعيد البرنامج الفرعي (subroutine) المَسئول عن لعب مباراة واحدة من لعبة الورق HighLow نقاط المُستخدِم بالمباراة كقيمة مُعادة (return value) إلى البرنامج main حيث تُستخدَم. اُنظر البرنامج:

import textio.TextIO;

public class HighLow {


   public static void main(String[] args) {

      System.out.println("This program lets you play the simple card game,");
      System.out.println("HighLow.  A card is dealt from a deck of cards.");
      System.out.println("You have to predict whether the next card will be");
      System.out.println("higher or lower.  Your score in the game is the");
      System.out.println("number of correct predictions you make before");
      System.out.println("you guess wrong.");
      System.out.println();

      int gamesPlayed = 0;     // عدد المباريات التي لعبها المستخدم
      int sumOfScores = 0;     // مجموع نقاط المستخدم
      double averageScore;     // متوسط نقاط المستخدم
       // يحمل رد المستخدم على سؤاله عما إذا كان يريد لعب مباراة إضافية
      boolean playAgain;       

      do {
         int scoreThisGame;        // نقاط المستخدم بمباراة واحدة
         scoreThisGame = play();   // العب مباراة مع المستخدم
         sumOfScores += scoreThisGame;
         gamesPlayed++;
         System.out.print("Play again? ");
         playAgain = TextIO.getlnBoolean();
      } while (playAgain);

      averageScore = ((double)sumOfScores) / gamesPlayed;

      System.out.println();
      System.out.println("You played " + gamesPlayed + " games.");
      System.out.printf("Your average score was %1.3f.\n", averageScore);

   }  // ‫نهاية main()


   /**
    * Lets the user play one game of HighLow, and returns the
    * user's score in that game.  The score is the number of
    * correct guesses that the user makes.
    */
   private static int play() {

       // أنشئ كائن مجموعة ورق لعب جديدة وخزن مرجعه بالمتغير
      Deck deck = new Deck();  

      Card currentCard;  // ورقة اللعب الحالية التي يراها المستخدم

       // ورقة اللعب التالية التي يتوقع المستخدم ما 
       // إذا كانت أكبر أو أقل من ورقة اللعب الحالية
      Card nextCard;   

      int correctGuesses ;  // عدد توقعات المستخدم الصحيحة

      char guess;   // ‫تخمين المستخدم ويحمل القيمة L أو H

      deck.shuffle();  // رتب مجموعة ورق اللعب عشوائيًا

      correctGuesses = 0;
      currentCard = deck.dealCard();
      System.out.println("The first card is the " + currentCard);

      while (true) {  // تنتهي الحلقة عند التخمين الخاطئ

         /* اقرأ تخمين المستخدم */

         System.out.print("Will the next card be higher (H) or lower (L)?  ");
         do {
             guess = TextIO.getlnChar();
             guess = Character.toUpperCase(guess);
             if (guess != 'H' && guess != 'L') 
                System.out.print("Please respond with H or L:  ");
         } while (guess != 'H' && guess != 'L');

         /* اسحب ورقة اللعب التالية وأظهرها للمستخدم */

         nextCard = deck.dealCard();
         System.out.println("The next card is " + nextCard);

         /* افحص تخمين المستخدم */

         if (nextCard.getValue() == currentCard.getValue()) {
            System.out.println("The value is the same as the previous card.");
            System.out.println("You lose on ties.  Sorry!");
            break;  // أنهي المباراة
         }
         else if (nextCard.getValue() > currentCard.getValue()) {
            if (guess == 'H') {
                System.out.println("Your prediction was correct.");
                correctGuesses++;
            }
            else {
                System.out.println("Your prediction was incorrect.");
                break;  // أنهي المباراة
            }
         }
         else {  // إذا كانت ورقة اللعب التالية أقل
            if (guess == 'L') {
                System.out.println("Your prediction was correct.");
                correctGuesses++;
            }
            else {
                System.out.println("Your prediction was incorrect.");
                break;  // أنهى المباراة
            }
         }

         /* تهيئة ضرورية للتكرار التالي ضمن الحلقة  */

         currentCard = nextCard;
         System.out.println();
         System.out.println("The card is " + currentCard);

      } // نهاية الحلقة

      System.out.println();
      System.out.println("The game is over.");
      System.out.println("You made " + correctGuesses 
                                           + " correct predictions.");
      System.out.println();

      return correctGuesses;

   }  // ‫نهاية play()


} // ‫نهاية الصنف HighLow

ترجمة -بتصرّف- للقسم Section 4: Programming Example: Card, Hand, Deck من فصل Chapter 5: Programming in the Large II: Objects and Classes من كتاب Introduction to Programming Using Java.

 

اقرأ أيضًا 

تعرف على أشهر لغات برمجة الألعاب


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...