OzbekPython / Bob 24

Yakuniy loyiha — Ilon öyini

pygame yordamida klassik «Ilon» öyinini qadamma-qadam yaratamiz: öyin sikli, hodisalar, çiziş, töqnaşuv, hisob va qayta boşlaş.

Yuqori daraja20 daqiqa16 kod misoli24 / 24

Loyiha haqida

Kursning yakuniy loyihasida köpçilik eski tugmali telefonlardan eslaydigan «Ilon» öyinini yozamiz. Qoidalar oddiy: ilon maydonda harakatlanadi, siz uni strelkalar bilan boşqarasiz. Ilon ovqatni yesa, uzayadi va hisob oşadi. Devorga yoki öz dumiga urilsa — öyin tugaydi.

Oddiy körinsa ham, bu loyihada dasturlaşning köp muhim mavzulari jamlangan: sikllar, röyxatlar, kortejlar, klasslar, random moduli, hodisalarni qayta işlaş va holatni boşqariş. Öyin yoziş — örganganlarni mustahkamlaşning eng qiziqarli usuli.

Öyinni pygame kutubxonasi bilan yozamiz. U oyna oçiş, rasm çiziş, klaviaturani öqiş va tovuş çiqariş uçun oddiy vositalar beradi.

Terminal
pip install pygame

Örnatilganini tekşiriş uçun python -m pygame.examples.aliens buyruği bilan kutubxonaga ilova qilingan namunaviy öyinni işga tuşirib körişingiz mumkin.

Boşlanğiç skript va undagi kamçiliklar

Loyiha uçun asos sifatida kurs muallifi Sarvarbek Nazarov yozgan qisqa skriptni olamiz. Uning boşlanişi quyidagiça (izohlar özbekçaga ögirildi):

Python
import pygame

# pygame ni işga tuşiramiz
pygame.init()

# Oyna ölçami
window_size = (400, 400)
screen = pygame.display.set_mode(window_size)
pygame.display.set_caption("Snake")

snake_size = 10
snake_pos = [200, 200]
snake_body = [[200, 200], [210, 200], [220, 200]]

food_size = 10
food_pos = [random.randrange(1, (window_size[0]-food_size)//10)*10, random.randrange(1, (window_size[1]-food_size)//10)*10]
food_spawn = True

direction = "RIGHT"
change_to = direction
score = 0
speed = 200

Ğoya töğri: ilon tanasi — koordinatalar röyxati, ovqat — tasodifiy nuqta, yönaliş — özgaruvçi. Lekin skriptni işga tuşirsangiz, darhol xato çiqadi:

Natija
NameError: name 'random' is not defined

Sababi — random moduli import qilinmagan. Xuddi şunday, game_over() funksiyasi içida işlatilgan time va sys modullari ham unutilgan. Bu yangi boşlovçilarda juda köp uçraydigan xato: Python faqat import qilingan modullarni taniydi.

Skriptning yönalişni özgartiruvçi qismi ham qiziq:

Python
for event in pygame.event.get():
    if event.type == pygame.KEYDOWN:
        if event.key == pygame.K_UP:
            change_to = "UP"
        if event.key == pygame.K_DOWN:
            change_to = "DOWN"

# Ilon birdan teskari tomonga burilib ketmasligi kerak
if change_to == "UP" and direction != "DOWN":
    direction = "UP"
if change_to == "DOWN" and direction != "UP":
    direction = "DOWN"

Bu yaxşi ğoya — ilon öz içiga qarab burila olmasligi kerak. Biz uni saqlab qolamiz, faqat qisqaroq yozamiz. Boşqa kamçiliklar:

  • import random, import time, import sys yöq.
  • Oynani yopiş (pygame.QUIT) hodisasi qayta işlanmagan — oynaning ✕ tugmasi işlamaydi.
  • speed = 200 eʼlon qilingan, lekin öyin tezligini boşqaradigan pygame.time.Clock işlatilmagan.
  • Tananing boşlanğiç holati: boş [200, 200], qolgan bölaklar esa öng tomonda, ilon ham öngga harakatlanmoqda — yaʼni boş birinçi qadamdayoq öz tanasiga kiradi.
  • 10, 400 kabi «sehrli sonlar» kod böylab tarqalgan; ölçamni özgartiriş uçun köp joyni tahrirlaş kerak.
  • game_over() time.sleep(3) bilan oynani muzlatib qöyadi va dasturdan çiqib ketadi — qayta öynaş imkoni yöq.

Endi şu ğoyalar asosida toza, tuşunarli va kengaytiriş oson bölgan versiyani qadamma-qadam quramiz.

1-qadam: öyin sikli

Har qanday öyinning yuragi — öyin sikli (game loop). U soniyasiga önlab marta takrorlanadi va har aylanişda uçta işni bajaradi:

  1. Hodisalar — foydalanuvçi nima qildi? (tugma bosildimi, oyna yopildimi)
  2. Yangilaş — öyin holatini özgartiriş (ilonni suriş, töqnaşuvni tekşiriş).
  3. Çiziş — yangi holatni ekranga çiqariş.

pygame.time.Clock esa siklning tezligini boşqaradi: soat.tick(10) sikl soniyasiga 10 martadan tez aylanmasligini taʼminlaydi. Busiz öyin kuçli kompyuterda juda tez, kuçsizida sekin işlardi.

Python
import sys

import pygame

pygame.init()
ekran = pygame.display.set_mode((600, 400))
pygame.display.set_caption("Birinçi oyna")
soat = pygame.time.Clock()

while True:
    # 1. Hodisalar
    for hodisa in pygame.event.get():
        if hodisa.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

    # 2. Yangilaş (hozirça heç narsa)

    # 3. Çiziş
    ekran.fill((18, 24, 27))
    pygame.draw.rect(ekran, (76, 175, 80), (290, 190, 20, 20))
    pygame.display.flip()   # çizilganlarni ekranga çiqaramiz

    soat.tick(10)           # soniyasiga 10 kadr

pygame da hamma narsa avval yaşirin «kanvas»ga çiziladi, pygame.display.flip() esa uni bir yöla ekranga çiqaradi. Şu sababli tasvir miltillamaydi. Koordinatalar boşi (0, 0) — oynaning çap yuqori burçagi, y esa pastga qarab ösadi.

2-qadam: konstantalar va katakli maydon

Ilon piksel böylab emas, kataklar böylab yuradi. Şuning uçun maydonni katakli tör deb tasavvur qilamiz: ilon va ovqatning holatini katak raqamlari bilan saqlaymiz (ustun, qator), faqat çizişda piksellarga aylantiramiz. Bu hisob-kitobni ança soddalaştiradi.

Barça sozlamalarni dastur boşida konstantalar sifatida (katta harflar bilan) yozamiz:

Python
KATAK = 20                      # bitta katak — 20x20 piksel
USTUNLAR, QATORLAR = 30, 20     # maydon 30x20 katak
ENI, BALANDLIK = KATAK * USTUNLAR, KATAK * QATORLAR

# Yönalişlar: (dx, dy) — har qadamda ustun va qator qançaga özgaradi
YUQORI, PAST, CHAP, ONG = (0, -1), (0, 1), (-1, 0), (1, 0)

def piksel(joy: tuple[int, int]) -> tuple[int, int]:
    ustun, qator = joy
    return ustun * KATAK, qator * KATAK

print("Oyna ölçami:", ENI, "x", BALANDLIK)
print("Markaziy katak:", (USTUNLAR // 2, QATORLAR // 2))
print("Uning piksel koordinatasi:", piksel((USTUNLAR // 2, QATORLAR // 2)))
print("Öng pastki katak:", piksel((USTUNLAR - 1, QATORLAR - 1)))

Natija:

Natija
Oyna ölçami: 600 x 400
Markaziy katak: (15, 10)
Uning piksel koordinatasi: (300, 200)
Öng pastki katak: (580, 380)

Endi maydonni kattalaştiriş uçun faqat bitta qatorni özgartiriş kifoya. Yönalişlarni satr ("UP") emas, (dx, dy) kortej körinişida saqlaganimiz esa harakatni bitta formula bilan yoziş imkonini beradi.

3-qadam: Ilon klassi

Ilonning maʼlumotlari (tanasi, yönalişi) va xatti-harakatlari (yuriş, buriliş, uzayiş) bitta klassga yiğiladi. Tanani kataklar röyxati sifatida saqlaymiz, birinçi element — boş.

Harakat ğoyasi juda çiroyli: har qadamda boşning oldiga yangi katak qöşamiz va dumdan bitta katakni olib taşlaymiz. Ilon ovqat yegan bölsa, dumni olib taşlamaymiz — şu tarzda u bir katakka uzayadi.

Bu klass pygame ga boğliq emas, şuning uçun uni şu yerning özida sinab köriş mumkin:

Python
USTUNLAR, QATORLAR = 30, 20
YUQORI, PAST, CHAP, ONG = (0, -1), (0, 1), (-1, 0), (1, 0)


class Ilon:
    def __init__(self) -> None:
        x, y = USTUNLAR // 2, QATORLAR // 2
        self.tana = [(x, y), (x - 1, y), (x - 2, y)]  # birinçi element — boş
        self.yonalish = ONG
        self.keyingi_yonalish = ONG
        self.osish = 0

    @property
    def bosh(self) -> tuple[int, int]:
        return self.tana[0]

    def buril(self, yangi: tuple[int, int]) -> None:
        # Teskari tomonga birdan buriliş mumkin emas
        dx, dy = self.yonalish
        if yangi != (-dx, -dy):
            self.keyingi_yonalish = yangi

    def harakat(self) -> None:
        self.yonalish = self.keyingi_yonalish
        x, y = self.bosh
        dx, dy = self.yonalish
        self.tana.insert(0, (x + dx, y + dy))
        if self.osish > 0:
            self.osish -= 1      # dum joyida qoladi — ilon uzayadi
        else:
            self.tana.pop()      # dumni olib taşlaymiz

    def uzay(self) -> None:
        self.osish += 1

    def devorga_urildimi(self) -> bool:
        x, y = self.bosh
        return not (0 <= x < USTUNLAR and 0 <= y < QATORLAR)

    def ozini_tishladimi(self) -> bool:
        return self.bosh in self.tana[1:]


ilon = Ilon()
print("Boşida:   ", ilon.tana)
ilon.harakat()
print("1 qadam:   ", ilon.tana)
ilon.buril(CHAP)               # teskari yönaliş — eʼtiborsiz qoldiriladi
ilon.harakat()
print("ÇAP bosildi:", ilon.tana)
ilon.buril(YUQORI)
ilon.uzay()                    # göyo ovqat yedi
ilon.harakat()
print("Yuqoriga + uzaydi:", ilon.tana)
print("Uzunligi:", len(ilon.tana))

Natija:

Natija
Boşida:    [(15, 10), (14, 10), (13, 10)]
1 qadam:    [(16, 10), (15, 10), (14, 10)]
ÇAP bosildi: [(17, 10), (16, 10), (15, 10)]
Yuqoriga + uzaydi: [(17, 9), (17, 10), (16, 10), (15, 10)]
Uzunligi: 4

keyingi_yonalish nima uçun kerak? Agar ilon öngga ketayotganda siz bitta kadr içida tez-tez ↑ va ← tugmalarini bossangiz, yönalişni darhol özgartiradigan sodda kod avval «yuqori», keyin «çap» ni qabul qiladi — ilon esa bir joyda turib orqasiga burilib, özini tişlab oladi. Biz yangi yönalişni faqat haqiqiy harakat yönalişiga nisbatan tekşiramiz va uni keyingi qadamda qöllaymiz, şu bois bunday xato bölmaydi.

@property dekoratori bosh ni metod emas, oddiy atribut kabi öqiş imkonini beradi: ilon.bosh() emas, ilon.bosh.

4-qadam: töqnaşuvlar

Öyin ikki holatda tugaydi:

  • Devorga uriliş — boşning ustun yoki qator raqami maydondan çiqib ketsa. Python dagi 0 <= x < USTUNLAR kabi zanjir soliştiriş buni juda öqimli qiladi.
  • Özini tişlaş — boş tananing qolgan qismidagi biror katakka töğri kelsa: self.bosh in self.tana[1:].

Ikkalasini sinab köramiz (bu misol oldingi kodning qisqa nusxasidan foydalanadi):

Python
USTUNLAR, QATORLAR = 30, 20

def devorga_urildimi(bosh: tuple[int, int]) -> bool:
    x, y = bosh
    return not (0 <= x < USTUNLAR and 0 <= y < QATORLAR)

def ozini_tishladimi(tana: list[tuple[int, int]]) -> bool:
    return tana[0] in tana[1:]

print(devorga_urildimi((15, 10)))   # maydon içida
print(devorga_urildimi((30, 10)))   # öng devordan ötdi
print(devorga_urildimi((5, -1)))    # yuqori devordan ötdi

# Boş (5, 5) katakka qaytib keldi — özini tişladi
tana = [(5, 5), (6, 5), (6, 6), (5, 6), (5, 5), (4, 5)]
print(ozini_tishladimi(tana))
print(ozini_tishladimi([(1, 1), (0, 1), (0, 2)]))

Natija:

Natija
False
True
True
True
False

5-qadam: ovqat

Ovqat ilonning tanasi ustida paydo bölmasligi kerak. Asl skriptda tasodifiy nuqta tanlanadi va bu tekşirilmaydi. Biz avval barça böş kataklar röyxatini tuzamiz va ulardan birini random.choice() bilan tanlaymiz. Tananing kataklarini set ga aylantirganimiz in tekşiruvini tezlaştiradi.

Python
import random

USTUNLAR, QATORLAR = 30, 20

def yangi_ovqat(tana: list[tuple[int, int]]) -> tuple[int, int]:
    """Ilon tanasida bölmagan tasodifiy katakni qaytaradi."""
    band = set(tana)
    erkin = [
        (x, y)
        for x in range(USTUNLAR)
        for y in range(QATORLAR)
        if (x, y) not in band
    ]
    return random.choice(erkin)

random.seed(2024)   # natija har safar bir xil bölişi uçun (faqat sinov uçun)
tana = [(15, 10), (14, 10), (13, 10)]
for _ in range(3):
    ovqat = yangi_ovqat(tana)
    print("Ovqat:", ovqat, "| tanada emasmi?", ovqat not in tana)

Natija:

Natija
Ovqat: (24, 4) | tanada emasmi? True
Ovqat: (9, 6) | tanada emasmi? True
Ovqat: (29, 15) | tanada emasmi? True

6-qadam: hodisalar va boşqaruv

Klaviaturani öqiş uçun pygame.KEYDOWN hodisasidan foydalanamiz. Har bir tugmaga alohida if yoziş örniga luğat tuzamiz: tugma → yönaliş. Şunda strelkalar bilan birga W, A, S, D tugmalarini qöşiş bir qatorlik iş:

Python
import pygame

YUQORI, PAST, CHAP, ONG = (0, -1), (0, 1), (-1, 0), (1, 0)
TUGMALAR = {
    pygame.K_UP: YUQORI, pygame.K_w: YUQORI,
    pygame.K_DOWN: PAST, pygame.K_s: PAST,
    pygame.K_LEFT: CHAP, pygame.K_a: CHAP,
    pygame.K_RIGHT: ONG, pygame.K_d: ONG,
}


class Oyin:
    # ... boşqa metodlar ...

    def hodisalar(self) -> None:
        for hodisa in pygame.event.get():
            if hodisa.type == pygame.QUIT:
                self.chiqish()
            if hodisa.type != pygame.KEYDOWN:
                continue
            if hodisa.key == pygame.K_ESCAPE:
                self.chiqish()
            if self.tugadi:
                if hodisa.key in (pygame.K_SPACE, pygame.K_RETURN):
                    self.qayta_boshla()
            elif hodisa.key in TUGMALAR:
                self.ilon.buril(TUGMALAR[hodisa.key])

pygame.event.get() oxirgi kadrdan beri yiğilgan barça hodisalarni qaytaradi va navbatni tozalaydi. Uni har kadrda çaqiriş şart — aks holda operatsion tizim oynani «javob bermayapti» deb hisoblaydi.

7-qadam: çiziş va hisob matni

Çiziş uçun bir neçta oddiy funksiyalar yetarli:

FunksiyaVazifasi
ekran.fill(rang)butun ekranni bir rangga böyaydi
pygame.draw.rect(ekran, rang, rect)törtburçak çizadi
pygame.draw.ellipse(ekran, rang, rect)ellips (aylana) çizadi
pygame.draw.line(ekran, rang, a, b)çiziq çizadi
shrift.render(matn, True, rang)matndan rasm (Surface) yasaydi
ekran.blit(sirt, joy)bitta rasmni boşqasining ustiga qöyadi

Ranglar (qizil, yashil, koʻk) kortejlari bilan beriladi, har biri 0 dan 255 gaça. Matn çiqariş ikki bosqiçli: avval render() matnni rasmga aylantiradi, keyin blit() uni ekranga qöyadi.

Python
import pygame

KATAK = 20
FON = (18, 24, 27)
ILON_RANGI = (76, 175, 80)
BOSH_RANGI = (46, 125, 50)
OVQAT_RANGI = (229, 57, 53)
OQ = (240, 240, 240)


class Oyin:
    # ... boşqa metodlar ...

    def katak_rect(self, joy: tuple[int, int]) -> pygame.Rect:
        x, y = joy
        return pygame.Rect(x * KATAK, y * KATAK, KATAK, KATAK)

    def chizish(self) -> None:
        self.ekran.fill(FON)
        # Ovqat — katakdan biroz kiçikroq doira
        pygame.draw.ellipse(self.ekran, OVQAT_RANGI, self.katak_rect(self.ovqat).inflate(-4, -4))
        # Ilon — boşi töqroq rangda, burçaklari yumaloq
        for i, joy in enumerate(self.ilon.tana):
            rang = BOSH_RANGI if i == 0 else ILON_RANGI
            pygame.draw.rect(self.ekran, rang, self.katak_rect(joy).inflate(-2, -2), border_radius=5)
        # Hisob
        sirt = self.shrift.render(f"Hisob: {self.hisob}", True, OQ)
        self.ekran.blit(sirt, (10, 8))
        pygame.display.flip()

Rect.inflate(-2, -2) törtburçakni har tomondan biroz kiçraytiradi — şunda ilon bölaklari orasida ingiçka çiziq paydo böladi va tana bölaklarga bölingani körinadi.

8-qadam: tezlikning oşişi

Öyin qiziqarli bölişi uçun u asta-sekin qiyinlaşişi kerak. Tezlikni alohida özgaruvçida saqlaş örniga uni hisobdan hisoblaymiz: har 3 ta ovqatdan keyin ilon soniyasiga bir qadam tezroq yuradi, lekin maʼlum çegaradan oşmaydi. Bu qiymat soat.tick(self.tezlik) ga beriladi.

Python
BOSHLANGICH_TEZLIK = 8
MAKS_TEZLIK = 20
HAR_NECHA_OVQATDA = 3

def tezlik(hisob: int) -> int:
    return min(MAKS_TEZLIK, BOSHLANGICH_TEZLIK + hisob // HAR_NECHA_OVQATDA)

for hisob in (0, 2, 3, 10, 30, 50):
    print(f"Hisob {hisob:>2} → soniyasiga {tezlik(hisob)} qadam")

Natija:

Natija
Hisob  0 → soniyasiga 8 qadam
Hisob  2 → soniyasiga 8 qadam
Hisob  3 → soniyasiga 9 qadam
Hisob 10 → soniyasiga 11 qadam
Hisob 30 → soniyasiga 18 qadam
Hisob 50 → soniyasiga 20 qadam

9-qadam: «Öyin tugadi» oynasi va qayta boşlaş

Asl skript öyin tugaşi bilan 3 soniya kutib, dasturdan çiqib ketardi. Bizda esa öyin self.tugadi bayroği bilan ikki holatda böladi: öynaş va tugadi. Tugagan holatda ilon harakatlanmaydi, ekranga yarim şaffof qora parda va xabar çiqadi, Space bosilsa esa qayta_boshla() hamma narsani boşlanğiç holatga qaytaradi. Rekord (eng yuqori hisob) esa qayta boşlaşda saqlanib qoladi.

Python
import pygame

ENI, BALANDLIK = 600, 400
OQ = (240, 240, 240)
SARIQ = (255, 213, 79)


class Oyin:
    # ... boşqa metodlar ...

    def yangilash(self) -> None:
        if self.tugadi:
            return
        self.ilon.harakat()
        if self.ilon.devorga_urildimi() or self.ilon.ozini_tishladimi():
            self.tugadi = True
            self.rekord = max(self.rekord, self.hisob)
            return
        if self.ilon.bosh == self.ovqat:
            self.hisob += 1
            self.ilon.uzay()
            self.ovqat = yangi_ovqat(self.ilon)

    def tugadi_oynasi(self) -> None:
        parda = pygame.Surface((ENI, BALANDLIK), pygame.SRCALPHA)
        parda.fill((0, 0, 0, 170))          # oxirgi son — şaffoflik (0..255)
        self.ekran.blit(parda, (0, 0))
        self.matn("Öyin tugadi!", self.katta_shrift, SARIQ, (ENI // 2, BALANDLIK // 2 - 50))
        self.matn(f"Hisobingiz: {self.hisob}", self.shrift, OQ, (ENI // 2, BALANDLIK // 2 + 5))

pygame.SRCALPHA bayroği bilan yaratilgan Surface şaffoflikni qöllab-quvvatlaydi: rangning törtinçi qiymati (alpha) qançalik kiçik bölsa, parda şunçalik şaffof böladi. Pardaning ostida öyinning oxirgi holati körinib turadi — öyinçi qayerda xato qilganini köradi.

Töliq kod

Barça qadamlarni birlaştiramiz. Kodni ilon_oyini.py fayliga saqlang va python ilon_oyini.py buyruği bilan işga tuşiring. Boşqaruv: strelkalar yoki W A S D, qayta boşlaş — Space, çiqiş — Esc.

Python
import random
import sys

import pygame

# ---------- Sozlamalar ----------
KATAK = 20                      # bitta katakning ölçami (piksel)
USTUNLAR, QATORLAR = 30, 20     # maydon ölçami (katak)
ENI, BALANDLIK = KATAK * USTUNLAR, KATAK * QATORLAR
BOSHLANGICH_TEZLIK = 8          # soniyasiga qadamlar soni
MAKS_TEZLIK = 20
HAR_NECHA_OVQATDA = 3           # har 3 ta ovqatda tezlik +1

# Ranglar (R, G, B)
FON = (18, 24, 27)
TOR = (28, 36, 40)
ILON_RANGI = (76, 175, 80)
BOSH_RANGI = (46, 125, 50)
OVQAT_RANGI = (229, 57, 53)
OQ = (240, 240, 240)
SARIQ = (255, 213, 79)

# Yönalişlar: (dx, dy)
YUQORI, PAST, CHAP, ONG = (0, -1), (0, 1), (-1, 0), (1, 0)
TUGMALAR = {
    pygame.K_UP: YUQORI, pygame.K_w: YUQORI,
    pygame.K_DOWN: PAST, pygame.K_s: PAST,
    pygame.K_LEFT: CHAP, pygame.K_a: CHAP,
    pygame.K_RIGHT: ONG, pygame.K_d: ONG,
}


class Ilon:
    def __init__(self) -> None:
        x, y = USTUNLAR // 2, QATORLAR // 2
        self.tana = [(x, y), (x - 1, y), (x - 2, y)]  # birinçi element — boş
        self.yonalish = ONG
        self.keyingi_yonalish = ONG
        self.osish = 0

    @property
    def bosh(self) -> tuple[int, int]:
        return self.tana[0]

    def buril(self, yangi: tuple[int, int]) -> None:
        # Teskari tomonga birdan buriliş mumkin emas
        dx, dy = self.yonalish
        if yangi != (-dx, -dy):
            self.keyingi_yonalish = yangi

    def harakat(self) -> None:
        self.yonalish = self.keyingi_yonalish
        x, y = self.bosh
        dx, dy = self.yonalish
        self.tana.insert(0, (x + dx, y + dy))
        if self.osish > 0:
            self.osish -= 1      # dum joyida qoladi — ilon uzayadi
        else:
            self.tana.pop()      # dumni olib taşlaymiz

    def uzay(self) -> None:
        self.osish += 1

    def devorga_urildimi(self) -> bool:
        x, y = self.bosh
        return not (0 <= x < USTUNLAR and 0 <= y < QATORLAR)

    def ozini_tishladimi(self) -> bool:
        return self.bosh in self.tana[1:]


def yangi_ovqat(ilon: Ilon) -> tuple[int, int]:
    """Ilon tanasida bölmagan tasodifiy katakni qaytaradi."""
    band = set(ilon.tana)
    erkin = [
        (x, y)
        for x in range(USTUNLAR)
        for y in range(QATORLAR)
        if (x, y) not in band
    ]
    return random.choice(erkin)


class Oyin:
    def __init__(self) -> None:
        pygame.init()
        self.ekran = pygame.display.set_mode((ENI, BALANDLIK))
        pygame.display.set_caption("Ilon öyini")
        self.soat = pygame.time.Clock()
        self.shrift = pygame.font.Font(None, 32)
        self.katta_shrift = pygame.font.Font(None, 64)
        self.rekord = 0
        self.qayta_boshla()

    def qayta_boshla(self) -> None:
        self.ilon = Ilon()
        self.ovqat = yangi_ovqat(self.ilon)
        self.hisob = 0
        self.tugadi = False

    @property
    def tezlik(self) -> int:
        return min(MAKS_TEZLIK, BOSHLANGICH_TEZLIK + self.hisob // HAR_NECHA_OVQATDA)

    # ---------- 1. Hodisalar ----------
    def hodisalar(self) -> None:
        for hodisa in pygame.event.get():
            if hodisa.type == pygame.QUIT:
                self.chiqish()
            if hodisa.type != pygame.KEYDOWN:
                continue
            if hodisa.key == pygame.K_ESCAPE:
                self.chiqish()
            if self.tugadi:
                if hodisa.key in (pygame.K_SPACE, pygame.K_RETURN):
                    self.qayta_boshla()
            elif hodisa.key in TUGMALAR:
                self.ilon.buril(TUGMALAR[hodisa.key])

    # ---------- 2. Holatni yangilaş ----------
    def yangilash(self) -> None:
        if self.tugadi:
            return
        self.ilon.harakat()
        if self.ilon.devorga_urildimi() or self.ilon.ozini_tishladimi():
            self.tugadi = True
            self.rekord = max(self.rekord, self.hisob)
            return
        if self.ilon.bosh == self.ovqat:
            self.hisob += 1
            self.ilon.uzay()
            self.ovqat = yangi_ovqat(self.ilon)

    # ---------- 3. Çiziş ----------
    def katak_rect(self, joy: tuple[int, int]) -> pygame.Rect:
        x, y = joy
        return pygame.Rect(x * KATAK, y * KATAK, KATAK, KATAK)

    def matn(self, yozuv: str, shrift: pygame.font.Font, rang, markaz) -> None:
        sirt = shrift.render(yozuv, True, rang)
        self.ekran.blit(sirt, sirt.get_rect(center=markaz))

    def chizish(self) -> None:
        self.ekran.fill(FON)
        for x in range(0, ENI, KATAK):
            pygame.draw.line(self.ekran, TOR, (x, 0), (x, BALANDLIK))
        for y in range(0, BALANDLIK, KATAK):
            pygame.draw.line(self.ekran, TOR, (0, y), (ENI, y))

        pygame.draw.ellipse(self.ekran, OVQAT_RANGI, self.katak_rect(self.ovqat).inflate(-4, -4))
        for i, joy in enumerate(self.ilon.tana):
            rang = BOSH_RANGI if i == 0 else ILON_RANGI
            pygame.draw.rect(self.ekran, rang, self.katak_rect(joy).inflate(-2, -2), border_radius=5)

        hisob_sirti = self.shrift.render(f"Hisob: {self.hisob}   Rekord: {self.rekord}", True, OQ)
        self.ekran.blit(hisob_sirti, (10, 8))

        if self.tugadi:
            parda = pygame.Surface((ENI, BALANDLIK), pygame.SRCALPHA)
            parda.fill((0, 0, 0, 170))
            self.ekran.blit(parda, (0, 0))
            markaz_x, markaz_y = ENI // 2, BALANDLIK // 2
            self.matn("Öyin tugadi!", self.katta_shrift, SARIQ, (markaz_x, markaz_y - 50))
            self.matn(f"Hisobingiz: {self.hisob}", self.shrift, OQ, (markaz_x, markaz_y + 5))
            self.matn("Space — qayta boşlaş, Esc — çiqiş", self.shrift, OQ,
                      (markaz_x, markaz_y + 45))

        pygame.display.flip()

    def chiqish(self) -> None:
        pygame.quit()
        sys.exit()

    def ishga_tushir(self) -> None:
        while True:
            self.hodisalar()
            self.yangilash()
            self.chizish()
            self.soat.tick(self.tezlik)


if __name__ == "__main__":
    Oyin().ishga_tushir()

Kodning tuzilişiga yana bir bor eʼtibor bering:

  • Konstantalar dastur boşida — ölçam, rang va tezlikni bir joyda özgartirasiz.
  • Ilon klassi faqat ilon mantiqini biladi, ekran haqida heç narsa bilmaydi. Şuning uçun uni pygame siz sinab köra oldik.
  • Oyin klassi öyin siklini uçta aniq metodga ajratadi: hodisalar(), yangilash(), chizish().
  • pygame.time.Clock tezlikni barqaror qiladi, tezlik esa hisobga qarab oşadi.

Xulosa

  • Öyin sikli uç bosqiçdan iborat: hodisalar → yangilaş → çiziş; Clock.tick() uning tezligini belgilaydi.
  • Holatni kataklarda saqlab, faqat çizişda piksellarga aylantiriş hisob-kitobni soddalaştiradi.
  • Ilon harakati: boşga katak qöşiş va dumdan olib taşlaş; uzayiş — dumni bir marta qoldiriş.
  • Yönalişni (dx, dy) kortej sifatida saqlaş if zanjirlarini qisqartiradi.
  • Töqnaşuv: maydon çegarasini va boşning tanaga tuşişini tekşiriş.
  • Mantiqni (Ilon) çizişdan (Oyin) ajratiş kodni sinaş va kengaytirişni osonlaştiradi.
  • import larni unutmang: NameError köpinça şu sababli çiqadi.

Maşqlar

  1. Devorsiz rejim yozing: ilon maydonning bir çetidan çiqsa, qarama-qarşi çetidan kirsin. Buning uçun boş koordinatasini % (qoldiq) operatori bilan hisoblaydigan oralash(joy) funksiyasini yozing.
Yeçim
Python
USTUNLAR, QATORLAR = 30, 20

def oralash(joy: tuple[int, int]) -> tuple[int, int]:
    x, y = joy
    return x % USTUNLAR, y % QATORLAR

print(oralash((30, 5)))    # öng çetdan çiqdi → çapdan kiradi
print(oralash((-1, 5)))    # çap çetdan çiqdi → öngdan kiradi
print(oralash((10, -1)))   # yuqoridan çiqdi → pastdan kiradi
print(oralash((7, 7)))     # maydon içida — özgarmaydi

Ilon.harakat() içida yangi boşni oralash((x + dx, y + dy)) qilib qöşsangiz va devorga_urildimi() tekşiruvini olib taşlasangiz, devorsiz rejim tayyor.

  1. Pauza qöşing: P tugmasi bosilganda öyin töxtasin, yana bosilganda davom etsin. Pauza paytida ekranda «Pauza» yozuvi çiqsin.
Yeçim
Python
import pygame

OQ = (240, 240, 240)
ENI, BALANDLIK = 600, 400


class Oyin:
    # __init__ va qayta_boşla() içida: self.pauza = False

    def hodisalar(self) -> None:
        for hodisa in pygame.event.get():
            if hodisa.type == pygame.QUIT:
                self.chiqish()
            if hodisa.type == pygame.KEYDOWN and hodisa.key == pygame.K_p and not self.tugadi:
                self.pauza = not self.pauza
            # ... qolgan tugmalar avvalgidek ...

    def yangilash(self) -> None:
        if self.tugadi or self.pauza:
            return
        # ... qolgani avvalgidek ...

    def pauza_yozuvi(self) -> None:
        # çiziş() içida, flip() dan oldin çaqiriladi
        if self.pauza:
            self.matn("Pauza", self.katta_shrift, OQ, (ENI // 2, BALANDLIK // 2))
  1. Rekordni faylga saqlang: öyin tugaganda rekord rekord.txt fayliga yozilsin, dastur işga tuşganda esa öqilsin. Fayl bölmasa yoki buzilgan bölsa, rekord 0 bölsin. pathlib dan foydalaning.
Yeçim
Python
from pathlib import Path

REKORD_FAYLI = Path("rekord.txt")

def rekordni_oqi() -> int:
    try:
        return int(REKORD_FAYLI.read_text(encoding="utf-8"))
    except (FileNotFoundError, ValueError):
        return 0

def rekordni_yoz(qiymat: int) -> None:
    REKORD_FAYLI.write_text(str(qiymat), encoding="utf-8")

REKORD_FAYLI.unlink(missing_ok=True)   # sinov uçun toza holatdan boşlaymiz
print("Boşida:", rekordni_oqi())
rekordni_yoz(17)
print("Yozilgandan keyin:", rekordni_oqi())
REKORD_FAYLI.write_text("buzilgan", encoding="utf-8")
print("Buzilgan fayl:", rekordni_oqi())

Oyin.__init__ da self.rekord = rekordni_oqi(), öyin tugaganda esa rekordni_yoz(self.rekord) çaqiriladi.

  1. Maxsus «oltin olma» qöşing: har 5-ovqat oddiy emas, oltin bölsin va 3 oçko bersin (ilon esa 3 katakka uzaysin). Avval mantiqni pygame siz yozib sinab köring.
Yeçim
Python
def ovqat_turi(yeyilgan_soni: int) -> str:
    """Navbatdagi ovqat turini qaytaradi: har 5-si oltin."""
    return "oltin" if (yeyilgan_soni + 1) % 5 == 0 else "oddiy"

OCHKO = {"oddiy": 1, "oltin": 3}

hisob = 0
uzunlik = 3
for yeyilgan in range(10):
    tur = ovqat_turi(yeyilgan)
    hisob += OCHKO[tur]
    uzunlik += OCHKO[tur]
    print(f"{yeyilgan + 1:>2}-ovqat: {tur:<6} hisob={hisob:>2} uzunlik={uzunlik}")

pygame versiyasida self.ovqat_turi ni saqlang, oltin olmani SARIQ rangda çizing va yeyilganda self.ilon.uzay() ni OCHKO[tur] marta çaqiring.