Ana içeriğe geç
Kesit Python
Temel · Bitirme

Mini projeler: oyunlar ve araçlar

Sayı tahmin, adam asmaca, taş-kağıt-makas, şifre üretici, Sezar şifresi, hesap bölüşme ve ATM menüsünü gereksinimden tasarıma, adım adım inşadan teste kadar kurarak temel kavramları gerçek programlarda birleştirirsin.

Uygulamada aç, 20 alıştırmayı çözÜcretsiz, hesap gerekmez, kurulum yok.

Bu sayfa dersin okuma sürümüdür. Metin bir kutuyu çalıştırmanı ya da adıma geçmeni söylüyorsa dersi uygulamada aç.

Neden mini projeler?

Değişken, koşul, döngü, fonksiyon, liste ve sözlüğü tek tek öğrendin. Gerçek program ise bunların birlikte çalışmasıdır ve asıl beceri boş bir dosyaya bakıp "nereden başlayacağım?" sorusunu yanıtlayabilmektir. Bu derste yedi küçük, eğlenceli ama ciddi biçimde tasarlanmış proje yapacağız. Her biri aynı dört adımı izler:

  1. Gereksinimler: program tam olarak ne yapmalı? Kenar durumlar neler?
  2. Tasarım: işi hangi fonksiyonlara bölerim? Hangisi hesap yapar, hangisi kullanıcıyla konuşur?
  3. Adım adım inşa: en küçük çalışan parçayı yaz, dene, sonra büyüt.
  4. Test: fonksiyonları tek tek, oyun döngüsünü hazır girdilerle dene.
ProjeBirleştirdiği kavramlar
Sayı tahmin oyunurandom + seed, while, int dönüşümü, try/except
Adam asmacaküme, metin birleştirme, oyun durumu
Taş-kağıt-makassözlükle kural tablosu, random.choice, skor
Şifre üreticistring modülü, random.Random, kurallı üretim
Sezar şifrelemeord/chr, mod aritmetiği, metin dönüştürme
Bahşiş ve hesap bölüşmekayan nokta, yuvarlama, doğrulama
ATM menüsüwhile True, match, durum (bakiye) yönetimi

Tasarımın altın kuralı: mantığı girdi/çıktıdan ayır

Yeni başlayan biri sayı tahmin oyununu tek bir while içinde, input ve print arasına gömülmüş if'lerle yazar. Çalışır ama test edilemez: her denemede klavyeden sayı girmen gerekir. Profesyonel yol iki katmandır:

Son madde küçük ama güçlü bir hiledir: fonksiyon varsayılan olarak gerçek input'u kullanır, testte ise ona hazır cevaplar veren bir fonksiyon verirsin. Buna bağımlılık enjeksiyonu (dependency injection) denir.

def selamla(girdi=input):
    ad = girdi("Adın? ")
    return f"Merhaba {ad}!"

cevaplar = iter(["Ayşe"])
print(selamla(lambda istem: next(cevaplar)))   # klavye beklemeden test

Proje 1: Sayı tahmin oyunu

Gereksinimler: bilgisayar 1-100 arasında sayı tutar; oyuncunun 7 hakkı var; her tahminde "daha büyük/küçük" ipucu verilir; sayı olmayan ya da aralık dışı girdi hak yemez; bilinirse kaç denemede bilindiği, bilinemezse sayı söylenir.

Tasarım: tahmini_oku(metin) → int ya da None; tahmin_degerlendir(tahmin, hedef) → "büyük" / "küçük" / "doğru"; oyna(hedef, hak, girdi) → döngü.

  1. Önce tahmin_degerlendir'i yaz ve üç durumla dene; en kolay ve en önemli parça budur.
  2. tahmini_oku: int()'i try içine al, aralığı denetle.
  3. Döngü: hak bitene kadar sor; geçersiz girdide continue.
  4. Rastgele hedefi en dışta üret ve random.seed ile sabitlenebilir bırak: test ederken hep aynı sayı gelsin.
import random

def tahmini_oku(metin, alt=1, ust=100):
    try:
        sayi = int(metin)
    except ValueError:
        return None
    return sayi if alt <= sayi <= ust else None

def tahmin_degerlendir(tahmin, hedef):
    if tahmin < hedef:
        return "büyük"
    if tahmin > hedef:
        return "küçük"
    return "doğru"

def oyna(hedef, hak=7, girdi=input):
    deneme = 0
    while deneme < hak:
        tahmin = tahmini_oku(girdi("Tahmin: "))
        if tahmin is None:
            print("1-100 arası bir sayı gir.")
            continue
        deneme += 1
        sonuc = tahmin_degerlendir(tahmin, hedef)
        if sonuc == "doğru":
            print(f"Bildin! {deneme} denemede.")
            return deneme
        print(f"Daha {sonuc} bir sayı dene.")
    print(f"Hakkın bitti, sayı {hedef} idi.")
    return None

random.seed(42)
hedef = random.randint(1, 100)
cevaplar = iter(["50", "on", "25", str(hedef)])
oyna(hedef, girdi=lambda istem: next(cevaplar))

Kendi bilgisayarında gerçek oyun için son satırı oyna(random.randint(1, 100)) yapman yeter. İkili arama (binary search) stratejisiyle (hep aralığın ortasını söyle) 1-100 arası her sayı en fazla 7 denemede bulunur; hak sayısının 7 olmasının nedeni budur.

Proje 2: Adam asmaca

Gereksinimler: gizli kelime harf harf tahmin edilir; bilinen harfler açılır, bilinmeyenler _; 6 yanlışta oyun biter; aynı harfi tekrar söylemek hak yemez.

Tasarım: oyun durumunu iki kümede tut: dogru ve yanlis harfler. Ekranda görünen her şey bu iki kümeden hesaplanır; ayrı sayaçlar tutmazsın, böylece tutarsızlık da olmaz.

MAX_HATA = 6

def maske(kelime, tahminler):
    return " ".join(h if h in tahminler else "_" for h in kelime)

def kazandi_mi(kelime, tahminler):
    return set(kelime) <= tahminler

def adam_asmaca(kelime, girdi=input):
    dogru, yanlis = set(), set()
    while len(yanlis) < MAX_HATA and not kazandi_mi(kelime, dogru):
        harf = girdi(f"{maske(kelime, dogru)}  Harf: ").strip().lower()
        if harf in dogru | yanlis:
            print("Bunu zaten söyledin.")
        elif harf in kelime:
            dogru.add(harf)
        else:
            yanlis.add(harf)
            print(f"Yok! Kalan hak: {MAX_HATA - len(yanlis)}")
    print("Kazandın!" if kazandi_mi(kelime, dogru) else f"Kaybettin, kelime: {kelime}")

cevaplar = iter(["a", "e", "k", "a", "n", "r"])
adam_asmaca("ankara", lambda istem: (print(istem), next(cevaplar))[1])

set(kelime) <= tahminler "kelimedeki tüm harfler tahmin edildi mi?" sorusunu tek ifadede yanıtlar (alt küme). Son satırdaki lambda, testte istemi (prompt) de ekrana yazar ki oyunun akışını görebilesin.

Proje 3: Taş-kağıt-makas

Tasarım: kuralı if merdiveniyle değil, veriyle anlat: "kim kimi yener" bir sözlüktür. Yeni bir seçenek (kertenkele, Spock) eklemek kod değil veri değişikliği olur.

import random

YENER = {"taş": "makas", "makas": "kağıt", "kağıt": "taş"}

def kazanan(oyuncu, bilgisayar):
    if oyuncu not in YENER or bilgisayar not in YENER:
        raise ValueError("geçersiz seçim")
    if oyuncu == bilgisayar:
        return "berabere"
    return "oyuncu" if YENER[oyuncu] == bilgisayar else "bilgisayar"

rng = random.Random(7)                 # kendi tohumlu üreteci
skor = {"oyuncu": 0, "bilgisayar": 0, "berabere": 0}
for secim in ["taş", "kağıt", "makas", "taş"]:
    pc = rng.choice(list(YENER))
    sonuc = kazanan(secim, pc)
    skor[sonuc] += 1
    print(f"{secim:6} - {pc:6} → {sonuc}")
print(skor)

random.Random(7) programın geri kalanını etkilemeyen, kendine ait bir rastgele üreteç (generator) oluşturur; testte global random.seed yerine bunu tercih etmek daha temizdir.

Proje 4: Şifre üretici

Gereksinimler: en az 8 karakter; en az bir küçük harf, bir büyük harf, bir rakam ve bir sembol. Tasarım: önce her gruptan birer karakter seç (kuralı garanti et), kalan uzunluğu tüm havuzdan doldur, sonra karıştır (yoksa ilk dört karakter hep aynı düzende olur).

import random
import string

SEMBOLLER = "!@#%&*?"

def sifre_uret(uzunluk=12, rng=random):
    if uzunluk < 8:
        raise ValueError("en az 8 karakter")
    gruplar = [string.ascii_lowercase, string.ascii_uppercase, string.digits, SEMBOLLER]
    karakterler = [rng.choice(g) for g in gruplar]
    havuz = "".join(gruplar)
    karakterler += [rng.choice(havuz) for _ in range(uzunluk - len(gruplar))]
    rng.shuffle(karakterler)
    return "".join(karakterler)

print(sifre_uret(12, random.Random(1)))
print(sifre_uret(12, random.Random(1)))   # aynı tohum → aynı şifre
Gerçek şifre için random değil secretsrandom modülü tahmin edilebilir bir üreteç kullanır; oyun ve simülasyon içindir. Gerçek şifre, token ya da doğrulama kodu üretirken secrets modülünü kullan: secrets.choice(havuz). Bu derste tohumla test edebilmek için random kullanıyoruz.

Proje 5: Sezar şifreleme

Her harfi alfabede k adım kaydır: k=3 için A→D, Z→C. Sona gelince başa dönmek mod aritmetiğidir: (konum + k) % 26. Çözmek, ters yönde kaydırmaktır: sezar(metin, -k). Ayrı bir çözme algoritması yazmana gerek yok.

def sezar(metin, k):
    sonuc = []
    for h in metin:
        if "A" <= h <= "Z":
            sonuc.append(chr((ord(h) - ord("A") + k) % 26 + ord("A")))
        elif "a" <= h <= "z":
            sonuc.append(chr((ord(h) - ord("a") + k) % 26 + ord("a")))
        else:
            sonuc.append(h)
    return "".join(sonuc)

gizli = sezar("Toplanti Cuma 14:00", 3)
print(gizli)
print(sezar(gizli, -3))
for k in range(1, 4):                      # kaba kuvvetle kırma
    print(k, sezar(gizli, -k))

Yalnızca 25 olası anahtar olduğu için Sezar şifresi bir döngüyle kırılır; tarihsel bir oyuncaktır, gerçek güvenlik değildir. Türkçe alfabeyle (29 harf) çalışan sürümünü Meydan okuma aşamasında yazacaksın.

Proje 6: Bahşiş ve hesap bölüşme

Gereksinimler: hesap tutarı, bahşiş yüzdesi ve kişi sayısı alınır; bahşiş, toplam ve kişi başı tutar 2 ondalıkla verilir; kişi sayısı en az 1, tutar negatif olamaz.

def hesap_bolustur(hesap, bahsis_yuzde, kisi):
    if kisi < 1 or hesap < 0 or bahsis_yuzde < 0:
        raise ValueError("geçersiz değer")
    bahsis = hesap * bahsis_yuzde / 100
    toplam = hesap + bahsis
    return round(bahsis, 2), round(toplam, 2), round(toplam / kisi, 2)

bahsis, toplam, kisi_basi = hesap_bolustur(1240, 12.5, 3)
print(f"Bahşiş: {bahsis:.2f} TL | Toplam: {toplam:.2f} TL | Kişi başı: {kisi_basi:.2f} TL")

Kuruş farkına dikkat: 1395 TL'yi 3 kişiye bölünce 465.00 çıkar ama 1000 TL'yi 3'e bölünce 333.33 × 3 = 999.99 olur. Gerçek bir uygulamada son kişiye kalan kuruş eklenir ya da para birimi kuruş cinsinden tamsayıyla tutulur.

Proje 7: ATM menüsü

Tasarım: sonsuz döngü (while True) menüyü gösterir, match seçime göre dallanır, return ya da break çıkışı sağlar. Bakiye döngü boyunca yaşayan durumdur (state).

def atm(bakiye, girdi=input):
    while True:
        secim = girdi("1) Bakiye 2) Yatır 3) Çek 4) Çıkış: ").strip()
        match secim:
            case "1":
                print(f"Bakiye: {bakiye:.2f} TL")
            case "2" | "3":
                tutar = float(girdi("Tutar: "))
                if secim == "3" and tutar > bakiye:
                    print("Yetersiz bakiye")
                    continue
                bakiye += tutar if secim == "2" else -tutar
                print(f"Yeni bakiye: {bakiye:.2f} TL")
            case "4":
                print("İyi günler!")
                return bakiye
            case _:
                print("Geçersiz seçim")

cevaplar = iter(["1", "2", "250", "3", "5000", "9", "4"])
print(atm(1000, lambda istem: next(cevaplar)))

Bu sürümde float("abc") programı çökertir; alıştırmalarda tutarı doğrulayan ve PIN denetimi ekleyen sağlam sürümü yazacaksın.

Gerçek işteBu projeler oyuncak gibi görünse de aynı kalıplar her yerdedir: ATM menüsü bir komut satırı aracının ya da sohbet botunun ana döngüsüdür; taş-kağıt-makas sözlüğü bir iş kuralı tablosudur (kampanya kuralları, onay matrisleri); şifre üretici kurallı veri üretimidir (test verisi, kupon kodları). "Mantığı arayüzden ayır" ilkesi de web uygulamalarında aynen geçerlidir: hesap fonksiyonları Flask'tan da, mobil uygulamadan da, testten de çağrılabilir. Mülakatlarda istenen ev ödevleri de çoğunlukla bu büyüklüktedir ve değerlendirmede çalışmasından çok tasarımına ve testlerine bakılır.
Uzmanlaş

Durum makinesi olarak oyun

Her oyun bir durum makinesidir (state machine): adam asmacada durum (doğru, yanlış harfler), olay (yeni tahmin), geçiş (kümeye ekle), bitiş koşulu (kazandı ya da 6 hata). Durumu tek bir yapıda tutup "durum + olay → yeni durum" fonksiyonu yazarsan, oyunun her anını test edebilir, kaydedip geri yükleyebilir, hatta geri al (undo) özelliği ekleyebilirsin.

Tekrarlanabilir rastgelelik

Rastgelelik içeren kodu test etmenin iki yolu var: tohumu sabitlemek (random.Random(42)) ya da sonucun özelliklerini test etmek (şifrenin uzunluğu, içerdiği karakter grupları). İyi test ikisini birlikte kullanır. Üreteci parametre olarak almak (rng=random) ise testte sabit tohumlu, gerçekte secrets.SystemRandom() gibi güvenli bir üreteç vermeni sağlar.

Sonraki adım: arayüz

Mantık ayrı olduğu için aynı fonksiyonlarla terminal yerine bir grafik arayüz (tkinter), web sayfası (Flask) ya da Discord botu yazabilirsin; yalnızca döngü katmanı değişir. Orta seviyede bu projeleri sınıflarla (class AdamAsmaca) ve testlerle yeniden kuracaksın.

Alıştırmalara geç (20)