Basit Bir Oyun Projesi İle C# [3]

      Tekrar merhaba. Son yazımızda "Çıkış" düğmesi için gerekli kodları hazırlamıştık. Şimdi ise daha ayrıntılı kodlar yazmamızın vakti geldi. "Yeni Oyun Başlat" düğmesi için gerekli kodları yazacağız.

      Önce "Yeni Oyun Başlat" düğmesinin ne gibi işleri göreceğini sıralayalım. Sonra da kodlamaya başlayalım.

      * Yeni bir oyun başlatıldığında ilk iş puanların sıfırlanması olmalı. Çünkü kullanıcı oyunu bir süre oynamış ve sonra yeni bir oyuna başlamak istemiş olabilir.
      * Bir hedef sayı belirlenmeli.
      * Belirlenen hedef sayı, arayüzün üst-ortasında bulunan etiket üzerinde gösterilmeli.
      * Hedef sayıya ulaşmak için kullanılacak 5 adet sayı belirlenmeli.
      * Bu 5 adet sayıdan sadece 2 tanesinin toplamı hedef sayıyı vermeli.
      * Belirlenen sayılar düğmelerin üzerine yazılmalı.

      Şimdi yazdığımız maddeleri dikkatlice inceleyelim. Dikkat ederseniz, yeni bir oyun başlatırken yaptığımız işlemlerden bazıları, kullanıcının her toplam tahmininden sonra yinelenecek. Örneğin kullanıcı 2 sayı seçip ilk tahminini yaptıktan sonra tekrar bir hedef sayı belirlenecek ve tekrar düğmelerin üzerine sayılar yazılacak.

      Kullanıcının toplam sayıyı hesaplamaya çalıştığı her sefere bir tur diyelim. Eğer "Yeni Oyun Başlat" düğmesine ait fonksiyonun içine yeni bir tur başlatmak için gerekli kodları yazarsak aynı kodları kopyalayıp 2. sayı seçildikten sonra da çalıştırılmak üzere düğmelerin altına da yazmamız gerekecektir. Hatta kullanıcının hangi düğmeyi seçeceğini de bilemeyeceğimize göre, her düğmenin altına aynı kodları yapıştırmalıyız.

      Aynı kodları tekrar tekrar başka fonksiyonlar altına yapıştırmaktansa tek bir fonksiyon yazsak ve bu fonksiyondan yeni bir tur başlatmasını istesek nasıl olur. İdeal bir tasarım için yeni bir turu başlatan bir fonksiyon yazmalı ve gerekli her fonksiyondan bu fonksiyona referans vermeliyiz.

      Aslında fonksiyonların tasarımındaki en önemli özellik, her fonksiyonun "sadece bir iş" yapmasıdır. Örneğin bir fonksiyon sadece sayıları belirlemeli, bir fonksiyon sadece düğmelerin üzerlerine sayıları yazmalı, bir fonksiyon sadece hedef sayıyı belirlemeli, gibi.

      Yazacağınız fonksiyonların tasarımı tabi ki size aittir. Ancak fonksiyonların tasarımındaki genel kurallara uymak, diğer insanların tecrübelerinden faydalanmak demektir ki, fonksiyonlar konusunda bu tecrübeler önemli. İzninizle bildiğim iki genel kuralı sizlerle paylaşayım:

      * Bir fonksiyon sadece bir iş yapmalıdır.
      * Bir fonksiyon içinde en fazla 15 ila 50 satır kod bulunması idealdir.

      Bir fonksiyon neden sadece bir iş yapmalıdır: Yazılımınızı geliştirmeniz sırasında 2 farklı iş yapan bir fonksiyondaki küçük bir kod parçacığına ihtiyaç duyabilirsiniz. Ancak o fonksiyon birden çok işi yapmak için tasarlanmış olduğu için o kod parçacığını fonksiyondan ayrı çalıştıramazsınız. Yeni bir fonksiyon yazmanız gerekecektir. Halbuki bu 2 farklı işi yapan 2 farklı fonksiyon yazmış olsaydınız, ihtiyaç duyduğunuz kod parçacığı farklı bir fonksiyon içinde tek başına duruyor olacaktı. Siz de fonksiyonu tekrar kullanabilecektiniz. Ayrıca her fonksiyonun bir işi yapması, kodun yönetilebilirliğini de artıracaktır. Bu şekilde hataları daha kolay ayıklayabilir veya işlemlerinizi daha kolay güncelleyebilirsiniz. Yazılımınızı mümkün ve mantıklı olduğu kadar küçük fonksiyonlara bölerek yapılandırmanız size zaman kazandıracaktır.

      Bir fonksiyon içinde neden en fazla 15 ila 50 satır kod bulunmalı: Bu madde ilk maddeye referans içeriyor. Eğer bir fonksiyon 15 ila 50 satırdan fazla kod barındırıyorsa, muhtemelen o fonksiyon daha küçük parçalara ayrılması gerekirken ayrılmamış demektir. Yani muhtemelen o fonksiyon içinde birden çok iş yapmaktasınız. İkinci gerekçe ise kodun okunabilirliği ve takip edilebilirliği. Okuduğum yazılardan birinde, bir fonksiyondaki kodların ekranda tek seferde (sayfayı aşağı yukarı hareket ettirmeye gerek duymayan) gösterilebilmesinin önemli olduğu vurgulanıyordu. Böylelikle yazılımcı, fonksiyonun yaptığı işe daha kolay hakim olabilir deniyordu.

      Yukarıda yazmış olduğumuz kuralların istisnaları tabi ki de olabilir. Ancak kural denen şeyler genellikle birlikte çalışabilirlik adına oluşturulur. Bu nedenle yazdığınız kodları başkalarının da kullanabileceğini veya güncelleyebileceğini göz önünde bulundurarak genel kurallara uygun kod yazmalısınız.

      Bir yandan kod yazarken bir yandan da bu bilgileri vermek zorundayım. Çünkü siz okuyucuların yazılıma yeni başladığını varsayıyorum. Satır aralarını atlayıp da direk kod kısımlarına dalmayın:) Arada kaçırdığınız önemli bilgiler olabilir benden söylemesi:)

      Yeni bir tur başlatma işi için ayrı bir fonksiyon yazmaya karar verdik. O kısma geleceğiz. Öncelikle yazılım ilk başlatıldığında kullanıcının arayüzde hangi özellikleri kullanabilir olacağını ve hangilerini kullanamaz olacağını ayarlamamız gerekmektedir. Örneğin arayüz kullanıcının karşısına ilk açıldığı anda kullanıcının toplamı elde etmek için basacağı düğmeleri kullanamaması lazım. Çünkü ortada henüz bir hedef sayı yok. Ancak hedef sayı belirlendikten sonra bu düğmeler kullanılabilir olmalıdır.

      Öyleyse form üzerinde düğmelerin ve herhangi başka bir nesnenin bulunmadığı boş bir yere fare ile çift tıklayarak form açıldığı anda yapılacak işlemleri yazalım.





      İlk ekran resminde arayüz yüklenirken çağırılacak olan fonksiyonu görüyoruz. Bu fonksiyon yazılımcı için Visual Studio tarafından otomatik üretiliyor. Arayüz oluşturulduktan hemen sonra çağırılıyor. Arayüz ile ilgili diğer fonksiyonlar da üretilebilir. Yazılarımız sırasında bu konulara da yeri geldikçe değineceğiz.

      Fonksiyonu incelediğimizde sadece tek bir satır kod görüyoruz. Görüldüğü üzere arayüz oluşturulduktan hemen sonra başka bir fonksiyonun çağırılmasını istemişiz. Çağırılacak olan fonksiyon ise "Düğmeleri Pasifleştir" fonksiyonu. Yani düğmeleri kullanıma kapatacağız.

      Arayüze eklenen her nesnenin (Düğmeler, etiketler vs..) kendi özellikleri vardır. Eklendikleri arayüz içindeki konumları, boyutları, üzerlerine fare ile tıklandığında yapacakları iş, üzerlerine fare konumlandırıldığında yapacakları iş gibi birçok fonksiyon ve özellik bu nesnelere bağlıdır. Arayüz ilk açıldığında hedef toplama ulaşmak için kullanılacak düğmelerin kullanılamaz durumda olmalarını istiyoruz. Bu amaçla düğmelerin "Enabled" (Etkin) özelliklerini "False" (Yanlış\Hayır\Sıfır) olarak değiştireceğiz.

      Yazdığımız koda dikkat edelim:
"button2.Enabled = button3.Enabled = button4.Enabled = button5.Enabled = button6.Enabled = false;"

      Görüldüğü üzere birçok değeri birbirine eşitlemiz durumdayız. Bu yöntem bazen birçok satırlık kodu bir satıra indirmek ve kodun okunabilirliğini artırmak için idealdir. Her değer, kendisinin "sağında" olan değere eşitlenir.

      Öyleyse önce "button6" nesnesinin "Enabled" özelliği "false" değerine eşitlenecek, sonra button5 nesnesinin "Enabled" özelliği "button6" nesnesinin "Enabled" özelliğinin değerine eşitlenecek. Bu şekilde devamla en son "button2" nesnesinin "Enabled" özelliği "button3" nesnesinin "Enabled" özelliğine eşitlenecek.

      Böylelikle hedef toplama erişmek için kullanılacak olan düğmeleri, yeni bir tur başlayana dek pasif konuma getirmiş oluyoruz.

      Şimdi "Yeni Oyun Başlat" düğmesine dönelim. Yeni bir oyun başlatılırken puanlar sıfırlanmalı ve bir hedef sayı belirlenmelidir. Öyleyse iki fonksiyona ihtiyacımız olacak.



      Şimdi yukarıda çağırılan fonksiyonlara bakalım.



      Fonksiyon içinde, kaç el oynandığını ve başarılı deneme sayısını gösteren etiket nesnelerinin "Text" (metin) özelliklerini ilk hallerine döndürüyoruz. Tabi hedef sayıyı gösteren etiketin metnini de ilk haline getiriyoruz.

      Şimdi asıl zor ve çetrefilli kısma geldik. "yeniTur" fonksiyonu birçok fonksiyon çağıracak.



      Yeni bir tura, düğmeleri etkinleştirerek başlıyoruz ki, kullanıcı hedef toplama ulaşmak için düğmeler üzerine tıklayabilsin.



      Yukarıdaki kodda görüldüğü üzere, düğmeleri pasifleştirirken yaptığımız işin tam tersini yapıyoruz.

      Yeni tur fonksiyonunun içinde "hedefSayi" adında bir değişken tanımladık. Dikkat ederseniz değişkenimiz başka bir fonksiyondan dönen değere eşitlenmiş durumda. Şimdi "hedefSayiyiBelirle" fonksiyonuna bakalım.



      Yukarıdaki fonksiyonun adına baktığımız zaman "private int" adlandırmasını görüyoruz. "int" metni, fonksiyondan dönecek bir değer olduğunu, ve bu değerin bir tamsayı olduğunu göstermektedir. Fonksiyonun içeriğine baktığımızda "hedefSayi" adında bir iç değişken tanımlandığını görüyoruz.

      "Random", CSharp içinde çağırılabilen bir "Class" (Sınıf) nesnesidir. Sınıfların tanımlarına ve özelliklerine önümüzdeki yazılarda değineceğiz. "rastgeleSayiUreteci" adında bir "Random" nesnesi oluşturduk. "rastgeleSayiUreteci" nesnesine ait, "Next" fonksiyonunu "100" tamsayı değişkeni ile çağırıyoruz.

      Peki tamam, biraz "Class" nesnelerinden bahsetmek farz oldu, konuyu ileriye atalım demiştim ama anlatılması gerekli sanıyorum. "Class" (Sınıf) nesneleri, belirli işleri yapmak için özelleştirilmiş kod barındırıcılarıdır. Şimdi düşünelim, birazdan göreceğiniz üzere "hedef sayıyı belirlemek", "hedef sayılara ulaşmak için gerekli sayıları belirlemek" gibi işlemler için birçok fonksiyon yazacağız. Bu fonksiyonların birçoğu ise sadece "yeniTur" fonksiyonu tarafından çağırılacak. Yani bu fonksiyonlar aslında "yeniTur" fonksiyonuna "Özel" (private) olan fonksiyonlar. Peki biz bu fonksiyonların hepsini bir "Class" içine koysaydık ve bu class içinde bu fonksiyonları dışarıya karşı gizleseydik nasıl olurdu. Bu sınıftan sadece yeni tur başlatma fonksiyonunu dışarıya açsaydık, diğer fonksiyonlar sınıfın kendi iç fonksiyonları olsaydı, bence süper olurdu. Böylelikle yeni bir tur başlatmak için bir sınıf oluşturmuş ve sadece o sınıf tarafından kullanılacak kodları dışarıdan gizlemiş olurduk. Yani yazdığınız kodlara geri döndüğünüzde sınıfın içindeki dışarıdan çağırılamayan fonksiyonları görür ve sınıfın yaptığı işe çabucak hakim olurdunuz. Dikkat ederseniz, "button2" gibi nesneler de birer sınıftır. Bu sınıflar kendi içlerinde fonksiyonlar ve değişkenler barındırabiliyorlar. Peki, ileriki yazılarımızda bu konuya dönmek üzere konuyu kapatalım.

      "hedefSayi" değişkenine, sayı üreteci sınıfından üretilmiş olan sayıyı atıyoruz. Rastgele üretilen sayının ise alabileceği en büyük değerin 100 olmasını istiyoruz. Fonksiyonun son satırına baktığımızda "return hedefSayi;" kodunu görüyoruz. Yani fonksiyon görevini tamamlamış ve elde ettiği değeri kendisini çağıran fonksiyona döndürmektedir.

      Şimdi "yeniTur" fonksiyonunun içindeki kodları incelemeye devam edelim. "List<int> sayilar" kod satırına gelelim. Dikkat ederseniz yeni bir "Class" nesnesi ile karşı karşıyayız. CSharp içinde (aslında .Net FrameWork içinde demek daha doğru) tanımlı başka bir sınıf. Biraz inceleyelim.

      "List" deyimi, sınıfın özelliğini de anlatır nitelikte, değişkenlerden oluşan bir liste tanımlamak üzereyiz. Hemen sonrasında bakalım; <int> deyimini görüyoruz. Yani aslında bir tamsayı listesi oluşturmaktayız. Burada oluşturacağımız listenin içereceği değerlerin tamsayı olduğunu belirttiğimize göre, listenin başka herhangi bir nesnenin listesi olmasını da sağlayabilirdik. Örneğin reel sayı listesi, kendi tanımladığımız bir "Class" listesi, boolean değişkenler listesi vs.

      Dikkat edersek, "Random" sınıfından bir nesne oluştururken " = new .." deyimini kullanmıştık. Burada kullanmadık. Bu demektir ki henüz "sayilar" adını verdiğimiz listemiz bir nesne değil, sadece bir referans.

      Şimdi gelelim "yeniTur" fonksiyonundaki diğer satırlara. Bir "if () {}else{}" yapısı görmekteyiz. Bu yapıya neden ihtiyaç duyduğumuzu hemen anlatalım.

      Eğer belirlemiş olduğumuz hedef sayı 5'den küçükse, kullanıcının hedef sayıya ulaşmak için seçebileceği 5 adet düğmeden en az ikisinde aynı sayıları vermemiz gerekir, ki bunun olmasını istemedik. Örnek ile bakalım:

      Hedef sayı: 4
      Birinci düğme üzerindeki sayı: 0
      İkinci düğme üzerindeki sayı: 1
      Üçüncü düğme üzerindeki sayı: 2
      Dördüncü düğme üzerindeki sayı: 3
      Beşinci düğme üzerindeki sayı: ?

      Şimdi beş numaralı düğme üzerindeki sayının "4" olabileceğini biliyoruz. Ancak oyunun kolaylaşmaması adına, hedef sayıyı doğrudan sağlayan bir sayının seçilebilir olmasını istemiyoruz. Öyleyse 4 sayısını veremeyiz. Yine oyunun zorluğunu artırmak adına, hedef sayıdan büyük bir sayının var olmasını da istemiyoruz. Yani 5 değerini de kullanamayız (Böyle yapsaydık kullanıcı gözüyle birkaç sayıyı anında elerdi.).

      Geriye 4 sayısından küçük bir sayı seçilmesi kalıyor ki bu durumda diğer sayılardan biriyle aynı değeri kullanmamız gerekecek. Seçilebilir sayılardan iki tanesinin birbiriyle aynı olmasını da istemiyoruz. Bunu neden istemediğimize dair bir gerekçe yok:) Göze hoş görünsün:)


      Aklınıza gelmiş olabilir diye söylüyorum, sıfırdan küçük sayılar da istemiyoruz:) Unutmayın bu oyunu çocuklara toplama işlemini sevdirmek için yazdığımızı düşünmüştük. Sıfırdan küçük sayıların seçilebilir olduğu sürümü sizden bekliyorum:) Projeyi tamamladıktan sonra ikinci versiyonu sıfırdan küçük sayıların da seçilebilir olduğu, büyüklere toplama işlemini sevdiren bir yazılım olarak hazırlayabilirsiniz:)

      Evet, denetimimize dönüyoruz. Hedef sayının 5'den küçük olmaması lazım. Öyleyse bir "if" (Eğer) cümlesi ile, belirlenen hedef sayıyı denetleyelim. Eğer hedef sayı 5'den küçükse 5'e eşitleyelim, hedef sayıya ulaşmak için kullanılacak sayılara "0, 1, 2, 3, 4" sayılarını ekleyelim (Başka bir kombinasyona izin vermemiştik.). Aşağıdaki kod ile (ekran resmindeki kod), hedef sayının 5'den küçük olması durumunda yapılacak işlemleri hazırlamış olduk.

             if (hedefSayi < 5)
            {
                hedefSayi = 5;
                sayilar = new List<int>(); // sayilar nesnesini boş bir referans olmaktan çıkardık..
                sayilar.Add(0); // sayilar nesnesine bir tamsayı ekledik..
                sayilar.Add(1); // sayilar nesnesine diğer tamsayılar ekleniyor..
                sayilar.Add(2);
                sayilar.Add(3);
                sayilar.Add(4);
            }

      Peki belirlenen hedef sayı 5'den küçük değilse? Bu durumda seçilebilir sayıların hangileri olacağı ile ilgili birkaç fonksiyona daha ihtiyaç duyacağız. Gördüğümüz üzere, "sayilar" değişkenine, bir fonksiyondan dönen değeri atıyoruz.

            if (hedefSayi < 5)
            {
                hedefSayi = 5;
                sayilar = new List<int>();
                sayilar.Add(0);
                sayilar.Add(1);
                sayilar.Add(2);
                sayilar.Add(3);
                sayilar.Add(4);
            }
            else
            {
                sayilar = sayilariBelirle(hedefSayi);
            }

      "sayilariBelirle" fonksiyonu ne tür bir değişken döndürmelidir? "sayilar" değişkenimiz bir tamsayı listesi olduğuna göre, "sayilariBelirle" fonksiyonu da bir tamsayı listesi döndürecek demektir. Şimdi "sayilariBelirle" fonksiyonuna bir göz atalım.



      Fonksiyonu incelediğimizde öncelikle seçilebilir sayıları tutacak olan ve fonksiyondan dönecek olan listeyi oluşturduğumuzu görüyoruz. Sonra "adet" adında bir değişken oluşturuyoruz. Bu değişken, "sayilar" listesinin eleman sayısını takip edecek ve eleman sayısı 5 olduğunda sayıların belirlenmesi için çalışmakta olan kodu durduracak.

      Tamsayı listesinin eleman sayısı istenen sayıya ulaşana kadar çalışan bir kod? Evet, bir "while" (oldukça\şart sağlandığı sürece) döngüsü bu işi görecektir. Hemen bakalım.

      while (adet < 5)
      {
            // kodlar..
            // kodlar..
      }

      Görüldüğü üzere mantık oldukça basit:
      * "adet" değişkeni 5 sayısından küçük ise "while" döngüsünün "{" süslü parantezinden başla, "}" süslü parantezine kadar git.
      * Geri dön. "adet" değişkeninin 5'den hala küçük olup olmadığına bak, eğer küçükse son süslü paranteze kadar tekrar git.
      * Ta ki "adet" değişkeni 5'e eşit olana veya 5'den büyük olana dek bu döngüyü tekrarla.

      Akla iki soru geliyor:
      * Ya "adet" değişkeni hiçbir zaman 5 sayısından büyük olmazsa? Bu durumda fonksiyon sonsuz döngüye girer. Peki kullanıcılarınız, yazılım sonsuz döngüye girdiğinde bunu size nasıl bildirir? "Ekran dondu." diyerek bildirir. Kullanıcılarınızın ekranlarını dondurmayın:) Sonsuz döngüler genellikle istediğiniz şeyler değildir.

      * Ya "adet" değişkeni, "while" döngüsünün hemen başında 5 sayısından zaten büyükse veya 5'e eşitse? Hiçbir şey olmaz. İki süslü parantez arasındaki kod hiç çalışmaz.

      İlk "while" döngüsünün içine bakalım. Bir "devam" değişkeni tanımlıyoruz. Bu değişken bir mantıksal (boolean) değişken. Hemen sonrasında başka bir "while" döngüsü görünmekte. "while" döngüsünü inceleyelim.

      Döngünün koşulu olarak "devam" değişkeni verilmiş. Boolean değişkenler başlı başına 0 veya 1 değerlerinden birini ifade ettikleri için koşul olarak sadece adlarının yazılması yeterlidir. "(devam)" koşulu, "devam değişkeni true değerine eşit ise" demektir. Aynı cümleyi aşağıdaki şekilde de yazabilirdik:
      "while(devam == true)"

      "while" deyiminden hemen sonra sayıları belirlediğimiz kod parçacığına geliyoruz. İlk satırda yine bir rastgele sayı üreteci kullandık. Sayı üreteci ile, oluşturduğumuz "yeniSayi" değişkenine 0 ile 100 arasında bir değer atıyoruz. Sonrasında ise bir "if" cümlesi var. Bu cümlede oluşturduğumuz "yeniSayi" değişkeninin istediğimiz şartlara uyup uymadığını denetliyoruz. Peki şartlarımızı inceleyelim:

      "if (yeniSayi < hedefSayi && !sayilar.Contains(yeniSayi))"
      "Eğer yeniSayi hedefSayi'dan küçükse ve sayilar listesi yeniSayi değerini içermiyorsa"

      Yani oluşturduğumuz yeni sayının hedef sayıdan küçük olmasını ve daha önce oluşturulmuş bir sayı ile aynı olmamasını istemekteyiz. "if" cümlesinde aradığımız koşul sağlanırsa, "if" cümlesine ait olan iki süslü parantez arasındaki kod çalıştırılacak demektir.

                    if (yeniSayi < hedefSayi && !sayilar.Contains(yeniSayi))
                    {
                        sayilar.Add(yeniSayi);
                        adet++;
                        devam = false;
                    }
      
      Kodlara baktığımızda "sayilar" listesine yeniSayi değişkenini eklediğimizi, belirlediğimiz sayı adedini tutan değişkenin değerini 1 artırdığımızı ve "devam" değişkeninin değerini "false" olarak değiştirdiğimizi görüyoruz.

      Yani şartlara uygun bir sayı bulduğumuzda "while" döngüsünden çıkmak için "devam" değerini "false" değerine eşitledik. "if" cümlesindeki şartlar sağlanmazsa "while" döngüsü şartları sağlayan sayıyı bulana dek devam eder.

      İçteki "if" cümlesinden ve "while" döngüsünden çıktıktan sonra "adet" değişkeni 1 artmış olacağına göre, en dıştaki "while" döngüsünden de çıkılması ihtimali vardır. Her yeni sayı belirlendikten sonra en dıştaki "while" döngüsü, "adet" değişkeninin değerini denetler. "adet" değişkeninin değeri 5 olduğu anda en dıştaki "while" döngüsünden de çıkılacak demektir.

      Kodları incelerken bir tamsayı değişkeninin değerini "++" deyimini kullanarak artırabildiğimizi farketmişsinizdir. Bir sayı değişkeninin değerini "++" ile de 1 artırabilirsiniz. Aynı şekilde "--" ile sayı değişkeninin değerini 1 azaltabilirsiniz. Ayrıca bir sayı değişkenine değer eklemek için "=" simgesinden hemen önce "+" veya "-" işaretlerini de kullanabilirsiniz. Aşağıda bu durumlara örnek olarak verilen üçlü satırlar aynı işleri yapmaktadır:

      adet++;                  adet--;
      adet = adet + 1;     adet = adet - 1;
      adet + = 1;            adet -= 1;

      "sayilariBelirle" fonksiyonunun içinde gezmeye devam ediyoruz. Şimdi ise bir boolean değişken tanımlıyor ve bu değişkenin, değerini "sayilarToplamiVerebiliyorMu" adında bir değişkenden almasını istiyoruz.

      Burada amacımız; Rastgele belirlemiş olduğumuz 5 adet sayının herhangi bir ikili toplama kombinasyonunda hedef sayıyı verip vermediğini tespit etmek. Eğer bu sayılardan herhangi ikisinin toplamı, hedef sayıya eşitse yapmamız gereken başka bir iş kalmadı demektir. Sayıları bu şekilde kullanabiliriz. Eğer ikili toplama kombinasyonlarından hiçbiri hedef sayıya ulaşamıyor ise çalışmaya devam edeceğiz. Öncelikle "sayilarToplamiVerebiliyorMu" fonksiyonunun içeriğine bir göz atalım.



      Fonkyisonu incelediğimizde, fonksiyonu çağıran koddan iki adet değişken beklediğini görüyoruz. Bu konuyu artık bildiğinizi varsaydığımdan her defasında üstünden geçmiyorum. Fonksiyonun beklediği değişkenler "(List<int> sayilar, int hedefSayi)" parantezleri arasında gösterilmiş. Bir tamsayı listesi ve hedefSayi adında bir tamsayı. Tabi bu sayılar, belirlemiş olduğumuz rastgele sayılar ve ulaşılması gereken toplam. Fonksiyon, bu dış verileri denetleyerek, listede verilen sayıların herhangi bir iki toplama kombinasyonunda hedef sayıya erişebilip erişemediğini bulacak.

      Dikkat ettiyseniz bu fonksiyon bir mantıksal değişken döndürüyor. Fonksiyonun hemen başında da dönecek olan değişkeni tanımlıyoruz. "toplamVarMi" değişkeni, ikili kombinasyonun bulunması durumunda "true" aksi halde "false" değerini alacak. Başlangıç değeri olarak da "false" değer veriyoruz. Böylece birazdan açıklayacağımız kodlarda "true" değerini almazsa ön tanımlı değeri olan "false" değerinin fonksiyondan döndürüleceğini biliyoruz.

      Şimdi iç içe iki adet "for" döngüsü görüyoruz. Öncelikle "for" döngülerini açıklayalım, sonra da burada kurguladığımız mantığı açıklayalım.

      "for" döngüleri, kendi süslü parantezleri içinde yer alan kodları istenen sayıda tekrarlamak için kullanılmaktadır. Hemen örnek bir kod yazalım:

      int sayi = 0; // "sayi" adında bir tamsayı değişken (ön tanımlı değeri 0 (Sıfır)).

/* Döngü içinde "i" adında bir değişken tanımladık."i" değeri 10 sayısından küçük olduğu sürece  döngü devam edecek. "i" değeri döngünün başına her gelişte 1 artacak..*/
      for (int i = 0; i < 10; i++)
      {
            sayi = sayi + 5;
      }

      Yukarıdaki "for" döngüsünü incelediğimizde, döngü için verilen değişkenlerin birbirinden ";" ile ayrıldığını görüyoruz. Yani öncelikle döngünün başlangıcı olan tamsayı değerini, sonra döngünün varacağı tamsaayı değerini sonra da döngünün her defasında başlangıç tamsayı değerinin kaç artacağını belirtiyoruz.

      Yukarıdaki örnekte, "i" değişkeninin değeri döngünün başına her gelişte bir artacaktır. "i" değişkeninin değeri 10 değerine eşit olduğunda ise döngü sonlanır. Şimdi bir örnek daha verelim.

      int sayi = 0;
      for (int i = 5; i < 20; i = i + 2)
      {
            sayi = sayi + 5;
      }

      Yukarıdaki örneği, "for" döngüsünün çalışma prensibini netleştirmek adına yazdık. Döngünün başlangıç değeri 5 olarak verilmiş. Döngünün, "i" değeri 20'den küçük olduğu sürece devam etmesi ve her yeni turda "i" değerinin 2 artması isteniyor. Bu durumda "i" değerinin alacağı değerler kümesi aşağıdaki gibi olacaktır:

      {5, 7, 9, 11, 13, 15, 17, 19, 21}

      Dikkat ederseniz "i" değeri 21 değerini de aldı. "for" döngüsü, "i" değerinin 20 sayısından büyük olduğunu gördüğü anda döngüyü kapatacaktır. Yani "i" değerinin 21 olduğu durumda "for" döngüsüne ait süslü parantezler içindeki kod çalışmaz.

      Bu kısa bilgiden sonra "sayilarToplamiVerebiliyorMu" fonksiyonumuza geri dönelim. Sayıların ikili toplama kombinasyonlarından herhangi birinde hedef sayıyı verip vermediğini denetleyeceğiz. Bu hesabı el ile yapmak için şöyle bir yöntem izleyebiliriz:

      * İlk sayıyı kendisinden sonraki tüm sayılarla ikili olarak toplar ve sonucu hedef sayı ile karşılaştırırız.
      * İkinci sayıyı kendisinden sonraki tüm sayılarla ikili olarak toplar ve sonucu hedef sayı ile karşılaştırırız.
      * Üçüncü sayıyı kendisinden sonraki tüm sayılarla ikili olarak toplar ve sonucu hedef sayı ile karşılaştırırız.
      * Dördüncü sayıyı kendisinden sonraki tüm sayılarla ikili olarak toplar ve sonucu hedef sayı ile karşılaştırırız.
      * Beşinci sayıdan sonra başka bir sayı olmadığı için işlemleri tamamlamış oluruz.

      Yukarıdaki işlemleri bir de resim ile anlatalım:

      Ekran resminden de görülebileceği üzere, bu şekildeki bir yaklaşımla tüm sayıları birbirleriyle eşleştirebiliyoruz. Evet, artık el ile hesabını yapabildiğimize göre bu işlemi kod ile de yapabiliriz.


            for (int i = 0; i < 4; i++)
            {
                for (int j = i + 1; j < 5; j++)
                {
                    if (sayilar[i] + sayilar[j] == hedefSayi)
                    {
                        toplamVarMi = true;
                        break;
                    }
                }
                if (toplamVarMi) { break; }

            }

      İlk "for" döngüsüne dikkat ediniz. Başlangıç değeri ilk sayı, ancak bitiş değeri sondan birinci sayı. Son sayının kendisinden sonra bir sayı olmadığı için döngüyü orada bitireceğiz (Resimdeki 5 sayısından çıkan bir ok yok.).

      Şimdi ikinci "for" döngüsüne bakalım. Başlangıç değeri seçilen sayıdan sonraki sayı, yani "i + 1". Bitiş değeri ise son sayı. Örnek resime baktığımızda alt satırdaki beş sayısına yönelik oklar olduğunu görebiliriz.

      Kısacası, ilk "for" döngüsü ekran resmindeki ilk satırı, ikinci "for" döngüsü ise ikinci satırı ifade etmektedir. Bu kısmı anladıktan sonra geriye sadece kombinasyondaki sayıları toplamak ve hedef sayı ile karşılaştırmak kalıyor. "if" cümlesini inceleyelim:


            for (int i = 0; i < 4; i++)
            {
                for (int j = i + 1; j < 5; j++)
                {
                    if (sayilar[i] + sayilar[j] == hedefSayi)
                    {
                        toplamVarMi = true;
                        break;
                    }
                }
                if (toplamVarMi) { break; }

            }

      Eğer i'inci sayı ile j'inci sayının toplamı hedef sayıya eşitse "if" cümlesine ait süslü parantezler arasındaki kod çalıştırılacak demektir. Bu kod ise, fonksiyondan dönecek değişkeni "true" değerine eşitliyor. Kodun ikinci satırındaki "break" deyimi, o an içinde bulunulan "for" döngüsünden çıkmak için kullanılır. Yani bir kombinasyon hedef sayıya eşitse diğer kombinasyonlara bakmaya gerek yok demiş oluyoruz.

      Dıştaki "for" döngüsünün son satırında da "break" deyimini görüyoruz. İlk "for" döngüsü içinden çıkıldıktan sonra, hedef sayı bulunmuşsa dıştaki "for" döngüsünden de çıkmak için yine "break" deyimini kullanmaktayız.

      Tabi isteseydik hedef sayıyı veren kaç kombinasyon olduğunu sayabilirdik. Aslında dikkat ederseniz, şu durumda kullanıcının önüne, düşük bir ihtimal de olsa, hedef sayıya birden çok kombinasyonla ulaşabileceği sayılar sunma ihtimalimiz var. Bu durumu önlemek isteseydik kaç farklı kombinasyonun hedef sayıyı verdiğini hesaplardık. 1'den fazla geçerli kombinasyonun yakalanması durumunda da tamsayılar listemizi yenilerdik. Şu an bu ayrıntıya girmiyoruz. Ancak uygulaması çok zor değil. İsterseniz kodlarınıza bu ayrıntıyı da ekleyebilirsiniz.

      Şimdi "sayilariBelirle" fonksiyonuna dönelim. Hatırlarsanız 5 adet sayı belirlemiştik. Bu sayıların farklı kombinasyonları ile hedef sayıya erişebilip erişemediklerini denetlemek üzere de bir fonksiyon çağırmıştık. Kaldığımız yerden devam edelim. "sayilariBelirle" fonksiyonunun son satırlarına bakıyoruz:


            bool toplamVarMi = sayilarToplamiVerebiliyorMu(sayilar, hedefSayi);

            if (toplamVarMi == false)
            {
                sayilariHedefSayiyaGoreDuzenle(sayilar, hedefSayi);
            }


            return sayilar;

      Yukarıdaki kodlara baktığımızda, eğer hedef sayıyı veren en az bir kombinasyon bulunamadıysa, "sayilariHedefSayiyaGoreDuzenle" fonksiyonunun çağırıldığını görüyoruz. Eğer bir kombinasyon yakalandıysa sayı belirleme işlemlerimiz tamamlanmış demektir. Fonksiyon bu durumda belirlediği sayıları döndürür.

      Hiçbir kombinasyonun yakalanamadığı durumda çağırılacak olan fonksiyona bir göz atalım.



      Fonksiyonu inceleyelim. Bu fonksiyon, belirlediğimi kurallara göre rastgele üretilmiş olan sayıların, hiçbirinin ikili toplamının hedef sayıyı vermediği durumda çağırılıyor. Fonksiyon içinde, üretilmiş sayılardan bir tanesi rastgele seçiliyor. Böylelikle hedef toplamı verecek olan ikili kombinasyonun ilk sayısı belirlenmiş oluyor. Seçilen sayı hedef toplamdan çıkarılıyor. Çıkarma işlemi sonucunda elde kalan sayı ise hedef toplamı veren ikili kombinasyonun ikinci sayısı oluyor. İkinci sayı (kalan değer), üretilmiş sayılar arasında bulunmadığı için, kalan değere en yakın üretilmiş sayının değeri değiştiriliyor ve kalan değere eşitleniyor. Böylece elimizdeki sayılar arasında hedef toplamı veren bir ikili toplama kombinasyonu bulunmuş oluyor. Rastgele üretilmiş sayılar, yeni haliyle fonksiyondan döndürülüyor.

      Şimdi "SayilariBelirle" fonksiyonuna dönelim. İhtiyaç olması durumunda "sayilariHedefSayiyaGoreDuzenle" fonksiyonunu çağırmıştık. Sonrasında ise "SayilariBelirle" fonksiyonundan çıkıyoruz.

      "SayilariBelirle" fonksiyonundan çıkmamız demek, bu fonksiyonu çağıran fonksiyona dönmemiz demektir. Öyleyse "YeniTur" fonksiyonuna dönüyoruz.

      "YeniTur" fonksiyonunda, sayılar belirlendikten sonra son bir fonksiyon daha çağırılıyor ve "Yenitur" fonksiyonundan da çıkılıyor.

      Şimdi "SayilariYazdir" fonksiyonuna bakalım.



      Görüldüğü üzere, sayıları yazdırmak için çağırdığımız fonksiyon, kullanıcının sayıları seçmek için kullanacağı düğmelerin "text" özelliklerini değiştiriyor. Bu düğmelerin "text" özelliklerine, rastgele belirlenmiş olan sayıların "string" türüne dönüştürülmüş hallerini atıyor.

      Evet, "Yen Oyun Başlat" düğmesi için gerekli tüm fonksiyonları yazdık ve açıkladık. Oyunumuzu bitirmemize az kaldı:) Bir sonraki yazımızda düğmelere tıklandığı zaman çalıştırılacak kodları hazırlayacağız.

Hiç yorum yok:

Yorum Gönder