BrainJS – Rede Neural com Javascript

Recentemente conheci o BrainJS, uma biblioteca de Rede Neural Artificial toda desenvolvida em Javascript disponível para instalação com npm no NodeJS (veja como instalar o NodeJS + npm).

 

Como funciona uma rede neural?

A resposta curta é: com magia negra.

Esta é uma rede neural não artificial: um cérebro de chipanzé.

A reposta um pouco mais longa, é que a rede neural recebe uma série de dados (inputs) e a resposta esperada para cada caso de conjunto de inputs. 

Depois de coletar alguns inputs com respostas que você já saiba você pode executar o "treinamento" desta rede neural, e o resultado deste treinamento será um algoritmo. Este algoritmo calculará uma  probabilidade de resposta para os próximos inputs que você colocar neste rede neural.

A rede neural é utilizada para sistemas de inteligência artificial, com o constante treinamento da rede (e input de informações) suas respostas ficam mais acertadas com o tempo.

Leia mais sobre redes neurais artificiais na Wikipedia.

 

BrainJS

Agora que você já tem uma idéia de como uma rede neural artificial funciona, pode fazer como eu e ficar impressionado por isso funcionar em javascript (server-side com NodeJS) ou client side, utilizando a artimanha do browserify

Eu fiz uma série de experimentos com o BrainJS, quase todos relacionados a seletores de cores – sabem como é difícil a vida de um daltônico? Pensei em uma ferramenta para me auxiliar na identificação de cores e está dando certo…

Treinando uma rede neural com BrainJS

// instanciamos nossa rede neural, spongebob
var spongebob = new NeuralNetwork();
 

Agora vamos inputar dados de altura (h) e largura (w) e os resultados esperados: se o objeto é vertical ou horizontal.

spongebob.train([
  {input: {w:200,h:100}, output: {horizontal: 1}},
  {input: {w:500,h:400}, output: {horizontal: 1}},
  {input: {w:100,h:110}, output: {vertical: 1}},
  {input: {w:5,h:7}, output: {vertical: 1}},
]);

O resultado do treinamento da rede é um objeto que traz informações do sucesso (ou não) do treinamento e do número de iterações feitas para treinar sua rede neural. O treinamento pode levar um tempo pois é aqui que o algoritmo da sua rede neural é calculado.

 

Fazendo a rede neural trabalhar

Agora vamos fazer a rede neural trabalhar! Depois de inputar informações que você sabia a resposta você pode colocar dados que você não saiba a resposta e deixar a rede fazer o trabalho dela.

var resultado = spongebob.run({w:50,h:400});

Cada run vai resulta um objeto com as prováveis respostas, no exemplo acima:

horizontal: 0.45137855383391234
vertical: 0.5486215106258696

 

É ou não magia negra?

Veja um demo do  BrainJS

Criar elementos no DOM com DocumentFragment

O uso do DocumentFragment para criação de nós no DOM é muito importante no que diz respeito a performance do seu javascript, quando é necessário fazer a inserção de vários elementos de uma vez só.

DocumentFragment é um container para nós DOM, que  pode receber elementos e depois ser adicionado (através do método appendChild) a um nó. O benefício se dá tanto na performance do script (como veremos logo mais, e também pode ser visto nesse post do John Resig) como na renderização da página (reflow e repaint) no browser, uma vez que ao invés de ter que adicionar n elementos, um a um, eles são adicionados de uma vez só.

Abaixo tem um trecho de código usando o DocumentFragment, exemplificando o uso:

var div = document.getElementById("minha_div"); //passo 1
var
fragment = document.createDocumentFragment();
for(var i=0; i<100; i++) {
 var d = document.createElement("p");
 fragment.appendChild(d); //passo 2
}
div.appendChild(fragment); //passo 3

O uso é bem simples: deve-se criar um DocumentFragment, atribuindo a uma variável (passo 1), então para cada elemento criado esse elemento é adicionado ao documentFragment (passo 2) e, por final, se adiciona o DocumentFragment ao element que será o container desses nós no DOM.

Aqui http://jsfiddle.net/chrisbenseler/RQ28C/ tem um teste comparando a criação com e sem DocumentFragment; para criar 5000 elementos

e adicionar a uma div, com DocumentFragment o browser (Firefox 14.0.1 do Ubuntu) leva em média 230 milisegundos; já sem, 330 milisegundos.

Lazy Load – Documentação e demo

Nas últimas semanas participei do Front in Curitiba e esta semana participei da trilha de front-end do The Developers Conference, os eventos e conversas de corredor deram uma animada para eu continuar  brincando com código e terminar o Pong em HTML5, que quero integrar com NodeJS para tornar multiplayer.

Mas antes disso resolvi melhorar o repositório do jQuery Lazy Load, criar uma mini-documentação (que é tudo que o plugin exige) e uma página de demo.

Na documentação exemplifiquei como deixar um fallback para usuários com javascript desabilitado, o plugin ainda não teve nenhuma atualização de código, mas os próximos passos são:

  • Parametrizar as configurações
  • Documentar e exemplificar o uso de imagens de loading
  • Criar a página em inglês da documentação e demo

Espero que a documentação seja útil! 🙂

Pong em HTML5 Canvas

Recentemente eu escrevi um turorial de Canvas (HTML5) para iniciantes, que comecei a escrever para aprender Canvas.

Comecei a buscar formas divertidas de aprender canvas e como games em HTML5 me chamaram a atenção desde a primeira vez que li sobre o assunto, porque não aprender canvas desenvolvendo algum game? 🙂

É também uma forma de mostrar o que é possível com HTML5 e expandir as idéias para as aplicações que podem ser feitas com um pouco de criatividade.

Pong em Canvas

Vou explicar o desenvolvimento do Pong em HTML5.

Para facilitar a manutenção procurei desenvolver todo o game com orientação a objetos em quase tudo, então recomendo leitura sobre o assunto caso você não se sinta confortável com esse tipo de código.

O primeiro passo é criar a estrutura do game:

function Pong(){
    _this = this;
}

Todo o game é estruturado em basicamente 2 objetos de dados e algumas funções (métodos) auxiliares que fazem a manipulação dos dados, animação e regras do jogo.

 

Os objetos do game

Players

Cada player é um objeto onde é armazenado as informações do jogador, como pontuação, id, localização do seu "pad" e regra de movimentação (se ele deve ir para a direita ou esquerda).

this.player = function(){

    this.score = 0,
    this.left = false,
    this.right = false;    

    this.paddle = {
        x : 0,
        y : (_this.HEIGHT / 2)-60,
        w : 7,
        h : 120
    };

}

 

 

Bola

A bola é outro objeto do game, nela ficam armazenados sua posição (x,y) e o raio utilizado para o desenho.

this.bola = {
      x : 8,
      y : 50,
      r : 6
}

 

Métodos do Pong

Estruturei uma série de funções para facilitar o desenho e aplicação das regras do game. Acredito que o código ficou bastante legível e você pode ler ele diretamente no game.

 

init()

Chamada no início do jogo esta função define o contexto do canvas, os valores iniciais dos objetos (players, bola) e armazena também o tamanho do canvas, é utilizado para posicionar os pads e a bola. A função também cria os 2 jogadores e posiciona seus "pads" no local correto.

As variáveis "wind" e "gravity" são as duas forças que atuam na bola (horizontal e verticalmente).

 

                this.init = function(selector){

                    _this.ctx = $("#canvas")[0].getContext("2d"),

                    _this.WIDTH = $("#canvas")[0].width,

                    _this.HEIGHT = $("#canvas")[0].height,

                    _this.gravity = 4,

                    _this.wind = 10,

                    _this.p1 = new _this.player(),

                    _this.p1.id = 1,

                    _this.p2 = new _this.player(),

                    _this.p2.id = 2,

                    _this.p2.paddle.x = (_this.WIDTH-7);

                    

                    $(document).keydown(_this.onKeyDown);

                    $(document).keyup(_this.onKeyUp);

                    

                }

 

 

 
_this.ctx = $("#canvas")[0].getContext("2d"),
 
_this.WIDTH = $("#canvas")[0].width,
 
_this.HEIGHT = $("#canvas")[0].height,
 
_this.gravity = 4,
 
_this.wind = 10,
 
_this.p1 = new _this.player(),
 
_this.p1.id = 1,
 
_this.p2 = new _this.player(),
 
_this.p2.id = 2,
 
_this.p2.paddle.x = (_this.WIDTH-7);
 
 
 
$(document).keydown(_this.onKeyDown);
 
$(document).keyup(_this.onKe

Funções de desenho

Os métodos circle, rect e clean são apenas atalhos para os métodos de desenho do Canvas, utilizados para a cada frame redesenhar a bola e os pads em suas novas posições.

 

                this.circle = function(x,y,r){

                    _this.ctx.fillStyle = "rgba(0,0,255,.9)";

                    _this.ctx.beginPath();

                    _this.ctx.arc(x, y, r, 0, Math.PI*2, true);

                    _this.ctx.closePath();

                    _this.ctx.fill();

                }

                

                this.rect = function(x,y,w,h){

                    _this.ctx.fillStyle = "rgba(255,0,0,.6)";

                    _this.ctx.beginPath();

                    _this.ctx.rect(x,y,w,h);

                    _this.ctx.closePath();

                    _this.ctx.fill();

                }

                

                this.clear = function(){

                    _this.ctx.clearRect(0, 0, _this.WIDTH,_this.HEIGHT);

                }

 

 

Interação com teclado

Como fazer um objeto se movimentar no canvas utilizando o teclado? Na função init do game defini que a ação "keyup" e "keydown" do jQuery iriam disparar um método do game. 

A cada ação de Keydown é feito uma verificação de qual tecla foi pressionada. Se a tecla pressionada foi a direcional esquerda ou a direcional direita (veja a tabela de keycodes para javascript) o objeto do player 1 é alterado, modificando a flag "left" ou a flag "right" para true.

O Keydown faz a ação inversa, tornando as flags "false" dependendo de qual tecla foi liberada do teclado.

 

 

_this.onKeyDown = function(evt) {

    switch(evt.keyCode){

        /* seta direita */

        case 39:

            _this.p1.right = true;

            break;

        /* seta esquerda */

        case 37:

            _this.p1.left = true;

            break;

        /* espaco */

        case 32:

            _this.play();

            break;

    }

  

}

 

/* Keyup events */

_this.onKeyUp = function(evt) {

    switch(evt.keyCode){

        /* seta direita */

        case 39:

            _this.p1.right = false;

            break;

        /* seta esquerda */

        case 37:

            _this.p1.left = false;

            break;

    }

}

 

 

 

As regras e animação do jogo

Neste ponto nós já temos os objetos e players, bola, e todas as informações do canvas e forças que vão atuar no nosso jogo. Também temos os controles configurados, ao pressionar as teclas nosso objeto player é alterado.

Agora precisamos "dar vida" ao game, para isso iremos redesenhar o nosso canvas a cada 25 milisegundos. Primeiro vamos a lógica:

A cada 25 milisegundos iremos:

  1. Calcular e desenhar a bola em sua nova posição (de acordo com os valores de wind e gravity, que são as forças que atuam na bolinha)
  2. Verificar se o pad do player 1 deve ir para cima ou para baixo (variáveis left e right indicando que as teclas estão pressionadas)

    1. Em caso positivo, desenhar o pad na nova posição
  3. Se a bolinha bater em cima ou em baixo do canvas, inverter o valor de "gravity" para que a bolinha continue dentro do nosso canvas
  4. Verificar se é gol (se a bola acertou a lateral esquerda ou direita do canvas sem atingir os pads)

 

Estas são as regras do game convertidas para "bom português", agora vamos para o código:

 

this.animate = function (){

    /* limpa e redesenha a bola */

    _this.clear();

    _this.circle(_this.bola.x,_this.bola.y,_this.bola.r);

    

    var speed = 8;

    

    /* player 1 */

    if (_this.p1.right && _this.p1.paddle.y < (_this.HEIGHT – _this.p1.paddle.h)) _this.p1.paddle.y += speed;

    else if (_this.p1.left && _this.p1.paddle.y > 0) _this.p1.paddle.y -= speed;

    _this.rect(_this.p1.paddle.x,_this.p1.paddle.y,_this.p1.paddle.w,_this.p1.paddle.h);

    

    /* saida lateral */

    if (_this.bola.y + _this.gravity > _this.HEIGHT || _this.bola.y + _this.gravity < 0){

        _this.gravity = -_this.gravity;

    }

    

    if(_this.bola.x + _this.wind> _this.WIDTH){

        if(_this.bola.y > _this.p2.paddle.y && _this.bola.y < (_this.p2.paddle.y + _this.p2.paddle.h)){

           _this.wind = -_this.wind;

           _this.gravity = _this.randomize();

        }else{

           _this.p1.score++;

           $('#score-1').html(_this.p1.score);

            _this.bola.x = (_this.WIDTH / 2),

            _this.bola.y = (_this.HEIGHT / 2);

            _this.wind = -_this.wind;

           _this.play();

        }

    }else if(_this.bola.x + _this.wind < 0){

        if(_this.bola.y > _this.p1.paddle.y && _this.bola.y < (_this.p1.paddle.y + _this.p1.paddle.h)){

           _this.wind = -_this.wind;

           _this.gravity = _this.randomize();

        }else{

           _this.p2.score++;

           $('#score-2').html(_this.p2.score);

            _this.bola.x = (_this.WIDTH / 2),

            _this.bola.y = (_this.HEIGHT / 2);

            _this.wind = -_this.wind;

           _this.play();

        }

    }

    

    

    _this.bola.y += _this.gravity;

    _this.bola.x += _this.wind;

}

 

O código acima também já está marcando a pontuação e manipulando o HTML para exibição dos pontos. Retirei a parte do player 2 para facilitar, ele é um bot e está explicado abaixo:

 

Player 2 (NPC)

Ainda não trabalhei muito no bot, estava ansioso para compartilhar e colocar o Pong no ar e não consegui deixar exatamente como eu gostaria. Ele "persegue" a bolinha a partir de certo ponto da nossa quadra (do elemento canvas).

 

    /* bot */

    var delay = 300;

    if(_this.bola.y < (_this.p2.paddle.y + (_this.p2.paddle.h / 3)) & _this.p2.paddle.y > 0 && _this.bola.x > delay){

        _this.p2.paddle.y -= speed;

    }else if(_this.bola.y > (_this.p2.paddle.y – (_this.p2.paddle.h / 3)) && _this.p2.paddle.y < (_this.HEIGHT – _this.p2.paddle.h) && _this.bola.x > delay){

        _this.p2.paddle.y += speed;

    }

    _this.rect(_this.p2.paddle.x,_this.p2.paddle.y,_this.p2.paddle.w,_this.p2.paddle.h);

 

 

Espero que este post desperte curiosidade e idéias! Sugestões, críticas e dúvidas nos comentários.

O que é Page Visibility – Palestra do SampaJS

Pelo segundo ano consecutivo o SampaJS aconteceu quando eu estava viajando e não pude participar. O Daniel Filho falou sobre Page Visibility, uma API bacana que permite melhorar ainda mais a interação com o usuário verificando se sua página está ativa no navegador do usuário.

Page Visibility

 

E se você quer saber mais sobre Page Visibility vale a pena ver a documentação na W3C.