12 de junho de 2013

Cantinho da Criança

Está pronto e publicado no Google Play o jogo infantil Cantinho da Criança!

O objetivo do jogo é ajudar crianças a reconhecer algumas coisas do dia-a-dia, auxiliando no aprendizado e no desenvolvimento. Indicado para crianças de 0 a 3 anos.

Colocamos imagens bem coloridas e uma música bem animada para ensinar e divertir ao mesmo tempo.

Se tiver filhos nessa faixa etária, não deixe de conferir: Google Play - Apps by Haeres

5 de junho de 2013

Abrace Android

Já faz um tempo que não escrevo neste blog. As minhas aventuranças no mundo Haskell pararam um pouco depois do último post que fiz ("C, C++, Java, Haskell?").

Dediquei um bom pedaço deste tempo ausente para tentar fazer um jogo em C. Tive um bom começo: fiz um personagem se movimentar, pular e colidir com outros objetos. Comecei a perceber onde estava me metendo quando estas perguntas surgiram na minha mente:

  • Como vou distribuir meu jogo?
  • Como vou lançar uma atualização?
  • Como vou comercializar?

Neste momento estava com uma conexão carinhosa com o projeto e foi difícil aceitar que estas dificuldades poderiam ser maiores do que meu esforço disponível. Passada esta fase de aceitação, decidi que precisava tirar todas estas preocupações da cabeça para conseguir me dedicar ao desenvolvimento do jogo. Para isto precisava de uma plataforma que me oferecesse todos os itens que coloquei na lista acima.

Android

Escolhi a plataforma Android por conseguir acesso a um ambiente de desenvolvimento de forma mais fácil, pois poderia continuar utilizando meu notebook rodando Windows para programar e realizar testes no meu celular Samsung Galaxy 5. Sem chance de investir em um ambiente para iOS no momento.

Para garantir que a plataforma atendia a todos os itens que listei acima, fiz o processo do começo ao fim:

  1. Criei um aplicativo de exemplo
  2. Testei no meu celular
  3. Publiquei o aplicativo (Como vou distribuir meu jogo?)
  4. Alterei algumas coisas e lancei atualizações para ele (Como vou lançar uma atualização?)
  5. Criei uma versão free com propagandas e uma paga sem propagandas (Como vou comercializar?)

O resultado disto tudo foi muito agradável. Agora não me restam dúvidas sobre o ambiente Android, basta continuar meu aprendizado na plataforma e o desenvolvimento do projeto em paralelo. Espero que em breve eu consiga postar no blog um link para o resultado do projeto publicado. :)

20 de janeiro de 2013

C, C++, Haskell, Java?

Estou empolgado e agoniado com meu aprendizado sobre OpenGL. Ao mesmo tempo em que aprender OpenGL me deixa muito satisfeito, também me deixa inquieto: dediquei muito tempo aprendendo Haskell e fui envolvido de uma forma a gostar muito da linguagem e da comunidade. Porém, fazer algo em OpenGL se mostrou muito difícil e até mesmo solitário. Existem pouquíssimos materiais sobre isso e a maioria deles estão obsoletos.

Tive que largar Haskell por um tempo e religar os neurônios da linguagem C para aprender o fluxo das versões mais modernas do OpenGL, com shaders e buffer objects. O motivo foi simples: não consegui fazer nada funcionar em Haskell e não tive certeza se a culpa era minha ou das bibliotecas que utilizava.

Pois bem, fazer uma câmera se movimentar em um mundo 3D em C também não foi nada fácil. Mas com a abundância de material, consegui deixar funcionando de uma forma que me deixou bem satisfeito.

Decidi então voltar para Haskell e aplicar este conhecimento que acabei de conquistar.

Que trabalheira! Fazer tudo se encaixar da maneira certa e com os tipos corretos me fez suar algumas camisas. Se não fosse tão teimoso, não teria conseguido. Tive que trocar o pacote OpenGL pelo OpenGLRaw pois o pacote OpenGL não tem binding para "glUniformMatrix*"; também troquei o GLFW pelo GLFW-b porque o primeiro dependia do pacote OpenGL.

Recentemente li um artigo no InfoQ questionando se a linguagem C ainda era útil. Me fez pensar em muita coisa, inclusive em abandonar definitivamente Haskell e voltar às origens: ponteiros, gerenciamento de memória manual, strings cabulosas, ...

Talvez seja porque sou muito novo neste mundo, mas me sinto meio improdutivo programando em Haskell. A impressão que tenho é que, a menos que seja algo muito bem consolidado e utilizado por muitas pessoas da comunidade, muito tempo será gasto atrás de bibliotecas que normalmente são apenas um binding para uma escrita em C. Quando não for isto, só para ter uma breve ideia do que a API de uma biblioteca faz, acabo lendo alguns papers relembrando o que são Functors e Monads (para entender Arrows acho que vou precisar comprar um livro!).

Entender ponteiros e gerenciamento de memória em C também foi difícil. Mas naquela época eu era um jovenzinho no mundo da programação, meu conhecimento era muito limitado, se baseava muito em bater no código até que ele fizesse o que eu queria. Agora não consigo mais aceitar fazer alguma coisa sem entender o que estou fazendo.

Eu lembro que tive bons motivos para trocar C por Java. Depois de passar um tempo utilizando Java, vejo também as partes negativas da linguagem. Meu trabalho de conclusão da graduação foi dedicado a evidenciar os motivos desta troca e do futuro promissor de linguagens como Haskell.

No final das contas, acredito que esta pseudo-decepção acontece com toda linguagem em que você dedica bastante tempo para aprendê-la. A propaganda e os primeiros passos são maravilhosos. Parece que você descobriu um novo mundo onde todos são ricos e podem desfrutar de todas as coisas boas. Com o tempo você percebe que não é bem assim: cada escolha que a linguagem fez significa pontos positivos e negativos para nós usuários. Foi assim com C, Python, Java e agora com Haskell.

Java escolheu uma abordagem orientada a objetos: mesmo que seja uma função estática ou uma variável global, será necessária a roupagem de uma classe para acomodar este código. Haskell escolheu o funcional puro: mesmo operações que envolvam I/O recebem a roupagem de um monad.

Não que isto seja ruim. A utilização de monads para modelar computação sequencial foi uma grande descoberta que proporcionou a criação de inúmeras soluções. Estas escolhas trazem pontos positivos e negativos. Só devemos ter cuidado com o purismo extremo para não criar aberrações (e.g. Singleton).

Enfim, estou agoniado pois não sei o que escolher:

  • C me parece flexível mas com as complexidades que não quero ter. C11 parece solucionar algumas coisas (e.g. <threads.h>), mas o GCC ainda não suporta;
  • Sou bem produtivo em Java mas o overhead obrigatório e a necessidade de ter uma JVM me irritam profundamente;
  • C++ ainda é uma incógnita para mim, se for para ir neste nível ainda prefiro utilizar C. Java já me fez ver os motivos para dizer não à O.O. Por outro lado, boa parte de quem utilizava C está indo para C++, inclusive o GCC está utilizando C++. As bibliotecas disponíveis para C++ também pesam bastante (e.g. GLM, Boost);
  • Haskell é minha linguagem da vez mas está começando a me incomodar um pouco, sentimento de improdutividade me deixa muito tenso. Talvez precise dedicar mais tempo para que isto passe. Não sei se este esforço irá valer a pena ou poderia ser melhor aproveitado aprimorando meus conhecimentos em outra linguagem.

A diferença de desempenho entre C e Haskell não me preocupa muito, pois não vou escovar bits para ter um bom desempenho tão cedo.

No final das contas, a opinião mais correta sempre é: depende do projeto, escolha a melhor linguagem para o seu projeto. Pois bem, não tenho projeto nenhum, mas quero me aprofundar no desenvolvimento de jogos.

E agora?

9 de janeiro de 2013

DuDuHoX com som: OpenAL

Trabalhar no DuDuHoX está cada vez mais difícil. Já pensei em desistir em alguns problemas que tive, seja com código ou com bibliotecas.

Colocar animação no jogo foi muito complicado. Porque o motor do jogo não tem estados intermediários em que o personagem está indo de uma casa para outra. O estado do jogo só permite que o personagem ocupe uma casa e não metade de uma e metade de outra.

Minha solução para isto foi colocar a informação de animação no código da interface gráfica (OpenGL). Após pressionar alguma tecla que movimente o personagem, o motor do jogo já estará no estado novo completo, mas a interface não aceitará mais nenhuma entrada enquanto não terminar de exibir a animação de movimentação.

Outro grande problema que tive foi quando decidi colocar sons no jogo. Meus objetivos são: música de fundo e sons posicionais. A biblioteca que achei para isto foi a OpenAL. Ela é feita em C mas existe o pacote Haskell que faz o binding.

Mas, nem tudo é tão bonito. Instalar a biblioteca OpenAL no Windows foi um tremendo caos. Foi uma tarde inteira tentando fazer isto. Quando finalmente consegui instalar, percebi que também iria precisar da biblioteca ALUT. Esta não foi tão difícil de instalar, porém, ocorriam erros de link durante a compilação. Felizmente encontrei um bug reportado para o GHC que tratava exatamente do meu problema: Ticket 1243. Depois de aplicar o patch, tudo funcionou.

Fiz uma música no OpenMPT, desenhei o personagem no GIMP e coloquei tudo dentro do jogo.

Apesar das dificuldades, estou muito feliz com resultado que estou obtendo em fazer o DuDuHoX em Haskell. Decidi gravar um gameplay com o CamStudio e compartilhar com vocês através do YouTube:

31 de dezembro de 2012

OpenGL em Haskell

Continuo trabalhando no DuDuHoX.

A primeira interface do jogo é em console. Utiliza o pacote System.Console.ANSI. Funciona muito bem em Linux e Mac OS, apenas no Windows tem um pequeno problema de buffer, forçando com que o jogador tenha que apertar enter após cada jogada.

DuDuHoX versão console

Esta semana decidi fazer uma nova interface para o jogo: com gráficos e sem o problema de buffer do console do Windows.

Primeiramente tentei utilizar SDL, pois foi ela que utilizei para fazer DuDuRoX em C. Infelizmente, não tive sucesso nenhum, nem mesmo consegui fazer um exemplo compilar.

Pensei em utilizar diretamente OpenGL para o gráfico, mas não sabia o que utilizar para gerenciar a entrada do usuário (apertar de teclas). Já vi reclamações a respeito de OpenGL/GLUT. Foi então que depois de algumas pesquisas conheci o GLFW, um biblioteca C open-source que fazia tudo que eu precisava, e o mais importante: existe o pacote GLFW para Haskell que faz o binding.

Depois de algumas horas aprendendo OpenGL e GLFW para Haskell, o resultado é este:

DuDuHoX versão gráfica

Já está jogável com esta interface, porém a versão console possui mais funcionalidades. Então, para as próximas semanas, vou tentar passar todas as funcionalidades da versão console para a versão gráfica e adicionar algumas texturas bacanas. A lista de coisas a fazer e todo o código está disponível no GitHub: https://github.com/thiago-negri/DuDuHoX.

Feliz 2013!

15 de agosto de 2012

Programação funcional pura e útil

Acho que todos os programadores acostumados com o modelo imperativo ficam espantados em imaginar quão maluco é o modelo funcional puro. Afinal, como podemos fazer qualquer coisa útil sem gerar efeitos colaterais? Sem imprimir no console? Sem abrir uma tela? Sem gerar logs? Sem alterar variáveis? OMG.

Este também foi um espanto meu ao conhecer Haskell e por uma feliz insistência descobri que o problema está na cabeça do programador e não no modelo funcional puro.

Diferenças

Viver muito tempo no contexto das linguagens imperativas acabou modelando minha forma de pensar em uma maneira sequêncial. Eu sempre precisei dizer como fazer cada passo da computação, quais estados manter, quais variáveis alterar, quando retornar um valor, etc.

Enquanto que no mundo funcional puro, fui obrigado a pensar em uma maneira de transformação de dados. Ao invés de pensar em como fazer cada etapa, preciso pensar como transformar um dado em outro, i.e. como extrair a representação que quero a partir do dado que tenho.

Os exemplos clássicos para demonstrar esta diferença envolve a manipulação de listas.

Exemplo #1

Para calcular a somatória no estilo imperativo, é necessário pensar no controle sobre o estado da soma atual, que deve ser acumulado durante a iteração da lista.

int sum(int[] list) {
  int result = 0;
  for (int i : list)
    result += i;
  return result;
}

Do outro lado, no mundo funcional, é necessário pensar em como transformar uma lista em um único número que represente a soma de todos os elementos, i.e. a soma de uma lista vazia sempre será zero e a soma de qualquer outra lista é definida pelo seu primeiro valor acrescentado da soma do restante da lista.

sum [] = 0
sum (x:xs) = x + sum xs

Exemplo #2

Já em um exemplo um pouco mais complexo, em que precisamos manter duas listas no mundo imperativo, uma de entrada e outra de saída, precisamos incluir o controle sobre o índice de cada lista, para evitar erros de indexação.

String[] toText(int[] list) {
    String[] result = new String[list.length];
    for (int i = 0; i < list.length; ++i)
        result[i] = Integer.toString(list[i]);
    return result;
}

Enquanto que o mesmo exemplo no mundo funcional continua simples como a somatória da lista, a única diferença é que ao invés de transformarmos a lista utilizando a adição, utilizamos o próprio construtor de listas (representado pelo operador ':').

toText [] = []
toText (x:xs) = show x : toText xs

Motivação

Para demonstrar que o modelo funcional puro pode fazer muitas coisas além dos exemplos clássicos, de uma forma que considero bonita e com vantagens gratuitas, escrevo este post.

Calculadora

Hoje vamos criar uma calculadora em Haskell. Todo o código será puro, sem nenhum tipo de efeito colateral, incluindo uma DSL para escrever testes, o próprio executor dos testes e dois transformadores de cenários de teste, um para escrever o teste no formato de texto e outro para escrever o mesmo teste no formato de um JUnit.

Formato dos testes

Primeiramente, definimos o formato em que os testes serão escritos. Neste caso, será uma sequência de ações e validações:

data TestSequence =
    Do Action TestSequence
  | Check Assertion TestSequence
  | Done

Com esta estrutura de dados, os testes poderão ser escritos neste formato:

test =
  Do a.
  Do b.
  Do c.
  Check d$
  Done

A necessidade de ter o "Done" no final do teste me incomoda. Para evitar que precise escrever isto, indicarei que os testes sempre terminem de forma aberta, i.e. permitindo continuar a sequência:

type Test = TestSequence -> TestSequence

Agora podemos escrever o teste da seguinte forma:

test =
  Do a.
  Do b.
  Do c.
  Check d

Para mim, já está classificada como uma DSL funcional para testes.

O único tipo de ação disponível na calculadora é o de apertar seus botões e a única validação será a verificação do seu display. Os botões disponíveis são os padrões de uma calculadora.

data Action =
    Press Button

data Assertion =
    DisplayHasNumber Int

data Button =
    Zero
  | One
  | Two
  | Three
  | Four
  | Five
  | Six
  | Seven
  | Eight
  | Nine
  | Plus
  | Minus
  | Times
  | Divide
  | Equals
  | Clear
    deriving (Show)

Primeiro teste

O primeiro passo está pronto. Já temos uma DSL para testes compilando. Hora de escrever nosso primeiro teste, que também já compila:

sample :: Test
sample =
    Do (Press One).
    Do (Press Plus).
    Do (Press One).
    Do (Press Equals).
    Check (DisplayHasNumber 2).

    Do (Press Clear).
    Check (DisplayHasNumber 0).

    Do (Press Two).
    Do (Press Zero).
    Check (DisplayHasNumber 20).
    Do (Press Divide).
    Do (Press Two).
    Check (DisplayHasNumber 2).
    Do (Press Equals).
    Check (DisplayHasNumber 10)

Transformadores do teste

Como nosso teste está escrito na forma de uma estrutura de dados, isto nos dá muitas possibilidades para tratamento do caso de teste. Para facilitar as transformações desta estrutura, definimos uma função que aplica uma função genérica a cada passo do teste e agrupa os resultados em uma lista:

unroll :: (TestSequence -> a) -> Test -> [a]
unroll f t = g (t Done)
  where g Done = [f Done]
        g v@(Do _ next) = f v : g next
        g v@(Check _ next) = f v : g next

Teste -> Texto

Podemos processar o teste e gerar uma saída textual do cenário:

prettyPrint :: Test -> String
prettyPrint = unlines . unroll prettyPrintTestSequence

prettyPrintTestSequence :: TestSequence -> String
prettyPrintTestSequence s =
    case s of
      Done              -> "end"
      Do action _       -> prettyPrintAction action
      Check assertion _ -> prettyPrintAssertion assertion

prettyPrintAction :: Action -> String
prettyPrintAction (Press button) = 
    "press " ++ prettyPrintButton button

prettyPrintButton :: Button -> String
prettyPrintButton = map toLower . show

prettyPrintAssertion :: Assertion -> String
prettyPrintAssertion (DisplayHasNumber number) = 
    "the display should be showing the number " ++ 
    show number

Testando no GHCi:

\> putStr $ prettyPrint sample
press one
press plus
press one
press equals
the display should be showing the number 2
press clear
the display should be showing the number 0
press two
press zero
the display should be showing the number 20
press divide
press two
the display should be showing the number 2
press equals
the display should be showing the number 10
end

Teste -> JUnit

Também podemos gerar casos de teste para uma possível solução Java da nossa calculadora:

generateJUnit :: Test -> String
generateJUnit = 
    ("@Test\npublic void test() {\n" ++) . 
    unlines .
    unroll generateJUnitTestSequence

generateJUnitTestSequence :: TestSequence -> String
generateJUnitTestSequence s =
    case s of
      Done      -> "}"
      Do a _    -> generateJUnitAction a
      Check a _ -> generateJUnitAssertion a

generateJUnitAction :: Action -> String
generateJUnitAction (Press b) =
    generateJUnitButton b ++ ".press();"

generateJUnitButton :: Button -> String
generateJUnitButton b = "getButton" ++ show b ++ "()"

generateJUnitAssertion :: Assertion -> String
generateJUnitAssertion (DisplayHasNumber n) =
    "assertEquals(" ++ show n ++ ", getDisplayNumber());"

Testando no GHCi:

\> putStr $ generateJUnit sample
@Test
public void test() {
getButtonOne().press();
getButtonPlus().press();
getButtonOne().press();
getButtonEquals().press();
assertEquals(2, getDisplayNumber());
getButtonClear().press();
assertEquals(0, getDisplayNumber());
getButtonTwo().press();
getButtonZero().press();
assertEquals(20, getDisplayNumber());
getButtonDivide().press();
getButtonTwo().press();
assertEquals(2, getDisplayNumber());
getButtonEquals().press();
assertEquals(10, getDisplayNumber());
}

Teste de fato

E claro, também podemos utilizar nosso teste no formato de uma estrutura de dados para realmente testar uma calculadora:

data TestResult =
    Ok
  | Failed FailureMessage

type FailureMessage = String

instance Show TestResult where
    show Ok = "Test passed"
    show (Failed m) = "Test failed: " ++ m

checkTest :: Test -> TestResult
checkTest t = 
    evalState 
    (threadCheckState . unroll checkTestSequence $ t)
    mkCalculator

threadCheckState :: [State Calculator TestResult] ->
                    State Calculator TestResult 
threadCheckState = go 0
  where go _ [] = return Ok
        go n (x:xs) = x >>= (f n xs)
        f n xs Ok = go (n + 1) xs
        f n _ (Failed m) = 
          return . Failed $
            "Step " ++ show n ++ ". " ++ m

checkTestSequence :: TestSequence -> 
                     State Calculator TestResult
checkTestSequence Done = return Ok
checkTestSequence (Do a _) = checkAction a
checkTestSequence (Check a _) = checkAssertion a

checkAction :: Action -> State Calculator TestResult
checkAction (Press b) = do
    modify $ pressButton b 
    return Ok

checkAssertion :: Assertion -> State Calculator TestResult
checkAssertion (DisplayHasNumber n) =
    get >>= \c ->
      if displayNumber c == n
        then return Ok
        else return . Failed $ 
          "Wrong number in display, should be " ++
          show n ++ " but was " ++ show (displayNumber c)

O coração da calculadora

Finalmente, para que o testador-de-fato compile, precisamos definir uma primeira versão da nossa calculadora. Aqui você poderia tentar criar sua própria implementação. Cheguei neste código para atender ao cenário do teste:

data Calculator = Calculator {
    displayNumber :: Int
  , operation :: Maybe (Int -> Int -> Int)
  , savedNumber :: Int
  }

pressButton :: Button -> Calculator -> Calculator
pressButton b =
  case b of
    Zero    -> appendNumber 0
    One     -> appendNumber 1
    Two     -> appendNumber 2
    Three   -> appendNumber 3
    Four    -> appendNumber 4
    Five    -> appendNumber 5
    Six     -> appendNumber 6
    Seven   -> appendNumber 7
    Eight   -> appendNumber 8
    Nine    -> appendNumber 9
    Plus    -> saveOperation (+)
    Minus   -> saveOperation (-)
    Times   -> saveOperation (*)
    Divide  -> saveOperation div
    Equals  -> performOperation
    Clear   -> clear

appendNumber :: Int -> Calculator -> Calculator
appendNumber i c = 
  c { 
    displayNumber = (displayNumber c) * 10 + i 
  }

saveOperation :: (Int -> Int -> Int) -> 
                 Calculator -> Calculator
saveOperation f c = 
  c { 
    savedNumber = (displayNumber c)
  , displayNumber = 0
  , operation = Just f
  }

performOperation :: Calculator -> Calculator
performOperation c = 
  c { 
    savedNumber = newNumber
  , displayNumber = newNumber 
  }
  where newNumber = 
          case (operation c) of
            Nothing -> displayNumber c
            Just f  -> let a = savedNumber c
                           b = displayNumber c 
                        in
                           f a b

clear :: Calculator -> Calculator
clear = const mkCalculator

mkCalculator :: Calculator
mkCalculator = 
  Calculator { 
    displayNumber = 0
  , operation = Nothing
  , savedNumber = 0
  }

Testando a calculadora

Agora podemos executar o testador-de-fato contra nossa calculadora no GHCi. Vamos experimentar:

\> checkTest sample
Test passed

Isto significa que nossa calculadora está atendendo ao cenário de teste. Para garantir que o teste capture um comportamento que não corresponda ao cenário de teste, podemos introduzir um erro: alteramos o valor da multiplicação na função "appendNumber" de "10" para "100". Se rodarmos novamente o teste, teremos este resultado:

\> checkTest sample
Test failed: Step 9. Wrong number in display, should be 20 but was 200

Magavilha.

Conclusão

Construímos um código básico de uma calculadora funcionando que atende ao especificado no cenário de teste. Agora basta adicionarmos novos testes e continuar incrementando nossa calculadora, parecido com um TDD. Tudo isto foi feito de uma forma 100% pura, sem depender de nada que envolva IO, estado, variáveis ou logs.

Além do benefício de podermos fazer qualquer tipo de transformação nos cenários de teste, também podemos rodar quantas transformações e quantos testes quisermos de forma paralela, pois tanto o teste quanto a calculadora são thread-safe pelo simples fato de serem escritos de forma pura.

Conseguimos fazer tudo isto em míseras 251 linhas de código ou 5635 caracteres, incluindo comentários e linhas em branco. Quantas classes e/ou linhas seriam necessárias para contemplar todas as funcionalidades e seguranças demonstradas neste post na sua linguagem imperativa favorita?

Espero que este post sirva para inspirar vossas mentes a pensar "fora-da-caixa" do mundo imperativo. Existem muitas vantagens em aprender uma linguagem funcional pura, mesmo que você utilize linguagens imperativas para trabalhar.

O código inteiro está disponível no GitHub: https://gist.github.com/3354394. Para testar, é só baixar o código e carregar no GHCi.

Exemplos reais

Apenas para finalizar, alguns exemplos de aplicativos reais em uso desenvolvidos em Haskell:

12 de agosto de 2012

Criando um jogo de labirinto em Haskell

Quando eu estava aprendendo a programar em C, eu fiz vários pequenos projetos para colocar meu conhecimento à prova.

É muito bom olhar estes códigos antigos e perceber que aprendi muito nestes anos. Também é prazeroso ver que mesmo sem conhecer quase nada, consegui fazer muitas coisas divertidas.

Uma das primeiras coisas que fiz em C, foi um jogo de labirinto para terminal do Windows. Fiquei inacreditavelmente feliz e me achando muito inteligente por conseguir fazer um jogo assim.

Após algum tempo já programando, depois de conhecer a biblioteca SDL para criar jogos gráficos em C, o joguinho de labirinto chegou na sua terceira versão: DuDuRoX.

Como agora estou aprendendo Haskell e quero manter a tradição, irei criar um novo DuDuRoX nesta linguagem: DuDuHoX. Notem a pequena variação no nome demonstrando minha imensa criatividade.

Quem quiser acompanhar o progresso e fazer comentários, todo o código está disponível no GitHub (link no parágrafo acima).

Bastantes coisas já estão funcionando, até já é possível jogar. Quem quiser experimentar, é só baixar a plataforma Haskell, fazer um clone do repositório, executar o comando cabal install na pasta do jogo e ele estará disponível no seu terminal como "DuDuHoX".

Quem quiser dar uma olhada em códigos antigos, meus primeiros passos na programação estão postados no site Viva O Linux:

Alguns códigos mais "avançados", acabei não compartilhando, porém tenho eles salvos no meu computador, se alguém se interessar posso compartilhar também:

  • MultiMines: Jogo de campo minado.
  • HChess: Jogo de xadrez.
  • ObjectDetector: Tratador de imagem para realçar as bordas de objetos.
  • LConv: Conversor de "linguagens", a partir de uma pseudo-linguagem, o usuário conseguia definir regras de criptografia e aplicar a textos.
  • GATesting: Algoritmo genético para encontrar qualquer formula matemática que trouxesse como resultado o valor informado.