في أصيل يومٍ ممطر، تجلس إلى طاولة المطبخ تلعب الورق مع جدّتك. اللعبة هي نسختها الخاصة من Camicia.
في البداية تبدو مجرد مباراة ودّية أخرى: أوراق تُلقى على الطاولة، وضحكات تتعالى حولها، وابتسامة انتصار عابرة من نونا. لكن مع استمرار اللعبة، يحدث شيء غريب. الأوراق ذاتها تظل تدور عائدة من جديد. تلعب ورقة تلو الأخرى، ومع ذلك لا تبدو النهاية قادمة أبدًا.
تبدأ تتساءل. هل ستنتهي هذه اللعبة يومًا؟ أم هل يمكن أن نواصل اللعب إلى الأبد؟
لاحقًا، وبدافع الفضول، تبحث على الإنترنت، ولدهشتك تكتشف أن ما حدث لم يكن مجرد سوء حظ. ربما صادفت أنت وجدّتك واحدة من أطول المتتاليات الممكنة! وفجأة تجد نفسك مأسورًا. ما بدأ كلعبة عابرة تحوّل إلى سعي: كم يمكن أن تدوم لعبة كهذه حقًا؟ هل يمكنك أن تجد متتالية أطول مما لعبتها على طاولة المطبخ؟ ربما حتى طويلة بما يكفي لتحقيق رقم قياسي عالمي جديد؟
وهكذا، وأنت لا تملك سوى رزمة أوراق لعب وقليلٍ من البراعة الخوارزمية، تقرّر أن تحقّق في الأمر...
في هذا التمرين، ستُحاكي لعبة شديدة الشبه بلعبة الورق الكلاسيكية Camicia. سيستقبل برنامجك التهيئة الأولية لرزمتي لاعبين، وعليه محاكاة اللعبة حتى تنتهي (أو يكتشف أنها لن تنتهي أبدًا).
مثال صغير لمباراة تنتهي.
| الجولة | اللاعب أ | اللاعب ب | الكومة | العقوبة المستحقة |
|---|---|---|---|---|
| 1 | 2 A 7 8 Q 10 | 3 4 5 6 K 9 J | - | |
| 1 | A 7 8 Q 10 | 3 4 5 6 K 9 J | 2 | - |
| 1 | A 7 8 Q 10 | 4 5 6 K 9 J | 2 3 | - |
| 1 | 7 8 Q 10 | 4 5 6 K 9 J | 2 3 A | اللاعب ب: 4 |
| 1 | 7 8 Q 10 | 5 6 K 9 J | 2 3 A 4 | اللاعب ب: 3 |
| 1 | 7 8 Q 10 | 6 K 9 J | 2 3 A 4 5 | اللاعب ب: 2 |
| 1 | 7 8 Q 10 | K 9 J | 2 3 A 4 5 6 | اللاعب ب: 1 |
| 1 | 7 8 Q 10 | 9 J | 2 3 A 4 5 6 K | اللاعب أ: 3 |
| 1 | 8 Q 10 | 9 J | 2 3 A 4 5 6 K 7 | اللاعب أ: 2 |
| 1 | Q 10 | 9 J | 2 3 A 4 5 6 K 7 8 | اللاعب أ: 1 |
| 1 | 10 | 9 J | 2 3 A 4 5 6 K 7 8 Q | اللاعب ب: 2 |
| 1 | 10 | J | 2 3 A 4 5 6 K 7 8 Q 9 | اللاعب ب: 1 |
| 1 | 10 | - | 2 3 A 4 5 6 K 7 8 Q 9 J | اللاعب أ: 1 |
| 1 | - | - | 2 3 A 4 5 6 K 7 8 Q 9 J 10 | - |
| 2 | - | 2 3 A 4 5 6 K 7 8 Q 9 J 10 | - | - |
status: "finished", cards: 13, tricks: 1
هذا مثال صغير لمباراة تدخل في حلقة.
| الجولة | اللاعب أ | اللاعب ب | الكومة | العقوبة المستحقة |
|---|---|---|---|---|
| 1 | J 2 3 | 4 J 5 | - | - |
| 1 | 2 3 | 4 J 5 | J | اللاعب ب: 1 |
| 1 | 2 3 | J 5 | J 4 | - |
| 2 | 2 3 J 4 | J 5 | - | - |
| 2 | 3 J 4 | J 5 | 2 | - |
| 2 | 3 J 4 | 5 | 2 J | اللاعب أ: 1 |
| 2 | J 4 | 5 | 2 J 3 | - |
| 3 | J 4 | 5 2 J 3 | - | - |
| 3 | J 4 | 2 J 3 | 5 | - |
| 3 | 4 | 2 J 3 | 5 J | اللاعب ب: 1 |
| 3 | 4 | J 3 | 5 J 2 | - |
| 4 | 4 5 J 2 | J 3 | - | - |
بداية الجولة 4 تطابق بداية الجولة 2. تذكّر أن قيمة البطاقات العددية لا تهم.
status: "loop", cards: 8, tricks: 3
"finished" أو "loop"
لمن يرغب في خوض تحدٍ أكثر إثارة، لا يزال البحث عن أرقام قياسية أخرى لأطول لعبة لها نهاية مفتوحًا. هناك 653,534,134,886,878,245,000 (حوالي 654 كوينتليون) احتمال، ولم نحسبها كلها بعد!
from_quote وعيوبهتتولّى دالة Card.from_quote تحليل سلسلة نصية إلى Card.
هذا يجعل من الممكن كتابة كود مثل هذا:
card : Card
card = "Q"
يستدعي هذا الكود Card.from_quote("Q") ضمنيًا في وقت الترجمة.
يمكن أن يؤدي تشغيل الكود في وقت الترجمة بهذا الأسلوب (أو بأساليب أخرى مشروحة أدناه) إلى تسريع البرنامج، لأنه لن يحتاج إلى التعامل مع التحليل والتحقق من صحة البيانات في وقت التشغيل.
علاوة على ذلك، يضمن رفض البيانات غير الصالحة (مثل تنسيق تاريخ غير صحيح) في وقت الترجمة بدلًا من وقت التشغيل، مما يجعل تطبيقك أكثر متانة.
وأخيرًا، يجعل الكود جميلًا وسهل القراءة.
على سبيل المثال، إليك أحد الاختبارات في هذا التمرين:
result = simulate_game({
player_a: ["2", "A"],
player_b: ["3", "4", "5", "6", "K"],
})
لكن قد يفترض من يقرأ هذا الكود أن دالة simulate_game تأخذ سجلًا يحتوي على مصفوفتين من النوع List(Str)، بينما هو في الحقيقة سجل يحتوي على مصفوفتين من النوع List(Card).
قد يؤدي هذا إلى الالتباس والأخطاء.
لهذا السبب، قد تفضّل الصيغة الأطول قليلًا لكن الأكثر وضوحًا: "Q".Card، وهي أيضًا تستدعي from_quote في وقت الترجمة.
في برامجك الخاصة، قد تفضّل استخدام from_str بدلًا من ذلك:
Card := { ... }.{
from_str : Str -> Try(Card, [InvalidCard])
from_str = |card_str| ...
}
يمكنك أيضًا استدعاء from_str (أو أي دالة نقية أخرى) في وقت الترجمة، مثل هذا:
Ok(card) = Card.from_str("Q")
ملاحظة: وضع هذا التعريف في المستوى الأعلى يجعله يعمل في وقت الترجمة (لأن الدالة نقية ومدخلها معلوم في وقت الترجمة).
داخل دالة، يمكنك استخدام from_str في وقت التشغيل.
استخدم match مع [] و[card, .. as rest] للتمييز بين رزمة فارغة وبطاقتها التالية وبطاقاتها المتبقية.
يمكن أن تُبقي تحديثات السجل مثل { ..state, pile: new_pile } انتقالات اللعبة صريحة دون حالة قابلة للتغيير.
يمكن لـ Set تخزين صفوف أو مصفوفات كمفاتيح عند تتبّع حالات اللعبة التي رُئيت سابقًا.
سجّل في Exercism لتتعلّم وتتقن Roc عبر 135 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.