Processing math: 100%

Prijavi problem


Obeleži sve kategorije koje odgovaraju problemu

Još detalja - opišite nam problem


Uspešno ste prijavili problem!
Status problema i sve dodatne informacije možete pratiti klikom na link.
Nažalost nismo trenutno u mogućnosti da obradimo vaš zahtev.
Molimo vas da pokušate kasnije.

Flappy bird

У популарној игри Flappy Bird птица лети и не сме да додирне препреку. Игра је реализована тако што се птица налази стално у средини екрана (хоризонтално). Корисник управља птицом помоћу једног тастера тастатуре тако што је притиском на тај тастер подиже горе (она иначе стално пада на доле). Реализуј ову игру помоћу PyGame. Птичицу представи кругом, а препреке правоугаоницима који се спуштају са врха екрана наниже и са дна екрана навише. Између свака два пара правоугаоника треба да постоји пролаз кроз који се птица може провући. Програм се искључује у тренутку када птица дотакне препреку.

Централни део задатка чини провера да ли се наша лоптица сударила са правоугаоником. Круг сече правоугаоник ако и само ако јој се центар налази унутар правоугаоника или сече неку од његових ивица. Правоугаоници који чине наше препреке су постављени тако да су им све четири ивице паралелне координатним осама (две ивице су вертикалне, две су хоризонталне).

Размотримо прво како ћемо одредити да ли круг сече неку од вертикалних ивица препреке (с обзиром на природу игре, биће довољно проверавати само судар са левом вертикалном ивицом сваког правоугаоника). Нека је центар круга тачка O са координатама (cx,cy) и нека му је полупречник једнак r. Нека су темена вертикалне ивице правоугаоника тачке A са координатама (x,y1) и тачка B са координатама (x,y2). Јасно је да је пресек могућ само ако и само ако се довољно приближила тој ивици и није се превише удаљила од ње. Пресек може да постоји ако и само ако је хоризонтално растојање између центра круга и вертикалне праве на којој се налази дуж мање или једнако r тj. пресек може да постоји акко важи |cxx|r тј. xrcxx+r. Ако се cx налази у интервалу [xr,x+r] пресек може, али не мора да постоји – то зависи од y-координате центра cy. Јасно је да пресек сигурно постоји ако се cy налази у висини дужи (између y1 и y2), међутим, могуће је да је центар мало изнад или мало испод висине дужи, а да пресек и даље постоји.

../_images/flappy.png

Одредимо које су то граничне висине на којима се круг и дуж секу. Горњи гранични положај је онај у којем круг само додирује дуж тј. садржи тачку A. Означимо са M тачку која представља пројекцију тачке A на вертикалну праву која садржи центар круга O. Троугао AOM је правоугли, са хипотенузом OA и катетама OM и MA. На основу Питагорине теореме важи да је |OA|2=|OM|2+|MA|2. Пошто A лежи на кругу, знамо да је |OA|=r. Такође, знамо да је |MA|2=(cxx)2. На основу тога, можемо да израчунамо да је |OM|=r2(cxx)2. Дакле, гранична висина центра cy изнад које круг престаје да сече дуж је y1|OM| тј. y1r2(cxx)2. На веома сличан начин можемо одредити да је доња гранична висина једнака y2+r2(cxx)2.

Дакле, потребан услов да би круг секао дуж је да важи да cx припада интервалу [xr,x+r], а ако то важи, онда је потребан и довољан услов пресека то да се cy налази у интервалу [y1d,y2+d], где је d=r2(cxx)2.

Продискутујмо још и то да услов да cx припада интервалу [xr,x+r] увек обезбеђује да је поткорена величина ненегативна. Заиста, важи да је |cxx|r, па отуда следи да је (cxx)2r2.

 
1
def krugSeceVertikalnuDuz(cx, cy, r, x, y1, y2):
2
    if abs(cx - x) > r:
3
        return False
4
    d = math.sqrt(r**2 - (cx - x)**2)
5
    return y1 - d <= cy and cy <= y2 + d
6

(krugsecevertikalnuduz)

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

6
 
1
def krugSeceHorizontalnuDuz(cx, cy, r, x1, x2, y):
2
    if abs(cy - y) > r:
3
        return False
4
    d = math.sqrt(r**2 - (cy - y)**2)
5
    return x1 - d <= cx and cx <= x2 + d
6

(krugsecehorizontalnuduz)

Међутим, можемо и једноставније, ако анализу пресека круга са хоризонталном дужи успемо да сведемо на проблем пресека са вертикалном дужи. Пошто изометрије чувају инциденцију важи да круг сече дуж ако и само ако се њихове слике добијене применом неке изометријске трансформације секу. Потребно је да пронађемо неку изометријску трансформацију која хоризонталну дуж пресликава у вертикалну. Једна од најједноставнијих таквих је осна симетрија око праве y=x. Том трансформацијом се произвољна тачка (x,y) пресликава у тачку (y,x). Круг са центром у тачки (cx,cy) полупречника r се пресликава у круг са центром у тачки (cy,cx) такође полупречника r. Хоризонтална дуж са теменима (x1,y) и (x2,y) пресликава се у вертикалну дуж са теменима (y,x1) и (y,x2). Стога се пресек са хоризонталном дужи може веома једноставно испитати на следећи начин.

3
 
1
def krugSeceHorizontalnuDuz(cx, cy, r, x1, x2, y):
2
    return krugSeceVertikalnuDuz(cy, cx, r, y, x1, x2)
3

(krugsecehorizontalnuduzopt)

Сада можемо веома једноставно дефинисати функцију која проверава да ли се круг и правоугаоник секу.

11
 
1
def tackaPripadaPravougaoniku(x, y, x1, y1, w, h):
2
    return x1 <= x and x <= x1 + w and \
3
           y1 <= y and y <= y1 + h
4
5
def krugSecePravougaonik(cx, cy, r, x1, y1, w, h):
6
    return tackaPripadaPravougaoniku(cx, cy, x1, y1, w, h) or \
7
       krugSeceVertikalnuDuz(cx, cy, r, x1, y1, y1 + h) or \
8
       krugSeceVertikalnuDuz(cx, cy, r, x1 + w, y1, y1 + h) or \
9
       krugSeceHorizontalnuDuz(cx, cy, r, x1, x1 + w, y1) or \
10
       krugSeceHorizontalnuDuz(cx, cy, r, x1, x1 + w, y1 + h)
11

(krugsecepravougaonik)

Препреке чувамо у листи. Свака је препрека уређен пар два правоугаоника. Сваки правоугаоник је одређен четворком бројева који представљају координате његовог горњег левог темена, ширину и висину. У сваком кораку анимације све препреке померамо надесно (тако што пролазимо кроз листу препрека и умањујемо све x координате горњих левих темена правоугаоника). Ако се прва препрека у низу померила испред левог краја прозора, избацујемо је из низа. Ако се последња препрека у низу померила довољно од десног краја прозора, на крај низа убацујемо нову препреку са насумично одређеним положајем пролаза. У сваком кораку такође проверавамо и да ли птица сече неки од правоугаоника који сачињавају препреке и ако сече неки, искључујемо програм. Реагујемо на догађаје тастатуре и када детектујемо притисак на дугме, умањујемо y координату птице (тако подижемо птицу навише). Иначе, у сваком кораку анимације птицу спуштамо мало наниже.

145
 
1
import random, math
2
import pygame as pg
3
import pygamebg
4
5
# inicijalizujemo rad biblioteke PyGame
6
pg.init()
7
8
# Podrazumevano, kada se pritisne taster tastature, generise se samo jedan dogadjaj KEY_DOWN, a dogadjaj
9
# KEY_UP se generise nakon otpustanja tastera. Narednim pozivom menjamo to podrazumevano ponasanje.
10
# Prilikom duzeg drzanja tastera generisace se vise dogadjaja KEY_DOWN, a nakon otpustanja tastera generisace
11
# se dogadjaj KEY_UP. Prvi dogadjaj KEY_DOWN se generise cim je pritisnut taster. Nakon toga se ceka broj
12
# milisekundi zadat prvim parametrom funckije set_repeat (ovom slucaju 10), a zatim se naredni dogadjaji
13
# KEY_DOWN generisnu u pravilnim vremenskim intervalima odredjenim drugim parametrom funckije key_repeat
14
# (u ovom slucaju ce se dogadjaji KEY_DOWN generisati na svakih 10 milisekundi, sve dok je taster pritisnut).
15
pg.key.set_repeat(10, 10)
16
17
# dimenzija prozora
18
dim = (sirina, visina) = (800, 400) # otvaramo prozor
19
prozor = pygamebg.open_window(sirina, visina, "Flappy bird")
20
21
22
# da li je nastupio kraj igre
23
kraj_igre = False
24
25
# parametri ptice koja prolazi kroz prepreke - ona je kruznog oblika
26
ptica_x = sirina // 2
27
ptica_y = visina // 2
28
ptica_r = 30
29
30
# parametri prepreka koje se pojavljuju
31
sirina_prepreke = 60
32
visina_otvora = 130
33
34
# funkcija generiše dva pravougaonika koji zajedno predstavljaju prepreku kroz koju ptica prolazi
35
# pravougaonici su određeni koordinatama gornjeg levog temena, širinom i visinom
36
def napravi_prepreku():
37
    vrh_otvora = random.randint(0, visina - visina_otvora)
38
    return [[sirina, 0, sirina_prepreke, vrh_otvora],
39
                 [sirina, vrh_otvora + visina_otvora, sirina_prepreke, visina - vrh_otvora - visina_otvora]]
40
41
# funkcija kojom se određuje pređeni put tekuće prepreke nakon kojeg se pojavljuje nova prepreka
42
def odredi_rastojanje_nove_prepreke():
43
    # nova prepreka se ne može pojaviti pre nego što je tekuća prešla trećinu širine prozora
44
    # a mora se pojaviti nakon što je tekuća prepreka prešla ceo prozor
45
    return random.randint(sirina // 3, sirina)
46
47
# provera da li krug sa datim centrom i poluprečnikom seče vertikalnu duz (x, y1) (x, y2)
48
def krug_sece_vertikalnu_duz(cx, cy, r, x, y1, y2):
49
    if cx - r <= x and x <= cx + r:
50
        d = math.sqrt(r**2 - (cx - x)**2)
51
        if y1 - d <= cy and cy <= y2 + d:
52
            return True
53
54
# provera da li krug sa datim centrom i poluprečnikom seče horizontalnu duć (x1, y) (x2, y)
55
def krug_sece_horizontalnu_duz(cx, cy, r, x1, x2, y):
56
    return krug_sece_vertikalnu_duz(cy, cx, r, y, x1, x2)
57
58
# provera da li krug sa datim centrom i poluprečnikom seče pravougaonik zadat koordinatama
59
# gornjeg levog temena, širinom i visinom
60
def krug_sece_pravougaonik(krug, pravougaonik):
61
    (cx, cy, r) = krug
62
    (x0, y0, w, h) = pravougaonik
63
    if krug_sece_vertikalnu_duz(cx, cy, r, x0, y0, y0 + h):        # provera sudara sa prednjom ivicom
64
        return True
65
    if krug_sece_horizontalnu_duz(cx, cy, r, x0, x0 + w, y0 + h):  # provera sudara sa donjom ivicom
66
        return True
67
    if krug_sece_horizontalnu_duz(cx, cy, r, x0, x0 + w, y0):      # provera sudara sa gornjom ivicom
68
        return True
69
    return False                                                   # proveru sudara sa desnom ivicom preskacemo, jer u igri to nije moguće
70
71
# krecemo sa jednom pocetnom preprekom
72
prepreke = napravi_prepreku()
73
# odredjujemo kada treba da se pojavi naredna prepreka
74
rastojanje_nove_prepreke = odredi_rastojanje_nove_prepreke()
75
76
def crtaj_scenu():
77
    prozor.fill(pg.Color("white"))                                         # bojimo pozadinu
78
    pg.draw.circle(prozor, pg.Color("blue"), (ptica_x, ptica_y), ptica_r)  # crtamo pticu
79
    for pravougaonik in prepreke:                                          # crtamo prepreke
80
        pg.draw.rect(prozor, pg.Color("green"), pravougaonik)
81
82
def crtaj_kraj():
83
    prozor.fill(pg.Color("white"))                                           # bojimo pozadinu prozora u belo
84
    font = pg.font.SysFont("Arial", 60)                                      # font kojim će biti prikazan tekst
85
    poruka = "Kraj igre!"                                                    # poruka koja će se ispisivati
86
    tekst = font.render(poruka, True, pg.Color("black"))                     # gradimo sličicu koja predstavlja tu poruku ispisanu crnom bojom
87
    (sirina_teksta, visina_teksta) = (tekst.get_width(), tekst.get_height()) # određujemo veličinu tog teksta
88
    (x, y) = ((sirina - sirina_teksta) / 2, (visina - visina_teksta) / 2)    # položaj određujemo tako da tekst bude centriran
89
    prozor.blit(tekst, (x, y))                                               # prikazujemo sličicu na odgovarajućem mestu na ekranu
90
    
91
def crtaj():
92
    if not kraj_igre:
93
        crtaj_scenu()
94
    else:
95
        crtaj_kraj()
96
        
97
def obradi_dogadjaj(dogadjaj):
98
    global ptica_y
99
    if dogadjaj.type == pg.KEYDOWN:
100
        # strelicom na gore ili razamkom podizemo pticu
101
        if dogadjaj.key == pg.K_UP or dogadjaj.key == pg.K_SPACE:
102
            ptica_y -= 10
103
104
def novi_frejm():
105
    global ptica_y, prepreke, rastojanje_nove_prepreke, kraj_igre
106
    
107
    # ptica pada
108
    ptica_y += 3
109
110
    # prepreke se pomeraju nalevo
111
    for pravougaonik in prepreke:
112
        pravougaonik[0] -= 3
113
114
    # proveravamo da li se poslednja prepreka dovoljno pomerila da bi se ubacila nova prepreka
115
    if prepreke[-1][0] + prepreke[-1][2] + rastojanje_nove_prepreke < sirina:
116
        prepreke = prepreke + napravi_prepreku()                       # ubacujemo novu prepreku
117
        rastojanje_nove_prepreke = odredi_rastojanje_nove_prepreke()   # određujemo kada treba da se pojavi naredna prepreka
118
119
    # izbacujemo prepreke koje su ispale sa levog dela prozora
120
    while prepreke[0][0] + prepreke[0][2] < 0:
121
        prepreke.pop(0)
122
123
    # proveravamo da li je ptica pala na zemlju
124
    if ptica_y + ptica_r > visina:
125
        kraj_igre = True
126
127
    # proveravamo da li je ptica udarila u neku prepreku
128
    for pravougaonik in prepreke:
129
        if krug_sece_pravougaonik((ptica_x, ptica_y, ptica_r), pravougaonik):
130
            kraj_igre = True
131
    
132
sat = pg.time.Clock()
133
kraj = False
134
while not kraj:
135
    crtaj()
136
    pg.display.update()
137
    for dogadjaj in pg.event.get():
138
        if dogadjaj.type == pg.QUIT:
139
            kraj = True
140
        else:
141
            obradi_dogadjaj(dogadjaj)
142
    sat.tick(50)
143
    novi_frejm()
144
pg.quit()  # isključujemo rad biblioteke PyGame
145

(flappy)