یک بعدازظهر بارانی، پشت میز آشپزخانه نشستهاید و با مادربزرگتان ورقبازی میکنید. بازی، نسخهی خودش از کامیکیا است.
اولش مثل یک مسابقهی دوستانهی دیگر به نظر میرسد: ورقها روی میز کوبیده میشوند، خنده دور میز میپیچد و گاهی لبخند پیروزمندانهی نونا. اما هر چه بازی طول میکشد، اتفاق عجیبی میافتد. همان ورقها مدام برمیگردند. شما یکی پس از دیگری ورق بازی میکنید، اما به نظر نمیرسد بازی پایانی داشته باشد.
کمکم کنجکاو میشوید. آیا این بازی بالاخره تمام میشود؟ یا میتوانیم تا ابد بازی کنیم؟
بعداً، از سر کنجکاوی، در اینترنت جستوجو میکنید و با کمال تعجب میفهمید که آن اتفاق فقط بدشانسی نبوده است. شما و مادربزرگتان ممکن است به یکی از طولانیترین دنبالههای ممکن برخورده باشید! ناگهان جذب این ماجرا میشوید. چیزی که با یک بازی سرگرمکننده شروع شده بود، به یک ماجراجویی تبدیل شده است: چنین بازیای واقعاً چقدر میتواند طول بکشد؟ آیا میتوانید دنبالهای حتی طولانیتر از آن یکی که پشت میز آشپزخانه بازی کردید پیدا کنید؟ شاید حتی آنقدر طولانی که رکورد جهانی تازهای ثبت کنید؟
و به این ترتیب، تنها با یک دست ورق و کمی نبوغ الگوریتمی، تصمیم میگیرید که بررسی کنید…
در این تمرین، بازیای را شبیهسازی میکنید که بسیار به بازی کارتی کلاسیک Camicia شباهت دارد. برنامهی شما پیکربندی اولیهی دستههای کارت دو بازیکن را دریافت میکند و باید بازی را تا وقتی که تمام شود شبیهسازی کند (یا تشخیص دهد که هرگز تمام نمیشود).
مثال کوچکی از مسابقهای که تمام میشود.
| دور | بازیکن A | بازیکن B | تل | جریمهی باقیمانده |
|---|---|---|---|---|
| 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 | Player B: 4 |
| 1 | 7 8 Q 10 | 5 6 K 9 J | 2 3 A 4 | Player B: 3 |
| 1 | 7 8 Q 10 | 6 K 9 J | 2 3 A 4 5 | Player B: 2 |
| 1 | 7 8 Q 10 | K 9 J | 2 3 A 4 5 6 | Player B: 1 |
| 1 | 7 8 Q 10 | 9 J | 2 3 A 4 5 6 K | Player A: 3 |
| 1 | 8 Q 10 | 9 J | 2 3 A 4 5 6 K 7 | Player A: 2 |
| 1 | Q 10 | 9 J | 2 3 A 4 5 6 K 7 8 | Player A: 1 |
| 1 | 10 | 9 J | 2 3 A 4 5 6 K 7 8 Q | Player B: 2 |
| 1 | 10 | J | 2 3 A 4 5 6 K 7 8 Q 9 | Player B: 1 |
| 1 | 10 | - | 2 3 A 4 5 6 K 7 8 Q 9 J | Player A: 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 | - | - |
وضعیت: "finished"، کارتها: ۱۳، حقهها: ۱
این هم مثال کوچکی از مسابقهای است که به حلقه میافتد.
| دور | بازیکن A | بازیکن B | تل | جریمهی باقیمانده |
|---|---|---|---|---|
| 1 | J 2 3 | 4 J 5 | - | - |
| 1 | 2 3 | 4 J 5 | J | Player B: 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 | Player A: 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 | Player B: 1 |
| 3 | 4 | J 3 | 5 J 2 | - |
| 4 | 4 5 J 2 | J 3 | - | - |
شروع دور ۴ با شروع دور ۲ یکسان است. یادآوری: ارزش کارتهای عددی اهمیتی ندارد.
وضعیت: "loop"، کارتها: ۸، حقهها: ۳
"finished" یا "loop"
برای کسانی که میخواهند چالش هیجانانگیزتری را بپذیرند، جستوجو برای یافتن رکوردهای دیگر طولانیترین بازی پایاندار هنوز باز است. ۶۵۳٬۵۳۴٬۱۳۴٬۸۸۶٬۸۷۸٬۲۴۵٬۰۰۰ (تقریباً ۶۵۴ کوینتیلیون) حالت ممکن وجود دارد و ما هنوز همهی آنها را محاسبه نکردهایم!
from_quote
تابع Card.from_quote یک رشته را به یک Card تبدیل میکند.
این کار امکان نوشتن چیزی مانند این را فراهم میکند:
card : Card
card = "Q"
این code بهصورت ضمنی Card.from_quote("Q") را در زمان کامپایل فراخوانی میکند.
اجرای code در زمان کامپایل با این روش (یا روشهای دیگری که در ادامه توضیح داده میشود) میتواند سرعت برنامه را افزایش دهد، چون نیازی نیست تجزیه و اعتبارسنجی دادهها را در زمان اجرا مدیریت کند.
علاوه بر این، تضمین میکند دادههای نامعتبر (مثلاً قالب نادرست تاریخ) در زمان کامپایل رد شوند، نه در زمان اجرا، و این کار برنامهتان را مقاومتر میکند.
در نهایت، code را خوشخوان و خوانا میکند.
برای مثال، این یکی از testهای این تمرین است:
result = simulate_game({
player_a: ["2", "A"],
player_b: ["3", "4", "5", "6", "K"],
})
با این حال، ممکن است کسی که این code را میخواند تصور کند تابع 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 میتواند تاپلها یا فهرستها را بهعنوان کلید ذخیره کند.