Opis gry - zaawansowane kółko i krzyżyk na urządzenia mobile i stacjonarne

Gra została napisana w czystym javascript oraz HTML5, CSS3 , korzysta ona tylko tylko z jednej biblioteki: modernizr, która jest wykorzystana do sprawdzenia, czy przeglądarka ma wbudowaną obsługę: Canvas oraz "Web Workers". W przypadku braku tych technologii zostaje wypisany stosowny komunikat i uruchomienie gry staje się niemożliwe. Bibliotekę tą można pobrać ze strony: http://modernizr.com.

W pliku loader.js następuje inicjalizacja gry, sprawdzana jest także szerokość urządzenia, na którym gra jest uruchamiana oraz definiowane są zmienne konfiguracyjne w obiekcje json "ttt", takie jak m.in.: tłumaczenia, kolory, czy maksymalny poziom zagłębienia algorytmu oraz ilość pól dla szerokości i długości planszy i nawet długość ciągu krzyżyków i kółek dla których następuje wygrana. Poniżej podaje przykład dla klasycznego kółka i krzyżyk, tj.: dla planszy 3x3.

 

    action :{
        rows : 3, // było 9 - ilość rzędów
        cols : 3, // było 9 - ilość rzędów,
        len : 3,  // było 5 - ilość kółek lub krzyżyków określające wygraną
        max_level : 3,
        max_depth : 2,
        inf : 1000000,
        human : 1,
        blank : 0,
        comp  : -1,
        who_first : 1
    }

Natomiast plik display.canvas.js odpowiada tylko za wyświetlanie gry, w technologii z użyciem Canvas oraz odbiera dane z "Web Workers".

Dzięki zastosowaniu "Web Workers" ruch komputera przeliczany jest w tle, przez co gra chodzi szybciej i stabilniej. "Web Workers" odpowiada za ruch komputera, który jest wyliczany algorytmem "Alpha-beta pruning", jest to zmodyfikowana forma algorytmu minimax, więcej na temat tego algorytmu można znaleźć na stronie: http://en.wikipedia.org/wiki/Alpha-beta_pruning. Implementacja algorytmu "Alpha-beta pruning" znajduje się w pliku game.logic.worker.js, a trzon tego algorytmu stanowi funkcja alphaBetaPruning pokazana poniżej:

 

    function alphaBetaPruning( node, depth, alpha, beta, player ){

        var eval = evaluate(   node, player );
        if(  (eval['win'] != 0 ) || ( eval['score'] == 0 )  || (depth >= max_depth ) ){
            return  { 'alphabeta': eval['score'], 'tree' : null };
        }

        var children = possibleMoves( node, player );
        depth++;


        if( player == human ){
            var tree = [];
            for( var i=0;  i<children.length;  i++ ){
                tree[i] = {};
                tree[i].matrix = children[i];
                var tree_children  = alphaBetaPruning( children[i], depth, alpha, beta, -player  );
                var alpha = ( tree_children['alphabeta'] > alpha  ) ?  tree_children['alphabeta'] : alpha;

                tree[i].alphabeta = alpha;
                if( beta <= alpha ){
                    break;
                }
            }
            return {  'alphabeta':  alpha, 'tree': tree };
        }else{
            var tree = [];
            for( var i=0;  i<children.length;  i++ ){
                tree[i] = {};
                tree[i].matrix = children[i];
                var tree_children = alphaBetaPruning(  children[i], depth, alpha, beta, -player );
                var beta = ( tree_children['alphabeta'] < beta  ) ?  tree_children['alphabeta'] :  beta; 

                tree[i].alphabeta = beta;
                if( beta <= alpha ){
                    break;
                }
            }
            return {  'alphabeta':  beta, 'tree': tree };
        }
    }

Funkcja ta jest odzwierciedleniem pseudokodu, który można znaleźć w wikipedii. Natomiast istotne są dwie inne funkcje: evaluate oraz possibleMoves zawarte w tym algorytmie. Funkcja possibleMoves podaje każdy możliwy ruch do wykonania, natomiast evaluate ocenia daną plansze na podstawie ilości kółek lub krzyżyków w jednym: rzędzie, wierszu lub po skosie nadając im wagi, które w wyniku końcowym są sumowane, oto kod tej funkcji:

 

    function evaluate( matrix, player  ){
        var eval_entity =getEvalEntity( matrix ); //wszystkie możliwe kombinacje ruchów wygrywających.
        var score = 0;
        var win = 0;
        var win_xy = {};
        for( i=0; i<eval_entity.length; i++  ){
            var item = eval_entity[i];
            var is_min = inArray( -1, item );
            var is_max = inArray( 1, item );

            if( is_min &&  is_max ){
                score += 0;
            }else{
                if( !is_min && !is_max  ){
                    score += 1*player;
                }else{
                    var sum = 0;
                    for( j=0; j<item.length; j++ ){
                        sum += parseInt( item[j].v );
                    }
                    var sign = ( sum > 0  ) ? 1 : -1
                    var sum_abs = Math.abs(sum);
                    if( len == sum_abs ){
                        win = sign;
                        win_xy = item;
                    }
                    score += sign * Math.pow( 10, (sum_abs + 1)  ) ;
                }
            }
        }
        return { score: score, win: win, win_xy : win_xy };
    }