Camicia

Camicia

متوسط

مقدمه

یک بعدازظهر بارانی، پشت میز آشپزخانه نشسته‌اید و با مادربزرگتان ورق‌بازی می‌کنید. بازی، نسخه‌ی خودش از کامیکیا است.

اولش مثل یک مسابقه‌ی دوستانه‌ی دیگر به نظر می‌رسد: ورق‌ها روی میز کوبیده می‌شوند، خنده دور میز می‌پیچد و گاهی لبخند پیروزمندانه‌ی نونا. اما هر چه بازی طول می‌کشد، اتفاق عجیبی می‌افتد. همان ورق‌ها مدام برمی‌گردند. شما یکی پس از دیگری ورق بازی می‌کنید، اما به نظر نمی‌رسد بازی پایانی داشته باشد.

کم‌کم کنجکاو می‌شوید. آیا این بازی بالاخره تمام می‌شود؟ یا می‌توانیم تا ابد بازی کنیم؟

بعداً، از سر کنجکاوی، در اینترنت جست‌وجو می‌کنید و با کمال تعجب می‌فهمید که آن اتفاق فقط بدشانسی نبوده است. شما و مادربزرگتان ممکن است به یکی از طولانی‌ترین دنباله‌های ممکن برخورده باشید! ناگهان جذب این ماجرا می‌شوید. چیزی که با یک بازی سرگرم‌کننده شروع شده بود، به یک ماجراجویی تبدیل شده است: چنین بازی‌ای واقعاً چقدر می‌تواند طول بکشد؟ آیا می‌توانید دنباله‌ای حتی طولانی‌تر از آن یکی که پشت میز آشپزخانه بازی کردید پیدا کنید؟ شاید حتی آن‌قدر طولانی که رکورد جهانی تازه‌ای ثبت کنید؟

و به این ترتیب، تنها با یک دست ورق و کمی نبوغ الگوریتمی، تصمیم می‌گیرید که بررسی کنید…

دستورالعمل‌ها

در این تمرین، بازی‌ای را شبیه‌سازی می‌کنید که بسیار به بازی کارتی کلاسیک Camicia شباهت دارد. برنامه‌ی شما پیکربندی اولیه‌ی دسته‌های کارت دو بازیکن را دریافت می‌کند و باید بازی را تا وقتی که تمام شود شبیه‌سازی کند (یا تشخیص دهد که هرگز تمام نمی‌شود).

قوانین

  • دسته‌ی کارت بین دو بازیکن تقسیم می‌شود. کارت‌های هر بازیکن از چپ به راست خوانده می‌شوند و کارت سمت چپ، بالای دسته است.
  • هر دور شامل بازی کردن حداقل یک کارت از سوی هر دو بازیکن است.
  • بازیکنان به نوبت کارت بالای دسته‌ی خود را روی تل وسط قرار می‌دهند.
  • اگر کارت یک کارت عددی باشد (۲ تا ۱۰)، نوبت به‌سادگی به بازیکن دیگر می‌رسد.
  • اگر کارت یک کارت پرداخت باشد، باید جریمه‌ای پرداخت شود:
    • J → بازیکن مقابل باید ۱ کارت بپردازد.
    • Q → بازیکن مقابل باید ۲ کارت بپردازد.
    • K → بازیکن مقابل باید ۳ کارت بپردازد.
    • A → بازیکن مقابل باید ۴ کارت بپردازد.
  • اگر بازیکنی که در حال پرداخت جریمه است، کارت پرداخت دیگری رو کند، از پرداخت جریمه دست می‌کشد. سپس بازیکن دیگر باید بر اساس کارت پرداخت جدید جریمه بپردازد.
  • اگر جریمه بدون وقفه به‌طور کامل پرداخت شود، بازیکنی که آخرین کارت پرداخت را گذاشته است، تل وسط را جمع می‌کند و آن را در پایین دسته‌ی خود قرار می‌دهد. سپس همان بازیکن دور بعد را شروع می‌کند.
  • اگر کارت‌های بازیکنی تمام شود و نتواند کارتی بازی کند (چه در حال پرداخت جریمه باشد و چه وقتی که نوبتش است)، بازیکن دیگر تل وسط را جمع می‌کند.
  • به لحظه‌ای که بازیکن کارت‌های تل وسط را جمع می‌کند، «حقه» می‌گویند.
  • اگر پس از یک حقه، همه‌ی کارت‌ها در اختیار یک بازیکن باشد، بازی تمام می‌شود.
  • بازی به‌محض اینکه دسته‌ها دقیقاً همان وضعیتی را داشته باشند که پیش‌تر در جریان بازی داشتند، وارد حلقه می‌شود، آن هم بدون در نظر گرفتن کارت‌های عددی!

مثال‌ها

مثال کوچکی از مسابقه‌ای که تمام می‌شود.

دور بازیکن 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"
    • کارت‌ها: تعداد کل کارت‌هایی که در طول بازی بازی شده‌اند
    • حقه‌ها: تعداد دفعاتی که تل وسط جمع شده است
Advanced

برای کسانی که می‌خواهند چالش هیجان‌انگیزتری را بپذیرند، جست‌وجو برای یافتن رکوردهای دیگر طولانی‌ترین بازی پایان‌دار هنوز باز است. ۶۵۳٬۵۳۴٬۱۳۴٬۸۸۶٬۸۷۸٬۲۴۵٬۰۰۰ (تقریباً ۶۵۴ کوینتیلیون) حالت ممکن وجود دارد و ما هنوز همه‌ی آن‌ها را محاسبه نکرده‌ایم!

مزایا و معایب 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 می‌تواند تاپل‌ها یا فهرست‌ها را به‌عنوان کلید ذخیره کند.

ویرایش از طریق GitHub این لینک در پنجره یا زبانه‌ی جدیدی باز می‌شود
Roc Exercism

آماده‌اید Camicia را شروع کنید؟

در Exercism ثبت‌نام کنید تا Roc را همراه با 135 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.