Postanowiłem przetestować dziś możliwości JavaScript i nowego obiektu w HTML mianowicie Canvas. Moim celem było napisanie prostej gry kołko krzyżyk, którą zapewne dobrze znacie i kojarzycie.

Canvas jest to nowy element w języku HTML5, który umożliwa rysowanie grafiki 2D oraz 3D w locie(przez skryptowanie).

Na początku potrzebowałem kodu html. Jest to standardowy dokument html, który posiada w body element canvas

<!DOCTYPE html>
<html>
<head>
    <title>Kołko krzyżyk</title>
    <script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
<script>
//tutaj bedzie skrypt
</script>
</head>
<body>
<canvas id="game" width="300" height="300" style="border:1px solid #d3d3d3;"></canvas>

</body>
</html>

Następnie zacząłem pisać potrzebne funkcje i zmienne. Pierwszą zmienną, którą zadeklarowałem jest zmienna move reprezentująca ilość ruchów, zmienna ta jest losowana po to by czasem zaczynało kółko, a czasem krzyżyk. Użyłem również funkcji, którą znalazłem w Internecie do podawania współrzędnych myszki, gdy użytkownik kliknie na obiekt Canvas:

var move = Math.floor((Math.random() * 10) + 1)%2;
    var gameFields = [0, 0, 0 ,0, 0, 0, 0, 0, 0];

    function relMouseCoords(event)
    {
            var totalOffsetX = 0;
            var totalOffsetY = 0;
            var canvasX = 0;
            var canvasY = 0;
            var currentElement = this;

            do{
                totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft;
                totalOffsetY += currentElement.offsetTop - currentElement.scrollTop;
            }
            while(currentElement = currentElement.offsetParent)

            canvasX = event.pageX - totalOffsetX;
            canvasY = event.pageY - totalOffsetY;

            return {x:canvasX, y:canvasY}
    }
    HTMLCanvasElement.prototype.relMouseCoords = relMouseCoords;

Teraz potrzebowałem linii reprezentujących siatkę gry, wykorzystałem do tego metody:

  • lineTo(x,y) – definiuje linie od punktu x do y
  • moveTo(x,y) – przenosi ścieżkę do określonego punktu bez rysowania
  • clearRect(x,y,x1,y1) – czyści obszar od punktu x,y do x1, y1
  • getContext(‚2d’)  – pobiera obszar do rysowania w canvas
  • stroke() – rysuje zdefiniowane wcześniej linie(ścieżki)
function drawMap()
{
        var ctx = document.getElementById("game").getContext("2d");
        ctx.clearRect(0, 0, 300, 300);
        ctx.beginPath();
        ctx.moveTo(100,0);
        ctx.lineTo(100,300);
        ctx.moveTo(200,0);
        ctx.lineTo(200,300);
        ctx.moveTo(0,100);
        ctx.lineTo(300,100);
        ctx.moveTo(0,200);
        ctx.lineTo(300,200);
        ctx.stroke();
}

Następnie potrzebowałem funkcji, która sprawdzi jakie pola są uzupełnione i narysuje kółko lub krzyżyk w tym miejscu.

function drawMoves()
    {
        var ctx= document.getElementById("game").getContext("2d");
        ctx.font="60px Georgia";
        var moveX = 100;
        var moveY = 70;
        for(i = 0 ; i <=8; i++)
        {
            if(i > 5)
                moveY = 100;
            else if (i > 2)
                moveY = 200;
            else
                moveY = 0;

            var fill = '';
            if(gameFields[i] == 1)
                fill = 'X';
            else if(gameFields[i] == 2)
                fill = 'O';
            else
                fill = '';

            ctx.fillText(fill, 25 + i % 3 * moveX, 70 + moveY);
        }
    }

Funkcja fillText(text, x, y) rysuje tekst w miejscu x,y.

Kolejną rzeczą, którą potrzebowałem jest funkcja sprawdzająca, w którym miejscu gracz kliknął i zapisująca ruch. Funkcja również sprawdza czy dozwolona liczba ruchów nie przekroczyła 9 oraz wywołuje funkcje sprawdzającą czy gra nie jest już wygrana.

 function canvasClicked()
    {
       var cords = document.getElementById("game").relMouseCoords(event);
       move++;
       if(move > 9)
       {
            endGame();
            return;
       }


       if(cords.x < 100 && cords.y < 100)
            gameFields[0] = move % 2 == 1 ? 1 : 2;
       else if(cords.x>100 && cords.x <= 200 && cords.y < 100)
            gameFields[1] = move % 2 == 1 ? 1 : 2;
       else if(cords.x>200 && cords.x <= 300 && cords.y < 100)
           gameFields[2] = move % 2 == 1 ? 1 : 2;
       else if(cords.x < 100 && cords.y < 200 && cords.y > 100)
            gameFields[6] = move % 2 == 1 ? 1 : 2;
       else if(cords.x>100 && cords.x <= 200 && cords.y < 200 && cords.y > 100)
           gameFields[7] = move % 2 == 1 ? 1 : 2;
       else if(cords.x>200 && cords.x <= 300 && cords.y < 200 && cords.y > 100)
           gameFields[8] = move % 2 == 1 ? 1 : 2;
       else if(cords.x < 100 && cords.y < 300 && cords.y > 200)
           gameFields[3] = move % 2 == 1 ? 1 : 2;
       else if(cords.x>100 && cords.x <= 200 && cords.y < 300 && cords.y > 200)
           gameFields[4] = move % 2 == 1 ? 1 : 2;
       else if(cords.x>200 && cords.x <= 300 && cords.y < 300 && cords.y > 200)
           gameFields[5] = move % 2 == 1 ? 1 : 2;


        if(check(1))
        {
            alert('X wygrywa');
            endGame();
            return;
        }
        else if(check(2))
        {
            alert('O wygrywa');
            endGame();
            return;
        }

       drawMoves();
    }

Ostatnie dwie funkcje potrzebne do gry to funkcja endGame() kończąca gre oraz check() sprawdzająca czy kółko lub krzyżyk nie wygrało gry.

function endGame()
    {
        move = 0;
        gameFields = [0, 0, 0 ,0, 0, 0, 0, 0, 0];
        drawMap();
    }

    function check(x)
    {
        if((gameFields[0] == x && gameFields[1] == x && gameFields[2] == x) || (gameFields[3] == x && gameFields[4] == x && gameFields[5] == x) || (gameFields[6] == x && gameFields[7] == x && gameFields[8] == x))
        {
            return true;
        }
        if((gameFields[0] == x && gameFields[3] == x && gameFields[6] == x) || (gameFields[1] == x && gameFields[4] == x && gameFields[7] == x) || (gameFields[2] == x && gameFields[5] == x && gameFields[8] == x))
        {
            return true;
        }
        if((gameFields[0] == x && gameFields[5] == x && gameFields[7] == x) || (gameFields[2] == x && gameFields[3] == x && gameFields[7] == x))
        {
            return true;
        }
    }

Gdy już to mamy, możemy uruchomić grę

$(function (){
        $("#game").click(canvasClicked);
        drawMap();
        drawMoves();
    });

Użyłem do tego funkcji JQuery document.ready() ale równie dobrze można to zrobić bez JQuery, używając standardowych funkcji JavaScript.

I to tyle. Działający kod możecie zobaczyć pod adresem http://blog.rpodwika.pl/game.html

Gra nie jest jeszcze w pełni ukończona, ale znalezienie błędów pozostawiam Wam. Są co najmniej 3, o których wiem.

Dzięki za przeczytanie artykułu i polecam korzystać z Canvas, gdyż Flash odchodzi do lamusa i nie będzie rozwijany przez Adobe.