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 };
}