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:
- Gereksinimler: program tam olarak ne yapmalı? Kenar durumlar neler?
- Tasarım: işi hangi fonksiyonlara bölerim? Hangisi hesap yapar, hangisi kullanıcıyla konuşur?
- Adım adım inşa: en küçük çalışan parçayı yaz, dene, sonra büyüt.
- Test: fonksiyonları tek tek, oyun döngüsünü hazır girdilerle dene.
| Proje | Birleştirdiği kavramlar |
|---|---|
| Sayı tahmin oyunu | random + seed, while, int dönüşümü, try/except |
| Adam asmaca | küme, metin birleştirme, oyun durumu |
| Taş-kağıt-makas | sözlükle kural tablosu, random.choice, skor |
| Şifre üretici | string modülü, random.Random, kurallı üretim |
| Sezar şifreleme | ord/chr, mod aritmetiği, metin dönüştürme |
| Bahşiş ve hesap bölüşme | kayan 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:
- Saf fonksiyonlar (mantık): girdi alır, değer döndürür, ekrana dokunmaz.
tahmin_degerlendir(50, 37)→"küçük". - Döngü fonksiyonu (arayüz): kullanıcıyla konuşur, mantık fonksiyonlarını çağırır. Girdiyi
girdi=inputparametresiyle alı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ü.
- Önce
tahmin_degerlendir'i yaz ve üç durumla dene; en kolay ve en önemli parça budur. tahmini_oku:int()'i try içine al, aralığı denetle.- Döngü: hak bitene kadar sor; geçersiz girdide
continue. - Rastgele hedefi en dışta üret ve
random.seedile 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
random 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.
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.