Angular 6 ve SignalR 2 ile Web’de Mobil Etkileşimli Hafıza Oyunu Yapma Part 2

Selamlar,

Bu bölümde önceki makalede kaldığımız yerden devam edeceğiz.

Web ara yüzü için Angular 6 kullanılmıştır. Puzzle projesi aşağıdaki gibi Angular CLI ile yaratılır.

Şimdi bizi oyunların seçildiği bir menü ve QRBarcode’un olduğu bir sayfanın karşılaması gerekmektedir. Ayrıca QRBarcode okunduktan sonra12 tane kartın gene aynı sayfa üzerinde dizilmesi gerekmektedir.

Main.component.html:

5-) Bu sayfada yaşadığım projenin genelindeki 5. zorluk, sayfa arka resminin dinamik olarak, seçilen oyuna göre değişmesinin sağlanmasıdır : Bunun için “backImage” adında bir directive yazılmıştır. İlgili “background-image directive” kodlarına bu yazının hemen devamında erişebilirisiniz. 

  • IsLogin“: Değişkeni default’da “false”‘dur. QrBarcode okutulduktan sonra “true”‘ya atanmıştır. Kısaca amacı ‘frozen’ ve ‘moana’ menülerinin en başta gelmesi ve QrBarcode okutulup oyun başladığı zaman gizlenmesidir.
  • “<img *ngFor=”let category of categoryList”” : Categorydeki tüm oyunlar gezilerek ekrana basılır.
  • Menulerden “moana” veya “frozen” tıklanınca ==>”changeRoot()” methodu çağrılmıştır. Bu method da, yazının hemen devamında incelenecektir. Arkada seçilen oyuna göre değişen müzik, müzik ses yüksekliği, oyun sırasındaki arka resim ve oyun kazanıldığı zaman karşımıza çıkan bildirim resmi hep bu method altında değiştirilir.
  • <img src=”{{barcodeImageUrl}}” width=”{{width}}” height=” {{height}}” alt=’QrBarcode’ class=’barcode’ (load)=”DeleteImage()”>” : Sayfa üzerine oluşturulan QrBarcode bu resim html elementi ile “barcodeImageUrl” değişkenine bağlı olarak ekrana basılmıştır. “width” ve “height” bilgileri de ilgili değişkenler ile atandıktan sonra ilgili QrBarcode resmi ekrana basılır.
    • 6. Olarak benim bu projede yaşadığım zorluk QrBarcode resminin silineceği zaman idi. En doğru zaman, resmin yüklenme işleminin bittiği zaman olmasından dolayı, resmin “(load)” eventinde “DeleteImage()” methodu çağrılmıştır. İlgili method yazının devamında incelenecektir. QrBarcode resmi (load) yani resmin yüklenme işlemi bitmeden silinir ise, ekrana basılamıyacaktır. Bu neden ile yüklenmesi beklenmektedir.
  • <div *ngIf=”IsLogin && isWin==false”>” : QrBarcode okutulmuş ama herhangi bir kazanma durumu olmamış ise(yani oyun bitmemiş ise) kartların dizildiği ==>”<table class=”box”>” tablosu gösterilir.
  • <textarea cols=”1″ rows=”2″ [(ngModel)]=”moveCount” id=”txt_area” disabled></textarea>” : Oyunda yapılan hamle yani açılan kart sayısını counter şeklinde sayan, “moveCount” değişkenine 2 yönlü bağlanılır.
  • 7. olarak projede yaşadığım zorluk, kartlar sıralanırken her bir sırada 3lü kolonlar şeklinde sıralanmasını Angular 6’de sağlamaktı. Bunun için “pairs” adında bir filter yazdım. Gene bunu, yazının devamında inceleyebilirsiniz. Gelen kart dizisi 3×3’lük bir matrise dönüştürüldükten sonra önce satırlar için ==>”<tr *ngFor=”let cards of cardList | pairs”> ” sonra her bir kolon için ==>“<td *ngFor=”let card of cards”>” şeklinde gezilerek ekrana basılır.
  • Ekrana gelen her bir kartın arka resmi, kapalı olarak ekrana basılmıştır.
    • [src]=”card.controlCardBgImage”“: Kartın üzerindeki resim. Başta kartın arka resmi ve tıklanınca da önyüzdeki resmi, ilgili modelin “controlCardBgImage” property’si ile atanmaktadır.
    • alt=”{{card.name}}-{{categoryID}}”“: SEO amaçlı kartın altına kartın adı ve ait olduğu oyun kategorisi ile yazılır.
    • [attr.isShow]=”card.isShow”” : Angular 6’de custom attribute “attr” ile ekrana yazdırılmaktadır. “isShow” function’ı kart mobil cihazdan tıklandı ve şu an açılarak gösteriliyor demektir.
    • [attr.isDone]=”card.isDone”” : Kart ve eşleniği bulunduğu zaman, sayfada her zaman açık kalmaları için “isDone” değişkeni her 2 kart içinde true’ya çekilir. Kısaca o kartlar için artık hiçbir kural işlemez. Çünkü eşleniği bulunan kartlar ile artık herhangi bir işlem yapılmaz. “isDone” bu anlama gelmektedir.
      • Not: Bir sayfa üzerinde “isDone”==false ve “isShow==true” olan en fazla 2 adet kart olabilir. Eğer kartlar aynı değil ise “isDone”‘ olmayan yani eşleniği henüz bulunmamış tüm kartların “isShow”‘u false’a atanır ve böylece açık kartlar kapatılır.
    • id=”{{card.id}}-{{categoryID}}”” : ID değeri herbir kart için Unique olması adına [card.id’si ve “-” categoryID’si] şeklinde ekrana basılmaktadır.
    • name=”{{card.name}}-{{categoryID}}”” : Resmin ismi gene Unique olacak şekilde [resim ismi ve “-” “categoryID”] değerleri ile atanmaktadır.
    • <img [src]=”winImage” *ngIf=”isWin” class=”win”>” : Divin en sonuna konan “winImage” değişkenine bağlı olan bu resim, eğer tüm kartlar açılır yani eşleniği bulunur ise, default olarak “false” olan “isWin” değişkeni “true”‘ya atanır ve bu kutlama resmi client’ın karşısına çıkarılır.

Main.component.html:

background-image.ts: Aşağıda görüldüğü gibi “@Input” olarak seçilen oyuna ait resimin gönderilmesi ile div içerisinde bulunan alanın arka resmi dinamik olarak değişir. Kısacası herbir oyun için farklı bir tema seçilir.

main.component.ts/changeRoot(): Sayfa üzerinde seçilen oyuna göre :

  • Ana “root” değişkeni yani hangi category’de olunduğu burada atanır.
    • Sayfa arka resmi “bgImage” yine burada atanır.
    • Mobile ve Web sayfalarında dizilen kartların arka resimleri “cardBgImage” değişkeni ile atanır.
    • Kazanıldığı zaman karşımıza çıkacak resim “winImage” değişkeni ile değiştirilir.
    • Arkada çalan müzik “soundRoot” değişkeni ile tanımlanır.
    • Çalınacak müzik yüksekliği “soundMax” değişkeni ile gene burada tanımlanır. Seçilen oyun kategorisine göre farklılık göstermektedir.

main.component.ts/DeleteImage: QrBarcode’un yüklenme işlemi bittiği zaman, önceki makalede anlatılan  Puzzle:Hub sınıfının “DeleteImage()” methodu yaratılan barcode resminin ismi ile tetiklenir. Buradaki tek eksik, sayfa sürekli refreshlenir ise yani resim tam yüklenmeden sayfa yenilenir ise, ilgili klasör altında silinmeden onlarca QrBarcode resmi oluşabileceği dir. Bunun için ayrıca düzenli çalışan bir servisin, zamanı geçmiş resimleri temizlemesi gerekmektedir.

app/threepairs.ts: Aşağıda görüldüğü gibi “pairs” fitler ile gelen 12 elemanlı kart dizisi 4×3‘lük bir matrise dönüştürülmektedir. Kısaca gelen “List<FrozenPuzzle>” 3 boyutlu bir dizi içerisine atılmaktadır.

Böylece adım adım “Main.Component.html”‘e değinmiş olduk. Şimdi sıra main.component.ts’e geldi:

Main.component.ts: Yukarıda zaten methodlara değindiğimiz bu kısma kaldığı yerden devam ediyoruz:

Eklenen import dosyalar:

  • “ChangeDetectorRef” : Kütüphanesinin amacı: Yaşadığım 8. zorluk olarak html kodu değişen resimlerin browser cache’inden dolayı angular tarafında yeni hali ile render edilmediği görülmüştür. Bu neden ile ilgili kütüphane kullanılarak, resimlerin render edilmesi yani değişimi, manuel olarak zorlanmıştır.
  • Signalr için “HubConnection” kütüphanesi eklenir. SignalR Angular projesine “npm install @aspnet/signalr” komutu ile eklenir.
  • “PuzzleService” : Webservisleri olan tüm işlemler bu yazılan servis kütüphanesi üzerinden yürütülmektedir. PuzzleService’i yazının devamında detaylıca incelenecektir.

Sayfa üzerindeki oyunlar “Category” enum’u olarak eklenmiştir.

MainComponent üzerindeki global değişkenler sayfanın en tepesinde tanımlanmıştır:

  • “root”: Seçilen oyunun adını üzerinde barındırır.
  • “categoryID”: Seçilen oyuna ait category değerini tutar.
  • “soundRoot” : Seçilen oyunun seslerinin tutulduğu klasörün path’ini tutar.
  • “soundExtension” : Oyunlarda kullanılan seslerin uzantıları oyun türüne göre değişiklik göstermektedir. İlgili oyuna ait ses uzantılarını tutar.
  • “soundMax” :  Ses yükseklikleri oyuna göre farklılık gösterdiği için, her oyuna göre ses düzeyi değerini tutar.
  • “moveCount” : Yarışma amaçlı toplam tıklanan resim sayısını her el için tutar.
  • “_hubConnection” : SignalR “Puzzle: Hub” sınıfına bağlantı yapılacak nesnedir.
  • “cardList” : Webservisinden random çekilen 12 kart datası bu dizide tutulur.
  • “bgPath” : Projedeki genel resim yolunu tutar.
  • “servicePath” : WebServisinin bağlanılacağı sunucunun url’ini tutar.
  • “isWin” : Oyunun sonucunun tutulduğu değişkendir. Duruma göre sayfa üzerinde farklı bölümler gösterilir ya da gizlenir.
  • “winImage” : Oyun kazanıldığı zaman seçilen oyuna göre gösterilecek resim yolunu tutar.
  • “bgImage” : Sayfanın arka resminin yolunu, seçilen oyuna göre tutan değişkendir.
  • “cardBgImage” : Seçilen oyuna göre farklılık gösteren, dizilen kartların arkasındaki resmin yolunu tutar.
  • “barcodeImageName” : Oluşan QrBarcode’un Guid ismini tutar.
  • “barcodeImageUrl” : Geçici olarak yaratılan QrBarcode’un path’ini tutar.
  • “height,width” : Sayfaya basılacak QrBarcode’un genişlik ve uzunluğunu tutar.
  • “IsLogin” : QrBarcode’un okutulup okutulmadı bilgisini tutar.
  • “_connectionId” : Main.component.html’den Puzzle:Hub signalR sınıfına bağlanan client’ın ConnectionID’sini tutar.
  • “_connectionIDControlPage” : ControlPage sayfasından signalR sinifına bağlanan client’ın connectionID’sini tutar.
  • constructor’da “PuzzleService” ve “ChangeDetectorRef” : Dependency Injection  ile sayfaya implemente edilir.

main.component.ts/ngOnInit() Part1:

  • “this._hubConnection = new HubConnection(this.servicePath + “puzzle?key=main”);” : Puzzle:Hub signalR sınıfına bağlanılır. ==>”key=main” ile signalR Hub sınıfına hangi sayfadan bağlanıldığı belirtilmektedir.
  • “this._hubConnection .start()” : Bağlantı işlemi yapılır. Bu esnada signalR Puzzle sınıfı tarafında “OnConnectedAsync()” methodu tetiklenmektedir.
  • ” this._hubConnection.invoke(“CreateBarcode”, this._connectionId, imageName, this.categoryID)” : Puzzle:Hub signalR sınıfındaki CreateBarcode() methodu sayfanın connectionID’si, oluşturulan QrBarcode’un Guid ismi ve seçilen oyunun kategori bilgisi ile tetiklenir. Server side tarafda geçici oluşturulan QrBarcode image yolu, CreateBarcode() methodu tarafından geri dönülür. Ve “barcodeImageUrl” değişkenine atanır. Böylece QrBarcode sayfaya basılmış olunur. “CreateBarcode()” methodu bir önceki makalede anlatılmıştır.
  • “this.service.GetCategories()” :  Methodu ile sayfaya seçim amaçlı basılacak oyun listesi “categoryList” değişkenine atanır.

main.component.ts/ngOnInit() Part2: Bu bölümde QrBarcode okutulduğu zaman ==> Mobilede, ControlPage sayfasına gidilir.

  • ControlPage sayfası signalR sınıfına connect olduktan sonra, “GetAllCards()” methodu çağrılır ve ControlPage için listelenen random kart dizisi geri dönülür. Get işlemi tamamlandıktan sonra ControlPage’den signalR sınıfının “TriggerMainPage()” methodu tetiklenir. Amaç ControlPage için oluşan “connectionID”‘nin Main sayfasına bildirilmesidir.
  • TriggerMainPage()” methodu da aşağıda görülen MainPage sayfasındaki “Connected()” function’ını tetiklemektedir. Parametre olarak ControlPageConnectionID’si gönderilir. “TriggerMainPage()” methodu yazının devamında incelenecektir.
  • Bu method içinde “MainPage” için cache’den “ControlPage” için listelenen random kart dizisi “MainPage” sayfası için de geri dönülür. Ve daha sonra ilgili cache temizlenir. Böylece 2 sayfa için de aynı random kart listesi ekrana basılmış olunur.
  • var data = result.forEach(card => { card.controlCardBgImage = this.cardBgImage;  });” :  MainPage içinde servisten gönderilen herbir kart, gezilerek arka resmi seçilen oyun kategorisine göre atanır.
  • this.bgImage = this.bgPath + “back2.jpg”” :  Sayfanın arka resmi seçilen oyun kategorisine göre atanır.
  • “var soundID = this.getRandomInt(2, this.soundMax);  this.playAudio(soundID);” : Seçilen oyun kategorisine göre random müzik çalınır.

main.component.ts/ngOnInit() Part3: Bu bölümde cep telefonundan secilen kartın açılması ve eşleniğe göre başarılı olunup olunmaması kararının verildiği signalR “OpenCard()” methodu incelenecektir.

  • ControlPage’de resim tıklandığı zaman signalR Puzzle:Hub sınıfının “OpenCard()” methodu MainPageConnectionID ve seçilen cardID parametreleri ile birlikte tetiklenir. Aşağıda signalR server side taraftan trigger edilen, MainPage üzerindeki “OpenCard()” function’ı incelenmektedir.
  • “var card = this.cardList.filter(card => card.id == id)[0];” : Mobile cihazda gösterilen ControlPage sayfasında tıklanan kart ==> Main sayfasında ID’si ile bulunur.
  • “if (!card.isShow) {” : Açık kartın bir daha açılması engellenir.
  • “this.moveCount+=1;” : Her bir kartın açılma hareketi sayılır. Böylece toplam kaç denemede kartların tamamı açıldı izlenebilir.
  • “card.controlCardBgImage = this.bgPath + card.name;” : Seçilen kartın resmi gösterilir.
  • “this.cdRef.detectChanges()” : Yaşadığım 9. zorluk bazen resimlerin html’i değişse de, sayfa üzerinde angular tarafından render olmuyorlardı. Bunu aşmak için zorlama dediğimiz, “detectChange()” methoduna gidilmiştir. Yani kısacacası Angular 6’ya, html’i değişen var mı? Bak bakalım denilmiştir :)
  • “if (this.cardList.filter(c => c.isShow == true && c.isDone == false).length > 1) {” :Tamamlanmamış yani eşleniği bulunmamış, açık başka bir kart var mı diye bakılmıştır.
  • “var pairID = this.CloneID(id)” :  Açılan resmin ID’sine eşlenik diğer ID, bir önceki makalede bahsedilen “CloneID()” methodu ile bulunur.
  • ” var IsPairCardOpen = this.cardList.filter(crd => crd.id == pairID && crd.isShow).length > 0 ? true : false;” : Açılan kartın eşlenik ID’si tüm kart listesi içinde aranır ve “isShow” propertysine bakılır. “True” ise 2 aynı kart bulunmuş olunur. Burada kartın eşleniğinin açık olup olmadığı Linq ile bakılmıştır.
    • ” this.cardList.filter(cd => cd.isShow && cd.isDone == false).forEach(f => {” : Herbir kart gezilerek  açık ama tamamlanmamış olanlar’ın “isDone” propertysi “true“‘ya atanmıştır.
    • ” this.playSucess(“success.wav”);” : Başarıldığını belirten bir ses çalınır.
      • Not: Başarı müziği çalınırken, arka fonda çalan oyun müziği ile bir çakışma olduğu için, Html tarafında “this.audioSucess.play();” methodu da çalışsada bir hata gözükmektedir. “playSuccess()” methoduna yazının devamıda deyinilecektir.
    • ” if (this.cardList.filter(c => c.isDone == false).length == 0)” : Tüm kartların açıldığı “isDone==false” parametresi ile anlaşılır.
    • “this.playSucess(“win.wav”); this.isWin = true;” : Kazanıldı müziği çalınır ve “isWin” değişkeni ile seçilen oyun kategorisine göre başarı resmi gösterilir.
    • ” setTimeout(() => {” : 6sn bir bekleme süresinden sonra oyundaki kartlar tekrardan dizilip yeni bir random müzik ile baştan başlanır. 6sn, başarı sayfasının ekranda görünme süresidir.
      • ” this.service.GetAllCards(this._connectionId, this.categoryID, true).subscribe(result => {” : Tüm kartlar MainPage için Random çekilir. İlgili servis yazının devamında detaylıca anlatılacaktır.
      • “var data = result.forEach(card => { card.controlCardBgImage = this.cardBgImage; });” :  Tüm kartlar tekrar kapalı hale getirilir.
      • “this.moveCount=0;” : Toplam hareket sayısı “0” lanır.
      •  this.NotifyControlPage(id, true, isReset);” : Oyunun tekrardan başlaması için, ControlPage sayfasının da “0” lanması için bildiri gönderilir. “NotifyControlPage()” methoduna, yazının devamında değinilecektir.
  • Açılan 2 kart aynı değil ise : “setTimeout(() =>” ile ilgili resimlerin 1sn süre ile gösterilmesi sağlanır.
    • “this.cardList.filter(cd => cd.isShow == true && cd.isDone == false).forEach(f => {” : Tüm kartlar içinde, açılmış ama aynısı bulunamamış kartlar :
      • “f.controlCardBgImage = this.cardBgImage;” : Tekrardan kapatılır.
      • “f.isShow = false;” : Gösterildi değişkeni false’a atanır.
      • “f.isDone = false;” : Eşi bulundu mu? değişkeni false’a atanır.
    • “this.NotifyControlPage(id, false, isReset);” : Açılan kartların yanlış kartlar olduğu bilgisi ControlPage’e gönderilir. Bu karar aşamasının tamamı MainPage’de yapılmaktadır. 2. bir yol olarak tüm bu karar mekanizması MainPage’den bağımsız ControlPagede de yapılabilirdi. O zaman 2 sistem birbirinden tamamen bağımsız çalışabilirdi. Ama karar süreci kodları, 2 sayfa için de çoklanacaktı. Yani projede kod tekrarı olucaktı. Bu şekilde geribildirim de, socket üzerine extra bir yük getirmektedir. Bu tamamen benim seçimimdir.

Angular Projesin’de yani Web tarafında kullanılan oyun modeli, FrozenPuzzle’dır.

FrozenPuzzle: Server side tarafta kullanılan FrozenPuzzle modelinin aynısıdır.

PuzzleCategory: Her oyun bir kategoriye atanmıştır. İlgili modeli aşağıdaki gibidir.

Bir sonraki makalede Main.component.ts’de kullanılan tüm functionları ve ControlPage tarafında neler döndüğünü hep beraber inceleyeceğiz.

Bir sonraki makalede görüşmek üzere hepinize hoşçakalın.

Herkes Görsün:

Bunlar da hoşunuza gidebilir...

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir