Які математичні / обчислювальні принципи стоять за цією грою?


196

Мої діти мають цю веселу гру під назвою Spot It! Обмеженнями гри (як я найкраще можу описати) є:

  • Це колода з 55 карт
  • На кожній картці є 8 унікальних зображень (тобто картка не може мати 2 однакових зображень)
  • З огляду на будь-які 2 карти, вибрані з колоди, є 1 і лише 1 відповідна картинка .
  • Збігаючі малюнки можуть бути по-різному масштабовані на різних картах, але це лише для того, щоб ускладнити гру (тобто маленьке дерево все ще відповідає більшому дереву)

Принцип гри такий: переверніть дві карти, і хто вперше підбере відповідну картинку, отримує бал.

Ось малюнок для уточнення:

визначте це

(Приклад: на нижній 2 картці вище видно, що відповідна картина - зелений динозавр. Між нижньою правою та середньою правою картиною це голова клоуна.)

Я намагаюся зрозуміти наступне:

  1. Яка мінімальна кількість різних зображень необхідна для відповідності цим критеріям і як би ви це визначили?

  2. Використовуючи псевдокод (або Ruby), як би ви генерували 55 ігрових карток з масиву N зображень (де N - мінімальне число з питання 1)?

Оновлення:

Картини трапляються більше ніж двічі на колода (всупереч тому, що деякі вважали). Дивіться цю картинку з 3-х карт, на кожній з яких є блискавка:3 картки


64
+1 за перетворення гри на щось, що болить моєму мозку.
кабаре

3
Мінімальна кількість фотографій на картці чи мінімальна кількість фотографій, враховуючи, що на картці є 8? Також, чи має бути кожна картина зіставною?
trutheality

7
Я думаю, вам потрібно додати більше обмежень. В іншому випадку ви можете покласти яблуко на кожну карту, а потім додати будь-яку кількість унікальних зображень до кожної картки. Кожна пара карток відповідатиме лише зображенню яблука.
mbeckish

8
@cabaret: У такому випадку вам сподобається встановити . Неймовірно весело і обтяжуюче.
dmckee --- кошеня колишнього модератора

4
Хоча це чудове питання, його вже задавали на сайті математики (мною). Тут здається трохи поза темою. - math.stackexchange.com/questions/36798/…
Джавід

Відповіді:


148

Кінцеві проективні геометрії

В аксіомах про проективної (площині) геометрії трохи відрізняються від евклідової геометрії:

  • Кожні дві точки мають рівно одну лінію, яка проходить через них (це те саме).
  • Кожні два рядки зустрічаються рівно в одній точці (це трохи відрізняється від Евкліда).

Тепер додайте в суп "кінцеве", і у вас виникне питання:

Чи можемо ми мати геометрію лише з 2 балами? З 3 балами? З 4? З 7?

Є ще відкриті питання щодо цієї проблеми, але ми це знаємо:

  • Якщо є геометрії з Qточками, то Q = n^2 + n + 1і nназивається orderгеометрією.
  • У n+1кожному рядку є пункти.
  • Від кожної точки проходьте точно n+1лінії.
  • Загальна кількість рядків також Q.

  • І нарешті, якщо nце просто, тоді існує геометрія порядку n.


Що може стосуватися головоломки, можна запитати.

Поставте cardзамість pointі pictureзамість lineі аксіоми стають:

  • Кожні дві картки мають рівно одне спільне зображення.
  • На кожні дві картинки існує рівно одна картка, на якій є обидві.

Тепер давайте візьмемо, n=7і ми маємо order-7кінцеву геометрію Q = 7^2 + 7 + 1. Це робить Q=57лінії (малюнки) і Q=57точки (картки). Я думаю, що розробники головоломок вирішили, що 55 - це кругле число, ніж 57, і залишили 2 картки.

Ми також отримуємо n+1 = 8, тому від кожної точки (картки) проходить 8 рядків (8 зображень) і кожен рядок (малюнок) має 8 балів (з'являється у 8 картках).


Ось подання найвідомішої кінцевої проективної (порядку 2) площини (геометрії) із 7 точками, відомої як площина Фано , скопійована з Ноель Еванс - Сторінка задачі кінцевої геометрії

введіть тут опис зображення

Я думав створити образ, який би пояснив, як з вищевказаної площини порядку-2 можна було скласти аналогічну головоломку з 7 карт та 7 картинок, але тоді посилання з питань-близнюків math.exchange має саме таку схему: Dobble-et- la-geometrie-finie

Fano Plane


9
Отже, ця гра демонструє неевклідову геометрію? Чи було б правильно сказати, що Картки праві?
RMorrisey

2
Це звучить приголомшливо, але я не маю впевненості, що він насправді добре моделює проблему. @ypercube, чи можете ви пояснити трохи більше, чому ви вважаєте, що аналогія між карткою / малюнком та точкою / лінією є дійсною?
Нейт Кол

@Nate: Перша аналогія every two cards have exactly one picture in common, викладена у запитанні. Другий for every two pictures there is exactly one card that has both of them, ОП може сказати нам, чи задоволений ігровий набір.
ypercubeᵀᴹ

4
Дивовижна відповідь! Прекрасне розуміння, розуміючи, що гра відповідає властивостям Проективного літака «Порядок 7», плюс одне з найкращих пояснень Проективних літаків для непрофесійних людей, які я бачив.
RBarryYoung

3
Блискуча. Мені потрібно буде прочитати це ще 100 разів, щоб спробувати зрозуміти, як генерувати набори карт в Python ...
Джаред

22

Для тих, хто має проблеми з зображенням геометрії проективних площин з 57 очками, існує дійсно приємний, інтуїтивний спосіб побудувати гру з 57 карт та 57 символів (виходячи з відповіді Юваля Філімуса на це запитання ):

  1. Для карток з 8 символами створіть сітку унікальних символів 7x7.
  2. Додайте ще 8 символів для "схилів" від 0 до 6, плюс один для нескінченного схилу.
  3. Кожна карта - це лінія на сітці (7 символів) плюс один символ зі схилу, встановленого для нахилу лінії. У рядків є зміщення (тобто початкова точка зліва) та нахил (тобто скільки символів потрібно підняти на кожен крок праворуч). Коли рядок залишає сітку вгорі, введіть її знову внизу. Дивіться цей приклад рисунка (фотографії з boardgamegeek ) для двох таких карток:

Дві приклади карт (червона та зелена), взяті як лінії з сітки

У прикладі я беру один рядок зі схилом нуля (червоний), а один - з нахилом 1 (зелений). Вони перетинаються рівно в одній загальній точці (сова).

Цей метод гарантує, що будь-які дві картки мають рівно один загальний символ, оскільки

  1. Якщо схили різні, то лінії завжди будуть перетинатися рівно в одній точці.
  2. Якщо нахили однакові, то лінії не будуть перетинатися, і спільного символу з сітки не буде. У цьому випадку символ нахилу буде однаковим.

Таким чином ми можемо побудувати картки 7х7 (7 зміщення та 7 нахилів).

Ми також можемо побудувати сім додаткових карток з вертикальних ліній через сітку (тобто взявши кожен стовпець). Для них використовується значок схилу нескінченності.

Оскільки кожна картка складається з семи символів із сітки та точно одного символу "нахилу", ми можемо створити одну додаткову карту, яка просто складається з усіх 8 символів нахилу.

Це залишає нам 7x8 + 1 = 57 можливих карток та 7 x 7 + 8 = 57 необхідних символів.

(Природно, це працює лише з сіткою розміру простих чисел (наприклад, n = 7). В іншому випадку лінії різного нахилу можуть мати нуль або більше одного перетину, якщо нахил є дільником розміру сітки.)


18

Отже, є k = 55 карток, що містять m = 8 фотографій, кожна з пулу з n загальних картинок. Ми можемо переформулювати питання : "Скільки фотографії п потрібні нам, так що ми можемо побудувати безліч K карт тільки з однією спільною картиною між будь-якою парою карт? рівнозначно запитуючи:

Враховуючи n- розмірний векторний простір і безліч всіх векторів, які містять рівно m елементів, рівних одному та всім іншим нулю, наскільки великим має бути n , щоб ми могли знайти набір k векторів, парні крапки яких добутку всі рівні 1 ?

Існують саме ( n вибирають m ) можливі вектори, з яких можна будувати пари. Тож нам принаймні потрібний достатньо великий n, щоб ( n вибирати m )> = k . Це лише нижня межа, тому для виконання парного обмеження сумісності нам, можливо, знадобиться значно більший n .

Просто для експерименту я написав невелику програму Haskell для обчислення дійсних наборів карт:

Редагувати: Я щойно побачивши рішення Ніла та Гаджета, я зрозумів, що алгоритм, який я використовую, не завжди знаходить найкраще можливе рішення, тому все нижче не обов'язково є дійсним. Я спробую найближчим часом оновити свій код.

module Main where

cardCandidates n m = cardCandidates' [] (n-m) m
cardCandidates' buildup  0  0 = [buildup]
cardCandidates' buildup zc oc
    | zc>0 && oc>0 = zerorec ++ onerec
    | zc>0         = zerorec
    | otherwise    = onerec
    where zerorec = cardCandidates' (0:buildup) (zc-1) oc
          onerec  = cardCandidates' (1:buildup) zc (oc-1)

dot x y = sum $ zipWith (*) x y
compatible x y = dot x y == 1

compatibleCards = compatibleCards' []
compatibleCards' valid     [] = valid
compatibleCards' valid (c:cs)
  | all (compatible c) valid = compatibleCards' (c:valid) cs
  |                otherwise = compatibleCards'    valid  cs

legalCardSet n m = compatibleCards $ cardCandidates n m

main = mapM_ print [(n, length $ legalCardSet n m) | n<-[m..]]
  where m = 8

Отримана максимальна кількість сумісних карт для m = 8 знімків на картці для різної кількості фотографій на вибір n для перших n виглядає так:

Цей метод грубої сили не надто далеко, хоча через комбінаторний вибух. Але я подумав, що це все ще може бути цікавим.

Цікаво, що , здається , що для даного м , K збільшується з ростом п тільки до деякого п , після чого вона залишається постійною.

Це означає, що на кожну кількість фотографій на картці є певна кількість фотографій на вибір, що призводить до максимально можливої ​​кількості юридичних карток. Якщо додати більше фотографій на вибір із минулого, то оптимальне число більше не збільшує кількість юридичних карток.

Перші кілька оптимальних k :

оптимальна k таблиця


Це лише перша спроба зв'язати, правда? Ви не включили вимогу "попарно крапкових продуктів, рівних 1" ...
Немо,

Мабуть, підсвічування синтаксису тут дійсно не підтримує Haskell ще ( meta.stackexchange.com/questions/78363 / ... ), але я буду кидати в підказці тільки в разі , якщо це в майбутньому.
BoltClock

@BoltClock дякую за вашу редакцію! я не знав, що ви можете дати підказки для виділення синтаксису, характерного для мови.
Thies Heidecke

Це ще не дуже добре відомо :)
BoltClock

9

Інші описали загальну основу для проектування (кінцева проективна площина) та показали, як генерувати кінцеві проективні площини основного порядку. Я просто хотів би заповнити деякі прогалини.

Кінцеві проективні площини можуть бути згенеровані для багатьох різних порядків, але вони найбільш прості у випадку просте замовлення p. Тоді цілі числа по модулюp утворює кінцеве поле, яке може бути використане для опису координат для точок і ліній у площині. Є 3 різних видів координат для точок: (1,x,y), (0,1,x), і (0,0,1), де xі yможе приймати значення від 0до p-1. 3 різні види точок пояснюють формулу p^2+p+1кількості балів у системі. Ми також можемо описати лінії з тими ж 3 -х різних видів координат: [1,x,y], [0,1,x]і[0,0,1] .

Ми обчислюємо, чи трапляються точка і пряма, чи точкове добуток їх координат дорівнює 0 мод p. Так, наприклад, точка (1,2,5)і лінія [0,1,1]є інцидентами, коли p=7з тих пір 1*0+2*1+5*1 = 7 == 0 mod 7, але точка (1,3,3)і лінія [1,2,6]не інцидент з тих пір1*1+3*2+3*6 = 25 != 0 mod 7 .

Переклад на мову карток і картинок означає, що карта з координатами (1,2,5)містить зображення з координатами [0,1,1], але карта з координатами (1,3,3)не містить зображення з координатами[1,2,6] . Ми можемо використовувати цю процедуру для розробки повного списку карток та зображень, які вони містять.

До речі, я думаю, що простіше уявляти картинки як точки, а картки - як лінії, але в проективній геометрії між точками та лініями є подвійність, тому це насправді не має значення. Однак у подальшому я буду використовувати точки для малюнків та лінії для карт.

Ті ж будівельні роботи для будь-якого кінцевого поля. Ми знаємо, що існує кінцеве поле порядку, qякщо і лише тоді q=p^k, головна сила. Поле називається, GF(p^k)що означає «поле Галуа». Полі не так просто побудувати у випадку просте потужність, як в основному випадку.

На щастя, важка робота вже зроблена та впроваджена у вільне програмне забезпечення, а саме Sage . Наприклад, щоб отримати проектну площинну конструкцію порядку 4, наприклад, просто наберіть

print designs.ProjectiveGeometryDesign(2,1,GF(4,'z'))

і ви отримаєте такий вигляд

ProjectiveGeometryDesign<points=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20], blocks=[[0, 1, 2, 3, 20], [0,
4, 8, 12, 16], [0, 5, 10, 15, 19], [0, 6, 11, 13, 17], [0, 7, 9, 14,
18], [1, 4, 11, 14, 19], [1, 5, 9, 13, 16], [1, 6, 8, 15, 18], [1, 7,
10, 12, 17], [2, 4, 9, 15, 17], [2, 5, 11, 12, 18], [2, 6, 10, 14, 16],
[2, 7, 8, 13, 19], [3, 4, 10, 13, 18], [3, 5, 8, 14, 17], [3, 6, 9, 12,
19], [3, 7, 11, 15, 16], [4, 5, 6, 7, 20], [8, 9, 10, 11, 20], [12, 13,
14, 15, 20], [16, 17, 18, 19, 20]]>

Я тлумачу сказане так: є 21 малюнок із позначкою від 0 до 20. Кожен із блоків (рядок у проективній геометрії) повідомляє мені, які зображення відображаються на картці. Наприклад, перша картка матиме зображення 0, 1, 2, 3 і 20; на другій картці будуть зображення 0, 4, 8, 12 та 16; і так далі.

Система порядку 7 може бути породжена

print designs.ProjectiveGeometryDesign(2,1,GF(7)) 

що генерує вихід

ProjectiveGeometryDesign<points=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
47, 48, 49, 50, 51, 52, 53, 54, 55, 56], blocks=[[0, 1, 2, 3, 4, 5, 6,
56], [0, 7, 14, 21, 28, 35, 42, 49], [0, 8, 16, 24, 32, 40, 48, 50], [0,
9, 18, 27, 29, 38, 47, 51], [0, 10, 20, 23, 33, 36, 46, 52], [0, 11, 15,
26, 30, 41, 45, 53], [0, 12, 17, 22, 34, 39, 44, 54], [0, 13, 19, 25,
31, 37, 43, 55], [1, 7, 20, 26, 32, 38, 44, 55], [1, 8, 15, 22, 29, 36,
43, 49], [1, 9, 17, 25, 33, 41, 42, 50], [1, 10, 19, 21, 30, 39, 48,
51], [1, 11, 14, 24, 34, 37, 47, 52], [1, 12, 16, 27, 31, 35, 46, 53],
[1, 13, 18, 23, 28, 40, 45, 54], [2, 7, 19, 24, 29, 41, 46, 54], [2, 8,
14, 27, 33, 39, 45, 55], [2, 9, 16, 23, 30, 37, 44, 49], [2, 10, 18, 26,
34, 35, 43, 50], [2, 11, 20, 22, 31, 40, 42, 51], [2, 12, 15, 25, 28,
38, 48, 52], [2, 13, 17, 21, 32, 36, 47, 53], [3, 7, 18, 22, 33, 37, 48,
53], [3, 8, 20, 25, 30, 35, 47, 54], [3, 9, 15, 21, 34, 40, 46, 55], [3,
10, 17, 24, 31, 38, 45, 49], [3, 11, 19, 27, 28, 36, 44, 50], [3, 12,
14, 23, 32, 41, 43, 51], [3, 13, 16, 26, 29, 39, 42, 52], [4, 7, 17, 27,
30, 40, 43, 52], [4, 8, 19, 23, 34, 38, 42, 53], [4, 9, 14, 26, 31, 36,
48, 54], [4, 10, 16, 22, 28, 41, 47, 55], [4, 11, 18, 25, 32, 39, 46,
49], [4, 12, 20, 21, 29, 37, 45, 50], [4, 13, 15, 24, 33, 35, 44, 51],
[5, 7, 16, 25, 34, 36, 45, 51], [5, 8, 18, 21, 31, 41, 44, 52], [5, 9,
20, 24, 28, 39, 43, 53], [5, 10, 15, 27, 32, 37, 42, 54], [5, 11, 17,
23, 29, 35, 48, 55], [5, 12, 19, 26, 33, 40, 47, 49], [5, 13, 14, 22,
30, 38, 46, 50], [6, 7, 15, 23, 31, 39, 47, 50], [6, 8, 17, 26, 28, 37,
46, 51], [6, 9, 19, 22, 32, 35, 45, 52], [6, 10, 14, 25, 29, 40, 44,
53], [6, 11, 16, 21, 33, 38, 43, 54], [6, 12, 18, 24, 30, 36, 42, 55],
[6, 13, 20, 27, 34, 41, 48, 49], [7, 8, 9, 10, 11, 12, 13, 56], [14, 15,
16, 17, 18, 19, 20, 56], [21, 22, 23, 24, 25, 26, 27, 56], [28, 29, 30,
31, 32, 33, 34, 56], [35, 36, 37, 38, 39, 40, 41, 56], [42, 43, 44, 45,
46, 47, 48, 56], [49, 50, 51, 52, 53, 54, 55, 56]]>

8

Я щойно знайшов спосіб зробити це з 57 або 58 фотографіями, але зараз у мене дуже сильний головний біль, я опублікую код рубіну через 8-10 годин після того, як я добре виспався! лише натяк на моє рішення, кожні 7 карт мають однакову марку, і загалом 56 карт можна побудувати за допомогою мого рішення.

ось код, який генерує всі 57 карт, про які говорив ypercube. він використовує точно 57 зображень, і вибачте, хлопець, я написав фактичний код C ++, але знаючи, що vector <something>це масив, що містить значення типу something, легко зрозуміти, що робить цей код. і цей код генерує P^2+P+1картки, використовуючи P^2+P+1зображення, кожне з яких містить P+1зображення, та обмінюється лише 1 загальною картиною для кожного основного значення P. це означає, що ми можемо мати 7 карт, використовуючи 7 фотографій, кожна з яких має 3 зображення (для p = 2), 13 карт із використанням 13 зображень (для p = 3), 31 карт із використанням 31 зображення (для p = 5), 57 карт для 57 фотографій (для р = 7) і так далі ...

#include <iostream>
#include <vector>

using namespace std;

vector <vector<int> > cards;

void createcards(int p)
{
    cards.resize(0);
    for (int i=0;i<p;i++)
    {
        cards.resize(cards.size()+1);
        for(int j=0;j<p;j++)
        {
            cards.back().push_back(i*p+j);
        }
        cards.back().push_back(p*p+1);
    }

    for (int i=0;i<p;i++)
    {
        for(int j=0;j<p;j++)
        {
            cards.resize(cards.size()+1);
            for(int k=0;k<p;k++)
            {
                cards.back().push_back(k*p+(j+i*k)%p);
            }
            cards.back().push_back(p*p+2+i);
        }
    }

    cards.resize(cards.size()+1);

    for (int i=0;i<p+1;i++)
        cards.back().push_back(p*p+1+i);
}

void checkCards()
{
    cout << "---------------------\n";
    for(unsigned i=0;i<cards.size();i++)
    {
        for(unsigned j=0;j<cards[i].size();j++)
        {
            printf("%3d",cards[i][j]);
        }
        cout << "\n";
    }
    cout << "---------------------\n";
    for(unsigned i=0;i<cards.size();i++)
    {
        for(unsigned j=i+1;j<cards.size();j++)
        {
            int sim = 0;
            for(unsigned k=0;k<cards[i].size();k++)
                for(unsigned l=0;l<cards[j].size();l++)
                    if (cards[i][k] == cards[j][l])
                        sim ++;
            if (sim != 1)
                cout << "there is a problem between cards : " << i << " " << j << "\n";

        }
    }
}

int main()
{
    int p;
    for(cin >> p; p!=0;cin>> p)
    {
        createcards(p);
        checkCards();
    }
}

знову вибачте за затриманий код.


37
У мене є елегантне підтвердження цього, але на жаль, це поле для коментарів занадто мало, щоб його містити.
sarnold

@Gajet: Ти запускав його p=4? (і 21 картки / картинки)
ypercubeᵀᴹ

4 не працює в моєму алгоритмі, оскільки 4 не є простим числом, в моєму алгоритмі важливо, щоб р був простим.
Ali1S232

@ypercube після перевірки знову було кілька помилок в моєму алгоритмі, але я перевірив це на 2, 3,5,7, і я можу довести будь-яке інше просте число, воно буде працювати, тому ось мій повний код (але в c ++)
Ali1S232

1
@Gajet: класне рішення! Тепер я зрозумів, чому мій жадібний алгоритм не завжди давав найкраще рішення.
Thies Heidecke

6

Ось рішення Gajet у Python, оскільки я вважаю Python більш читабельним. Я змінив його, щоб він працював і з непростими числами. Я використовував Thies прозорливість для створення більш легко зрозумілого коду відображення.

from __future__ import print_function
from itertools import *

def create_cards(p):
    for min_factor in range(2, 1 + int(p ** 0.5)):
        if p % min_factor == 0:
            break
    else:
        min_factor = p
    cards = []
    for i in range(p):
        cards.append(set([i * p + j for j in range(p)] + [p * p]))
    for i in range(min_factor):
        for j in range(p):
            cards.append(set([k * p + (j + i * k) % p
                              for k in range(p)] + [p * p + 1 + i]))

    cards.append(set([p * p + i for i in range(min_factor + 1)]))
    return cards, p * p + p + 1

def display_using_stars(cards, num_pictures):
    for pictures_for_card in cards:
        print("".join('*' if picture in pictures_for_card else ' '
                      for picture in range(num_pictures)))

def check_cards(cards):
    for card, other_card in combinations(cards, 2):
        if len(card & other_card) != 1:
            print("Cards", sorted(card), "and", sorted(other_card),
                  "have intersection", sorted(card & other_card))

cards, num_pictures = create_cards(7)
display_using_stars(cards, num_pictures)
check_cards(cards)

З виходом:

***      *   
   ***   *   
      ****   
*  *  *   *  
 *  *  *  *  
  *  *  * *  
*   *   *  * 
 *   **    * 
  **   *   * 
*    * *    *
 * *    *   *
  * * *     *
         ****

2
Я думаю, останні три карти у вашому прикладі не дійсні, тому що вони не діляться зображенням з п'ятою картою. Щойно я перевірив свій код протягом години, перш ніж я зрозумів це :) Цікаво, що максимальний розмір легальної картки становить 5 на 4 фотографії на карту і не збільшується, навіть якщо більше фотографій на вибір.
Thies Heidecke

1
@ Завдяки діаграмі, яку я створив за допомогою коду Gajet, набагато простіше зрозуміти, чому саме існують (p) + (p * p) + (1)конфігурації.
Ніл Г

1
@Neil: Дякуємо за оновлену діаграму, що набагато простіше зрозуміти, як працює рішення Gajet!
Thies Heidecke

1
@Gajet: Я думаю, що ти не прав all p except 4 and 6. Якщо ви хочете скласти кінцеву площину, де є p*p+p+1точки і лінії (картки та малюнки), то це пов’язано з finite fieldsі ні rings. Існують кінцеві поля порядку, pколи p є primeабо a prime power. Ваш код працює правильно для простих чисел, оскільки такі k * p + (j + i * k) % pвирази виражають k*p + j + i*kчерез множення та додавання у кінцевому полі порядку p.
ypercubeᵀᴹ

1
Він буде працювати правильно для простих чисел теж, якщо ви можете висловити ці операції (. Мульт і додавання) в кінцевих полях порядку p^2, p^3і т.д. Таким чином, він буде працювати4, 8, 9, 16, 25, 27, ...
ypercubeᵀᴹ

4

Використовуючи z3доказ теореми

Нехай Pбуде кількість символів на картці. Відповідно до цієї статті та ypercubeᵀᴹвідповіді, відповідно, є N = P**2 - P + 1картки та символи. Колода карт може бути представлена ​​матрицею падіння, яка містить рядок для кожної картки та стовпець для кожного можливого символу. Його (i,j)елемент - 1якщо на картці iє символ j. Нам потрібно лише заповнити цю матрицю такими обмеженнями:

  • кожен елемент або нульовий, або один
  • сума кожного ряду рівно P
  • сума кожного стовпця рівно P
  • будь-які два ряди повинні мати рівно один спільний символ

Це означає N**2змінні та N**2 + 2*N + (N choose 2)обмеження. Це здається керованим у не надто довгий часz3 невеликими витратами.

редагувати : На жаль, P = 8 здається занадто великим для цього методу. Я вбив процес через 14 годин обчислення.

from z3 import *
from itertools import combinations

def is_prime_exponent(K):
    return K > 1 and K not in 6  # next non-prime exponent is 10, 
                                 # but that is too big anyway

def transposed(rows):
    return zip(*rows)

def spotit_z3(symbols_per_card):
    K = symbols_per_card - 1
    N = symbols_per_card ** 2 - symbols_per_card + 1
    if not is_prime_exponent(K):
        raise TypeError("Symbols per card must be a prime exponent plus one.")

    constraints = []

    # the rows of the incidence matrix
    s = N.bit_length()
    rows = [[BitVec("r%dc%d" % (r, c), s) for c in range(N)] for r in range(N)]

    # every element must be either 1 or 0
    constraints += [Or([elem == 1, elem == 0]) for row in rows for elem in row]

    # sum of rows and cols must be exactly symbols_per_card
    constraints += [Sum(row) == symbols_per_card for row in rows]
    constraints += [Sum(col) == symbols_per_card for col in transposed(rows)]

    # Any two rows must have exactly one symbol in common, in other words they
    # differ in (symbols_per_card - 1) symbols, so their element-wise XOR will
    # have 2 * (symbols_per_card - 1) ones.
    D = 2 * (symbols_per_card - 1)
    for row_a, row_b in combinations(rows, 2):
        constraints += [Sum([a ^ b for a, b in zip(row_a, row_b)]) == D]

    solver = Solver()
    solver.add(constraints)

    if solver.check() == unsat:
        raise RuntimeError("Could not solve it :(")

    # create the incidence matrix
    model = solver.model()
    return [[model[elem].as_long() for elem in row] for row in rows]


if __name__ == "__main__":
    import sys
    symbols_per_card = int(sys.argv[1])
    incidence_matrix = spotit_z3(symbols_per_card)
    for row in incidence_matrix:
        print(row)

Результати

$python spotit_z3.py 3
[0, 0, 1, 1, 0, 1, 0]
[0, 0, 0, 0, 1, 1, 1]
[0, 1, 0, 1, 0, 0, 1]
[1, 1, 0, 0, 0, 1, 0]
[0, 1, 1, 0, 1, 0, 0]
[1, 0, 0, 1, 1, 0, 0]
[1, 0, 1, 0, 0, 0, 1]
python spotit_z3.py 3  1.12s user 0.06s system 96% cpu 1.225 total

$ time python3 spotit_z3.py 4
[0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0]
[0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0]
[0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1]
[0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0]        
[0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1]
[0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0]
[0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1]
[0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0]
[0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0]
[1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1]
[1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0]
[1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0]
[1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0]
python spotit_z3.py 4  664.62s user 0.15s system 99% cpu 11:04.88 total

$ time python3 spotit_z3.py 5
[1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0]
[0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0]
[0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0]
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1]
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0]
[0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0]
[0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1]
[1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0]
[0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0]
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0]
[0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1]
[1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0]
[0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0]
[0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1]
[1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0]
python spotit_z3.py 5  1162.72s user 20.34s system 99% cpu 19:43.39 total

$ time python3 spotit_z3.py 8
<I killed it after 14 hours of run time.>

4

Мені дуже подобається ця нитка. Я будую цей проект github python з частинами цього коду, щоб малювати власні картки у форматі png (так що можна замовити власні ігри з картками в Інтернеті).

https://github.com/plagtag/ProjectiveGeometry-Game


3

Я написав статтю про те, як генерувати подібні деки з кодом в Perl. Код не оптимізований, але він, принаймні, здатний генерувати колоди "розумних" замовлень ... та ще деякі.

Ось приклад із порядком 8, який повинен враховувати трохи складнішу основу математики, тому що 8 не є простим, хоча є дійсним замовленням для генерації подібних колод. Дивіться вище або статтю для більш детального пояснення нижче, якщо ви просто хочете створити трохи складніше Spot-It :-)

$ time pg2 8
elements in field: 8
  0. (1, 9, 17, 25, 33, 41, 49, 57, 65)
  1. (0, 9, 10, 11, 12, 13, 14, 15, 16)
  2. (2, 9, 18, 27, 36, 45, 54, 63, 72)
  3. (6, 9, 22, 26, 37, 43, 56, 60, 71)
  4. (7, 9, 23, 32, 34, 46, 52, 59, 69)
  5. (8, 9, 24, 30, 35, 42, 55, 61, 68)
  6. (3, 9, 19, 29, 39, 44, 50, 64, 70)
  7. (4, 9, 20, 31, 38, 48, 53, 58, 67)
  8. (5, 9, 21, 28, 40, 47, 51, 62, 66)
  9. (0, 1, 2, 3, 4, 5, 6, 7, 8)
 10. (1, 10, 18, 26, 34, 42, 50, 58, 66)
 11. (1, 14, 22, 30, 38, 46, 54, 62, 70)
 12. (1, 15, 23, 31, 39, 47, 55, 63, 71)
 13. (1, 16, 24, 32, 40, 48, 56, 64, 72)
 14. (1, 11, 19, 27, 35, 43, 51, 59, 67)
 15. (1, 12, 20, 28, 36, 44, 52, 60, 68)
 16. (1, 13, 21, 29, 37, 45, 53, 61, 69)
 17. (0, 17, 18, 19, 20, 21, 22, 23, 24)
 18. (2, 10, 17, 28, 35, 46, 53, 64, 71)
 19. (6, 14, 17, 29, 34, 48, 51, 63, 68)
 20. (7, 15, 17, 26, 40, 44, 54, 61, 67)
 21. (8, 16, 17, 27, 38, 47, 50, 60, 69)
 22. (3, 11, 17, 31, 37, 42, 52, 62, 72)
 23. (4, 12, 17, 30, 39, 45, 56, 59, 66)
 24. (5, 13, 17, 32, 36, 43, 55, 58, 70)
 25. (0, 49, 50, 51, 52, 53, 54, 55, 56)
 26. (3, 10, 20, 30, 40, 43, 49, 63, 69)
 27. (2, 14, 21, 32, 39, 42, 49, 60, 67)
 28. (8, 15, 18, 28, 37, 48, 49, 59, 70)
 29. (6, 16, 19, 31, 36, 46, 49, 61, 66)
 30. (5, 11, 23, 26, 38, 45, 49, 64, 68)
 31. (7, 12, 22, 29, 35, 47, 49, 58, 72)
 32. (4, 13, 24, 27, 34, 44, 49, 62, 71)
 33. (0, 57, 58, 59, 60, 61, 62, 63, 64)
 34. (4, 10, 19, 32, 37, 47, 54, 57, 68)
 35. (5, 14, 18, 31, 35, 44, 56, 57, 69)
 36. (2, 15, 24, 29, 38, 43, 52, 57, 66)
 37. (3, 16, 22, 28, 34, 45, 55, 57, 67)
 38. (7, 11, 21, 30, 36, 48, 50, 57, 71)
 39. (6, 12, 23, 27, 40, 42, 53, 57, 70)
 40. (8, 13, 20, 26, 39, 46, 51, 57, 72)
 41. (0, 65, 66, 67, 68, 69, 70, 71, 72)
 42. (5, 10, 22, 27, 39, 48, 52, 61, 65)
 43. (3, 14, 24, 26, 36, 47, 53, 59, 65)
 44. (6, 15, 20, 32, 35, 45, 50, 62, 65)
 45. (2, 16, 23, 30, 37, 44, 51, 58, 65)
 46. (4, 11, 18, 29, 40, 46, 55, 60, 65)
 47. (8, 12, 21, 31, 34, 43, 54, 64, 65)
 48. (7, 13, 19, 28, 38, 42, 56, 63, 65)
 49. (0, 25, 26, 27, 28, 29, 30, 31, 32)
 50. (6, 10, 21, 25, 38, 44, 55, 59, 72)
 51. (8, 14, 19, 25, 40, 45, 52, 58, 71)
 52. (4, 15, 22, 25, 36, 42, 51, 64, 69)
 53. (7, 16, 18, 25, 39, 43, 53, 62, 68)
 54. (2, 11, 20, 25, 34, 47, 56, 61, 70)
 55. (5, 12, 24, 25, 37, 46, 50, 63, 67)
 56. (3, 13, 23, 25, 35, 48, 54, 60, 66)
 57. (0, 33, 34, 35, 36, 37, 38, 39, 40)
 58. (7, 10, 24, 31, 33, 45, 51, 60, 70)
 59. (4, 14, 23, 28, 33, 43, 50, 61, 72)
 60. (3, 15, 21, 27, 33, 46, 56, 58, 68)
 61. (5, 16, 20, 29, 33, 42, 54, 59, 71)
 62. (8, 11, 22, 32, 33, 44, 53, 63, 66)
 63. (2, 12, 19, 26, 33, 48, 55, 62, 69)
 64. (6, 13, 18, 30, 33, 47, 52, 64, 67)
 65. (0, 41, 42, 43, 44, 45, 46, 47, 48)
 66. (8, 10, 23, 29, 36, 41, 56, 62, 67)
 67. (7, 14, 20, 27, 37, 41, 55, 64, 66)
 68. (5, 15, 19, 30, 34, 41, 53, 60, 72)
 69. (4, 16, 21, 26, 35, 41, 52, 63, 70)
 70. (6, 11, 24, 28, 39, 41, 54, 58, 69)
 71. (3, 12, 18, 32, 38, 41, 51, 61, 71)
 72. (2, 13, 22, 31, 40, 41, 50, 59, 68)
errors in check: 0

real    0m0.303s
user    0m0.200s
sys 0m0.016s

Кожен ідентифікатор від 0до 72може читатися і як ідентифікатор картки, і як ідентифікатор зображення. Наприклад, останній рядок означає, що:

  • карта 72містить фотографії 2, 13, 22, ..., 59, 68, і
  • картинка 72з'являється в картах 2, 13, 22, ..., 59, і 68.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.