Bingo do Zé

Agora que as eleições passaram (e Ricardo Coutinho venceu), resolvi parar um pouco e escrever sobre o Bingo do Zé.

Como tudo começou

O @pizzariaze @alexandrend lançou no dia 17 15 de outubro uma cartela com algumas pérolas do candidato Zé Maranhão. A ideia era que você imprimisse e fosse marcando ao assistir aos debates.

Bingo do Zé

Achei genial. Teve uma boa repercussão no Twitter e, no dia seguinte, Alan Kelon, um professor da UFPB e amigo, lançou um desafio na lista de estudantes de Computação da federal: fazer uma aplicação web com a ideia do bingo. Fiquei com isto na cabeça.

Naquele dia estava com insônia. Já eram mais de 2 da manhã e não tinha sono algum. Então Samara, minha namorada, me liga. Também estava lesando no computador. Contei pra ela sobre o bingo e, depois de rirmos bastante, veio a proposta: bora fazer agora? ;)

Ela disse sim :D

Primeira versão

Três horas da manhã de uma terça-feira, estávamos eu e ela com o notebook na cama e um modem 3G. Primeiro passo: procurar algo já pronto. Rapidamente encontrei este post que mostrava como fazer um bingo “padrão” com JavaScript, JQuery e CSS.

Primeira versão

Perfeito! Só precisaria modificar pra usar frases ao invés de números e estilizá-lo. Depois de pouco mais de uma hora chegamos a isso:

Primeiro Bingo do Zé

Alguns detalhes técnicos

Houve duas sacadas que acho interessante falar. Se você não for de computação ou não estiver interessado em saber como funciona o bingo, aguarde pela parte 2 deste post.

Gerando uma URL única para cada cartela

Fiz algumas funções pra compartilhar o bingo no twitter. Uma delas é que, quando você fecha uma linha ou coluna (exceto a central) ou a cartela toda, você pode enviar uma mensagem do tipo “Fiz 2 colunas no #BingoDoZe!”. Mas, pra ficar mais interessante, queria adicionar o link da sua cartela nesse tweet. Mas como?

O sistema é feito todo só com JavaScript. Não consigo (facilmente) usar um banco de dados. Primeiro pensei em reescrevê-lo em Ruby ou PHP, mas fiquei com preguiça. Pensei mais um pouco, fiquei olhando o código para ver se surgia alguma ideia, então me veio a luz. Antes de tudo, deixe-me explicar como funciona o código.

Eu tenho um array com as pérolas. Para gerar a cartela, eu o embaralho e coloco os 25 primeiros termos e nos campos do bingo. A solução que encontrei para gerar sempre a mesma é adicionar uma semente ao algoritmo do Math.random().

    function fillCards(seed){
        Math.seedrandom(seed);
        var shuffle = function() { return 0.5 - Math.random(); };
        var termos_shuffled = termos.sort(shuffle);
     
        for(var i = 0; i <= 24; i++){
            // Pula a imagem de Maranhão
            if (i == 12)
                continue;
     
            $('#cell'+i).html(termos_shuffled[i]);
        }
    }

Uma linguagem de programação gera números (pseudo-)aleatórios a partir de diversos cálculos em cima de um valor inicial. Se você puder definir qual é esse valor, então a função sempre retornará os mesmos resultados. O que faz isso é a linha 2, Math.seedrandom(seed).

Infelizmente, não tem como fazer isso nativamente em JavaScript. Mas basta adicionar o seedrandom.js, criado pelo David Bau e disponibilizado no seu blog. Funcionou perfeitamente.

Como seed usei um hash na URL, na forma http://labs.vitorbaptista.com/bingodoze/#367660 (neste caso, o seed é #367660). Ao entrar em http://labs.vitorbaptista.com/bingodoze, se já houver um hash, usa ele como seed. Caso contrário, gera um aleatoriamente.

Salvando que campos foram marcados

Depois do passo anterior os jogadores conseguem compartilhar suas cartelas. Mas quem acessar meu link vai encontrá-la vazia. Como persistir os campos marcados?

Seguindo a ideia de adicionar os dados à URL, tive a ideia de codificar o que foi marcado em um inteiro. Bem simples: tenho um número com 25 bits, cada um representando uma célula. Os que estiverem em 1, estão ativos; em 0, desativos.

Para isso, há duas funções principais: cartelaToMask() e maskToCartela(mask). A primeira retorna um inteiro que representa a cartela, e a segunda faz o caminho inverso, recebendo um inteiro e preenchendo a cartela. Vejamos:

    function cartelaToMask() {
        var mask = 0;
     
        for (var i = 0; i <= 24; i++) {
            if ($('#cell'+i).hasClass('ativo'))
                mask |= masks[i];
        }
     
        return mask;
    }

Bem simples. Há o inteiro mask inicializado com 0 e, para cada célula, checo se ela tem a classe CSS “ativo”. Se sim, eu coloco o bit correspondente em 1. No final, retorno o mask.

O array masks usado no algoritmo possui as potências de 2 já calculadas. Ou seja, masks[0] == 1, masks[1] == 2, masks[2] == 4, e por aí vai. É uma solução muito deselegante. O ideal seria gerar esses valores provavelmente usando bit shifting.

    function maskToCartela(mask) {
        for (var i = 0; i <= 24; i++) {
            if (mask & masks[i])
                $('#cell'+i).addClass('ativo');
        }
    }

O caminho contrário é bem parecido. Eu faço um laço de 0 até 24 pegando o bit correspondente (mask & masks[i]). Se ele estiver ativo, adiciono a classe “ativo” à célula. Pronto.GG

Esse inteiro é codificado em hexadecimal e adicionado a URL. Assim temos algo como http://labs.vitorbaptista.com/bingodoze/#367660-1d50170, onde #367660 é a semente e 1d50170 é a máscara.

Se você faz computação e teve problemas em entender a lógica dos operadores bit-a-bit usados nesse código, dê uma lida aqui. É simples e pode ser bem útil.

Eu pretendia só falar das estatísticas do Bingo do Zé, mas acabei me empolgando e falando demais. Vou deixá-las para a parte 2 deste post. Se você estiver curioso, pode ir vendo as estatísticas de tweets em http://summarizr.labs.eduserv.org.uk/?keyword=bingodoze