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

Selamlar,

Bu makalede, ilginizi çekebilecek birkaç oyunu hep beraber kodlıyacağız. Aslında oyunlar kızıma hediye olarak başladı. Ama sonra işler biraz büyüdü :) Kodları yazarken, yaşadığım sorunların düğüm noktalarını da, yine bu makalede maddeler halinde teker teker inceleyeceğiz.

Kısaca Oyun Tanımı: Angular 6 ile en başta Elsa’lı veya Moanalı (kızı olan bilir) seçime bağlı bir web ara yüzü, karşımıza gelecek. Üzerinde .Net Core 2.0 ile yazılan ve SignalR 2.0 bir websocket ile oluşturulan, QrBarcode olucak. Mobile bir cihaz ile QrBarcod okutulduktan sonra, 2 cihaz birbiri ile connect olup, oyun sayfasına geçilecek. Web sayfasında arkası dönük default 12 kart, 3er’li sıra şeklinde Random olarak dizilecek. Aynı kart dizilmi, Mobile uygulama için de farklı bir tasarım ile gerçekleştirilecek. Mobile ekranda herhangi bir karta tıklanınca, web ortamındaki Elsa’lı veya Moana’lı kart açılacak. Bir sonraki seçimde, aynı kartın bir eşleniği daha bulunduğunda 2 kart açık kalacak. Bulunmaz ise 2 kart da kapanacak. Tüm kartların eşleniği bulunana kadar, yani tüm kartlar açılana kadar oyun devam edecek. Tüm kartlar açıldıktan sonra bir kutlama ekranı çıkartılıp, yeni bir müzik ve yeni kart dizilimi ile oyuna tekrardan başlanacak.

Örnek Oyun Web Ekranı:

Şimdi gelin teknolojileri belirlemeden önce yapılacak işleri maddeler halinde sıralayalım:

  1. Karşılama Ekranı
    1. Server Side tarafta oluşturulacak bir QrBarcode. Amaç Mobile cihaz ile Web uygulaması’nı birbirine bağlamak.
    2. Moana veya Frozen oyunundan birinin seçileceği menü.
    3. Seçilen content’e göre arka müziğin ve thema’nın değiştirilmesi.
    4. QrBarcode tarandıktan sonra başka hiçbir işlem yapılmadan, aynı sayfa üzerinde oyun ekranına geçilmesi.

2. Database: Her oyuna ait kart bilgileri ve oyunun ait olduğu kategori bilgisi bir DB’de tutulmalıdır. Bu projede DbContext olarak entityframework kullanılmıştır.

3. Oyunun kuralları Net Olarak çıkarılmalıdır.

4. Oyunda  herşey genelleştirilmelidir. Her farklı module için farklı bir işlem yapılmamalıdır. Tüm resim, ses gibi dosya yolları için belli kurallar belirlenmelidir. Oyun kuralları tüm modeller için geçerli olmalı ve yönetim tek bir noktadan yapılmalıdır.

5. Oyunda kullanılan tüm video, ses, müzik ve resim yolları oyun modlarına göre kolaylıkla erişilebilir, genişletilebilir ve değiştirilebilir olmalıdır.

6. Kod kalabalığından ve mükerer koddan uzak kalınarak ana motor oluşturulmalıdır.

7. Teknolojileri cross platform çalışabilecek şekilde , modern teknolojiler ile belirlenmelidir.

Kurulum:

Proje için Puzzle adında bir klasör yaratılır. İlkin puzzleservice adında webapi servisi .Net Core 2.0 platformunda aşağıdaki gibi yaratılır. Esas amaç random olarak istenen kategoriye göre 12 kartın Azure üzerindeki DB’den çekilip verilmesidir.

Models / :

PuzzleCategory: Her bir oyun bir category’e aittir.

Ipuzzle:

FrozenPuzzle : Her bir oyun modunda kullanılacak kart model’idir. Her bir property’nin ne işe yaradığını sayfa üzerinde yazının devamında inceleyeceğiz.

PuzzleCategory: Her bir oyunun atandığı kategori modelidir.

ControlPage.html:

PuzzlesController.cs :

  • Cache amaçlı “static Dictionary<string,List>” bir liste kullanılmıştır. Amaç Web sayfası(“MainPage“) için çekilen rastgele kart bilgisinin bir daha mobile sayfa(“ControlPage“) için de çekilmesinin engellenmesidir. Zaten istense de aynı sırada random liste tekrar çekilemez.
  • Get()” methodu ile oyun kategorileri çekilip bir liste olarak geri dönülmektedir.
  • Get(int id, string connectionID, Boolean isReset = false)” : İlgili kategoriye göre DB’den 6 random kart çekilir. Daha sonra çekilen bu kartların eşlenikleri de oluşturulup, toplam 12 kart olarak geriye dönülür. Yaşadığım tüm zorlukları maddeler halinde makale boyunca sizlere sıralayacağım. Yaklaşık 17 madde bulunmaktadır. Bunlardan biri aşağıdaki gibidir.
    • 1-) Yaşadığım ilk zorluk 6 kart random çekildikten sonra Clonelarının yani eşleniklerinin aynı referance’ı paylaşmasıdır. Yani bir kart üzerinde bir değişiklik yapıldığı zaman, eşlenik kartın bundan etkilenmemesi gerekmektedir. Bunun için aşağıda görüldüğü gibi “CloneList()” methodu geliştirilmiştir. Çekilen 6 kart List<Ipuzzle> memory stream’e serilize edilir. Position’ı 0’a atanıp, tekrar deserialize edilir. Böylece farklı referance tipinde aynı resme sahip 6 farklı kart daha oluşturulmuş olunur.
    • 2-) Her bir oluşturulan eşlenik resime ait ID değerlerin, var olan diğer eşlerine ait ID’ye erişilebilecek bir algoritmada oluşturulmalıdır. Bunun için “CloneID()” methodu oluşturulmuştur. Örneğin 11 ID’sine eşlenik resim ID’si olarak, iki ID arasına ve sayının sonuna “0” konarak “110110” şeklinde oluşturulmuştur. Yine aynı şekilde eğer eşlenik resimden esas resme geçilmek istenirse de “Substring(0, ID.ToString().Length / 2)” ile sayının uzunluğunun yarısı alınır. Yani “110” değeri elimize geçmiş olur. Daha sonra, en son rakkam “0’dan kurtulunur. “firstID.Substring(0, firstID.Length – 1)“. Elimize geçen aradığımız son sayı değeri “11” olur. Bunun için “ID’nin <100” küçük olup olmadığına bakılır. Küçük ise gerçek ID, değil ise eşlenik ID olduğu anlaşılır. Burada amaç açılan 2 resmin aynı olup olmadığının ID’lerine göre kontrol etmektir.
    • AddRange()” methodu ile 2 liste tekrardan birleştirilir. Son olarak tüm kartlar tekrardan random olarak dizilir. Böylece ilk oluşturulan 6 random resim ve eşlenik resimler de kendi içinde bir daha karıştırılmış olunur.
    • Oluşturulan kartlar yok ise cache’e atılır. Eğer var ise 2. kere ControlPage’den çekiliyor anlamına gelmektedir. Bu da, bu kart listesine bir daha ihtiyacımızın olmıyacağı anlamına gelmektedir. Bu nedenle de ilgili Cache’den liste, boşuna yer kaplamaması adına(performance) çıkarılır.

Şimdi gelin nasıl oluyorda mobil ve web uygulaması birbirine bağlanıyor hep beraber inceleyelim. Öncelikle bunu için signalR socket teknolojisi kullanılmıştır. Öncelikle .Net Core 2.0’da signalR paketi kurulumu için “puzzleservice.csproj” dosyasına “<PackageReferenceInclude=”Microsoft.AspNetCore.SignalR”Version=”1.0.0-preview2-final”/>” paketi yazılır ve Restore edilir.

Öncelikle Web de oyuna ilk girildiği zaman, signalR Hub sınıfına bağlanılır. Ve client’a özel bir connectionID atanır.

Puzzle.cs[Part 1]:

Aşağıda görüldüğü gibi client Puzzle signalR hub sınıfına bağlandığı zaman, asenkron çalışan “OnConnectedAsync()” methodu tetiklenir. 3-) Yaşadığım bir diğer zorluk, bu projede signalR Hub sınıfına bağlanan 2 sayfa bulunmaktadır. 1.Web Sayfası MainPage ve 2.Mobilede çalışıcak Controlpage. Sorun, hangisinin bağlandığını fark etme ve herbiri için farklı aksiyonlar almaktır.

Bu sorunun çözümü olarak url’e “key” parametresi olarak gönderilen sayfanın adı yazılmıştır.

  • Eğer ana sayfa yani “Main” sayfası bağlanmış ise:  Önce oluşacak QrBarcode için bir “Guid” image ismi ve bağlanan client’ın “Context.ConnectionId“‘si ==> connect olan client’ın client side tarafındaki “GetConnectionId()” function’ına parametre olarak gönderilir. Kısaca önce kullanıcı Client Side taraftan==> signalR Puzzle :Hub sınıfına connect olur. Daha sonra Server Side tarafdaki “OnConnectedAsync()” methodu ==> Client Side taraftaki “GetConnectionId()” function’ını, ilgili parametreler ile tetikler.
  • Eğer bağlanılan sayfa “ControlPage” sayfasından ise, bu sefer Control Sayfasındaki client side tarafındaki “GetConnectionId()” methodu, sadece signalR sınıfına bağalanılınca otomatik oluşturulan connectionID parametresi ile tetiklenir.

Puzzle.cs[Part 2] / CreateBarcode() : Bu method aslında Main Page’de yukarıda bahsedilen “GetConnectionId()” methoduna ConnectionID gönderilerek trigger edildikten sonra Client Side taraftan==> Server Side tarafa doğru çağrılan bir methoddur.

Aşağıda görüldüğü gibi :

  • Client’ın signalR sınıfına bağlandığında oluşan connectionID’si, oluşturulacak QrBarcode resminin Guid adı ve hangi kategoriden bir oyuna girileceğini belirleyen bir categoryID değerleri parametre olarak alınmaktadır.
  • Code’a ==>QrBarcode ile Control Page’e gidilen bir url satırı oluşturulmaktadır.
  • Genişlik ve Uzunluk “200*200” bir kare set edilmiştir.
  • QrBarcode oluşturmak için Google’ın ücretsiz “chart.apis.google” servisi kullanılmıştır.
  • İlgili servise gönderilen “WebRequest” servis sonucu “WebResponse” ile alınır. Ve Stream’e çevrilir.
  • Karşılaştığım 4. Sorun Stream’den Image’ın çekilebileceği .NET Core 2.0 üzerinde yerleşik olarak gelen bir kütüphaneyi bulamamam oldu. Ben bunun için 3th Part Tool olan “SixLabors.ImageSharp” kütüphanesini buldum. Aşağıdaki gibi projeye eklenir. Hem image’ın strema’den alınması için hem de fiziksel bir klasöre yüklenmesi için kullanılmıştır. Ama .Net Core 2.1 güncellemesi ile ilgili kütüphane artık çalışmamaktadır. Yerine FileStream kullanılmıştır.
  • “image.Save(“wwwroot/images/” + imgName + “.png”);”: Oluşturulan QrBarcode resmi .Net Core Web Servisi projesinde istenen bir yere kaydedilemez. “wwwroot” altında olmak zorundandır.
    • Not : Bu projede ilgili QrBarcode resimleri “wwwroot/images” folder’ı altına kaydedilmiştir. Ayrıca .Net Core projesinde “Startup.cs” dosyası altında “Configure()” methodu içinde ==>”app.UseStaticFiles();” izni verilmeden hiçbir fiziksel dosya ile ister resim ister ses dosyası olsun çalışılamaz.
  • Son olarak kaydedilen resim, WebApi servis sunucu yolu ile birlikte “MainPage” sayfasına ekrana basılması amacı ile geri dönülür.

Puzzle.cs [Part 3] / DeleteImage(): QrBarcode sayfaya basıldıktan sonra, artık ihtiyaç kalmadığı için ClientSide taraftan ==> Server Side taraftaki “DeleteImage()” methodu, oluşan resmin Guid adı ile birlikte trigger edilir ve ilgili image “wwwroot/images/” klasöründen silinir.

Startup.cs: Cross-Domain için izinler, Mvc ve SignalR tanımları, SignalR için Routing tanımlaması ve Static dosyaların kullanılması gibi tüm .Net Core tanımlamaları bu sınıf altında yapılır.

SignalR Puzzle:Hub sınıfına ait diğer methodları yeri geldiği zaman inceleyeceğiz. Şu ana kadar bir client SignalR Puzzle : Hub sınıfına connect oldu.”OnConnectedAsync()” methodu trigger oldu. Yeni bir ConnectionID alındı. Daha sonra Puzzle: Hub sınıfındaki “CreateBarcode()” methodu çağırdı ve QrBarcode oluşturulup image olarak bir fiziksel dosyaya yazıldı. Üzerinde Controlpage sayfasının Url’i, Main.component sayfasına bağlanan client’ın connectionID’si ve oyun tipi anlamına gelen categoryID bilgileri bulunmaktadır. Daha sonra ilgili resimin tam yolu client side tarafa gönderildi. Son olarak bir sonraki makalede inceleyeceğimiz Main.Component.html sayfasında, ekrana basılan QrBarcode’un silinmesi amacı ile server side taraftaki “DeleteImage()” methodu trigger edilmiştir.

Bir sonraki makalede kaldığımız yerden devam edip, Angular 6 projemizi oluşturup yukarıda bahsettiğimiz Main ve ControlPage sayfalarımızı oluşturacağız. Bu sırada ihtiyacımız olan “Puzzle : Hub” sınıfı altındaki diğer methodlara da değineceyiz. Ayrıca yaşadığım diğer sorunları da adım adım incelemeye devam edeceğiz.

Bir sonraki makalede görüşmek üzere 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