Jakie są matematyczne / obliczeniowe zasady tej gry?

196

Moje dzieci mają zabawną grę o nazwie Spot It! Ograniczenia gry (jak najlepiej mogę to opisać) to:

  • Jest to talia 55 kart
  • Na każdej karcie znajduje się 8 unikalnych zdjęć (tzn. Karta nie może zawierać 2 tego samego obrazu)
  • Biorąc pod uwagę dowolne 2 karty wybrane z talii, jest 1 i tylko 1 pasujące zdjęcie .
  • Dopasowane zdjęcia mogą być różnie skalowane na różnych kartach, ale to tylko utrudnia grę (tzn. Małe drzewo nadal pasuje do większego drzewa)

Zasada gry jest taka: odwróć 2 karty, a ten, kto pierwszy wybierze pasujące zdjęcie, otrzyma punkt.

Oto zdjęcie dla wyjaśnienia:

zauważ to

(Przykład: z dolnych 2 kart powyżej widać, że pasującym obrazem jest zielony dinozaur. Między prawym dolnym i środkowym prawym zdjęciem jest głowa klauna.)

Próbuję zrozumieć, co następuje:

  1. Jaka jest minimalna liczba różnych zdjęć wymaganych do spełnienia tych kryteriów i jak to określić?

  2. Używając pseudokodu (lub Ruby), w jaki sposób wygenerowałbyś 55 kart do gry z szeregu N zdjęć (gdzie N jest minimalną liczbą z pytania 1)?

Aktualizacja:

Zdjęcia pojawiają się więcej niż dwa razy na pokład (w przeciwieństwie do tego, co niektórzy przypuszczali). Zobacz zdjęcie 3 kart, każda z błyskawicą:3 karty

Callmeed
źródło
64
+1 za przekształcenie gry w coś, co rani mój mózg.
kabaret
3
Minimalna liczba zdjęć na kartę, czy minimalna liczba zdjęć, biorąc pod uwagę, że jest 8 na kartę? Ponadto, czy każde zdjęcie musi być dopasowane?
trutheality
7
Myślę, że musisz dodać więcej ograniczeń. W przeciwnym razie możesz umieścić jabłko na każdej karcie, a następnie dodać dowolną liczbę unikalnych obrazów do każdej karty. Każda para kart będzie pasować tylko na obrazie jabłka.
mbeckish
8
@cabaret: W takim przypadku polubisz zestaw . Niewiarygodnie zabawne i denerwujące.
dmckee --- były moderator kociak
4
Chociaż jest to świetne pytanie, zostało już zadane na stronie matematycznej (przeze mnie). Wydaje się to trochę nie na temat. - math.stackexchange.com/questions/36798/…
Javid

Odpowiedzi:

148

Skończone geometrie rzutowe

W aksjomaty z rzutowej (płaszczyzny) geometrii są nieco inne niż w euklidesowej geometrii:

  • Co dwa punkty mają dokładnie jedną linię, która przez nie przechodzi (to jest to samo).
  • Co dwie linie spotykają się dokładnie w jednym punkcie (to trochę różni się od Euclida).

Teraz dodaj do zupy „skończone” i masz pytanie:

Czy możemy mieć geometrię z zaledwie 2 punktami? Z 3 punktami? Z 4? Z 7?

Nadal są otwarte pytania dotyczące tego problemu, ale wiemy o tym:

  • Jeśli istnieją geometrie z Qpunktów, wtedy Q = n^2 + n + 1i nnazywa się ordergeometrii.
  • W n+1każdej linii są punkty.
  • Z każdego punktu podaj dokładnie n+1linie.
  • Całkowita liczba linii jest również Q.

  • I wreszcie, jeśli njest liczbą pierwszą, to istnieje geometria porządku n.


Co to ma wspólnego z układanką, można zapytać.

Umieścić cardzamiast pointi picturezamiast linei aksjomaty stać:

  • Co dwie karty mają dokładnie jedno zdjęcie.
  • Na każde dwa zdjęcia przypada dokładnie jedna karta, która ma oba z nich.

Teraz weźmy n=7i mamy order-7skończoną geometrię z Q = 7^2 + 7 + 1. To tworzy Q=57linie (zdjęcia) i Q=57punkty (karty). Myślę, że twórcy puzzli zdecydowali, że 55 jest bardziej okrągłą liczbą niż 57, i zostawili 2 karty.

Otrzymujemy również n+1 = 8, więc z każdego punktu (karty) przechodzi 8 linii (pojawia się 8 zdjęć), a każda linia (zdjęcie) ma 8 punktów (pojawia się w 8 kartach).


Oto reprezentacja najsłynniejszej skończonej płaszczyzny rzutowej (rzędu 2) (geometrii) z 7 punktami, znanymi jako płaszczyzna Fano , skopiowana z Noelle Evans - Strona problemów ze skończoną geometrią

wprowadź opis zdjęcia tutaj

Zastanawiałem się nad stworzeniem obrazu wyjaśniającego, w jaki sposób powyższą płaszczyznę rzędu 2 można stworzyć podobną łamigłówkę z 7 kartami i 7 obrazkami, ale potem link z bliźniaczego pytania matematyki. Wymiany ma dokładnie taki schemat: Dobble-et- la-geometrie-finie

Samolot Fano

ypercubeᵀᴹ
źródło
9
Czy ta gra ma geometrię nieeuklidesową? Czy słuszne byłoby stwierdzenie, że karty mają rację?
RMorrisey,
2
Brzmi niesamowicie, ale nie mam pewności, że faktycznie dobrze modeluje problem. @ypercube, czy możesz wyjaśnić nieco więcej, dlaczego uważasz, że analogia między kartą / obrazem a punktem / linią jest prawidłowa?
Nate Kohl,
@Nate: Pierwsza analogia every two cards have exactly one picture in commonzostała podana w pytaniu. Drugi for every two pictures there is exactly one card that has both of them, OP może nam powiedzieć, czy zestaw gry to spełnia.
ypercubeᵀᴹ
4
Świetna odpowiedź! Świetny wgląd, zdając sobie sprawę z tego, że gra pasuje do właściwości Płaszczyzny Rzutowej 7-rzędowej, a także jedno z najlepszych wyjaśnień Płaszczyzn Rzutowych dla osób, które widziałem.
RBarryYoung
3
Znakomity. Będę musiał przeczytać to jeszcze 100 razy, aby spróbować dowiedzieć się, jak wygenerować zestawy kart w Pythonie ...
Jared
22

Dla tych, którzy mają problemy z wyobrażeniem geometrii płaszczyzny rzutowej z 57 punktami, istnieje naprawdę fajny, intuicyjny sposób na zbudowanie gry z 57 kartami i 57 symbolami (na podstawie odpowiedzi Yuvala Filmusa na to pytanie ):

  1. W przypadku kart z 8 symbolami utwórz siatkę unikatowych symboli 7x7.
  2. Dodaj dodatkowe 8 symboli dla „stoków” od 0 do 6 oraz jeden dla stoku nieskończoności.
  3. Każda karta to linia na siatce (7 symboli) plus jeden symbol z zestawu nachylenia dla nachylenia linii. Linie mają przesunięcie (tj. Punkt początkowy po lewej) i nachylenie (tj. Liczbę symboli, które należy przejść w górę dla każdego kroku w prawo). Gdy linia opuści siatkę u góry, wprowadź ponownie u dołu. Zobacz ten przykładowy rysunek (zdjęcia z boardgamegeek ) dla dwóch takich kart:

Dwie przykładowe karty (czerwona i zielona) pobrane jako linie z siatki

W tym przykładzie biorę jedną linię ze spadkiem zero (czerwony), a drugą ze spadkiem 1 (zielony). Przecinają się dokładnie w jednym wspólnym punkcie (sowa).

Ta metoda zapewnia, że ​​dowolne dwie karty mają dokładnie jeden wspólny symbol, ponieważ

  1. Jeśli nachylenia są różne, linie zawsze przecinają się dokładnie w jednym punkcie.
  2. Jeśli nachylenia są takie same, linie nie przecinają się i nie będzie wspólnego symbolu z siatki. W takim przypadku symbol nachylenia będzie taki sam.

W ten sposób możemy konstruować karty 7x7 (7 odsunięć i 7 nachyleń).

Możemy również zbudować siedem dodatkowych kart z linii pionowych przez siatkę (tj. Biorąc każdą kolumnę). Dla nich używana jest ikona nachylenia nieskończoności.

Ponieważ każda karta składa się z siedmiu symboli z siatki i dokładnie jednego symbolu „nachylenia”, możemy stworzyć jedną dodatkową kartę, która po prostu składa się ze wszystkich 8 symboli nachylenia.

To pozostawia nam 7x8 + 1 = 57 możliwych kart i 7 x 7 + 8 = 57 wymaganych symboli.

(Oczywiście działa to tylko z siatką wielkości pierwszej (np. N = 7). W przeciwnym razie linie o różnym nachyleniu mogą mieć zero lub więcej niż jedno przecięcie, jeśli nachylenie jest dzielnikiem wielkości siatki.)

Sven Zwei
źródło
18

Zatem istnieje k = 55 kart zawierających m = 8 zdjęć, każda z puli n zdjęć łącznie. Możemy przekształcić na pytanie "Ile zdjęć n potrzebujemy, tak, że możemy skonstruować zestaw k kart tylko z jednym wspólnym zdjęciu pomiędzy dowolnymi dwoma kartami? równoważnie, zadając:

Biorąc pod uwagę n- wymiarową przestrzeń wektorową i zbiór wszystkich wektorów, które zawierają dokładnie m elementów równych jednemu i wszystkim pozostałym zerom, jak duże ma być n , abyśmy mogli znaleźć zbiór k wektorów, których iloczynami par kropek są wszystko równe 1 ?

Istnieją dokładnie ( n wybierz m ) możliwe wektory do budowania par. Potrzebujemy więc przynajmniej wystarczająco dużego n , aby ( n wybrać m )> = k . Jest to tylko dolna granica, więc aby spełnić ograniczenie kompatybilności parami, prawdopodobnie potrzebujemy znacznie wyższej wartości n .

Dla odrobiny eksperymentów napisałem mały program Haskell do obliczania prawidłowych zestawów kart:

Edycja: Właśnie zobaczyłem rozwiązanie Neila i Gajeta, że ​​algorytm, którego używam, nie zawsze znajduje najlepsze możliwe rozwiązanie, więc wszystko poniżej niekoniecznie jest prawidłowe. Spróbuję wkrótce zaktualizować mój kod.

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

Wynikowa maksymalna liczba kompatybilnych kart dla m = 8 zdjęć na kartę dla różnych liczby zdjęć do wyboru n dla pierwszych kilku n wygląda następująco:

Ta metoda brutalnej siły nie dociera jednak zbyt daleko z powodu wybuchu kombinatorycznego. Ale pomyślałem, że to może być interesujące.

Co ciekawe, wydaje się, że dla danego m , k wzrasta z n tylko do pewnego n , po czym pozostaje stała.

Oznacza to, że na każdą liczbę zdjęć na kartę można wybrać pewną liczbę zdjęć, co daje maksymalną możliwą liczbę legalnych kart. Dodanie większej liczby zdjęć do wyboru z tej optymalnej liczby nie zwiększa już liczby legalnych kart.

Pierwsze kilka optymalnych wartości K to:

optymalny stół k

Thies Heidecke
źródło
To tylko wstępna próba ograniczenia, prawda? Nie uwzględniono wymogu „parowania produktów kropkowych równych 1” ...
Nemo,
Widocznie wyróżnienia składni tutaj naprawdę nie obsługują Haskell jeszcze ( meta.stackexchange.com/questions/78363/... ), ale będę rzucać w podpowiedziach tylko w przypadku, gdy ma miejsce w przyszłości.
BoltClock
@BoltClock dzięki za edycję! nie wiedziałem, że możesz dać wskazówki na temat podświetlania składni specyficznych dla języka.
Thies Heidecke,
Nie jest jeszcze zbyt dobrze znany :)
BoltClock
9

Inni opisali ogólne ramy projektowania (skończona płaszczyzna rzutowa) i pokazali, jak generować skończone płaszczyzny rzutowe pierwszego rzędu. Chciałbym tylko uzupełnić pewne luki.

Skończone płaszczyzny rzutowe mogą być generowane dla wielu różnych zamówień, ale są one najprostsze w przypadku zamówienia pierwszego p. Następnie moduł liczb całkowitych ptworzy pole skończone, które można wykorzystać do opisania współrzędnych punktów i linii w płaszczyźnie. Istnieją 3 różne rodzaje współrzędnych Za punkty: (1,x,y), (0,1,x), i (0,0,1), gdzie xi ymoże przyjmować wartości od 0do p-1. Trzy różne rodzaje punktów wyjaśniają wzór p^2+p+1na liczbę punktów w systemie. Możemy także opisać linie z tymi samymi 3 różnych rodzajów współrzędnych: [1,x,y], [0,1,x], i [0,0,1].

Obliczamy, czy punkt i linia padają na podstawie tego, czy iloczyn kropek ich współrzędnych jest równy 0 mod p. Na przykład punkt (1,2,5)i linia [0,1,1]są incydentami, gdy p=7od tego czasu 1*0+2*1+5*1 = 7 == 0 mod 7, ale punkt (1,3,3)i linia [1,2,6]nie są incydentami od tego czasu 1*1+3*2+3*6 = 25 != 0 mod 7.

Tłumaczenie na język kart i zdjęć oznacza, że ​​karta ze współrzędnymi (1,2,5)zawiera obraz ze współrzędnymi [0,1,1], ale karta ze współrzędnymi (1,3,3)nie zawiera obrazu ze współrzędnymi [1,2,6]. Możemy użyć tej procedury do opracowania pełnej listy kart i zdjęć, które zawierają.

Nawiasem mówiąc, myślę, że łatwiej jest myśleć o obrazach jako punktach, a karty jako o liniach, ale istnieje dualność w geometrii rzutowej między punktami i liniami, więc to naprawdę nie ma znaczenia. Jednak w dalszej części będę używać punktów do zdjęć i linii do kart.

Ta sama konstrukcja działa dla każdego skończonego pola. Wiemy, że istnieje skończone pole porządku qwtedy i tylko wtedy q=p^k, gdy jest siłą główną. Nazywa się to pole, GF(p^k)które oznacza „pole Galois”. Pola nie są tak łatwe do zbudowania w przypadku pierwszej mocy, jak w przypadku pierwszej.

Na szczęście ciężka praca została już wykonana i zaimplementowana w wolnym oprogramowaniu, a mianowicie Sage . Aby na przykład uzyskać rzutowy projekt rzędu 4, wystarczy wpisać

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

a otrzymasz wyjście, które wygląda

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]]>

Interpretuję powyższe w następujący sposób: jest 21 zdjęć oznaczonych od 0 do 20. Każdy z bloków (linia w geometrii rzutowej) mówi mi, które obrazy pojawiają się na karcie. Na przykład pierwsza karta będzie miała zdjęcia 0, 1, 2, 3 i 20; druga karta będzie miała zdjęcia 0, 4, 8, 12 i 16; i tak dalej.

System rzędu 7 może być wygenerowany przez

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

który generuje dane wyjściowe

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]]>
Edward Doolittle
źródło
8

Właśnie znalazłem sposób na zrobienie tego z 57 lub 58 zdjęciami, ale teraz mam bardzo silny ból głowy, opublikuję kod ruby ​​w 8-10 godzin po tym, jak dobrze spałem! tylko podpowiedź moje rozwiązanie co 7 kart ma ten sam znak i za pomocą mojego rozwiązania można zbudować 56 kart.

oto kod, który generuje wszystkie 57 kart, o których mówił Ypercube. używa dokładnie 57 zdjęć i przepraszam, napisałem rzeczywisty kod C ++, ale wiedząc, że vector <something>jest to tablica zawierająca wartości typu something, łatwo zrozumieć, co robi ten kod. i ten kod generuje P^2+P+1karty przy użyciu P^2+P+1zdjęć, z których każde zawiera P+1zdjęcie i udostępnia tylko 1 zdjęcie wspólne, dla każdej pierwszej wartości P. co oznacza, że ​​możemy mieć 7 kart przy użyciu 7 zdjęć, każdy z 3 zdjęciami (dla p = 2), 13 kart z 13 zdjęciami (dla p = 3), 31 kart z 31 zdjęciami (dla p = 5), 57 kart dla 57 zdjęć (dla p = 7) i tak dalej ...

#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();
    }
}

jeszcze raz przepraszam za opóźniony kod.

Ali1S232
źródło
37
Mam na to elegancki dowód, ale niestety to pole komentarza jest zbyt małe, aby je pomieścić.
sarnold
@Gajet: Uruchomiłeś to p=4? (i 21 kart / zdjęć)
ypercubeᵀᴹ
4 nie działa w moim algorytmie, ponieważ 4 nie jest liczbą pierwszą, w moim algorytmie ważne jest, aby p było liczbą pierwszą.
Ali1S232,
@ypercube po ponownym sprawdzeniu w moim algorytmie wystąpiły drobne błędy, ale sprawdziłem go pod kątem 2, 3,5,7 i mogę udowodnić, że zadziała dla dowolnej liczby pierwszej, więc oto mój pełny kod (ale w c ++)
Ali1S232
1
@Gajet: fajne rozwiązanie! Teraz rozumiem, dlaczego mój chciwy algorytm nie zawsze zapewniał najlepsze rozwiązanie.
Thies Heidecke
6

Oto rozwiązanie Gajeta w Pythonie, ponieważ uważam, że Python jest bardziej czytelny. Zmodyfikowałem go tak, aby działał również z liczbami innymi niż pierwotne. Użyłem wglądu Thies do wygenerowania łatwiejszego do zrozumienia kodu wyświetlacza.

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)

Z wyjściem:

***      *   
   ***   *   
      ****   
*  *  *   *  
 *  *  *  *  
  *  *  * *  
*   *   *  * 
 *   **    * 
  **   *   * 
*    * *    *
 * *    *   *
  * * *     *
         ****
Neil G.
źródło
2
myślę, że ostatnie trzy karty w twoim przykładzie są nieprawidłowe, ponieważ nie dzielą zdjęcia z piątą kartą. Właśnie sprawdziłem mój kod przez ponad godzinę, zanim go zrozumiałem :) Co ciekawe, wydaje się, że maksymalny rozmiar legalnego zestawu kart to 5 na 4 zdjęcia na kartę i nie zwiększa się nawet przy większej liczbie zdjęć do wyboru.
Thies Heidecke,
1
@To dzięki diagramowi, który stworzyłem za pomocą kodu Gajeta, znacznie łatwiej jest zrozumieć, dlaczego istnieją dokładnie (p) + (p * p) + (1)konfiguracje.
Neil G
1
@Neil: Dzięki za zaktualizowany schemat, znacznie łatwiej jest zobaczyć, jak działa rozwiązanie Gajeta!
Thies Heidecke,
1
@Gajet: Myślę, że się mylisz all p except 4 and 6. Jeśli chcesz stworzyć skończoną płaszczyznę, w której znajdują się p*p+p+1punkty i linie (karty i obrazy), to jest to związane finite fieldsi nie rings. Istnieją skończone pola porządku, pgdy p jest primelub a prime power. Twój kod działa poprawnie dla liczb pierwszych, ponieważ wyrażenia takie k * p + (j + i * k) % pwyrażają się k*p + j + i*kw kategoriach mnożenia i dodawania w skończonym polu rzędu p.
ypercubeᵀᴹ
1
Będzie ona działać poprawnie głównych potęg też, jeśli można wyrazić te operacje (. Mult i dodawanie) w skończonych dziedzinach porządku p^2, p^3itd Tak, to będzie działać4, 8, 9, 16, 25, 27, ...
ypercubeᵀᴹ
4

Korzystanie z z3twierdzenia twierdzącego

Niech Pbędzie liczbą symboli na kartę. Zgodnie z tym artykułem i ypercubeᵀᴹodpowiedzią są odpowiednio N = P**2 - P + 1karty i symbole. Talia kart może być reprezentowana za pomocą macierzy padania, która ma rząd dla każdej karty i kolumnę dla każdego możliwego symbolu. Jego (i,j)elementem jest, 1jeśli karta ima symbol j. Musimy tylko wypełnić tę macierz, mając na uwadze następujące ograniczenia:

  • każdy element ma wartość zero lub jeden
  • suma każdego wiersza jest dokładnie P
  • suma każdej kolumny jest dokładnie P
  • dwa dowolne wiersze muszą mieć dokładnie jeden wspólny symbol

Oznacza to N**2zmienne i N**2 + 2*N + (N choose 2)ograniczenia. Wydaje się, że jest to wykonalne w niezbyt długim czasie przy z3małych nakładach.

edycja : Niestety P = 8 wydaje się być zbyt duży dla tej metody. Zabiłem proces po 14 godzinach obliczeń.

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)

Wyniki

$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.>
pgy
źródło
4

Bardzo podoba mi się ten wątek. Buduję ten projekt github python z częściami tego kodu tutaj, aby narysować niestandardowe karty w formacie png (aby można było zamówić niestandardowe gry karciane w Internecie).

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

PlagTag
źródło
3

Napisałem artykuł o tym, jak generować tego rodzaju talie, z kodem w Perlu. Kod nie jest zoptymalizowany, ale może przynajmniej generować talie „rozsądnych” zamówień ... i jeszcze więcej.

Oto przykład z rozkazem 8, który musi rozważyć nieco bardziej skomplikowaną matematykę, ponieważ 8 nie jest liczbą pierwszą, chociaż jest prawidłowym rozkazem do generowania tego rodzaju talii. Zobacz wyżej lub artykuł, aby uzyskać bardziej szczegółowe wyjaśnienie, poniżej, jeśli chcesz wygenerować nieco trudniejsze 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

Każdy identyfikator od 0do 72można odczytać zarówno jako identyfikator karty, jak i identyfikator obrazu. Na przykład ostatni wiersz oznacza, że:

  • Karta 72zawiera zdjęcia 2, 13, 22, ..., 59, 68, I
  • obraz 72pojawia się na kartach 2, 13, 22, ..., 59, i 68.
polettix
źródło