أعتقد أنه ليس سراً على أي شخص أن "Fool" (المشار إليها فيما يلي باسم هذه الكلمة ستكتب بحرف صغير وبدون علامتي اقتباس) هي أكثر ألعاب الورق شيوعًا في روسيا ودول الاتحاد السوفيتي السابق (على الرغم من أنها غير معروفة تقريبًا خارجها). على الرغم من الاسم والقواعد البسيطة إلى حد ما ، إلا أن الفوز بها لا يزال يعتمد على مهارة اللاعب أكثر من الاعتماد على التوزيع العشوائي للبطاقات (في المصطلحات الإنجليزية ، تسمى الألعاب من كلا النوعين لعبة المهارة ولعبة الحظ ، على التوالي. المزيد من لعبة المهارة ).
الغرض من المقالة هو كتابة AI بسيطة للعبة. كلمة "بسيط" تعني ما يلي:
- خوارزمية صنع قرار بديهية (وهذا يعني ، عدم وجود تعلم آلي يتم فيه إخفاء هذه الخوارزمية بعمق "تحت الغطاء") ؛
- قلة الحالة (أي ، يتم توجيه الخوارزمية فقط بواسطة البيانات في الوقت الحالي في الوقت المناسب ، وبكل بساطة ، لا تتذكر أي شيء (على سبيل المثال ، لا "تعد" البطاقات التي تركت اللعبة).
(بالمعنى الدقيق للكلمة ، لم تعد الفقرة الأولى تعطي الحق في مثل هذه الذكاء الاصطناعي ليتم استدعاؤها بالذكاء الاصطناعي في حد ذاته ، ولكن فقط عبارة شبه مستعارة. لكن هذا المصطلح قد تم وضعه في تطوير اللعبة ، وبالتالي فإننا لن نغيره.)
أعتقد أن قواعد اللعبة معروفة للجميع ، لذا لن أذكرها مرة أخرى. أولئك الذين يريدون التحقق ، أنصحك بالاتصال بـ ويكيبيديا ، هناك مقال جيد حول هذا الموضوع.
لذلك دعونا نبدأ. من الواضح أنه في خداع ، كلما كانت البطاقة قديمة ، كلما كان ذلك أفضل في يدك. لذلك ، سنبني الخوارزمية على التقييم الكلاسيكي لقوة اليد ونتخذ قرارًا (على سبيل المثال ، إرم بطاقة معينة) استنادًا إلى هذا التقييم. نخصص القيم للخرائط ، على سبيل المثال ، مثل هذا:
- الآس (أ) - + 600 نقطة ،
- الملك (ك) - + 500 ،
- سيدة (Q) - +400 ،
- جاك (ي) - +300 ،
- عشرة (10) - +200 ،
- تسعة (9) - + 100 ،
- ثمانية (8) - 0 ،
- سبعة (7) - -100 ،
- ستة (6) - -200 ،
- خمسة (5) - 300 ،
- أربعة (4) - -400 ،
- ثلاثة (3) - -500 ،
- وأخيراً ، قم بإلغاء (2) - -600 نقطة.
(نحن نستخدم الأرقام التي هي مضاعفات 100 من أجل التخلص من الفاصلة العائمة في العمليات الحسابية والعمل فقط مع الأعداد الصحيحة. لمعرفة ما نحتاج إلى تقديرات سلبية ، انظر أدناه في المقالة.)
تعتبر بطاقات ورقة الرابحة أكثر قيمة من أي بطاقات بسيطة (حتى أن ورقة رابحة تتفوق على القيمة "العادية") ، والتسلسل الهرمي في بدلة الرابحة هو نفسه ، لذلك لتقييمها ، ببساطة أضف 1300 إلى القيمة "الأساسية" - ثم ، على سبيل المثال ، فإن ورقة رابحة سوف "تكلف" -600 + 1300 = 700 نقطة (أي ، أكثر قليلاً من مجرد بطاقة رابحة غير مؤلمة).
في التعليمة البرمجية (ستكون جميع أمثلة التعليمات البرمجية في المقالة في Kotlin) ، تبدو مثل هذه (ترجع الدالة RANK_MULTIPLIER
relativaCardValue()
التقدير ذاته ، ويكون RANK_MULTIPLIER
مجرد معامل يساوي 100):
for (c in hand) { val r = c.rank val s = c.suit res += ((relativeCardValue(r.value)) * RANK_MULTIPLIER).toInt() if (s === trumpSuit) res += 13 * RANK_MULTIPLIER
للأسف ، هذا ليس كل شيء. من المهم أيضًا مراعاة قواعد التقييم التالية:
- من المفيد أن يكون لديك العديد من البطاقات من نفس القيمة - ليس فقط لأنها يمكن أن "تملأ" الخصم ، ولكن أيضًا صد الهجوم بسهولة (خاصة إذا كانت البطاقات ذات قيمة عالية). على سبيل المثال ، في نهاية اللعبة ، يد (للبساطة ، نفترض أن ما يلي عبارة عن الماسات)
عرض $$ $ \ clubuit \ \ spadesuit 2 \ diamondsuit Q \ heartsuit Q \ Clubsuit Q \ spadesuit Q $$ عرض $$ مثالي تقريبًا (بالطبع ، إذا لم يعارض الخصم بينك وبين الملوك أو ارسالا ساحقا): سوف تتعرض للضرب من قبل السيدات ، وبعد ذلك شنق المنافسين يعطيه زوج من التعادل.
لكن هناك العديد من البطاقات التي لها نفس المجموعة (بالطبع ، غير رابحة) ، على العكس من ذلك ، لها عيب - سوف "تتداخل" مع بعضها البعض. على سبيل المثال ، اليدعرض $$ $ \ spadesuit 5 \ spadesuit J \ spadesuit A \ diamondsuit 6 \ diamondsuit 9 \ diamondsuit K $$ display $$ من المؤسف للغاية - حتى لو لم يقم الخصم "بضرب" البطاقة الرابحة الخاصة بك مع الخطوة الأولى ويذهب ببطاقة من بدلة الذروة ، فإن جميع البطاقات الأخرى التي يتم طرحها ستكون ذات بدلات أخرى ، وسيتعين عليهم تقديم بطاقات رابحة. بالإضافة إلى ذلك ، هناك احتمال كبير في أن يظل الاندفاع الخمسة غير مطالب به - لديك جميع الأوراق الرابحة بكرامة أعلى من خمس ، لذلك تحت أي ظرف من الظروف (ما لم تكن ، بالطبع ، إذا قمت بإدخال البطاقة مبدئيًا) فلن تتمكن من تغطيتها بأي بطاقة أخرى - من المحتمل جدًا أن تأخذها عالية. من ناحية أخرى ، قمنا باستبدال مقبس البستوني بعشرة أندية ، بينما استبدل الرابحة الستة بثلاثة أضعاف:
عرض $$ $ \ spadesuit 5 \ clubuit 10 \ spadesuit A \ diamondsuit 3 \ diamondsuit 9 \ diamondsuit K $$ display $$ على الرغم من حقيقة أننا استبدلنا البطاقات بأصغر سنا ، فإن هذه اليد أفضل كثيرًا - أولاً ، لن تضطر إلى منح بطاقة رابحة على غلاف البطاقات (وسيكون من الأرجح استخدام الآس البستوني) ، وثانياً ، إذا تغلبت على أي ورق ثم بطاقة تحتوي على ورقة رابحة ثلاثة ، هناك احتمال أن يرميك شخص ما ثلاثة من البستوني (لأنه عادة لا يوجد أي معنى في الاحتفاظ بهذه البطاقة) ، وستحصل على "الخمسة".
لتنفيذ هذه الاستراتيجيات ، نقوم بتعديل خوارزمية لدينا: هنا نعتبر عدد بطاقات كل دعوى وميزة ...
val bonuses = doubleArrayOf(0.0, 0.0, 0.5, 0.75, 1.25) var res = 0 val countsByRank = IntArray(13) val countsBySuit = IntArray(4) for (c in hand) { val r = c.rank val s = c.suit res += ((relativeCardValue(r.value)) * RANK_MULTIPLIER).toInt() if (s === trumpSuit) res += 13 * RANK_MULTIPLIER countsByRank[r.value - 1]++ countsBySuit[s.value]++ }
... نضيف هنا مكافآت لهم ( Math.max
استدعاء Math.max
حتى لا تتراكم مكافآت سلبية للبطاقات المنخفضة - لأنه في هذه الحالة يكون مفيدًا أيضًا) ...
for (i in 1..13) { res += (Math.max(relativeCardValue(i), 1.0) * bonuses[countsByRank[i - 1]]).toInt() }
... وهنا ، على العكس من ذلك ، فإننا نعتبر دعوى غير متوازنة في الدعاوى ( UNBALANCED_HAND_PENALTY
تعيين القيمة UNBALANCED_HAND_PENALTY
تجريبي على 200):
أخيرًا ، نأخذ في الاعتبار شيء عادي مثل عدد البطاقات في متناول اليد. في الواقع ، فإن الحصول على 12 بطاقة جيدة في بداية اللعبة أمر جيد جدًا (خاصةً أنه لا يزال من الممكن إلقاؤها أكثر من 6) ، ولكن في نهاية اللعبة ، عندما يكون هناك خصم فقط مع وجود ورقتين متبقيتين بجانبك ، فإن هذا ليس هو الحال على الإطلاق.
نلخص - بشكل كامل ، تبدو وظيفة التقييم كما يلي:
private fun handValue(hand: ArrayList<Card>, trumpSuit: Suit, cardsRemaining: Int, playerHands: Array<Int>): Int { if (cardsRemaining == 0 && hand.size == 0) { return OUT_OF_PLAY } val bonuses = doubleArrayOf(0.0, 0.0, 0.5, 0.75, 1.25)
لذلك ، لدينا وظيفة التقييم جاهزة. في الجزء التالي ، تم التخطيط لوصف قرارات مهمة أكثر تشويقًا بناءً على مثل هذا التقييم.
شكرا لكم جميعا على اهتمامكم!
ملاحظة: يعد هذا الرمز جزءًا من التطبيق الذي تم تطويره بواسطة المؤلف في وقت فراغه. وهو متوفر على GitHub (الإصدارات الثنائية لسطح المكتب و Android ، وبالنسبة إلى الإصدار الأخير ، يتوفر التطبيق أيضًا على F-Droid ).