Obvezna naloga

Znani ruski programer Fjodor Mihajlovič -- ah, kako dolge programe je pisal! takšni zločini bi morali biti kaznivi! -- je imel pogosto težave z naslednjo igro.

V začetku imamo deset kovancev. V vsakem koraku stavimo določeno vsoto in računalnik vrže kocko. Če pade liho število, stavo (in z njo stavljeni znesek) izgubimo, sicer dobimo poleg svojega vložka še toliko denarja, kolikor smo ga stavili. Pošteno. Igre je konec, ko ostanemo brez denarja.

Primer igre:

Stava? 3
Dobil stavo, imaš 13
Stava? 2
Dobil stavo, imaš 15
Stava? 4
Izgubil stavo, imaš 11
Stava? 4
Dobil stavo, imaš 15
Stava? 10
Izgubil stavo, imaš 5
Stava? 8
Goljuf! Za kazen ti vzamem en kovanec! Imaš 4
Stava? 3
Izgubil stavo, imaš 1
Stava? 1
Dobil stavo, imaš 2
Stava? 2
Dobil stavo, imaš 4
Stava? 4
Dobil stavo, imaš 8
Stava? 8
Izgubil stavo, imaš 0
Nasvidenje in upamo, da se kmalu spet vidimo.

Kot ste opazili, program pazi tudi, da igralec ne poskuša goljufati: če stavi več denarja, kot ga v resnici ima, mu za kazen vzame kovanec.

Rešitev

Potrebovali bomo spremnljivko, ki bo shranjevala število kovancev, ki jih ima igralec. Imenovali jo bomo kovancev in ji v začetku nastavili vrednost 10.

Sledila bo zanka, ki se bo vrtela, dokler ima igralec še kaj denarja (while kovancev > 0).

Znotraj zanke igralec pove, koliko denarja želi staviti; to bomo shranili v spremenljivko stava (stave = int(input("Stava? "))). Nato najprej preveri, če goljufa (if stava > kovancev) in v tem primeru mu zaplenimo kovanec ter ga ozmerjamo. Sicer vržemo kocko. Če je število liho, mu število kovancev povečamo za toliko, kolikor je stavil (kovancev += stava), sicer mu jih za toliko zmanjšamo.

from random import *

kovancev = 10

while kovancev > 0:
    stava = int(input("Stava? "))
    if stava > kovancev:
        kovancev -= 1
        print("Goljuf! Za kazen ti vzamem en kovanec! Imaš", kovancev)
    elif randint(1, 6) % 2 == 1:
        kovancev += stava
        print("Dobil stavo, imaš", kovancev)
    else:
        kovancev -= stava
        print("Izgubil stavo, imaš", kovancev)

print("Nasvidenje in upamo, da se kmalu spet vidimo")

Dodatna naloga

Obstaja pameten način za igranje te igre. Da bo lepše (in zanesljivejše) teklo, predpostavimo, da imamo v začetku 100 kovancev.

  • Stavimo en kovanec. Če zmagamo, imamo 100 + 1 = 101 kovanec in nehamo igrati.
  • Sicer pa imamo 100 - 1 = 99 kovancev. V drugem krogu stavimo 2 kovanca. Če zmagamo, imamo 99 + 2 = 101 kovanec in nehamo igrati.
  • Sicer pa imamo 99 - 2 = 97 kovancev. Zato stavimo 4 kovance. Če zmagamo, imamo 97 + 4 = 101 kovanec in nehamo igrati.
  • Sicer pa imamo 97 - 4 = 93 kovancev. Zato stavimo 8 kovancev. Če zmagamo, imamo 93 + 8 = 101 kovancev in nehamo igrati.
  • Sicer pa stavimo 16 kovancev. In če izgubimo še te, jih stavimo 32...

Po tej strategiji bomo vedno končali s 101 kovancem, torej bomo vedno pridobili 1 kovanec. Razen, če imamo takšno smolo, da prevečkrat zapored izgubimo.

Najprej napiši program, ki (sam s sabo, brez uporabnikovega tipkanja) odigra takšno igro. Izpis je lahko, recimo, tak:

Imam 100 ; stavil bom 1
Izgubil.
Imam 99 ; stavil bom 2
Izgubil.
Imam 97 ; stavil bom 4
Izgubil.
Imam 93 ; stavil bom 8
Izgubil.
Imam 85 ; stavil bom 16
Zmagal!

Potem pa program spremeni tako, da ne bo ničesar izpisoval, temveč bo odigral 1000 iger sam s seboj. Program naj prejšteje, v koliko od teh 1000 iger je zmagal, kolikokrat pa mu je zmanjkalo denarja.

Rešitev

Program ni bistveno drugačen. Spremenljivko stava že pred zanko nastavimo na 1, potem pa jo znotraj zanke podvajamo zanka *= 2. Zanka teče, dokler ima igralec dovolj denarja, da lahko stavi (while stava <= kovancev). Poleg tega zanko prekinemo takrat, ko igralec dobi stavo. Ker vemo, da bo imel takrat več kot 127 kovancev (no, vemo, da jih bo imel 128), lahko pogoj zapišemo kar while stava <= kovancev <= 127.

Iz programa pobrišemo input in ga nadomestimo s print-om, pa tudi preverjanja goljufij ni več potrebno.

from random import *

kovancev = 127
stava = 1
while stava <= kovancev <= 127:
    print("Imam", kovancev, "; stavil bom", stava)
    if randint(1, 6) % 2 == 1:
        kovancev += stava
        zmag += 1
        print("Zmagal!")
    else:
        kovancev -= stava
        print("Izgubil.")
    stava *= 2

Če hočemo prešteti, v koliko igrah zmaga, zapremo ves program v zanko

i = 0
while i < 1000:
    # prejšnji program
    i += 1

K temu dodamo še štetje zmag: pred zanko postavimo zmag = 0, in potem število zmag povečamo, kadar pade liho število (kar tudi konča igro).

from random import *

zmag = 0
i = 0
while i < 1000:
    denar = 127
    stava = 1
    while stava <= denar <= 127:
        if randint(1, 6) % 2 == 1:
            denar += stava
            zmag += 1
        else:
            denar -= stava
        stava *= 2
    i += 1

print("Zmagal sem v", zmag, "igrah in izgubil v", 1000 - zmag)

V razmislek

Se ta igra s takšno strategijo splača? Kakšen je poprečni dobiček?

Nalogo smo rešili s pomočjo programa. Bi znal izračunati verjetnost, da ti uspe zmagati v takšni igri s takšno strategijo? In potem iz te verjetnosti izračunati pričakovani dobiček?

Rešitev

Igralec izgubi igro, če neuspešno stavi vseh 1 + 2 + 4 + 8 + 16 + 32 + 64 = 127 kovancev. Izgubiti mora torej 7 zaporednih stav. Verjetnost, da izgubi eno je 1/2, verjetnost, da izgubi dve je 1/4 in tako naprej. Verjetnost, da izgubi 7 zaporednih stav je torej $1 / 2^7 = 1 / 128$. Verjetnost, da zmaga -- in tako konča z enim kovancem več, kot ga je imel v začetku -- je $1 - 1 / 128 = 127 / 128$. Pričakovani dobiček je torej

$$-127 \times \frac{1}{128} + 1 \times \frac{127}{128} = 0$$

Seveda se zgodi natančno isto tudi s poljubnim drugim številom kovancev. Če število kovancev ni oblike $2^n - 1$ (tako kot 127, ki je $2^7 - 1$), se nič ne spremeni. Če ima igralec v začetu 139 kovancev, je stvar torej natančno enaka, kot če bi jih imel 127, saj preostalih 12 nikoli ne bo mogel ne staviti ne izgubiti.

Zadnja sprememba: petek, 9. oktober 2020, 13.00