Одного дощового пообіддя ми сидимо за кухонним столом і граємо в карти з бабусею. Це - її власна версія гри Camicia.
Спершу здається, що це просто чергова товариська партія: карти ляскають по столу, сміх за столом, час від часу переможна усмішка Нонни. Але гра тягнеться далі, і відбувається щось дивне. Ті самі карти раз за разом повертаються. Ми викладаємо карту за картою, а кінця все не видно.
І ми починаємо замислюватися. Чи закінчиться ця гра колись? А чи могли б ми грати вічно?
Пізніше, з цікавості, ми шукаємо в інтернеті і, на свій подив, виявляємо, що те, що сталося, не було просто невдачею. Можливо, ми з бабусею натрапили на одну з найдовших можливих послідовностей! Умить це нас затягує. Те, що починалося як невимушена гра, перетворилося на справжній квест: як довго насправді може тривати така гра? Чи можемо ми знайти послідовність, довшу за ту, яку зіграли за кухонним столом? Може, навіть достатньо довгу, щоб встановити новий світовий рекорд?
І ось, маючи лише колоду карт і трохи алгоритмічної винахідливості, ми вирішуємо дослідити...
У цій вправі ми змоделюємо гру, дуже схожу на класичну карткову гру 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 | Гравець 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
"finished" або "loop"
Для тих, хто хоче взятися за цікавіше випробування, пошук інших рекордів найдовшої гри, яка завершується, досі відкритий. Існує 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 може зберігати кортежі або списки як ключі для відстеження раніше побачених станів гри.