Showing posts with label Game. Show all posts
Showing posts with label Game. Show all posts

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:



Wednesday, February 24, 2016

Jquery ile Mayın Tarlası Oyunu (2. Bölüm) - Mine Sweeper Game with Jquery (Section 2)

Bu yazıda mayın tarlası oyununda mayın olmayan yere tıklandığında mayınsız bölgenin otomatik açılması işlemini, mayına basıldığında tüm oyunun açılmasını ve gerekli renklendirmeleri yapacağız.

1. Bölümde ekranı oluşturmuş ve mayınları rastgele yerleştirmiştik. Şimdi bir önceki makalede yer alan CSS kodlarını mayın tuşunun durumuna göre aşağıdaki gibi güncelleyelim:

<style>

        .MineGameCanvas {

            width:500px;
            height:500px;

        }

        .MineButton {

            float:left;
            width:25px;
            height:25px;
        }

        .MineButtonPressed {

            float:left;
            width:25px;
            height:25px;
            pointer-events:none;
            background-color:gray;
            border:inset;
            
        }

        .MineButtonT {

            float:left;
            width:25px;
            height:25px;
            background-color:red;
            pointer-events:none;
        }

        .MineButtonT2 {

            float: left;
            width: 25px;
            height: 25px;
            background-color: orange;
            pointer-events:none;
            
        }
</style>

Burada mayına basılması durumu için "MineButtonT" sınıfı, mayına basılıp oyun sonlandığında diğer mayınları göstermek içi "MineButtonT2" ve boş mayın tarlası alanlarını göstermek için "MineButtonPressed" sınıfları eklenmiştir. Bir diğer önemli nokta ise "pointer-events:none" bilgisidir. Bununla tuşun olay yakalayıcıları etkisizleştirilmektedir (tuş disable yapılmaktadır).

Mayın taraması işleminin ve diğer ek fonksiyonların çalıştırılması için javascript kodu aşağıdaki gibi güncellenmiştir:

<script type="text/javascript">

        var mineCount = 50;
        var arr = [];
        var width = 20;
        var height = 20;
        var possibility = mineCount / (width * height);

        var BoomClass = "MineButtonT";
        var BoomClassShow = "MineButtonT2";
        var MinePressed = "MineButtonPressed";

        var MineSweeperWrapper = function () {

            this.HasMine = false,
            this.IsEnabled = true,
            this.IsPressed = false,
            this.IsFlagged = false,
            this.MineButton = null
        }

        function ButtonPressed(sender) {

            var btNumx = $(sender).attr("btNumx");
            var btNumy = $(sender).attr("btNumy");

            var x = parseInt(btNumx);
            var y = parseInt(btNumy);

            if (arr[x][y].HasMine) {

                arr[x][y].IsPressed = true;
                OpenAll();
            } else {

                EvaluateSituation(x, y);
            }
        }

        function PressMine(X, Y) {

            arr[X][Y].IsEnabled = false;
            $(arr[X][Y].MineButton).toggleClass(MinePressed);
        }

        function EvaluateSituation(X, Y) {

            var stack = [];

            var neighborCount = 0;

            if (ControlButtonHasMine(X, Y + 1, stack)) {
                neighborCount++;
            }

            if (ControlButtonHasMine(X, Y - 1, stack)) {
                neighborCount++;
            }

            if (ControlButtonHasMine(X + 1, Y, stack)) {
                neighborCount++;
            }

            if (ControlButtonHasMine(X + 1, Y - 1, stack)) {
                neighborCount++;
            }

            if (ControlButtonHasMine(X + 1, Y + 1, stack)) {
                neighborCount++;
            }

            if (ControlButtonHasMine(X - 1, Y, stack)) {
                neighborCount++;
            }

            if (ControlButtonHasMine(X - 1, Y - 1, stack)) {
                neighborCount++;
            }

            if (ControlButtonHasMine(X - 1, Y + 1, stack)) {
                neighborCount++;
            }

            if (arr[X][Y].IsEnabled)
                PressMine(X, Y);

            if (neighborCount > 0) {

                $(arr[X][Y].MineButton).val(neighborCount);

            } else {

                while (stack.length > 0) {

                    var neighbour = stack.pop();

                    EvaluateSituation(neighbour.X, neighbour.Y);

                }
            }

        }

        function ControlButtonHasMine(X, Y, stack) {

            if (X >= 0 && X < width && Y >= 0 && Y < height) {

                if (arr[X][Y].IsEnabled) {

                    stack.push(arr[X][Y]);

                    if (!arr[X][Y].HasMine) {

                        return false;

                    } else {

                        return true;
                    }
                }
            }
        }

        function OpenAll() {

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

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

                    if (arr[x][y].HasMine) {

                        if (!arr[x][y].IsPressed)
                            $(arr[x][y].MineButton).toggleClass(BoomClassShow);
                        else
                            $(arr[x][y].MineButton).toggleClass(BoomClass);
                    } else {

                        if (arr[x][y].IsEnabled) {

                            $(arr[x][y].MineButton).toggleClass(MinePressed);
                            arr[x][y].IsEnabled = false;
                        }
                    }

                    arr[x][y].IsEnabled = false;
                }
            }
        }

        $(document).ready(function () {

            arr = CreateArray(width, height, 0);

            PlantMines();

            GenerateMineField();


        });

        function GenerateMineField() {

            var canvas = $(".MineGameCanvas");

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

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

                    var button = $('<input id="Button-' + x + '-' + y + '" type="button" value="" class ="MineButton" btNumx="' + x + '" btNumy="' + y + '" onClick="ButtonPressed(this)"/>');
                    arr[x][y].MineButton = button;
                    arr[x][y].X = x;
                    arr[x][y].Y = y;
                    canvas.append(button);
                }
            }
        }

        function CreateArray(witdh, height, defaultValue) {

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

            return arr;
        }

        function PlantMines() {

            for (var x = 0; x < width; x++) {
                for (var y = 0; y < height; y++) {

                    if (!arr[x][y].HasMine) {

                        var deger = Math.random();

                        if (deger <= possibility) {
                            arr[x][y].HasMine = true;
                            mineCount--;

                            if (mineCount == 0)
                                return;
                        }
                        else {
                            arr[x][y].HasMine = false;;
                        }
                    }
                }
            }

            if (mineCount > 0)
                PlantMines();
        }

    </script>

Bir önceki makalede anlatılmayan ve yeni eklenen fonksiyonların işlevleri şöyledir:

* PressMine fonksiyonu bir tuşa basıldığında tuş ile ilgili gerekli sınıf değişiklikleri ve durum değişikliklerini yapmaktadır.

* EvaluateSituation fonksiyonu mayın olmayan boş bir alana tıklandığında mayınsız alanı tarama ve mayınları sayma işlemlerinin yerine getirildiği recursive fonksiyondur.

* ControlButtonHasMine fonksiyonu tuş üzerinde mayın olup olmadığını ve kontrol edilen tuşun geçerli bir tuş olup olmadığını kontrol eden fonksiyondur. Ayrıca tuş çevresinde yer alan komşu tuşların eğer kontrol edilmemişlerse yığına eklenme işlemi de burada yapılmaktadır. Yığın içinde yer alan komşu tuşlar EvaluateSituation fonsiyonu içinde tekrar çağrılarak değerlendirilmektedir.

* OpenAll fonksiyonu mayına basılıp oyun sonlandığında bütün mayın alanını açık hale getiren fonksiyondur.

Bu makalede önemli noktalardan ilki javascript sınıf tanımlamasıdır:

var MineSweeperWrapper = function () {

            this.HasMine = false,
            this.IsEnabled = true,
            this.IsPressed = false,
            this.IsFlagged = false,
            this.MineButton = null
        }

Yukarıda görüldüğü gibi constructer olarak kullanılan bir fonksiyon üzerinden tanımlama yapılmaktadır. Sınıf ve arr dizisi kullanılarak tarama işleminin hızlı yapılması sağlanmaktadır. Diğer durumda DOM üzerinden işlem yapmak daha zordur.

MineSweeperWrapper sınıfı gerekli algoritmik alanları ve ilgili DOM nesnesine referans içermektedir. Bu alanlardan "HasMine" tuş üzerinde mayın olup olmadığı bilgisini, "IsEnabled" tuşun kullanılabilir olup olmadığı bilgisini, "IsPressed" basılan tuşun işaret bilgisini, "IsFlagged" tuşa bayrak konulup konulmadığı bilgisini (sonraki makalede işlenecektir) ve son olarak "MineButton" DOM nesnesine referans bilgisini sağlamaktadır.

Bu yazıda diğer önemli nokta ise yığın (stack) kullanımıdır. Yığınlara kasik diziler ile aynı şekilde tanımlanırken yığına ekleme için push(), yığında çıkarma için pop() fonksiyonları kullanılmaktadır.

Yukarıdaki kodların çalıştırılması sonucu elde edilen sonuç aşağıdaki gibidir:

Tuesday, February 23, 2016

Jquery ile Mayın Tarlası Oyunu (1. Bölüm) - Mine Sweeper Game with Jquery (Section 1)

Bu yazıda mayın tarlası oyununu genel hatlarıyla oluşturacağız. Bu yazı sonunda mayın tarlası oyunu ekranı oluşturulmuş, mayınlar rastgele dağıtılmış ve tuşlara gerekli olay yakalayıcıları yerleştirilmiş olacaktır. İlk aşamada oyunun yerleşeceği canvas ögesini tanımlayalım:

<div class="MineGameCanvas"></div>

Ekranı oluşturmak için bazı CSS kodları tanımlamamız gerekecektir:

<style>

        .MineGameCanvas {

            width:500px;
            height:500px;
        }

        .MineButton {

            float:left;
            width:25px;
            height:25px;
        }

        .MineButtonT {

            float:left;
            width:25px;
            height:25px;
            background-color:red;
        }

</style>

Burada "MineGameCanvas" sınıfı mayın tarlası oyununun yerleşeceği pencereyi tanımlamakta, "MineButton" sınıfı üzerinde mayın tanımlanmamış tuşu, "MineButtonT" ise üzerinde mayın tanımlanmış tuşu ifade etmektedir. Burada dikkat edilmesi gereken unsur "float:left" belirtimidir. Float ile tuşların canvas içine düzgün yerleştirilmesi sağlanmaktadır. Bir sonraki aşama olarak gerekli javascript kodlarını tanımlayalım:

<script type="text/javascript">

        var mineCount = 50;
        var arr = [];
        var width = 20;
        var height = 20;
        var possibility = mineCount / (width * height);

        function ButtonPressed(sender) {

            var btNumx = $(sender).attr("btNumx");
            var btNumy = $(sender).attr("btNumy");

            alert("Pressed to " + btNumx + "," + btNumy );

            if (arr[btNumx][btNumy] == 1)
                alert('Boom');

        }

        $(document).ready(function () {

            arr = CreateArray(width, height, 0);

            PlantMines();

            GenerateMineField();
        });

        function GenerateMineField() {

            var canvas = $(".MineGameCanvas");

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

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

                    if (arr[x][y] == 0) {
                        var button = $('<input id="Button-' + x + '-' + y + '" type="button" value="" class ="MineButton" btNumx="' + x + '" btNumy="' + y + '" onClick="ButtonPressed(this)"/>');

                        canvas.append(button);
                    } else {

                        var button = $('<input id="Button-' + x + '-' + y + '" type="button" value="" class ="MineButtonT" btNumx="' + x + '" btNumy="' + y + '" onClick="ButtonPressed(this)"/>');

                        canvas.append(button);
                    }

                }
            }
        }

        function CreateArray(witdh, height, defaultValue) {

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

            return arr;
        }

        function PlantMines() {

            for (var x = 0; x < width; x++) {
                for (var y = 0; y < height; y++) {

                    if (arr[x][y] != 1) {

                        var deger = Math.random();

                        if (deger <= possibility) {
                            arr[x][y] = 1;
                            mineCount--;

                            if (mineCount == 0)
                                return;
                        }
                        else {
                            arr[x][y] = 0;
                        }
                    }
                }
            }

            if (mineCount > 0)
                PlantMines();
        }

</script>

Yukarıdaki javascript komutlarında yer alan bazı kod parçalarının ve fonksiyonların yaptığı işler şöyledir:

* Bir Jquery fonksiyonu olan ready ile döküman elementlerinin DOM'a yerleştirilmesi tamamlandıktan sonra oyun için gerekli işlemlerin yapılamasını sağlayan fonksiyonlar kullanılmaktadır.

* CreateArray fonksiyonu ile 2 boyutlu bir dizi oluşturulmaktadır. Oluşturacağımız dizi mayın lokasyonlarını içermektedir.

* ButtonPressed fonksiyonu bir tuşa basıldığında yapılması gereken kodları içermektedir.

* GenerateMineField fonksiyonu ile mayın tarlası için gerekli tuşlar dinamik olarak oluşturulmaktadır.

* PlantMines fonksiyonu ile mayınlar rastgele olarak oluşturulmakta ve dizi içine yerleştirilmektedir.

PlantMines fonksiyonu rastgele mayınları oluşturken Math.random fonksiyonunu kullanmaktadır. Mayın bulunma ihtimali her tuş için eşit olduğu için şöyle hesaplanmaktadır:

var possibility = mineCount / (width * height);

Burada toplam mayın sayısı toplam tuş sayısına bölünerek ihtimal eşit olarak dağıtılmış olmaktadır.

PlantMines fonksiyonu eğer tüm mayınları yerleştiremezse recursive olarak kendini tekrar çağırarak tüm mayınların yerleştirilmesini sağlamaktadır.

Tuşlar dinamik olarak Jquery ile aşağıdaki gibi oluşturulmaktadır:

var button = $('<input id="Button-' + x + '-' + y + '" type="button" value="" class ="MineButton" btNumx="' + x + '" btNumy="' + y + '" onClick="ButtonPressed(this)"/>');

Burada görüldüğü üzere Jquery fonksiyonu içinde direk olarak HTML olarak tanımlanan kod kullanılarak HTML elementleri dinamik olarak oluşturulup DOM içine yerleştirilebilmektedir.

Bir diğer önemli unsur ise "custom attribute" kullanımıdır. Jquery "attr" fonksiyonu ile bir DOM nesnesinin attribute değerleri alınabilmektedir.

Yukarıdaki kodların çalıştırılması sonucu elde edilen sonuç aşağıdadır: