Прављење игре „Скочко”

Последња игра коју правимо у овом курсу је игра „Скочко”, инспирисана познатим квизом „Слагалица”:

skocko

Рачунар (или други играч) задаје комбинацију од 4 знака и играч има 6 покушаја да је погоди. Након сваког покушаја играч добија одговор колико је добар тај покушај био:

  • за сваки знак којем је погодио место добија један црвени поен

  • за сваки знак којем није погодио место, али је погодио да се тај знак налази негде у добитној комбинацији добија један жути поен

Позиције жутих и црвених поена у одговору не зависе од тога на који знак се односе.

Знакове називамо по њиховим устаљеним именима:

пик

каро

херц

треф

звезда

скочко

♠️

♦️

♥️

♣️

🤓

Играч погађа комбинацију уносећи почетна слова назива знакова : P, K, H, T, Z или S.

Наш програм треба да:

  1. генерише (или прочита) скривену комбинацију;

  2. прочита комбинацију коју је унео играч;

  3. одреди број освојених црвених и жутих поена;

  4. запамти нови резултат у табелу;

  5. испише табелу свих досадашњих покушаја;
    кораке 2-5 понавља док играч није све погодио, а највише 6 пута

  6. уколико играч није погодио за 6 потеза, исписује које је било тачно решење.

Комбинација и варијација

У математици реч комбинација подразумева скуп одређене дужине, односно да редослед елемената није битан. На пример, у игри Лото извлачи се 7 бројева из бубња и победник је онај који има тих 7 бројева обележене на свом листићу. Зато се то назива лото комбинација.

Са друге стране, у игри Скочко редослед нам је важан, односно није исто „♣️♣️♥️♥️” и „♥️♣️♥️♣️”. То значи да покушај играча није скуп него уређена четворка. Математички прецизно било би рећи да је то варијација са понављањем дужине 4 скупа свих знакова.

Ипак, ради јаснијег изражавања, у овој лекцији ћемо то исто називати комбинацијом 🙂.

Генерисање скривене комбинације

До сада смо већ научили да за насумично генерисање користимо библиотеку random. За генерисање скривене комбинације користићемо функцију random.choices која из задате листе насумично бира елементе. Како играч уноси знакове словима, а исписујемо одговарајуће емотиконе, потребан нам је речник који то повезује:

znakovi = {
    "P" : "♠️",
    "H" : "♥️",
    "K" : "♦️",
    "T" : "♣️",
    "Z" : "⭐",
    "S" : "🤓"
}

Мењањем овог речника можеш лако изменити своју игрицу тако да ради са неким другим знаковима, на пример можеш изабрати 6 различитих животиња или знакове се карата заменити неким емотиконима који мислиш да личе на њих.

Добитну комбинацију можемо генерисати и као комбинацију слова и као комбинацију знакова. У овом коду добитну и комбинацију коју играч уноси обе ћемо чувати као комбинације слове:

Евалуација покушаја

Желимо да направимо функцију која на основу унете и добитне комбинације одређује број црвених и жутих поена. Број црвених можемо једноставно одредити:

Већи проблем представљају нам жути поени. Пробај из примера да смислиш алгоритам за њихово додељивање:

🔶🔶🤓🤓

🟡🟡

❤️🤓⭐❤️

🔴🔴

❤️🤓🤓🤓

🔴🟡

🤓🤓🤓🤓

🔴🔴

🤓⭐🤓⭐

🔴🔴🟡🟡

🤓🤓⭐⭐

🔴🔴🔴🔴

Допунимо ову табелу бројем знакова у комбинацији:

🔶

❤️

🤓

🔶🔶🤓🤓

🟡🟡

2

0

0

2

❤️🤓⭐❤️

🔴🔴

0

2

1

1

❤️🤓🤓🤓

🔴🟡

0

1

0

3

🤓🤓🤓🤓

🔴🔴

0

0

0

4

🤓⭐🤓⭐

🔴🔴🟡🟡

0

0

2

2

🤓🤓⭐⭐

🔴🔴🔴🔴

0

0

2

2

Прва комбинација има 2 кароа и 2 скочка, а добитна 2 скочка и 2 звезде. Како у пресеку имају 2 скочка, та комбинација осваја укупно 2 поена, за сада не знамо које боје. Међутим, као знамо да та комбинација није освојила ниједан црвени поен, закључујемо да су то 2 жута поена.

Пресек две комбинације је мањи од бројева за сваки знак.

На пример, пресек 2. комбинације са добитном 1 скочко и 1 звезда, што значи да та комбинација осваја укупно 2 поена. Како знамо да је та комбинација освојила 2 црвена, закључујемо да није освојила ниједан жути.

Видимо да нам је за одређивање броја жутих поена довољно да знамо овакву таблицу броја знакова. Њих можемо имплементирати као речнике. Слично као што смо правили листу којој су сви елементи звездице, можемо направити и речник којем су све вредности 0, а кључеви су кључеви речника znakovi:

recnik_brojeva_znakova = {znak:0 for znak in [*znakovi]}

Користећи то можемо да напишемо функцију која попуњава речник бројева знакова за дату комбинацију:

На основу тих речника сада можемо да одредимо број жутих поена. Како бисмо тестирали програм на примеру који смо анализирали, фиксирали смо добитну комбинацију да буде баш та:

Овим смо завршили најтежи део! Остало је још да све што смо направили обликујемо у игрицу користећи конзолу и табеле и дамо прилику играчу да погађа 6 пута исту комбинацију.

Испис покушаја

Да би игрица била прегледнија, представљаћемо је табелом од 8 колона - првих 4 су покушај играча, друга 4 су оцена тог покушаја.

За разлику од игре „Икс-окс” где смо сваки пут правили исту табелу, овде ћемо при сваком покушају додати само по један ред у табели и исписати поново исту табелу.

Пробај у свом окружењу да покренеш следећи код:

from rich.console import Console
from rich.table import Table
import random

znakovi = {
    "P" : "♠️",
    "H" : "♥️",
    "K" : "♦️",
    "T" : "♣️",
    "Z" : "⭐",
    "S" : "🤓"
}

def broj_crvenih_poena(uneta_kombinacija,dobitna_kombinacija):
    crveni_poeni = 0
    for i in range(4):
        if uneta_kombinacija[i] == dobitna_kombinacija[i]:
            crveni_poeni += 1
    return crveni_poeni

def brojevi_znakova(kombinacija):
    recnik_brojeva_znakova = {znak:0 for znak in [*znakovi]}
    for znak in kombinacija:
        recnik_brojeva_znakova[znak]+=1
    return recnik_brojeva_znakova

def ukupan_broj_poena(uneta_kombinacija,dobitna_kombinacija):
    recnik_unete = brojevi_znakova(uneta_kombinacija)
    recnik_dobitne = brojevi_znakova(dobitna_kombinacija)
    poeni = 0
    for znak in [*znakovi]:
        poeni += min(recnik_unete[znak], recnik_dobitne[znak])
    return poeni

dobitna_kombinacija = random.choices([*znakovi], k=4)
print("Kombinacija se unosi navodjenjem početnih slova znakova, bez razmaka:", *znakovi)
uneta_kombinacija = input("Unesite kombinaciju: ")
crveni_poeni = broj_crvenih_poena(uneta_kombinacija,dobitna_kombinacija)
zuti_poeni = ukupan_broj_poena(uneta_kombinacija,dobitna_kombinacija) - crveni_poeni

console = Console()
table = Table(title = "🤓 Skočko 🤓", show_lines = True, show_header = False)
for i in range(8):
    table.add_column(justify="center", width = 3)  

table.add_row(*[znakovi[x] for x in uneta_kombinacija], *["🔴" for i in range(crveni_poeni)], *["🟡" for i in range(zuti_poeni)])
console.print(table)

Испис свих покушаја

По правилима игре, играч има 7 покушаја да погоди комбинацију. Сваки покушај додајемо као ред у истој табели.

Треба да имамо у виду да функцијом add_row додајемо ред табели table, али да на тај начин није исписана та колона. Потребно је да поново испишемо целу табелу наредбом console.print(table). Да би се у сваком тренутку видела једна табела, потребно је додатно очистити табелу пре него што је исцртамо, па добијамо следећи код:

from rich.console import Console
from rich.table import Table
import random

znakovi = {
    "P" : "♠️",
    "H" : "♥️",
    "K" : "♦️",
    "T" : "♣️",
    "Z" : "⭐",
    "S" : "🤓"
}

def broj_crvenih_poena(uneta_kombinacija,dobitna_kombinacija):
    crveni_poeni = 0
    for i in range(4):
        if uneta_kombinacija[i] == dobitna_kombinacija[i]:
            crveni_poeni += 1
    return crveni_poeni

def brojevi_znakova(kombinacija):
    recnik_brojeva_znakova = {znak:0 for znak in [*znakovi]}
    for znak in kombinacija:
        recnik_brojeva_znakova[znak]+=1
    return recnik_brojeva_znakova

def ukupan_broj_poena(uneta_kombinacija,dobitna_kombinacija):
    recnik_unete = brojevi_znakova(uneta_kombinacija)
    recnik_dobitne = brojevi_znakova(dobitna_kombinacija)
    poeni = 0
    for znak in [*znakovi]:
        poeni += min(recnik_unete[znak], recnik_dobitne[znak])
    return poeni

console = Console()
table = Table(title = "🤓 Skočko 🤓", show_lines = True, show_header = False)
for i in range(8):
    table.add_column(justify="center", width = 3)  

dobitna_kombinacija = random.choices([*znakovi], k=4)
for i in range(6):
    console.print("Kombinacija se unosi navodjenjem početnih slova znakova, bez razmaka:")
    console.print(znakovi)
    uneta_kombinacija = input("Unesite kombinaciju: ")
    crveni_poeni = broj_crvenih_poena(uneta_kombinacija,dobitna_kombinacija)
    zuti_poeni = ukupan_broj_poena(uneta_kombinacija,dobitna_kombinacija) - crveni_poeni
    table.add_row(*[znakovi[x] for x in uneta_kombinacija], *["🔴" for i in range(crveni_poeni)], *["🟡" for i in range(zuti_poeni)])
    console.clear()
    console.print(table)

Крај игре

Играч је победио ако је успео у 7 покушаја да погоди комбинацију, односно да освоји 4 црвена поена. Уколико то успе пре 7. покушаја, нема потребе да погађа даље и тада се игра завршава. Као у игри „Погађање речи”, користимо променљиву pobeda:

from rich.console import Console
from rich.table import Table
import random

znakovi = {
    "P" : "♠️",
    "H" : "♥️",
    "K" : "♦️",
    "T" : "♣️",
    "Z" : "⭐",
    "S" : "🤓"
}

def broj_crvenih_poena(uneta_kombinacija,dobitna_kombinacija):
    crveni_poeni = 0
    for i in range(4):
        if uneta_kombinacija[i] == dobitna_kombinacija[i]:
            crveni_poeni += 1
    return crveni_poeni

def brojevi_znakova(kombinacija):
    recnik_brojeva_znakova = {znak:0 for znak in [*znakovi]}
    for znak in kombinacija:
        recnik_brojeva_znakova[znak]+=1
    return recnik_brojeva_znakova

def ukupan_broj_poena(uneta_kombinacija,dobitna_kombinacija):
    recnik_unete = brojevi_znakova(uneta_kombinacija)
    recnik_dobitne = brojevi_znakova(dobitna_kombinacija)
    poeni = 0
    for znak in [*znakovi]:
        poeni += min(recnik_unete[znak], recnik_dobitne[znak])
    return poeni

console = Console()
table = Table(title = "🤓 Skočko 🤓", show_lines = True, show_header = False)
for i in range(8):
    table.add_column(justify="center", width = 3)  

dobitna_kombinacija = random.choices([*znakovi], k=4)
pobeda = False
for i in range(6):
    console.print("Kombinacija se unosi navodjenjem početnih slova znakova, bez razmaka:")
    console.print(znakovi)
    uneta_kombinacija = input("Unesite kombinaciju: ")
    crveni_poeni = broj_crvenih_poena(uneta_kombinacija,dobitna_kombinacija)
    zuti_poeni = ukupan_broj_poena(uneta_kombinacija,dobitna_kombinacija) - crveni_poeni
    table.add_row(*[znakovi[x] for x in uneta_kombinacija], *["🔴" for i in range(crveni_poeni)], *["🟡" for i in range(zuti_poeni)])
    console.clear()
    console.print(table)

    if crveni_poeni == 4:
        console.print("Bravo! Pogodili ste tačnu kombinaciju. 🥳")
        pobeda = True
        break

if not pobeda:
    console.print("Nemate više poteza. Tačna kombinacija bila je:", *[znakovi[x] for x in dobitna_kombinacija])

Изглед игрице

Игра је сада готова! Можеш јој сада мењати изглед, боју и стил порука које се исписују и емотиконе који се користе у игри.

Ово је један пример како можеш променити игрицу:

from rich.console import Console
from rich.table import Table
import random

znakovi = {
    "P" : "🍃",
    "H" : "💖",
    "K" : "🔷",
    "T" : "🍀",
    "Z" : "⭐",
    "S" : "🤓"
}

def broj_crvenih_poena(uneta_kombinacija,dobitna_kombinacija):
    crveni_poeni = 0
    for i in range(4):
        if uneta_kombinacija[i] == dobitna_kombinacija[i]:
            crveni_poeni += 1
    return crveni_poeni

def brojevi_znakova(kombinacija):
    recnik_brojeva_znakova = {znak:0 for znak in [*znakovi]}
    for znak in kombinacija:
        recnik_brojeva_znakova[znak]+=1
    return recnik_brojeva_znakova

def ukupan_broj_poena(uneta_kombinacija,dobitna_kombinacija):
    recnik_unete = brojevi_znakova(uneta_kombinacija)
    recnik_dobitne = brojevi_znakova(dobitna_kombinacija)
    poeni = 0
    for znak in [*znakovi]:
        poeni += min(recnik_unete[znak], recnik_dobitne[znak])
    return poeni

console = Console()
table = Table(title = "🤓 Skočko 🤓", show_lines = True, show_header = False)
for i in range(4):
    table.add_column(justify="center", width = 3)  
table.add_column(justify="center", width = 8)  
for i in range(4):
    table.add_column(justify="center", width = 3)  

dobitna_kombinacija = random.choices([*znakovi], k=4)
pobeda = False
console.clear()
for i in range(6):
    console.print("Kombinacija se unosi navodjenjem početnih slova znakova, bez razmaka:", style = "green italic")
    console.print(znakovi)
    uneta_kombinacija = input("Unesite kombinaciju: ")
    crveni_poeni = broj_crvenih_poena(uneta_kombinacija,dobitna_kombinacija)
    zuti_poeni = ukupan_broj_poena(uneta_kombinacija,dobitna_kombinacija) - crveni_poeni
    table.add_row(*[znakovi[x] for x in uneta_kombinacija],"", *["🔴" for i in range(crveni_poeni)], *["🟡" for i in range(zuti_poeni)])
    console.clear()
    console.print(table)

    if crveni_poeni == 4:
        console.print("Bravo! Pogodili ste tačnu kombinaciju. 🥳", style = "green bold")
        pobeda = True
        break

if not pobeda:
    console.print("Nemate više poteza. Tačna kombinacija bila je:", *[znakovi[x] for x in dobitna_kombinacija], style = "red bold")

И она изгледа овако:

skocko