Skip to content

Instantly share code, notes, and snippets.

@baltazarparra
Last active September 16, 2016 01:35
Show Gist options
  • Save baltazarparra/2c00a151c42cb3513775c750314deb83 to your computer and use it in GitHub Desktop.
Save baltazarparra/2c00a151c42cb3513775c750314deb83 to your computer and use it in GitHub Desktop.
(function(jokenpo){
'use strict';
var $rock = document.querySelector('[data-js="rock"]');
var $paper = document.querySelector('[data-js="paper"]');
var $scissor = document.querySelector('[data-js="scissor"]');
var $userChoice = document.querySelector('[data-js="user-choice"]');
var $cpuChoice = document.querySelector('[data-js="cpu-choice"]');
var $userScore = document.querySelector('[data-js="userScore"]');
var $cpuScore = document.querySelector('[data-js="cpuScore"]');
var app = (function appController() {
return {
init: function init() {
this.initEvents();
},
initEvents: function initEvents() {
$rock.addEventListener('click', this.optionChoice('rock'));
$paper.addEventListener('click', this.optionChoice('paper'));
$scissor.addEventListener('click', this.optionChoice('scissor'));
},
cpuChoice: function cpuChoice() {
var cpuChoice = Math.random();
if (cpuChoice < 0.3) {
var cpuChoiceOutput = 'rock';
$cpuChoice.setAttribute("src", cpuChoiceOutput + ".svg");
return cpuChoiceOutput;
} else if (cpuChoice < 0.6) {
var cpuChoiceOutput = 'paper';
$cpuChoice.setAttribute("src", cpuChoiceOutput + ".svg");
return cpuChoiceOutput;
}
var cpuChoiceOutput = 'scissor';
$cpuChoice.setAttribute("src", cpuChoiceOutput + ".svg");
return cpuChoiceOutput;
},
optionChoice: function optionChoice(option) {
return function () {
var cpuOption = app.cpuChoice();
$userChoice.setAttribute("src", option + ".svg");
jokenpo.play(option, cpuOption);
}
}
};
})();
app.init();
})(window.jokenpo);
@fdaciuk
Copy link

fdaciuk commented Sep 13, 2016

Fala @baltazarparra! Vamos às sugestões de melhoria:

Primeiro:
Você tem 3 funções: rockChoice, paperChoice e scissorChoice. Consegue ver que essas funções fazem exatamente a mesma coisa? Digo, o "design" delas é igual =)
A única coisa que realmente muda é a imagem que você vai carregar para pedra, papel ou tesoura. Então, a primeira coisa a se fazer, é separar o código do cpuChoice em uma outra função, e chamá-la ali nessas 3 funções, ao invés de repetir código.

Segundo:
Ainda na linha de refactory, após a alteração acima, você terá ainda 3 funções com design igual. O que muda de uma para outra? Somente o nome da imagem, correto? Então você pode criar uma outra função que elimina essas 3, chamada optionChoice por exemplo, que recebe por parâmetro a opção clicada, e retorna uma nova função:

optionChoice: function optionChoice(option) {
  return function () {
    $userChoice.setAttribute("src", option + ".svg");
    this.cpuChoice();
  }
}

Dessa forma, no evento você pode chamar essa função assim:

$rock.addEventListener('click', this.optionChoice('rock'));

Sacou? Você executa a função this.optionChoice, passando por parâmetro a opção. Essa função optionChoice retorna uma nova função, que é a função que será usada pelo evento de click =)

Lembre-se que eventos sempre precisam que o segundo parâmetro passado seja uma função que será executada no momento do click, por isso nós precisamos retornar uma nova função dentro de optionChoice. Ficou claro até aqui?

Certo, mas ainda temos um problema: quando usamos eventos, o this dentro da função do evento se refere ao elemento do DOM, não mais ao nosso objeto. Como resolver isso?

Bom, quando nós executamos a função optionChoice, passando o parâmetro pra ela, o contexto dessa função ainda é o objeto retornado, então nós podemos passar uma referência ao this ali dentro e usá-lo na função que esse método optionChoice retorna:

optionChoice: function optionChoice(option) {
+ var self = this;
  return function () {
    $userChoice.setAttribute("src", option + ".svg");
    self.cpuChoice();
  }
}

Agora deve tudo estar funcionando como antes, mas com menos código xD

A parte de incremento fica simples: você pode criar as variáveis de pontuação e pendurá-las no this para usar toda a aplicação, ou criá-la antes do primeiro return, e ir incrementando conforme o resultado.

Mas como saber o resultado? Quem ganhou? Quem perdeu?
Pra isso, nós precisamos pensar em manipulação de dados, ao invés de manipular DOM e verificar no DOM o resultado. Faça essas alterações que falei acima, e tente fazer a regra de "quem ganhou" ou "quem perdeu". Se não conseguir sozinho não se preocupe. Me avise que nós continuamos pra fechar isso =)

Mas tente entender bem essa parte do refactory que eu comentei. Isso é bastante importante, e tem uso em várias aplicações do mundo real =)

@baltazarparra
Copy link
Author

baltazarparra commented Sep 13, 2016

Ótimo @fdaciuk,

Eu ia deixar refactor pro final,
mas com essas dicas, já vou começar agora.

Ao invés de criar uma var para segurar o this,

optionChoice: function optionChoice(option) {
+ var self = this;
  return function () {
    $userChoice.setAttribute("src", option + ".svg");
    self.cpuChoice();
  }
}

eu não poderia só chamar a função referenciando o objeto?

tipo
app.cpuChoice();
?

Eu entendi bem a parte do refactor até aqui,
tinha bastante redundância no code,
agora ficou mais legivel e com menos code.

@fdaciuk
Copy link

fdaciuk commented Sep 13, 2016

Ao invés de criar uma var para segurar o this, eu não poderia só chamar a função referenciando o objeto?

Pode também, é uma outra forma de fazer. Não comentei pois queria ver até onde você tinha entendido essa parte, e deu pra ver que ficou claro =)

Na real, o bom é evitar usar this, pois como ele pode mudar seu contexto dependendo da forma como você chama as suas funções, isso pode causar uma certa confusão. Mas deve funcionar corretamente sim, só trocando os this por app =)

Agora você precisa fazer a função de comparação. Pegar a escolha do usuário e a escolha da máquina, e ver quem ganhou. Quando alguém ganhar, só precisa incrementar a pontuação de cada um.

Tenta seguir nessa pegada que você está fazendo mesmo, depois nós refatoramos e eu te explico melhor a ideia de manipular dados para só então manipular DOM =)

@fdaciuk
Copy link

fdaciuk commented Sep 14, 2016

Um exemplo usando somente dados. A ideia de uso é essa: uma função jokenpo que receba por parâmetro o nome de dois jogadores e retorne dois métodos: play para jogar e score para mostrar o resultado. O uso é esse:

// Imagine que exportamos a função `jokenpo` na variável global `window`:

// Vou setar os dois jogadores: 'fernando' e 'machine'
var game = jokenpo('fernando', 'machine')

// Agora vou fazer a jogada. O método `play` retorna o score. Posso pegar o score também direto da variável `game`.
console.log(game.score()) // { fernando: 0, machine: 0 }

var score = game.play({
  fernando: 'scisor',
  machine: 'rock'
})
console.log(score()) // { fernando: 0, machine: 1 }

// Para fazer uma nova jogada, só continuar usando o método play:
var newScore = game.play({
  fernando: 'rock',
  machine: 'paper'
})
console.log(game.score()) // { fernando: 0, machine: 2 }
console.log(newScore()) // { fernando: 0, machine: 2 }

Deu pra sacar a ideia? Eu fiz uma implementação, mas tente fazer você. No final do dia eu compartilho contigo a solução que eu fiz =)

@baltazarparra
Copy link
Author

baltazarparra commented Sep 14, 2016

A função jokenpo, vai fazer parte do modulo que vou criar, para tratar os dados correto?
Nela vou verificar quem venceu a sessão e tratar esses dados?

Estou apanhando um pouco pra entender sobre esses estados da aplicação.

Se eu declaro uma var userPoints, como eu vou alterando ela no decorrer do uso?

@fdaciuk
Copy link

fdaciuk commented Sep 15, 2016

Dá uma olhada no código que postei acima: essa é a interface do jogo, que consome somente dados. Uma função que recebe dados, e retorna dados. Só isso, independente de onde você vai consumir ela. Ela é genérica o suficiente para ser consumida de qualquer forma:

  • diretamente no DOM;
  • com um componente do React;
  • até no backend, com NodeJS, se assim você quiser =)

Vou detalhar a ideia de como eu fiz, e deixar mais um dia para você tentar implementar. Se não conseguir, amanhã eu posto a solução de como eu implementei:

  • Você precisa de uma função chamada jokenpo, que recebe dois parâmetros, que serão os nomes dos dois jogadores que irão participar do jogo;
  • Essa função, ao ser executada, deve retornar um objeto com duas funções: score e play;
  • score é uma função que, ao ser chamada, retorna um objeto que tem como chave os nomes dos usuários passados como parâmetro na função jokenpo, e os valores são os pontos de cada um: { fernando: 0, machine: 0 };
  • A função play recebe um objeto, com o nome dos jogadores, e a escolha de cada um, e retorna a mesma função score anterior, que ao ser executada, vai sempre trazer o valor atualizado da pontuação dos jogadores selecionados.

A ideia é basicamente essa. Se você usar a função jokenpo com jogadores diferentes, um novo jogo será iniciado. No exemplo acima, eu usei os jogadores fernando e machine. Esse objeto que a função jokenpo retorna tem o estado do jogo para esses dois jogadores. Se você usar a função jokenpo novamente, com outros dois jogadores, isso se torna um novo jogo, com um novo placar =)

Uma dica: lembre-se que funções podem ser closures. Ao executar jokenpo, ela vai guardar os valores de suas variáveis internas, por isso você consegue atualizar os pontos dos usuários sem que eles sejam zerados a cada vez que você chama score ou play =)

Não é tão simples, mas também não é tão complicado. Se você conseguiu entender todo o conceito passado no curso, você consegue fazer. Lembre-se que você só aprende realmente botando a mão na massa. É normal ter dificuldades no início, você vai aprendendo, entendendo como relacionar as coisas, e cada vez fica mais fácil de pensar em novos algoritmos =)

@baltazarparra
Copy link
Author

(function(){

    'use strict';

    var jokenpo = (function dataController(user, cpu) {
        return {

            play: function play(user, cpu) {
                if(user === 'rock') {
                    return jokenpo.rockChoice(cpu);
                } else if (user === 'paper') {
                    return jokenpo.paperChoice(cpu);
                }
                return jokenpo.scissorChoice(cpu);
            },

            rockChoice: function rockChoice(cpu) {
                if(cpu === 'rock') {
                    console.log('draw');
                } else if (cpu === 'paper') {
                    console.log('lose');
                } else {
                    console.log('win');
                }
            },

            paperChoice: function paperChoice(cpu) {
                if(cpu === 'paper') {
                    console.log('draw');
                } else if (cpu === 'scissor') {
                    console.log('lose');
                } else {
                    console.log('win');
                }
            },

            scissorChoice: function scissorChoice(cpu) {
                if(cpu === 'scissor') {
                    console.log('draw');
                } else if (cpu === 'rock') {
                    console.log('lose');
                } else {
                    console.log('win');
                }
            },

            score: function score(user, cpu) {
                return { user: 0, cpu: 0 };
            }
        };
    })();

    window.jokenpo = jokenpo;

})();

@fdaciuk
Copy link

fdaciuk commented Sep 15, 2016

Show cara! Tá indo bem! A ideia é essa mesmo: faz funcionar, depois refatora =)

@fdaciuk
Copy link

fdaciuk commented Sep 15, 2016

Só pra ver se te ajuda, vou colocar como eu resolvi a parte de dados (sem manipulação de DOM):

;(function (win) {
  'use strict';

  function jokenpo (firstUser, secondUser) {
    var usersPoints = {};
    usersPoints[firstUser] = 0;
    usersPoints[secondUser] = 0;

    function play (choices) {
      var users = Object.keys(usersPoints);
      var usersChoices = users.reduce(function (acc, user) {
        acc[user] = choices[user];
        return acc;
      }, {});

      return whoWins(usersChoices);
    }

    function whoWins (usersChoices) {
      var combinations = {
        'rock paper': 'paper',
        'rock scisor': 'rock',
        'paper scisor': 'scisor'
      };

      var choices = Object.keys(usersChoices).map((user) => {
        return usersChoices[user];
      });

      if (areChoicesTheSame(choices)) {
        return generateScore({
          usersChoices: usersChoices,
          result: null
        });
      }

      var combinationMatched = Object.keys(combinations).filter(function (combination) {
        return combination.indexOf(choices[0]) > -1 &&
          combination.indexOf(choices[1]) > -1
      })[0];

      var result = combinations[combinationMatched];

      return generateScore({
        usersChoices: usersChoices,
        result: result
      });
    }

    function areChoicesTheSame (choices) {
      return choices[0] === choices[1];
    }

    function generateScore (result) {
      Object.keys(usersPoints).forEach(function (user) {
        if (result.usersChoices[user] === result.result) {
          usersPoints[user] += 1;
        }
      });
      return score;
    }

    function score (user) {
      if (user && !isValidUser(user)) {
        throw new Error('Usuário não existe');
      }
      return user ? usersPoints[user] : usersPoints;
    }

    function isValidUser (user) {
      return Object.keys(usersPoints).some(function (u) {
        return user === u;
      });
    }

    return {
      score: score,
      play: play
    };
  }

  win.jokenpo = jokenpo;
})(window);

Um teste de uso:

var game = jokenpo('fernando', 'machine');
console.log(game.score());

game.play({
  fernando: 'rock',
  machine: 'scisor'
});
console.log(game.score());

Veja se você consegue entender tudo.
Baixe ele e tente ir modificando as opções, colocando console.log para ver o que acontece em cada momento do código. Assim vai ficar mais fácil de entender =)

Se ficou alguma dúvida com relação a esse código, só avisar =)

@fdaciuk
Copy link

fdaciuk commented Sep 16, 2016

Vou tentar explicar a ideia base de como você pode manter os valores usando uma "closure":

  1. Você cria uma função que vai conter todo o código;
  2. Dentro dessa função, você vai iniciar as variáveis que você quer guardar os valores;
  3. Essa função retorna um objeto, com algum método que vai alterar os valores internos dela.

Só isso! Fazendo isso, a cada vez que você executar essa função, os valores internos serão diferentes. Mas se você usar o objeto retornado, você estará trabalhando com os valores guardados pela "closure".

Como fica na prática, seguindo os passos acima?

// Passo 1: Criar a função que vai guardar toda a minha aplicação.
function main () {
  // Passo 2: Iniciar a variável que vai guardar os valores.
  var value = 0;

  // Passo 3: Retornar um objeto que contenha um método que modifica o valor guardado. Só como exemplo, a cada vez que chamar esse método, vou somar 1 ao valor de "value", e retornar esse valor:
  return {
    sum: function sum () {
      value += 1;
      return value;
    }
  };
}

É só isso! Como é o funcionamento da closure? Vamos criar uma aplicação usando essa função:

var app = main();
console.log(app.sum()); // 1
console.log(app.sum()); // 2
console.log(app.sum()); // 3

Veja que, a cada vez que eu chamo app.sum(), é incrementado 1 em value. app é o objeto retornado pela função main.

O real uso da closure se dá quando você precisa criar mais de uma aplicação ao mesmo tempo, fazendo uma "concorrência" entre as duas aplicações, mas usando a mesma função:

var app = main();
var app2 = main();
console.log(app.sum()); // 1
console.log(app.sum()); // 2
console.log(app2.sum()); // 1
console.log(app.sum()); // 3
console.log(app2.sum()); // 2

Olha só que legal! Uma não interfere na outra! Essa é a ideia de usar closures =)

Consegui entender?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment