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:



No comments:

Post a Comment