Tuesday, March 29, 2016

Angularjs ve ASP.NET MVC ile Javascript Diziler Kullanılarak Dinamik Çoklu Veri Gönderimi - Sending Dynamic Multi Post Data Using Javascript Arrays With Angularjs and ASP.NET MVC

Bu yazıda ASP.NET MVC üzerinde angularjs kullanılarak kullanıcıdan alınan çoklu veri girişlerinin bir dizi içinde tutularak AJAX ile sunucuya gönderilmesi ve sunucuda işlenmesi sağlanacaktır. Bu amaçla oluşturacağımız giriş kontrolünün CSS kodlarını tanımlayalım.

<style>

    .mainContainerAngularExample {

        width:300px;
        margin:10px;
    }

    .detailAngularExample {

        margin-top:10px;
    }

    .detailAngularExample input:focus {

        background-color:lightblue;
    }

    .footerAngularExample {

        margin-top:10px;
        text-align:right;
    }

    .arrayContainerAngularExample {

        margin-top:10px;
        color:coral    
    }

</style>

Sonraki aşamada DOM elementleri ve veri katmanı arasındaki bağlantıyı tanımlamak için angularjs kodlarını yazalım. Aşağıdaki kodlarda "myApp" modulü altında "MainCtrl" kontrolcüsü tanımlanmaktadır. Scope (Kapsam) içinde yer alan "addItem" fonksiyonu ile diziye elemen eklenmekte ve "removeItem" ile diziden eleman silinmektedir. Bu amaçla diziler üzerinde tanımlı "splice" fonksiyonu kullanılmıştır. Scope içinde yer alan "id" değişkeni ile dizi elemanlarının isimlendirilmesi yapılmaktadır. Ayrıca eğer gerekli olursa (dizi içinde yer alan elemanların tekrar ekmesi durumunda) "id" eşsiz anahtar olarak da kullanılabilir. Burada en önemli nokda dizi içinde nesne(object) kullanılmasıdır. "np-repeat" işlemi gerçekleştirirken kendi scope (kavramı) nesnesini yaratmaktadır. Bu sırada javascript prototype yapısından dolayı "primitive (ilkel)" değişkenlerin değeri programlama dillerindeki fonksiyonlara parametre gönderilirken ki durumla benzer olarak kopyalanmaktadır. Bu sebeple dizi içindeki değişikliğin "parentScope" üzerinde geçerli olabilmesi için dizi içinde referans olarak JSON nesnesi kullanılmıştır.

var app = angular.module('myApp', []);

app.controller('MainCtrl', ['$scope','$http',function ($scope, $http) {

    $scope.pages = [
        { val: 'Item 1' }
    ];

    $scope.id = 1;

    $scope.addItem = function (index) {

        $scope.id++;
        $scope.pages.splice(index + 1, 0, { val: 'Item ' + $scope.id });
    }

    $scope.removeItem = function (index) {

        if (index != 0)
            $scope.pages.splice(index, 1);
    }

    $scope.saveData = function () {

        $http.post("./Home/SaveData", $scope.pages).success(function () {

            alert("Veriler Başarıyla Kaydedildi.");

        }).error(function () {
            
            alert("Veriler Kaydedilirken Bir Hata Oluştu!");
        });
    }
}]);

Sonraki aşamada HTML kodlarını tanımlayalım. Aşağıdaki kodlarda "ng-model" niteliği kullanılarak giriş elementiyle dizi elemanı arasındaki ilişki tanımlanmıştır. "ng-click" kullanılarak kontrolcü içinde yer alan ilgili fonksiyonların tıklanma durumunda çalıştırılması sağlanmaktadır. "ng-hide" ile ilk elemandaki sil tuşunun görünmemesi sağlanmaktadır. "saveData" fonksiyonu kullanılarak üzerinde çalıştığımız dizi AJAX POST isteğiyle sunucuya gönderilmektedir.

<div class="mainContainerAngularExample" ng-app="myApp" ng-controller="MainCtrl">
<div class="detailAngularExample" ng-repeat="page in pages">
<input ng-model="page.val" type="text" />
        <button class="btn btn-default btn-xs" ng-click="addItem($index)" type="button"><span class="glyphicon glyphicon-plus"></span>EKLE</button>
        <button class="btn btn-default btn-xs" ng-click="removeItem($index)" ng-hide="$index === 0" type="button"><span class="glyphicon glyphicon-minus"></span>SİL</button>
    </div>
<div class="footerAngularExample">
<button class="btn btn-success" ng-click="saveData()" type="button"><span class="glyphicon glyphicon-save"></span>KAYDET</button>
    </div>
<div class="arrayContainerAngularExample">
CurrentArray = {{pages}}
    </div>
</div>

Sunucu tarafında "postTemplate" ile gelen verinin bağlanacağı yapı tanımlanmaktadır. Son olarak "SaveData" fonksiyonu ile aşağıdaki gibi AJAX ile gönderilen dizi işlenebilmektedir.

public struct postTemplate
{
     public string val { get; set; }
}

[HttpPost]
public ActionResult SaveData(List<postTemplate> Datas)
{
    return Json("OK");
}

Yukarıdaki kodların çalıştırılması sonucunda aşağıdaki sonuç elde edilmektedir:

CurrentArray = {{pages}}

Friday, March 25, 2016

C# ile Zip Formatında Dosya Sıkıştırma - Zip Formatted File Compression with C#

Bu yazıda .NET framework 4.5 ve üzeri için nasıl zip sıkıştırması yapılacağı anlatılacaktır. Bu yazı kapsamında zip dosyasının içinde yer alan sıkıştırılacak dosya ve zip dosyası hafızada oluşturulacaktır. Elde edilen byte dizisi "File" sınıfı kullanılarak dosya sistemine yazılacaktır. Örnek olarak bir text dosyası oluşturulacak ve bu dosya ziplenecektir.

Zip dosyasının oluşturulması için "ZipArchive" sınıfı kullanılacaktır. Bu sınıfın kullanılabilmesi için  "System.IO.Compression" ve "System.IO.Compression.FileSystem" dll dosyalarına referans verilmelidir. Ayrıca "System.IO.Compression" dll dosyasının versiyonunun eski olmamasına dikkat edilmelidir. Referanslar eklendikten sonra aşağıdaki tanımlama kod sayfasına eklenmelidir.

using System.IO.Compression;

Text dosyasının oluşturulması amacıyla "StringBuilder" sınıfını kullanarak aşağıdaki gibi yazı içeriği oluşturalım:

StringBuilder builder = new StringBuilder();  
builder.AppendLine("Veli");
builder.AppendLine("Yigit");
builder.AppendLine("Yolcu");

Aşağıda yer alan ZipThat fonksiyonuyla zip dosyasının byte dizisi elde edilmektedir. Fonksiyon kullanılarak bir zip arşivi oluşturulmakta ve daha önce hazırladığımız yazı içeriği "ZipArchiveEntry" olarak zip arşivine eklenmektedir. Bu fonksiyon zip verisini "MemoryStream" içinde biriktirmekte ve işlem tamamlandığında sıkıştırılmış içeriği byte dizisi olarak döndürmektedir.

byte[] ZipThat(string FileName, byte[] TxtData)
{
    using (MemoryStream mst = new MemoryStream())
    {
        using (ZipArchive arc = new ZipArchive(mst, ZipArchiveMode.Create))
        {
            var zipEntry = arc.CreateEntry(FileName);

            using (Stream st = zipEntry.Open())
            {
                st.Write(TxtData, 0, TxtData.Length);
            }
        }

        return mst.ToArray();
    }
}

"Encoding" sınıfı kullanılarak StringBuilder" içinde yer alan yazının byte dizisi elde edilebilmektedir:

byte[] txtData = Encoding.Default.GetBytes(builder.ToString());

Son olarak oluşturulan zip verisi "File" sınıfı kullanılarak dosya sistemine yazılmaktadır.

File.WriteAllBytes(string.Format(@"C:\Deneme\{0}.zip", "ZipFile"), ZipThat("FileThatInZipArchive.txt"));

Wednesday, March 23, 2016

Jquery ile Yukarı Çık Uygulaması (Uçan Kartal) - Go Up Component with Jquery (Flying Eagle)

Bu yazıda bloğa yeni eklediğim yukarı çık uygulamasının nasıl oluşturulduğu üzerinde duracağım. Bu uygulama ile kullanıcı sayfanın aşağı taraflarına doğru indiğinde sağ alt köşede görünen bileşen yardımıyla en üstüne çıkabilmektedir. Bu yazı aynı zamanda jquery ile "scroll" işlemlerinin nasıl yapıldığı anlatılmaktadır.

Öncelikle CSS kodlarını tanımlayalım:

<style>

        .FlyingEagle {

            position:fixed;
            background-image:url('Images/eagle5.png');
            width:150px;
            height:150px;
            background-size:150px 150px;
            display:none;
            top:100%;
            left:100%;
            margin-top:-180px;
            margin-left:-180px;
            opacity:0.7;
        }

</style>

Yukarıdaki CSS kodlarında önemli noktalardan ilki "position:fixed" tanımlamasıdır. Bu tanımlama ile sınıfın üyesi olan elementin tarayıcı penceresine (window) göre pozisyon alması sağlanmaktadır. Yine "margin" değerleri kullanılarak elementin pozisyondan istediğimiz bir miktarda sapması (disposition) sağlanmaktadır. Elementin saydamlığı "opacity" niteliği kullanılarak azaltılmaktadır. Görüntünün taşıcıyı içine düzgün bir şekilde yerleştirilmesi amacıyla "background-size" niteliği kullanılmıştır. Bu nitelik ile görüntü elementin içine esnetilerek veya daraltılarak yerleştirilmektedir.

Sonraki aşamada javascript kodlarını tanımlayalım:

$(document).ready(function () {

            $(".FlyingEagle").click(function () {

                $("html, body").animate({ scrollTop: 0 }, 500);
            });

            $(window).scroll(function () {

                if ($(window).scrollTop() > 50) {

                    $(".FlyingEagle").show("fast");
                } else {

                    $(".FlyingEagle").hide("fast");
                }
            });
        });

Jquery "scroll" olayı yakalanarak kartal animasyonunun yönetimi yapılmaktadır. Jquery "show" fonksiyonu ile kartal gösterilmekte, "hide" fonksiyonu ile de saklanmaktadır. Jquery scrollTop fonksiyonu ekranın üstüne kaç piksellik kısımın kaydığını döndürmektedir. Bu yüzden sıfır değeri hiç kayma olmadığını göstermektedir. Bu kapsamda sayfa en fazla $(document).height() - $(window).height() kadar kaydırılabilir. Jquery "animation" fonksiyonu ile yukarı yumuşak bir biçimde çıkılması sağlanmaktadır. Son olarak da CSS sınıfı "FlyingEagle" olan bir "div" elementi sayfaya eklenir.

Sunday, March 20, 2016

Angularjs ve ASP.NET MVC ile Döviz Kurları Uygulaması - Exchange Rates Application With Angularjs and ASP.NET MVC

Bu yazıda TCMB (Türkiye Cumhuriyeti Merkez Bankası) tarafından yayınlanan ve kur bilgilerini içeren XML dosyasını kullanarak döviz kurlarını gösteren bir uygulama geliştireceğiz. Bu uygulama içinde gerekli TCMB verileri sunucu üzerinde çekilip AJAX ile tarayıcıya gönderilecektir. Neden direk tarayıcı üzerinden verileri çekmiyoruz diyen arkadaşlar varsa eğer öncelikle "same-origin policy" adı verilen kuralı incelemeleri gerekir. Bu kurala göre bir tarayıcı üzerinden değişik bir kaynaktaki (domaindeki) veriye ulaşım güvenlik endişesiyle engellenmektedir. Bu durum sadece AJAX için değil "iframe" için de geçerlidir.

Öncelikle TCMB kaynağından veriyi alacak sunucu kodunu tanımlayalım:

[HttpGet]
public ActionResult GetData()
{
     XDocument doc = XDocument.Load("http://www.tcmb.gov.tr/kurlar/today.xml");
     var dox = doc.Descendants()
         .Where(r => r.Name == "Currency")
         .Select(r => new {
             Isim = r.Element("Isim").Value,
             Kod = r.Attribute("Kod").Value,
             AlisKur = r.Element("BanknoteBuying").Value,
             SatisKur = r.Element("BanknoteSelling").Value
          });

      return Json(dox, JsonRequestBehavior.AllowGet);
}

Kod yukarıdan aşağıya incelendiğinde öncelikle dikkat çeken nokta [HttpGet] tanımlamasıdır. Bu tanımlama ile "GetData" fonksiyonuna ulaşılırken "Get" isteği kullanılması gerektiği belirtilmektedir. Yani fonksiyon "Post" veya "Delete" isteklerine cevap vermeyecektir.

Bir diğer önemli nokta ise "XDocument" sınıfıdır. Bu sınıf, XML verisini üzerinde LINQ sorgularının çalıştırılmasına olanak sağlayacak bir biçimde kullanmaktadır. Bilindiği üzere aynı işlemi yapan bir diğer sınıf da "XmlDocument" sınıfıdır. Bu sınıf XML verisini eski usül XML DOM olarak kullanmakta ve veri içindeki aramaları "XPath" ile yapmaktadır. LINQ arayüzünün daha basit olması sebebiyle bu uygulamada "XDocument" sınıfını kullandım.

Son aşamada "Json" fonksiyonu kullanılarak bir önceki LINQ işleminden dönen ön tanımsız nesneler JSON formatına dönüştürülüp istemciye gönderilmektedir. "JsonRequestBehavior.AllowGet" kullanılarak fonksiyonun "GET" isteklerine müsaade etmesi sağlanmıştır. Aksi durumda dönüşümü yapmayacaktır.

Sonraki aşamada AngularJs kodlarını tanımlayalım:

var currencyapp = angular.module('currencyapp', []);

currencyapp.controller('currencyListController', ['$scope', '$http',

  function ($scope, $http) {

      $http.get('./Home/GetData').success(function (data) {
          $scope.kurs = data;
      });

  }]);

Yukarıdaki kodlarda öncelikle "root scope" yani genel tanımlama amacıyla "currencyapp" modülü tanımlanmaktadır. Bu modülün altında "currencyListController" adında "$scope" ve "$http" servislerine bağımlı bir kontrolcü tanımlanmaktadır. Bu nesne aracılığıyla AJAX sorgusu gerçekleştirilmekte ve döviz verisi kapsam (scope) içine alınmaktadır. AJAX işlemi için "$http" servisinin "get()" fonksiyonu kullanılmaktadır.

AngularJs ile veri modeli oluşturduktan sonra son olarak bunu görüntü (View) ile ilişkilendirelim:

<div ng-app="currencyapp">
    <table ng-controller="currencyListController" class="table table-striped table-condensed">

        <thead>
            <tr>
                <th>Döviz Adı</th>
                <th>Döviz Kodu</th>
                <th>Alış</th>
                <th>Satış</th>
            </tr>
        </thead>
        <tbody>
            <tr ng-repeat="kur in kurs">
                <td>{{kur.Isim}}</td>
                <td>{{kur.Kod}}</td>
                <td>{{kur.AlisKur}}</td>
                <td>{{kur.SatisKur}}</td>
            </tr>
    </table>
</div>

Burada "ng-app" niteliği ile ilgili DOM nesnesinin altında yer alan ögelerin "currencyapp" uygulamasına dahil olduğu belirtilmektedir. "Table" nesnesi "ng-controller" niteliği ile "currencyListController" kontrolcüsü ile ilişkilendirilmektedir. Bu kontrolcünün kapsamı dahilinde yer alan "kurs" listesinin içindeki "kur" JSON nesneleri kullanılarak tablo oluşturulmaktadır. "{{kur.Isim}}" tanımlaması ile kur nesnesinin "Isim" değeri ilgili tablo kısmına yazılmaktadır. "tr" etiketinde "ng-repeat" niteliği kullanılarak ilgili listedeki tüm nesneler için tablo satırlarının oluşturulması sağlanmaktadır. Yani listede yer alan her bir nesne için bir tablo satırı oluşturulmaktadır.

Uygulama sonunda aşağıda gösterilen tablo elde edilmektedir:




Wednesday, March 16, 2016

Jquery AJAX ile Dinamik Görüntü Yükleme - Dynamically Loading Images with Jquery AJAX

Bu yazıda AJAX kullanılarak görüntülerin tarayıcıya dinamik olarak yüklenmesi sağlanacaktır. Bilindiği üzere HTTP protokolü yazı (text) temelli çalışan bir protokoldür. Bu sebeple AJAX ile bilgi alışverişi yazı (text) olarak yapılmaktadır. Bir görüntünün sunucudan tarayıcıya HTTP protokolüyle yüklenmesi için görüntünün yazıya dönüştürülmesi gerekir. Bu amaçla daha önce anlatılan Base64 kodlama kullanılacaktır. Öncelikle AJAX işlemini gerçekleştirecek javascript kodlarını tanımlayalım:

      
$.ajax({
          type: "POST",
          url: "./WebForm3.aspx/GetImage",
          data: '{"FotoId": "10" }',
          contentType: "application/json; charset=utf-8",
          dataType: "json",
          success: function (msg) {
                    
                 var imageSrc = "data:image/jpg;base64," + msg.d;
                 $("#Base64Image").attr("src", imageSrc);
             },
             error: function (msg) {

                 alert("Hata Oluştu.");
             }
       });

Burada önemli nokta HTML img nesnesinin src niteliğine elde ettiğimiz değerin nasıl ekleneceğidir. Aşağıdaki gibi resmin Base64 biçimi src niteliğine atanarak tarayıcının otomatik olarak görüntüyü çözümleyip göstermesi sağlanmaktadır.

data:image/jpg;base64,...

Burada src içinde kodlu verinin jpg görüntüsü olduğu ve Base64 olarak kodlandığı belirtilmektedir. Son olarak görüntüyü kodlayıp tarayıcıya gönderen sunucu taraflı kodu yazalım:

        
[WebMethod]
public static string GetImage(string FotoId)
{
    byte[] arr = File.ReadAllBytes("IMAGE_PATH");

    string base64 = Convert.ToBase64String(arr);

    return base64;
}

Burada "Convert" nesnesinin "ToBase64String" fonksiyonu kullanılarak byte dizisi biçimindeki görüntü Base64 olarak kodlanıp tarayıcıya gönderilmiştir.

Tuesday, March 15, 2016

Base64 (Taban 64) Kodlama - Base64 Encoding

Base64 kodlamanın RFC 4648'de kullanım amaçları anlatılmaktadır. Burada bahsedildiği gibi Base64 kodlamanın asıl kullanım amacı byte dizisi şeklindeki verinin yazı (text) biçiminde veri iletim ortamında değiştirilmeden iletilmesi veya ilgili protokollerle uyumlu bir şekilde iletilmesidir. Ayrıca iletim ortamında veri iletiminin ortak bir standarta dayalı yapılmasıyla verinin üzerinden geçtiği cihazlar tarafından aynı şekilde yorumlanması sağlanmaktadır. Base64 kodlamada byte dizisi ASCII kodlarıyla uyumlu 64 tabanında yazıya dönüştürülmektedir. Genellikle yazı tabanlı HTTP, SMTP veya XML gibi yapılarda kullanılır. Base64 kodlama yapılırken sayıların karakter karşılıklarıyla eşleştirilmesi için aşağıdaki tablo (Karakter Set - Character Set) kullanılır:

    Value Encoding  Value Encoding  Value Encoding  Value Encoding
         0 A            17 R            34 i            51 z
         1 B            18 S            35 j            52 0
         2 C            19 T            36 k            53 1
         3 D            20 U            37 l            54 2
         4 E            21 V            38 m            55 3
         5 F            22 W            39 n            56 4
         6 G            23 X            40 o            57 5
         7 H            24 Y            41 p            58 6
         8 I            25 Z            42 q            59 7
         9 J            26 a            43 r            60 8
        10 K            27 b            44 s            61 9
        11 L            28 c            45 t            62 +
        12 M            29 d            46 u            63 /
        13 N            30 e            47 v
        14 O            31 f            48 w         (pad) =
        15 P            32 g            49 x
        16 Q            33 h            50 y

Kodlama yapılırken wikipedia'da gösterildiği gibi byte dizisi 6'lılar şeklinde ayrılarak tablodaki karşılıkları bulunur. 24 bit bloklar kullanılarak kodlama yapılır. 24 bitlik blok içinde karşılığı olmayan kısımlar "pad" karakteri (=) ile gösterilir:

Text contentM
ASCII77 (0x4d)0 (0x00)0 (0x00)
Bit pattern010011010000000000000000
Index191600
Base64-encodedTQ==

Yukarıdaki örnekte ASCII kodlanmış yazının dönüştürülmesi gösterilmesine rağmen ilgili işlem diğer kodlamalar içinde aynıdır (UTF-8, UTF-16 vb.). Fakat herhangi bir yazı bu şekilde gönderilecek ise yazının nasıl kodlandığı da ayrıca verinin yanında gönderilmelidir. Base64 kodlamanın yanında 40 bitlik bloklar ile kodlama yapan Base32 kodlama insan tarafından daha okunabilir bir çıktı üretmesine rağmen Base64 kodlama daha yaygın olarak kullanılmaktadır.

Javascript'te btoa(str) fonksiyonu kullanılarak Base64 kodlama ile kodlanmış yazı elde edilirken atop(enc) ile tekrar orjinal veri elde edilebilmekedir (ie > 9).

function Base64ExampleFunction() {

            var str = "M";
            var enc = window.btoa(str);
            var dec = window.atob(enc);

            var res = "Encoded String: " + enc + "
" + "Decoded String: " + dec;
            document.getElementById("base64Div").innerHTML = res;
        }

Yukarıdaki örnek çalıştırıldığında aşağıdaki sonuç elde edilmektedir:


Thursday, March 10, 2016

Jquery ile Yılan Oyununun Gerçeklenmesi - Implementing Snake Game with Jquery

Bu yazıda telefonlarda eskiden sıklıkla oynanan yılan oyununu javascript ve jquery ile oluşturacağız. Yılan oyununun ana fikri yılanı oluşturan parçalarının tren katarları gibi birbirini izlemesidir. Yem alındıkça puan güncellenmeli ve yılan büyümelidir. Yılan kendine veya duvarlara çarptığında oyun bitecektir. İlk olarak CSS kodlarımızı oluşturalım:

<style>

    .SGContainer
    {
        width:204px;
        height:auto; 
    }

    .SGCanvas
    {
        background-color:lightblue;
        width:204px;
        height:204px;
        position:relative;
       
    }

    .SGCanvas:focus
    {
        border:solid;
        border-width:2px;
        border-color:darkgreen;
    }

    .SGSnakePart
    {
        background-color:black;
        position:absolute;
        width:5px;
        height:5px;
    }

    .SGSnakeFood
    {
        background-color:darkred;
        position:absolute;
        width:5px;
        height:5px;
    }

    .SGFinish
    {
        font-weight:bold;
        background-color:brown;
        color:white;
        z-index:1;
        position:absolute;
        top:50%;
        left:50%;
        margin-top:-25px;
        margin-left:-50px;
        width:100px;
        height:50px;
        justify-content: center;
        align-items: center;   
        display:flex;   
    }
</style>

Yukarıdaki CSS kodlarında önemli nokta flex kutusu (flex box) kullanımıdır. Flex kutusu klasik kutuya göre daha esnek özellikler sunmaktadır. Burada "align-items" ile flex kutusunun içeriği dikey olarak, "justify-content" ile de yatay olarak konumlandırılmıştır. Sonraki aşamada javascript kodlarını tanımlayalım:

var Snake = [];
var Food = [];

var Direction = "Right";
var MoveNumber = 5
var Canvas = null;
var Interval = null;
var IsGameStarted = false;
var Score = 0;

var SnakePart = function () {

    this.X = -1;
    this.Y = -1;
    this.DomObject = null;
}

function StartGame() {

    IsGameStarted = false;
    Canvas = $(".SGCanvas");
    $(Canvas).children().remove();
    Snake = [];
    Food = [];
    Direction = "Right";
    Score = 0;

    StopAnimation();

    for (var i = 10; i > -1; i--) {

        var part = new SnakePart();
        part.X = i * 5;
        part.Y = 100;

        var domPart = $('<div class="SGSnakePart"></div>');
        part.DomObject = domPart;

        part.DomObject.css({ top: part.Y, left: part.X });

        Snake.push(part);

        AddPartToCanvas(part.DomObject)
    }

    $(Canvas).focus();

    $(Canvas).keydown(function (event) {

        event.preventDefault();
        SGUpdateDirection(event.keyCode);
    });

    $(Canvas).focusout(function () {

        if (IsGameStarted)
            StopAnimation();
    });

    $(Canvas).focusin(function () {

        if (IsGameStarted)
            StartAnimation();
    });
    
    GenerateFood();

    StartAnimation();
    IsGameStarted = true;
}

function StartAnimation() {

    if (Interval == null)
        Interval = setInterval(SGUpdate, 50);
}

function StopAnimation() {

    if (Interval != null)
        clearInterval(Interval);
    Interval = null;
}

function AddPartToCanvas(part)
{
    $(Canvas).append(part);
}

function GenerateFood() {

    var x = parseInt((Math.random() * 100) % 40) * 5;
    var y = parseInt((Math.random() * 100) % 40) * 5;

    var part = new SnakePart();
    part.X = x;
    part.Y = y;

    var domPart = $('<div class="SGSnakeFood"></div>');
    part.DomObject = domPart;
    part.DomObject.css({ top: part.Y, left: part.X });

    Food.push(part);

    AddPartToCanvas(part.DomObject);
}

function EatFood() {

    if(Snake[0].Y == Food[0].Y && Snake[0].X == Food[0].X)
    {
        Score++;
        Snake.push(Food.pop());
        GenerateFood();
    } 
}

function SGUpdateDirection(key) {
  
    if (key == 39 && Direction != "Left") {

        Direction = "Right";
    }

    if (key == 37 && Direction != "Right") {

        Direction = "Left";
    }

    if (key == 38 && Direction != "Down") {

        Direction = "Up";
    }

    if (key == 40 && Direction != "Up") {

        Direction = "Down";
    }
}

function SGUpdate() {


    for (var i = Snake.length - 1; i > 0; i--) {

        Snake[i].X = Snake[i - 1].X;
        Snake[i].Y = Snake[i - 1].Y;
    }

    if (Direction == "Right") {

        Snake[0].X += MoveNumber;
    }

    if (Direction == "Left") {

        Snake[0].X -= MoveNumber;
    }

    if (Direction == "Up") {

        Snake[0].Y -= MoveNumber;
    }

    if (Direction == "Down") {

        Snake[0].Y += MoveNumber;
    }

    if (SGIsGameOver())
    {
        SGGameOver();
        return;
    }

    EatFood();
    UpdateScore();
    SGUpdateCanvas();
}

function SGUpdateCanvas()
{
    for (var i = Snake.length - 1; i > -1; i--) {

        $(Snake[i].DomObject).css({ top: Snake[i].Y, left: Snake[i].X });
    }
}

function SGIsGameOver() {

    for (var i = Snake.length - 1; i > 0; i--) {

        if (Snake[0].X == Snake[i].X && Snake[0].Y == Snake[i].Y)
            return true;
        if (Snake[0].X == 200 || Snake[0].X < 0 || Snake[0].Y == 200 || Snake[0].Y < 0)
            return true;
    }

    return false;
}

function UpdateScore() {

    $("#SGPuan").text(Score);
}

function SGGameOver() {

    IsGameStarted = false;
    StopAnimation();
    $(Canvas).append($('<div class="SGFinish">OYUN BİTTİ!</div>'));
}

Yukarda yer alan fonksiyonların açıklamaları şöyledir:

* StartGame(): Oyunun sıfırlanmasını ve başlamasını sağlayan fonksiyondur. Burada başlangıç olarak 10 parça yılan oluşturulmakta, canvasla ilgili klavye olaylarına jquery ile fonksiyon atanmakta ve ilgili fonksiyonlar aracılığıyla animasyon başlatılmaktadır. Jquery keydown olayı bir klavye tuşuna basıldığında tetiklenmektedir. Bu fonksiyon içinde kullanılan "event.preventDefault()" ile ok tuşlarının ekranı oynatması engellenmekte yani tuşun ön tanımlı özelliği pasivize edilmektedir. Jquery focus() fonksiyonu ile herhangi bir elemente focus (odaklanma) yapılması sağlanmaktadır. Bu fonksiyonun düzgün çalışması için odaklanılacak elementin "tabindex" niteliği -1 olmalıdır. Jquery focusin() odaklanma olduğunda, focusout() ise odaklanma kaybedildiğinde tetiklenmektedir. İlgili olaylar tetiklendiğinde animasyon durdurulmakta veya devam ettirilmektedir.

* StartAnimation(): Oyun animasyonunun başlatılmasını sağlayan fonksiyondur. Javascript setInterval(çağrılacakfonksiyon, süre) fonksiyonu ile animasyon oynatılmaktadır. Bu fonksiyon ile parametre olarak gönderilen çağrılacak fonksiyon her süre dolduğunda çalıştırılmaktadır. Bu fonksiyon setTimeout() fonksiyondan farklı olarak devamlı olarak çağrıma devam etmektedir. Javascript setTimeout() fonksiyonu süre dolduğunda çağrılacak fonksiyonu çalıştırır ve bir daha çalıştırmaz. Süre parametresi millisecond (ms) olarak ifade edilmektedir. 1000 ms = 1 sec olarak kullanılır.

* StopAnimation(): Javascript clearInterval(interval) fonksiyonu ile animasyonun durdurulması sağlanmaktadır. Parametre olarak setInterval() fonksiyonu tarafından döndürülen değer kullanılmaktadır.

* AddPartToCanvas(): Jquery ile oluşturulan DOM nesnelerinin DOM'a eklenmesini sağlamaktadır.

* GenerateFood(): Rastgele bir pozisyona yem yerleştirilmesini sağlamaktadır. Bunu sağlarken Math.Random() fonskiyonunu kullanmaktadır. Bu fonksiyon 0 ile 1 arasında [0,1) bir reel sayı üretmektedir. Koordinatlar üretilirken kanvas uzunluğumuzun 200px ve aralıkların 5 px olduğu göz önünde bulundurularak elde edilen sayı 100 ile çarpılmakta ve 2 basamaklı rastgele bir sayı elde edilmektedir. Bu sayının 40'a göre modu alınarak rastgele 40'tan küçük bir sayı elde edilmiş olmaktadır. Elde edilen son değer 5 ile çarpılarak koordinat hesaplanmış olmaktadır. Yukarıdaki matematiksel işlemler incelendiğinde elde edilen rastgele sayının [0,200) aralığında olduğu görülebilmektedir.

* EatFood(): Bu fonksiyon ile yılanın baş parçasının konumunun yem konumuyla aynı olup olmadığı kontrol edilmekte ve aynı ise puan artırılmakta, yeni bir yem oluşturulmakta ve yılan büyütülmektedir.

* SGUpdateDirection(key): Okunan klavye tuşunun koduna göre yılanın yönünü belirleyen fonksiyondur.

* SGUpdate(): Diğer bütün fonksiyonların yönetimini sağlayan ve yılanın hareketinin mantıksal hesaplamasını yapan ana animasyon fonksiyonudur.

* SGUpdateCanvas(): SGUpdate() fonksiyonunda belirlenen oyunun yeni durumuna göre kanvası güncelleyen fonksiyondur.

* SGIsGameOver(): Yılanın kanvas dışına çıkıp çıkmadığına veya zaten işgal ettiği bir koordinata yeniden girmesine veya girmemesine göre oyunun bitip bitmediğinin anlaşılmasını sağlayan fonksiyondur.

* UpdateScore(): Kanvasın sağ üstünde bulunan mavi puan sayısını güncelleyen fonksiyondur.

* SGGameOver(): Oyun bittiğinde gerekli işlemlerin yapıldığı fonksiyondur. Oyun bittiğinde animasyon durdurulmakta ve "OYUN BİTTİ!" yazısını taşıyan flex kutusu kanvasa eklenmektedir.

Son aşama olarak HTML kodlarını tanımlayalım:

<div class="SGContainer">
    <div style="margin-bottom:10px">
        <input id="Button1" class="btn btn-success" type="button" value="BAŞLA" onclick="StartGame()"/>
        <span id="SGPuan" class="label label-info" style="float:right; font-size:large">0</span>
    </div>
    <div class="SGCanvas" tabindex="-1">

    </div>
</div>

Yukarıda yer alan kodların çalıştırılması ile aşağıdaki sonuç elde edilmektedir:
(Oyun ok tuşlarıyla oynanmaktadır)


0

Saturday, March 5, 2016

Jquery ile Bulmaca Oyunu - Jquery Puzzle Game

Bu yazı daha önceki yazıların devamı olarak görülmelidir, bu sebeple bu yazıda öncekilerde anlatılan konular anlatılmayacaktır. Bu yazıda karışık olarak yer alan resim parçalarının doğru bir şekilde bir araya getirilerek çözülecek bir bulmaca oyunu tasarladık. Burada önceki yazılarda yer alan script ve css kodlarına (Jquery, Bootstrap) ek olarak sayfaya JqueryUI eklenmesi gereklidir. Bu amaçla JqueryUI CDN kodunu sayfaya ekleyelim:

<script src="https://code.jquery.com/ui/1.11.4/jquery-ui.min.js" type="text/javascript"></script>

JqueryUI sayfada kullanıcı etkileşimlerini veya animasyon efektlerini yönetmek amacıyla kullanılabilecek fonksiyonları içeren bir kütüphanedir. Bulmaca uygulamasında resimler üzerinde sürükle-bırak (drag-drop) yapılmasını sağlamak amacıyla kullanılmıştır.

Uygulamayı oluştururken ilk olarak CSS kodlarını tanımlayalım:

<style>
        .PuzzleContainer
        {
            width: 545px;
            height: 545px;
        }
        
        .PuzzleBoard
        {
            width: 545px;
            height: 545px;
        }
        
        .PuzzleBox
        {
            width: 50px;
            height: 50px;
            float: left;
            margin: 2px;
            border: solid;
            border-width: 1px;
        }
        
        .PuzzlePartContainerDiv
        {
            width: 50px;
            height: 50px;
            background: url(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijeyFxpSRzHq2d4NyPPy8bA9ybJECYVC1wmPTxj3yI5ih7711-T5Lfmqf44OT_4vx6TQVVko_cOMxGZoWW6CaZOpdRVr6LeMA88Go6S2nvsE8zktavDbEe9c2TBT6pwDvJd0dNUcvzKlo/s1600/puzzle1.jpg);
            background-size: 500px 500px;
            float: left;
            margin: 2px;
        }
        
        .PuzzlePartContainerDiv:hover
        {
            padding: 2px;
            background-clip: content-box;
        }
        
        .PuzzlePartContainerDiv:active
        {
            border: solid;
            border-width: 2px;
            border-color: red;
        }
        
        .PuzzleBoxHover
        {
            border: solid;
            border-color: red;
        }
        
        .PuzzleBoxActive
        {
            background-color: lightblue;
        }
        
        .PuzzleContainerActive
        {
            background-color: lightgray;
        }
</style>

Yukarıdaki CSS kodlarında "PuzzleContainer" karışık halde resim parçalarını içeren kanvasın sınıfını, "PuzzleBoard" resimlerin yerleştirileceği kanvasın sınıfını, "PuzzleBox" resimlerin yerleştirileceği kanvasın içinde yer alan resimlerin içine oturacağı kare alanların sınıfını, "PuzzlePartContainerDiv" "PuzzleBox" içine yerleştirilecek "PuzzleContainer" içinde yer alan resim parçalarının sınıfını, "PuzzlePartContainerDiv:hover" "PuzzleContainer" içinde yer alan resim parçalarının üzerine fareyle gelindiğinde uygulanacak CSS niteliklerini içeren sınıfı, "PuzzlePartContainerDiv:active" "PuzzleContainer"içinde yer alan resim parçalarının üzerine tıklandığında uygulanacak CSS niteliklerini içeren sınıfı, "PuzzleBoxHover" resim parçalarının "PuzzleBox" üstüne getirildiğinde uygulanacak nitelikleri içeren sınıfı, "PuzzleBoxActive" sürükleme esnasında "PuzzleBox" sınıfının üyelerine uygulanacak CSS niteliklerini içeren sınıfı ve son olarak "PuzzleContainerActive" sürükleme esnasında resim parçalarını içeren kanvasa uygulanacak nitelikleri içeren sınıfı ifade etmektedir.

Yukarıdaki önemli noktalardan ilki "background-size: 500px 500px;" belirtimidir. Bu nitelik ile arka plan olarak ayarlanan görüntünün arka planda 500px-500px olarak yeniden boyutlanması sağlanmaktadır. Bir diğer önemli nokta ise "background-clip: content-box;" niteliğidir. Bu nitelik ile arka plan resminin içerik kutusuna yani padding ile kısıtlanmış alana çizilmesi sağlanmaktadır. Yukarıda yer almayan fakat bootstrap ile eklenen ve bu uygulama için kritik olan bir diğer nitelik ise "box-sizing: border-box" belirtimidir. Bu nitelik "border-box" yapılarak margin dışındaki diğer belirtimlerin yani padding, border ve content değerlerinin, belirtilen width (genişlik) ve height (yükseklik) değerlerinin içinde olduğu belirtilmektedir. Yani bir ögeye border kalınlığı 2 olan bir border tanımlasak bile genişlik ve yükseklik değeri daha önce belirttiğimiz değer üzerinde sabit kalacaktır.

Bir sonraki aşama olarak javascript kodlarını tanımlayalım:

<script>

        var PuzzleArray = [];
        var PuzzleTmpArray = [];

        $(document).ready(function () {

            CreateMyWorld(true);
        });

        var PuzzleWrapper = function () {

            this.ActualX = -1,
            this.ActualY = -1,
            this.CurrentX = -1,
            this.CurrentY = -1
        }

        function CreateMyWorld(Shuffle) {

            $(".PuzzleContainer").children().remove();
            $(".PuzzleBoard").children().remove();

            PuzzleArray = CreateArray(10, 10);

            SliceImageAndCreatePuzzle();
            ShuffleImages(Shuffle);
            ConfigurePuzzle();
        }

        function CreateButtonClick() {

            var isShuffle = $("#CBShuffle").is(":Checked");
            CreateMyWorld(isShuffle);
        }

        function ConfigurePuzzle() {

            $(".PuzzlePartContainerDiv").draggable({ snap: ".PuzzleBox", snapMode: "inner", helper: "clone" });

            $(".PuzzleContainer").droppable({

                activeClass: "PuzzleContainerActive",
                drop: function (event, ui) {

                    var parentBox = $(ui.draggable).parent();

                    if ($(parentBox).is(".PuzzleBox")) {

                        var x = parseInt($(parentBox).attr("pX"));
                        var y = parseInt($(parentBox).attr("pY"));

                        PuzzleArray[x][y].CurrentX = -1;
                        PuzzleArray[x][y].CurrentY = -1;
                    }

                    $(parentBox).css("border", "solid");
                    $(parentBox).css("border-width", "1px");
                    $(ui.draggable).css("margin", "2px");
                    $(ui.draggable).appendTo(event.target);
                }
            });

            $(".PuzzleBox").droppable({

                activeClass: "PuzzleBoxActive",
                hoverClass: "PuzzleBoxHover",
                drop: function (event, ui) {

                    var x2 = parseInt($(event.target).attr("pX"));
                    var y2 = parseInt($(event.target).attr("pY"));

                    var container = $(ui.draggable).parent();

                    if ($(event.target).children().length > 0)
                        return false;

                    if ($(container).is(".PuzzleBox")) {

                        $(container).css("border", "solid");
                        $(container).css("border-width", "1px");
                    }

                    $(ui.draggable).css("margin", "0px");
                    $(event.target).css("border", "none");
                    $(ui.draggable).appendTo(event.target);

                    var x = parseInt($(ui.draggable).attr("pX"));
                    var y = parseInt($(ui.draggable).attr("pY"));

                    PuzzleArray[x2][y2].CurrentX = x;
                    PuzzleArray[x2][y2].CurrentY = y;

                    if (IsGameFinished()) {

                        $(".PuzzleBox").animate({ duration: "slow", margin: 0 });
                    }
                }
            });

        }

        function IsGameFinished() {

            for (var y = 0; y < 10; y++) {

                for (var x = 0; x < 10; x++) {

                    if (PuzzleArray[x][y].ActualX != PuzzleArray[x][y].CurrentX || PuzzleArray[x][y].ActualY != PuzzleArray[x][y].CurrentY)
                        return false;
                }
            }

            return true;
        }

        function SliceImageAndCreatePuzzle() {

            var ContainerDiv = $(".PuzzleContainer");
            var puzzleBoard = $(".PuzzleBoard");

            for (var y = 0; y < 10; y++) {

                for (var x = 0; x < 10; x++) {

                    var item = $('<div class="PuzzlePartContainerDiv" pX=' + x + ' pY=' + y + ' style="background-position:-' + x * 50 + 'px -' + y * 50 + 'px"></div>');
                    var itemBox = $('<div class="PuzzleBox" pX=' + x + ' pY=' + y + '></div>');

                    PuzzleArray[x][y].ActualX = x;
                    PuzzleArray[x][y].ActualY = y;

                    PuzzleTmpArray.push(item);
                    puzzleBoard.append(itemBox);
                }
            }
        }

        function ShuffleImages(Shuffle) {

            var containerDiv = $(".PuzzleContainer");

            if (Shuffle) {

                while (PuzzleTmpArray.length > 0) {

                    var rnd = Math.random();

                    var a = PuzzleTmpArray.shift();

                    if (rnd < 0.3) {
                        $(containerDiv).append(a);
                    } else
                        PuzzleTmpArray.push(a);
                }
            } else {

                while (PuzzleTmpArray.length > 0) {

                    var a = PuzzleTmpArray.shift();

                    $(containerDiv).append(a);
                }
            }
        }

        function CreateArray(width, height) {

            var arr = [];
            for (var x = 0; x < width; x++) {
                arr[x] = [];
                for (var y = 0; y < height; y++) {
                    arr[x][y] = new PuzzleWrapper();
                }
            }

            return arr;
        }

</script>


Yukarıdaki javascript kodları içinde yer alan fonksiyonların açıklamaları şöyledir:

* CreateMyWorld(Shuffle): İçine görüntülerin karıştırılıp karıştırmayacağını belirleyen bir boolean parametre alarak oyunu kuran ana fonksiyondur.

* CreateButtonClick(): Oyun üzerinde yer alan ve Shuffle paremetresini yanındaki CheckBox'dan alan yeşil "Oyunu Oluştur" tuşuna basıldığında çalışan fonksiyondur.

* ConfigurePuzzle(): Resim parçalarına sürüklenebilirlik (draggable) özelliğini kazandırıldığı ve resim parçalarını içeren ve bu parçaların yerleştirildiği alanlara düşürülebilirlik (droppable) özelliğinin kazandırıldığı fonksiyondur.

* IsGameFinished(): Oyunun tamamlanıp tamamlanmadığının sonucunu döndüren fonksiyondur.

* SliceImageAndCreatePuzzle(): Resimlerin parçalandığı, ve resimlerin yerleştirileceği kutuların oluşturulduğu fonksiyondur. Bu fonksiyon sonlandığında kutular DOM'a yerleştirilmiş olurken resim parçaları bir dizi içinde saklanmış olmaktadır.

* ShuffleImages(Shuffle): Resim parçalarının karıştırılıp karıştırılmayacağını belirleyen parametreye göre bu parçalara gerekli işlemleri yapıp DOM'a yerleştiren fonksiyondur.

Son olarak da oyun için gerekli HTML kodlarını ekleyelim:

<div>
<div>
<div class="checkbox-inline">
<label>
<input checked="checked" id="CBShuffle" type="checkbox" />Görüntüleri Karıştır</label>
</div>
<button class="btn btn-success" onclick="CreateButtonClick()" type="button">
OYUNU OLUŞTUR</button>
</div>
<br />
<div class="PuzzleBoard">
</div>
<div class="PuzzleContainer">
</div>
</div>


Yukarıda yer alan kodların çalıştırılması ile elde edilen sonuç aşağıdadır: