Nesne Grupları ve Miras (Inheritance) Kavramı E.Fatih YETKİN İskambil Kağıtları Hatırlatma, bir iskambil destesinde 4 tipten olmak üzere 52 adet kağıt vardır: Maça Kupa Karo Sinek Deste Nesnesi: Bir İskambil kağıdı Bir deste iskambil kağıdını göstermek üzere bir Python nesnesi tasarlayalım. Bunu için öncelikle herşeyi sayısal olarak kodlayalım. Maça 3 Papaz 13 Kupa 2 Kız 12 Karo 1 Vale 11 Sinek 0 class Card: def init (self, tip=0, deger=0): self.tip = tip self.deger = deger Örnekler: Kartı yazdırmak Card nesnesini kullanarak örneğin sinek üçlüsü; >>>from card import >>>Card(0,3) olacaktır. Sözkonusu sinek üçlüsünü yarattıktan sonra ekrana yazdırmak istersek bunun için ilk kullanılabilecek yol bir katar listesi kullanmak olacaktır. Bunun için biraz önce yaratmış olduğumuz Card sınıfına yeni bir fonksiyon ekleyelim. 1
Kartı Ekrana Yazdırmak Narf in nedeni class Card: tiplist = [ Maça", Kupa", Karo", "Sinek"] degerlist = ["narf", "As", "2", "3", "4", "5", "6", "7", 8", "9", "10", Vale", Kız", Papaz"] #init metodunu daha önce yazmıştık def str (self): return (self.tiplist[self.tip] + self.degerlist[self.deger]) tiplist te kullanılmış olan ilk eleman narf ın anlamı dizinin 0. elemanını hiçbir zaman kullanmamaktır. str içerisindeki tiplist ve degerlist adında iki adet listeyi sayısal değerleri gerçek değerlere dönüştürmek için kullanmaktayız. Örnekler Yöntemin Sorunları >>> card1 = Card(1, 11) >>> print card1 Karo Vale >>> card2 = Card(1, 3) >>> print card2 Karo Üç >>> print card2.suitlist[1] Karo Herhangi bir kartın adını değiştirmemiz listedeki aynı tipteki diğer 12 kartı da etkileyecektir. Örneğin Karo Valeyi Baklava Vale haline getirmeye kalkışacak olursak, diğer tüm karolar da baklava haline gelir. Örnek >>> card1.tiplist[1] = Baklava >>> print card1 Baklava Vale Kartları Karşılaştırmak Kartları karşılaştırabilmek için diğer tipteki verileri karşılaştırmakta kullanılan <, >, == gibi operatörleri aşırı yüklememiz gerekmektedir. Bu işlem için cmp fonksiyonu nesne tanımı içinde kullanılmalıdır. 2
cmp fonksiyonu Sorun def cmp (self, other): # tipleri kontrol if self.suit > other.suit: return 1 if self.suit < other.suit: return -1 # tipler aynı ise değerlere bak if self.rank > other.rank: return 1 if self.rank < other.rank: return -1 # değerler de aynı ise bir sorun var... return 0 Ancak kart oyunlarının hemen hemen tümünde As en büyük kağıttır. Bunu çözmek üzere cmp fonksiyonunu değiştirelim. Deste oluşturmak Desteyi yazdırmak Tek tek kartları tanımlayacak bir sınıf oluşturmuş durumdayız. Bu sınıftan yararlanarak bir adet kağıt destesi oluşturalım Bunun en kolay yolu içiçe for döngüleri kullanmak olacaktır. Oluşturmuş olduğumuz desteyi yazdırmak için iki farklı fonksiyon oluşturalım. Bunlardan ilki kendi oluşturduğumuz bir fonksiyon olsun ve çağrılırken deste.printdesteşeklinde çağrılsın İkincisi ise Python un print fonksiyonunu aşırı yükleyecek bir fonksiyon olsun Desteyi Karıştırmak Desteyi Karıştırmak İyi karışmış bir destede herhangi bir kart herhangi bir konumda yeralabilir. Bunu sağlamak için random modülünde bulunan randrange() fonksiyonunu kullanacağız. Randrange(a,b) fonksiyonu, a<=x<b aralığında rastgele tamsayı değerler üretir. def shuffle(self): import random ncards = len(self.cards) for i in range(ncards): j = random.randrange(i, ncards) self.cards[i], self.cards[j] = self.cards[j], self.cards[i] 3
Desteyi Karıştırmak Kağıt atmak Aynı fonksiyonu ikili atama yapmadan deneyelim. Bir iskambil oyununda önemli olabilecek bir başka yöntem de seçilen bir kağıdı desteden çıkartmak olabilir. Verilen kart destede ise desteden çıkartıp 1 değerini aksi halde 0 değerini döndürecek bir fonksiyon yazalım. Kağıt Atmak Kağıt Atmak def removecard(self, card): if card in self.cards: self.cards.remove(card) return 1 else: return 0 Fonksiyondaki if kontrolü içinde bulunan in operatörü sağ yanında bulunan eleman normal bir değişkense standart karşılaştırma operatörlerini kullanır. Ancak bir nesne ile çalışılıyorsa nesnenin cmp metoduna göre hareket eder. Desteden Kağıt Çekmek Desteden Kağıt Çekmek Deste içerisindeki kağıtları bir liste içinde tutmakta idik. Bu nedenle liste veritipine ait olan pop() metodu destenin en üstündeki kağıdı çekmek için kullnabiliriz. def popcard(self): return self.cards.pop() def isempty(self): return (len(self.cards) == 0) 4
Miras Kavramı Miras Kavramı Miras varolan bir sınıfı kullanarak yeni bir sınıf elde edilmesi olarak tanımlanabilir. En önemli getirisi, daha önceden yaratmış olduğumuz sınıfların niteliklerini bozmadan saklama imkanı vermesidir. Yeni yaratılan sınıf daha önceki sınıfın özelliklerine sahip olur. Metaforu genişletecek olursak miras bırakan sınıf aile(parent) sınıf iken mirası edinen sınıf çocuk(child) sınıf olmaktadır. Bir El Hazırlayalım Bir El Hazırlayalım Hazırlamış olduğumuz deste (Deck) ve kağıt (Card) nesnelerini kullanarak herhangi bir oyunda kağıtların dağıtılması işlemini nasıl yapabileceğimizi tasarlayalım. Hazırlayacağımız El sınıfı daha önce hazırladığımız Deck sınıfının bir alt sınıfı olacaktır. Bu nedenle yeni sınıfımızın tüm deste özelliklerini içermesini bekleriz. Ayrıca oynanan oyuna özel bazı nitelikler de eklememiz gerekebilir. Alt sınıfı tanımlamak Alt sınıfı tanımlamak Alt Sınıf tanımlarken aile sınıfın adı parantez içerisinde yazılır. Örneğin; class Hand(Deck): pass Bu tanımlama ile oluşan yeni Hand sınıfının Deck sınıfına ait tüm özellikleri miras almasını sağlamış olduk. Hand sınıfı için uygun yeni bir yapılandırıcı hazırlayalım. 5
Yapılandırıcı class Hand(Deck): def init (self, name=""): self.cards = [] self.name = name Ele kağıt ekleme Tüm kağıt oyunlarında oyuncunun elindeki kağıdı atması ve yeni kağıt alması esastır. Deck nesnesinde zaten kağıt atma özelliği vardı dolayısıyla Hand nesnesi bu fonksiyonu Deck ten miras almış durumda ancak kağıt çekme fonksiyonunu eklemek durumundayız. Kağıt çekme fonksiyonu def addcard(self,card) : self.cards.append(card) Kağıtları Dağıtalım Şimdi elimizde bir deste (Deck) ve bir el (Hand) nesneleri oluştu. Oyunu oynayabilmek için Destedeki kağıtları oyunun kuralına göre ellere dağıtmamız gerekmekte Kağıtları Dağıtalım Yazacağımız fonksiyonun birçok farklı oyun için kullanılabilir olması için mümkün olan en genel halde yazmamız gerekmektedir. Fonksiyon iki parametre almalıdır. Birincisi, dağıtılacak olan elleri içeren bir liste ve ikincisi de toplam dağıtılacak kart sayısı class Deck:... def deal(self, hands, ncards=999): nhands = len(hands) for i in range(ncards): if self.isempty(): break# kagitlar bitti mi card = self.popcard() # en ustten kagit cek hand = hands[i % nhands]# siradakini bul hand.addcard(card) # siradaki ele kagit ekle 6
Kağıtları Dağıtalım str mirası Fonksiyonun kullanımı >>> deck = Deck() >>> deck.shuffle() >>> hand = Hand("frank") >>> deck.deal([hand], 5) >>> print hand Hand frank contains 2 of Spades 3 of Spades 4 of Spades Ace of Hearts 9 of Clubs Yazdırma işlemini yaparken Deck nesnesinin str metodunu küçük bir değişiklikle kullanmış olduk. Bu değişiklik; def str (self): s = "Hand " + self.name if self.isempty(): s = s + " is empty\n" else: s = s + " contains\n" return s + Deck. str (self) CardGame sınıfı Papaz Kaçtı Şimdi de tüm kağıt oyunlarında kullanılabilecek genel bir sınıf yaratalım. Bu sınıfta ilk yapılacak şey bir deste kağıt oluşturmak ve kağıtları karıştırmak olacaktır. class CardGame: def init (self): self.deck = Deck() self.deck.shuffle() Oyundaki papazlardan birisi çıkartılır Daha sonra herkese eller dağıtılır Aynı renkte olan kağıtlar atılır Sonuçta tek olduğu için papaz kalıncaya dek oyuncular birbirlerinden kağıt çekerler. papazkacti sınıfı papazkacti sinifi Oynayacağımız oyunda kullanacağımız papazkacti sınıfı, Hand sınıfının bir altsınıfı olacaktır ve Hand sınıfındaki fonksiyonlara ek olarak eslestir fonksiyonuna sahip olacaktır. Bu fonksiyon aynı renkteki birbirine eş olan kağıtları oyuncunun elinden atmasını sağlar. class papazkacti(hand): def removematches(self): count = 0 originalcards = self.cards[:] for card in originalcards: match = Card(3 - card.suit, card.rank) if match in self.cards: self.cards.remove(card) self.cards.remove(match) print "Hand %s: %s matches %s" % (self.name,card,match) count = count + 1 return count 7
Sınıfı Deneyelim Oyuna ait sinifi yaratalim >>> game = CardGame() >>> hand = papazkacti("frank") >>> game.deck.deal([hand], 13) >>> print hand hand.removematches() Hand frank: 7 of Spades matches 7 of Clubs Hand frank: 8 of Spades matches 8 of Clubs Hand frank: 10 of Diamonds matches 10 of Hearts class papazkactigame(cardgame): def play(self, names): # Maça Papazı atalım self.deck.removecard(card(0,12)) # herbir oyuncu icin elleri hazirlayalim self.hands = [] for name in names : self.hands.append(papazkacti(name)) # kartlari dagitalim self.deck.deal(self.hands) print "---------- Kartlar Dagitildi" self.printhands() # baslangictaki eşlmeleri atalim matches = self.removeallmatches() print "---------- Esler atildi oyun baslar..." self.printhands() Oyuna ait sinifi yaratalim İki ek sınıf # 50 kartin tumu eslesene dek devam edilir turn = 0 numhands = len(self.hands) while matches < 25: matches = matches + self.playoneturn(turn) turn = (turn + 1) % numhands print "---------- Oyun bitti" self.printhands() def removeallmatches(self): count = 0 for hand in self.hands: count = count + hand.removematches() return count def playoneturn(self, i): if self.hands[i].isempty(): return 0 neighbor = self.findneighbor(i) pickedcard = self.hands[neighbor].popcard() self.hands[i].addcard(pickedcard) print "Hand", self.hands[i].name, "picked", pickedcard count = self.hands[i].removematches() self.hands[i].shuffle() return count İki ek sınıf def findneighbor(self, i): numhands = len(self.hands) for next in range(1,numhands): neighbor = (i + next) % numhands if not self.hands[neighbor].isempty(): return neighbor Deneme >>> import cards >>> game = cards.oldmaidgame() >>> game.play(["allen","jeff","chris"]) 8