Flow: Criando um Jogo da Velha, 1 de 2

Criando Tipos e Interfaces para um jogo com FlowType

Image for post
Image for post
Um exemplo não convencional para descrever como FlowType funciona!

Por que faz sentido usar FlowType ou TypeScript ao trabalhar com JavaScript? Uma boa maneira de responder essa questão é criar um pequeno jogo ou aplicativo para deixar os benefícios claros.

Então aqui está. Parte um. Vamos construir um Jogo da Velha (TicTacToe, em inglês) em Javascript. Embora possa parecer trivial ou muito simples, isso nos permitirá observar como podemos aproveitar o FlowType para tornar nosso aplicativo mais previsível e confiável.

Para instalar o Flow siga as instruções no site oficial.

Vamos pensar sobre como iremos estruturar nosso jogo. Em primeiro lugar, precisamos de um tabuleiro de 3x3 campos. Devemos modelá-lo como uma matriz contendo 3 linhas (contendo 3 células cada) ou como uma lista plana contendo 9 células?

Existem vantagens e desvantagens para ambas as abordagens. Usando uma lista plana, oferece a vantagem de buscar e atualizar rapidamente qualquer célula acessando a lista através de índices. A desvantagem é que não podemos ter certeza de que alguém pode mudar a estrutura adicionando ou removendo uma célula. Com a abordagem da matriz, ganhamos a capacidade de modelar o tabuleiro para refletir a estrutura exata. Três linhas contendo três células. Por outro lado, teremos mais trabalho de transformação de dados ao acessar ou atualizar uma célula.

Para tornar impossível definir uma estrutura arbitrária, iremos na rota da estrutura explícita.

Primeiramente, vamos definir um tipo Cell, que é a menor unidade que iremos precisar. Uma Cell pode conter três tipos, um círculo, um xís ou estar vazio. Agora, vamos definir todos os tipos possíveis:

type Circle = {type: 'Circle'}
type Cross = {type: 'Cross'}
type Empty = {type: 'Empty'}

type Cell
= Circle
| Cross
| Empty

Ao olhar no nosso tipo Cell, nós podemos observar quais são os possíveis tipos.

Agora que temos nossa menor unidade definida, podemos criar os tipos para Row e Board:

type Row = [Cell, Cell, Cell]
type Board = [Row, Row, Row]

Eles são bem autoexplicativo. Podemos ver que Board contém três Rows;

Agora que temos nossos tipos definidos, vamos começar a construir o nosso jogo. Nós iremos utilizar React para renderizar o jogo na tela. Aqui está nosso componente TicTacToe:

import React from 'react'
import { render } from 'react-dom'

class TicTacToe extends React.Component<any> {
render() {
return <div>TicTacToe</div>
}
}

render(<TicTacToe />, document.getElementById('root'))

Agora temos o ponto de partida para o nosso jogo. Perceba que também precisamos declarar tipos para os nossos componentes React, class TicTacToe extends React.Component<any>. Por hora, iremos usar any para previnir erros do Flow, mas iremos refinar essas definições nos próximos tópicos.

É hora de construir o tabuleiro. Nosso componente TicTacToe manterá o estado atual do jogo, bem como do tabuleiro. O estado do jogo inclui qual é o turno dos jogadores, bem como se o jogo terminou e se teve algum vencedor. Precisamos guardar essas informações, pois nossa interface deve refletir esses valores.

Vamos definir o tipo Player, podemos utilizar 0 ou 1:

type Player = 0 | 1

O que está faltando é definir o status do jogo, se ainda está ativo, se terminou ou se nós temos um vencedor. Aqui está uma tentativa de uma possível definição:

type Status = Result | {type: 'Running'}

E qual é o tipo de Result? Ele contém um vencedor ou nada, de fato, o jogo pode até ter terminado. Vamos adicionar um tipo Maybe:

type Maybe<A> = Just<A> | Nothing

E para definir Just e Nothing:

type Just<A> = {type: 'Just', result: A}
type Nothing = {type: 'Nothing'}

Agora podemos verificar o tipo no nosso código e reagir de acordo.

O Result pode ser definido como:

type Result = Maybe<[Player, Row]>

Isso pode parecer confuso de início, mas estamos realmente definindo uma tupla contendo um Player e uma Row. Podemos destacar a combinação vencedora e seu jogador e exibir esses estados na tela. Caso o jogo termine, mas não há vencedor, o tipo Nothing será nosso indicador.

Agora que temos tudo no lugar, vamos criar um tabuleiro vazio. Nosso componente TicTacToe precisará de um estado inicial. Então, também vamos definir a forma do estado do componente.

type State = {
board: Board,
player: Player,
status: Status
}

Precisamos passar ao nosso componente React qual é a forma do seu estado. Ao olhar a documentação do Flow, você irá perceber que podemos passar as definições de prop e state da seguinte maneira: Component<Props, State>.

Como não temos nenhuma prop, podemos inferir seu valor e passar o tipos do nosso state:

class TicTacToe extends React.Component<*, State>

Ficando com:

class TicTacToe extends React.Component<*, State> {
state = {
board: [],
status: {type: 'Running'},
player: 0,
}
...
}

Flow irá reclamar em:

board: [],
^^ empty array literal. Tuple arity mismatch. This tuple has 0 elements and cannot flow to the 3 elements of
board: Board,
^^^^^ tuple type

Para arrumar isso, iremos definir o estado inicial do tabuleiro:

const empty : Empty = {type: 'Empty'}
const emptyRow : Row = [empty, empty, empty]
const board : Board = [emptyRow, emptyRow, emptyRow]

E adicionar ao nosso componente:

class TicTacToe extends React.Component<*, State> {
state = {
board: board,
status: {type: 'Running'},
player: 0,
}
...
}

Ótimo! Nós temos nosso estado inicial e agora, podemos renderizar nosso componente:

class TicTacToe extends React.Component<*, State> {
state = {
board: board,
status: {type: 'Running'},
player: 0,
}
render() {
const {board} = this.state
return <div>{
board.map((row, i) => {
return <div style={{width: '600px', height: '150px'}} key={i}>
{row.map((cell, j) => {
return <div style={{
float: 'left',
textAlign: 'center',
border: '1px solid #eee',
padding: '75px'
}} key={j}>
cell
</div>
})}
</div>
})
}</div>
}
}

Desconsidere a parte dos estilos por enquanto, mas você pode ver que nós mapeamos a matriz primeiro e, em seguida, mapeamos as linhas para exibir as células. Por enquanto, só renderizaremos a palavra “cell”.

Nosso próximo passo inclui a refatoração do tabuleiro e das células em seus respectivos componentes, iremos adicionar interatividade, para que os jogadores possam começar a jogar.

Embora pareça um trabalho grande de início, principalmente para definir e exibir um tabuleiro simples de 3x3.

O bacana é que nosso código está garantindo que o tabuleiro tem 3 linhas contendo 3 células.

Se você tentar definir uma forma diferente Flow irá reclamar.

const board : Board = [emptyRow, emptyRow, null]

Flow nos diz que temos uma incompatibilidade:

const board : Board = [emptyRow, emptyRow, []]
^^ empty array literal. Tuple arity mismatch. This tuple has 0 elements and cannot flow to the 3 elements of
type Board = [Row, Row, Row]
^^^ tuple type

Finalizando

Para o primeiro artigo, iremos parar por aqui! Na próxima parte, veremos mais benefícios de definir a estrutura de tipos antes de iniciar seu código e adicionar a lógica necessária para nosso Jogo da Velha.

// @flow// Exemplo final para o capítulo 1import React from 'react'
import { render } from 'react-dom'
type Circle = {type: 'Circle'}
type Cross = {type: 'Cross'}
type Empty = {type: 'Empty'}
type Cell
= Circle
| Cross
| Empty
type Row = [Cell, Cell, Cell]
type Board = [Row, Row, Row]
type Player = 0 | 1type Just<A> = {type: 'Just', result: A}
type Nothing = {type: 'Nothing'}
type Maybe<A> = Just<A> | Nothingtype Result = Maybe<[Player, Row]>type Status = Result | {type: 'Running'}type State = {
board: Board,
player: Player,
status: Status
}
const empty : Empty = {type: 'Empty'}
const emptyRow : Row = [empty, empty, empty]
const board : Board = [emptyRow, emptyRow, emptyRow]
class TicTacToe extends React.Component<*, State> {
state = {
board: board,
status: {type: 'Running'},
player: 0,
}
render() {
const {board} = this.state
return <div>{
board.map((row, i) => {
return <div style={{width: '600px', height: '150px'}} key={i}>
{row.map((cell, j) => {
return <div style={{
float: 'left',
textAlign: 'center',
border: '1px solid #eee',
padding: '75px'
}} key={j}>
cell
</div>
})}
</div>
})
}</div>
}
}
render(<TicTacToe />, document.getElementById('root'))

Créditos

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store