Camicia

Camicia

Середня

Вступ

Одного дощового пообіддя ми сидимо за кухонним столом і граємо в карти з бабусею. Це - її власна версія гри Camicia.

Спершу здається, що це просто чергова товариська партія: карти ляскають по столу, сміх за столом, час від часу переможна усмішка Нонни. Але гра тягнеться далі, і відбувається щось дивне. Ті самі карти раз за разом повертаються. Ми викладаємо карту за картою, а кінця все не видно.

І ми починаємо замислюватися. Чи закінчиться ця гра колись? А чи могли б ми грати вічно?

Пізніше, з цікавості, ми шукаємо в інтернеті і, на свій подив, виявляємо, що те, що сталося, не було просто невдачею. Можливо, ми з бабусею натрапили на одну з найдовших можливих послідовностей! Умить це нас затягує. Те, що починалося як невимушена гра, перетворилося на справжній квест: як довго насправді може тривати така гра? Чи можемо ми знайти послідовність, довшу за ту, яку зіграли за кухонним столом? Може, навіть достатньо довгу, щоб встановити новий світовий рекорд?

І ось, маючи лише колоду карт і трохи алгоритмічної винахідливості, ми вирішуємо дослідити...

Вказівки

У цій вправі ми змоделюємо гру, дуже схожу на класичну карткову гру Camicia. Наша програма отримає початкову конфігурацію колод двох гравців і повинна змоделювати гру до її завершення (або визначити, що вона ніколи не завершиться).

Правила

  • Колода ділиться між двома гравцями. Карти гравця читаються зліва направо, де найлівіша карта лежить зверху колоди.
  • Раунд складається з того, що обидва гравці викладають принаймні по одній карті.
  • Гравці по черзі викладають верхню карту своєї колоди на центральну купу.
  • Якщо карта є числовою картою (2-10), хід просто переходить до іншого гравця.
  • Якщо карта є платіжною картою, потрібно сплатити штраф:
    • J → суперник має сплатити 1 карту
    • Q → суперник має сплатити 2 карти
    • K → суперник має сплатити 3 карти
    • A → суперник має сплатити 4 карти
  • Якщо гравець, який сплачує штраф, відкриває ще одну платіжну карту, він припиняє сплачувати штраф. Тоді інший гравець має сплатити штраф на основі нової платіжної карти.
  • Якщо штраф сплачено повністю й без переривання, гравець, який виклав останню платіжну карту, забирає центральну купу і кладе її вниз своєї колоди. Потім цей гравець починає наступний раунд.
  • Якщо в гравця закінчуються карти і він не може викласти карту (або під час сплати штрафу, або коли настає його черга), центральну купу забирає інший гравець.
  • Момент, коли гравець забирає карти з центральної купи, називається взяткою.
  • Якщо після взятки всі карти опиняються в одного гравця, гра завершується.
  • Гра входить у цикл, щойно колоди стають такими самими, якими вони вже були раніше під час гри, не враховуючи числові карти!

Приклади

Невеликий приклад партії, яка завершується.

Раунд Гравець 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 Гравець B: 4
1 7 8 Q 10 5 6 K 9 J 2 3 A 4 Гравець B: 3
1 7 8 Q 10 6 K 9 J 2 3 A 4 5 Гравець B: 2
1 7 8 Q 10 K 9 J 2 3 A 4 5 6 Гравець B: 1
1 7 8 Q 10 9 J 2 3 A 4 5 6 K Гравець A: 3
1 8 Q 10 9 J 2 3 A 4 5 6 K 7 Гравець A: 2
1 Q 10 9 J 2 3 A 4 5 6 K 7 8 Гравець A: 1
1 10 9 J 2 3 A 4 5 6 K 7 8 Q Гравець B: 2
1 10 J 2 3 A 4 5 6 K 7 8 Q 9 Гравець B: 1
1 10 - 2 3 A 4 5 6 K 7 8 Q 9 J Гравець 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 - -

status: "finished", cards: 13, tricks: 1

Це невеликий приклад партії, яка зациклюється.

Раунд Гравець A Гравець B Купа Штраф до сплати
1 J 2 3 4 J 5 - -
1 2 3 4 J 5 J Гравець 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 Гравець 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 Гравець B: 1
3 4 J 3 5 J 2 -
4 4 5 J 2 J 3 - -

Початок раунду 4 збігається з початком раунду 2. Нагадаємо, конкретні значення числових карт не важливі.

status: "loop", cards: 8, tricks: 3

Завдання

  • Використовуючи вхідні дані, змоделюйте гру за наведеними вище правилами.
  • Визначте таку інформацію про гру:
    • Status: "finished" або "loop"
    • Cards: загальна кількість карт, викладених протягом гри
    • Tricks: кількість разів, коли було забрано центральну купу
Advanced

Для тих, хто хоче взятися за цікавіше випробування, пошук інших рекордів найдовшої гри, яка завершується, досі відкритий. Існує 653,534,134,886,878,245,000 (приблизно 654 квінтильйони) можливостей, і ми ще не обчислили їх усі!

Переваги та недоліки from_quote

Функція Card.from_quote розбирає рядок тексту (англ. string) у 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 може зберігати кортежі або списки як ключі для відстеження раніше побачених станів гри.


Джерело

Beggar-My-NeighbourПосилання відкривається в новому вікні або вкладці
Редагувати через GitHub Посилання відкривається в новому вікні або вкладці
Roc Exercism

Час розпочати Camicia?

Зареєструйтеся на Exercism, щоб вивчати й опановувати Roc, а також 135 вправ та справжнє наставництво від людей, і все це безкоштовно.