Eräs korttipeli

Välineet

  • 52 kortin pakka (4 maata, arvot 1-13)
  • muistiinpanovälineet pisteenlaskua varten

Pelaajia

2-5

Tavoite

Kerätä eniten pisteitä voittamalla tikkejä ja luomalla voittamistaan korteista saman arvoisten korttien ryhmiä.

Aloitus

Arvotaan kuka toimii ensimmäisenä jakana. Jokaiselle pelaajalle jaetaan 7 korttia, jakajasta seuraavasta aloittaen. Jakajasta seuraava myös aloittaa ensimmäisen tikin.

Tikin pelaaminen

Jokainen pelaaja valitsee vuorollaan kädestään tikkiin laitettavat kortit. Hän asettaa ne kuvapuoli alaspäin pöydälle siten, että toiset pelaajat näkevät vain kuinka monta korttia pelaaja laittoi. Laitettavat kortit saavat olla mitä tahansa. Kun kaikki pelaajat ovat laittaneet korttinsa tikkiin, paljastetaan kaikki tikin kortit ja pelaaja, joka laittoi parhaimman yhdistelmän, voittaa tikin itselleen ja asettaa sen omaan erilliseen tikkipakkaansa.

Yhdistelmät

Yhdistelmiä ovat (parhausjärjestyksessä):

  • 7 kortin värisuora
  • 6 kortin värisuora
  • 5 kortin värisuora
  • 4 samaa korttia
  • 4 kortin värisuora
  • 3 kortin värisuora
  • 3 samaa korttia
  • 2 kortin värisuora
  • pari
  • yksittäinen kortti

Saman tyypin yhdistelmiä verrataan vertaamalla niiden suurimman arvoista korttia. Korttien keskinäinen suuruusjärjestys taas selvitetään vertaamalla ensin arvoa ja sen ollessa sama verrataan maata. Maiden järjestys on ♤ (suurin), ♡, ♢, ♧ (pienin).

Tikin voittaminen

Pelaajan laittamista korteista katsotaan vain yksi paras mahdollinen yhdistelmä. Esimerkiksi jos pelaaja 1 laittaa tikkiin kortit 3♤ – 3♡ – 4♡ – 4♢ – 5♢, niin näistä muodostuva yhdistelmä on 4♢ – 5♢, sillä 2 kortin värisuora voittaa parin ja 5♢ > 4♡ (koska 5>4).

Tikin pelaamisen jälkeen jaetaan pelaajille pakasta kortteja (taas jakajasta seuraavasta aloittaen) kunnes jokaisella on kädessään 7 korttia tai pakka loppuu. Kortteja jaetaan kullekin yksi kerrallaan (ja pelaajat, joilla on jo 7 korttia hypätään yli), jotta kortit saataisiin jaettua mahdollisimman tasaisesti pakan loputtua. Tikin voittanut pelaaja aloittaa seuraavan tikin.

Kierros

Tikkien pelaamista jatkekaan kunnes kaikki kortit on pelattu. Jos yhdelle pelaajalle jää kortteja käteen, kun kaikkien muiden pelaajien kädet ovat tyhjät, saa tämä pelaaja omat korttinsa omaan tikkipakkaansa.

Kierroksen lopuksi, jokainen pelaaja laskee tikkipakastaan kuinka monta pistettä hän saa. Tikkipakan kortit järjestetään saman arvoisten korttien ryhmiin ja jokainen ryhmä antaa pisteitä seuraavasti:

  • 1 kortti: 1p
  • 2 korttia: 5p
  • 3 korttia: 10p
  • 4 korttia: 20p

Jakaja vaihtuu kierroksittain.

Voittaminen

Kierroksia pelataan joko ennalta sovittu määrä tai siihen asti kunnes jokin pelaaja saavuttaa jonkin ennalta sovitun pistemäärän. Voittaja on se pelaaja, jolla on viimeisen kierroksen jälkeen eniten pisteitä.

♤♡♢♧

Muunnelmia

  • Pakan kokoa voi vaihdella ottamalla kutakin maata kortit johonkiin arvoon asti.
  • Perääntymiskierros: Kun pelaajat ovat asettaneet korttinsa tikkiin, niitä ei vielä paljastetakaan, vaan käydään toinen kierros, jolla kukin pelaaja voi halutessaan ottaa laittamiaan kortteja pois. Kuitenkin jokaista pois ottamaansa korttia kohden hänen täytyy paljastaa jokin toinen laittamansa kortti ja tämän paljastetun kortin pitää jäädä pöydälle. (Tämä tarkoittaa, että korkeintaan puolet laitetuista korteista voi vetää takaisin.)

Vangin pasianssin voittotodennäköisyys

Vangin pasianssi on korttipeli, jossa tavallisesta 52 kortin pakasta valitaan ensin pöydälle 13 satunnaista korttia. Näistä samanarvoiset yhdistellään pinoiksi. Tämän jälkeen pakasta aletaan nostamaan kortteja, joiden avulla pöydän kortteja voi poistaa. Tämä tehdään nostamalla kerralla kolme korttia, joista vain viimeinen otetaan huomioon. Jos pöydällä on tämän kortin osoittaman arvon pino, se voidaan poistaa. Näin jatketaan, kunnes pakka on tyhjä (tai pöytä tyhjä). Peli menee läpi, mikäli pöytä saadaan tyhjäksi. Kysymys kuuluukin: Mikä on pelin läpimenemisen todennäköisyys?

Esimerkki pelitilanteesta, jossa pöydälle valittu kortit ja ensimmäinen kolmikko pakasta nostettu. Tässä katsotaan korttia risti neljä. Koska pöydällä ei ole nelosia, niin pöydältä ei poisteta mitään.

Kokeile peliä täällä

Voimme olettaa, että jälkimmäisessä (pakan 3 kerrallaan nostelussa) itse asiassa vain valitaan 13 satunnaista korttia. (Koska jokainen joka kolmas kortti on yhtä satunnainen kuin 13 päällimmäistäkin; sillä oletuksella että pakan alkuperäinen järjestys on satunnainen.) Läpimeno on yhtäpitävää sen kanssa, että jälkimmäinen valinta sisältää ainakin yhden kerran jokaisen pöydälle tulleen arvon.

Laskentaa hankaloittaa se, että pöydälle valitut 13 korttia vaikuttavat jäljellä olevan pakan valintojen todennäköisyyksiin. Tämän vuoksi meidän täytyy huomioida minkä tyyppinen valinta pöydälle on tullut. Määritellään tämä ”tyyppi” tarkasti ottaen pöydän pinojen kokojen (laskevasti) järjestettynä vektorina. Ylläolevassa esimerkkikuvassa on tullut tyyppi (3, 2, 1, 1, 1, 1, 1, 1, 1, 1): 3 vitosta, 2 rouvaa, 1 kasi, 1 ässä, …, 1 kutonen. Tyypissä ei siis huomioida mitä kortteja on tullut, ainoastaan näiden lukumäärät. Tämä rajoittaakin mahdollisten tyyppien määrä huomattavasti: ne ovat itseasiassa luvun 13 ositukset osiin, joista jokainen on korkeintaan 4 (koska maita on 4, niin suurin mahdollinen pinon koko on 4). Ks. OEIS: http://oeis.org/A001400. Näitä on 39 kappaletta ja ne voi generoida seuraavalla Sage-koodilla:

#list of partitions of n to at most maxPartsN parts that are at most maxSize
def getPartitions(n, maxPartsN, maxSize):
    if n==0: return [[]]
    canMakeMax = maxPartsN*maxSize
    if n>canMakeMax: return []
    if n==canMakeMax: return [[maxSize]*maxPartsN]
    ret = []
    for x in xrange(min(n, maxSize), -1, -1):
        ret += [[x] + sP for sP in getPartitions(n-x, maxPartsN-1, x)]
    return ret

def getAllPossibleTypes(boardN, suits, vals):
    return getPartitions(boardN, min(boardN, vals), suits)

a = getAllPossibleTypes(13, 4, 13)
print "{} types:\n{}".format(len(a), a)

Seuraavaksi meidän täytyy laskea mikä on kullekin tyypille t=(t_1, t_2, \dots, t_r) todennäköisyys tulla pöydälle ja sitten mikä valita jäljellä olevasta pakasta 13 korttia siten, että jokaista tyypin korttia tulee ainakin yksi. Tässä onkin kätevää, että jälkimmäisessä valinnassa pakasta on poistettu tyypin mukaan juuri niitä kortteja, joita halutaan tulevan, joten sillä mitkä kortit ne olivat, ei ole väliä, vaan ainoastaan niiden tyypillä t. Merkitään näitä kysyttyjä todennäköisyyksiä

\begin{aligned} A(t) =&  \mathbb{P}(\text{saadaan 1. valinnassa tyyppi } t) \\  B(t) =&  \mathbb{P}(\text{kun pakasta poistettu tyypin } t \text{ mukaan, saadaan 2. valinnassa jokaista poistettua arvoa ainakin } 1) \end{aligned}

Lasketaan ensin A(t). Kuinka moni pakan 13-kombinaatioista on tyyppiä t =(t_1, t_2, \dots, t_r)? Lasketaan tulo

\begin{aligned}  \prod_{j=1}^r (13-j){{4}\choose{t_j}} \end{aligned}

tässä valitaan jokaiselle t:n komponentille arvo ja kerrotaan sillä kuinka monella tavalla tuon arvoiset kortit voidaan valita maiden joukosta. Huomaa: koska tyyppi on järjestetty vektori, arvojen valintoja ei jaeta r!:llä, mutta tulos täytyy jakaa sillä kuinka monella tavalla tyypin luvut voivat permutoida tyypin muuttumatta. Se on tyypin ”itsensä tyypin” yli laskettujen kertomien tulo \prod_{j} {y_j}!, kun tyypin tyyppi on (y_j)_j.

Sage-koodi näiden laskemiseen:

import fractions
from collections import Counter

def aProb(t, suits, vals):
    num = 1
    for i in xrange(len(t)):
        num *= (vals-i)*binomial(suits, t[i])
    num = num/reduce(lambda x,y: x*factorial(y), Counter(t).values(), 1)
    denom = binomial(suits*vals, sum(t))
    return fractions.Fraction(int(num), int(denom))

Sitten lukujen B(t) laskemiseen. Olkoon taas tyyppi t=(t_1, t_2, \dots, t_r). Nyt jäljellä oleva pakka on multijoukko X = \{(4-t_j)\times x_j\}, missä x_j on tyypin komponenttia j vastaavan kortin arvo ja t_j=0, \text{ kun } j>r. Tällaisen valinnan tekemisen todennäköisyyttä, että tiettyjä arvoja saadaan ainakin yksi, käsittelimme viime kerralla: https://membolicsythod.home.blog/2019/05/19/multijoukon-valintojen-todennakoisyyksia/ . Nyt vähintään kerran valituksi vaadittavat alkiot ovat x_1, x_2, \dots, x_r. Kuten sanottu, sillä ei ole väliä mitkä nuo arvot ovat, vain niiden tyypillä.

Lopulta kysytty läpimenotodennäköisyys saadaan laskettua osina (ehdollistetaan sillä millainen tyyppi tulee, summa on kaikkien tyyppien t yli):

\begin{aligned} \mathbb{P}(\text{voitto}) = \sum_{t} A(t)B(t) \end{aligned}

Koottu, lopullisen vastauksen antava koodi:

from fractions import Fraction
from collections import Counter

R.<z> = QQ['z']
def waysToSelect(multiplicities, mustHave=[]):
    g = R([1])
    for j,nj in enumerate(multiplicities):
        coeffs = [binomial(nj, k) for k in range(nj+1)]
        if j in mustHave: coeffs[0] = 0 #subtracts the 1
        g *= R(coeffs)
    return [g[k] for k in range(g.degree()+1)]

#list of partitions of n to at most maxPartsN parts that are at most maxSize
def getPartitions(n, maxPartsN, maxSize):
    if n==0: return [[]]
    canMakeMax = maxPartsN*maxSize
    if n>canMakeMax: return []
    if n==canMakeMax: return [[maxSize]*maxPartsN]
    ret = []
    for x in xrange(min(n, maxSize), -1, -1):
        ret += [[x] + sP for sP in getPartitions(n-x, maxPartsN-1, x)]
    return ret

def getAllPossibleTypes(boardN, suits, vals):
    return getPartitions(boardN, min(boardN, vals), suits)


def aProb(t, suits, vals):
    num = 1
    for i in xrange(len(t)):
        num *= (vals-i)*binomial(suits, t[i])
    num = num/reduce(lambda x,y: x*factorial(y), Counter(t).values(), 1)
    denom = binomial(suits*vals, sum(t))
    return Fraction(int(num), int(denom))

def bProb(t, suits, vals, pickN):
    n = suits*vals
    if len(t)==0 and pickN<=n: return Fraction(int(1), int(1))
    if (len(t) and pickN==0) or pickN>n-sum(t): return Fraction(int(0), int(1))
    deckMults = [suits-(t[i] if i<len(t) else 0) for i in range(vals)]
    w = waysToSelect(deckMults, range(len(t)))
    num = w[pickN] if len(w)>pickN else 0
    denom = binomial(suits*vals-sum(t), pickN)
    return Fraction(int(num), int(denom))

def winProb(suits, vals, boardN, pickN):
    ret = Fraction(int(0), int(1))
    n = suits*vals
    if boardN+pickN > n: return ret
    
    #for a type t, check if for some value all suits of that
    #value have come and, as a result, we can't win
    def someValueMaxed(t):
        for x in t:
            if x>=suits: return True
        return False
    
    ts = getAllPossibleTypes(boardN, suits, vals)
    for t in ts:
        if someValueMaxed(t): continue
        ret += aProb(t, suits, vals)*bProb(t, suits, vals, pickN)
    return ret

#the usual 4x13 deck, pick 13 in each phase of the game
p = winProb(4, 13, 13, 13)
print "{} \n= {}".format(str(p), float(p))

Tämä tulostaa läpimenotodennäköisyyden

\begin{aligned} \frac{964444044208}{262190765217675} = 0.00367840584853 \end{aligned}