Avançar para a área de conteúdo

ir para o conteúdo principal

developerWorks Brasil  >  Linux  >

Desenvolva Aplicativos Baseados em eSWT para o Telefone Inteligente Nokia S60

Aprenda a Escrever um Jogo Sudoku Remoto para a Plataforma S60

developerWorks
Ir para a página anteriorPágina 4 de 10 Ir para a próxima página

Opções de documento

Código de amostra


Classificar este tutorial

Ajude-nos a melhorar este conteúdo


Escrevendo o Jogo Sudoku

Após ter instalado e configurado todas as ferramentas de pré-requisito, você pode configurar o desenvolvimento do jogo Sudoku (consulte Download para conhecer o código fonte). Caso não esteja familiarizado com o Sudoku, ele é um quebra-cabeça numérico baseado em lógica que se tornou absolutamente popular nos últimos anos. Ele consiste em uma grade com nove linhas e nove colunas. O objetivo é preencher as células da grade com números de 1 a 9 de modo que cada número ocorra exatamente uma vez em cada linha, coluna e em cada uma das nove caixas 3x3. Inicialmente, a grade é preenchida parcialmente (o suficiente para fornecer um conjunto inicial de limitadores). As células pré-preenchidas não podem ser modificadas.

Em vez de gravar todo o código do zero, você pode utilizar uma das implementações disponíveis gratuitamente como ponto de partida. O projeto Exemplos do Eclipse fornece uma implementação baseada no Eclipse RCP. Um ponto de partida ainda melhor é a versão do eRCP do mesmo exemplo disponível no repositório do projeto DSDP. Embora o código por trás de um simples jogo Sudoku não seja muito complexo, o uso de uma implementação existente permite que você direcione o foco para as tarefas relevantes para este tutorial, como adaptar o código para execução na CLDC e utilizar a API do eSWT.

Criando o MIDlet do Sudoku

Para implementar um aplicativo Java na plataforma S60, ele deve ser construído e empacotado como um conjunto do MIDlet. O Eclipse MTJ permite que você crie novo projetos MIDlet utilizando um simples assistente. Na perspectiva do Java ME, clique em Arquivo > Novo > Projeto MIDlet. Insira o nome do projeto apropriado (por exemplo, sudoku) e certifique-se de que o Dispositivo de Destino correto seja especificado (isto é, o SDK da Plataforma S60 como o SDK e o S60Emulator como o Dispositivo). A Figura 7 mostra a página do assistente preenchida com valores de amostra. Quando você clicar em Concluir, você deverá ver o editor Descritor de Aplicativo aberto no ambiente de trabalho.


Figura 7. Assistente Novo Projeto MIDlet
Assistente Novo Projeto MIDlet

O editor Descritor de Aplicativo é utilizado para editar o arquivo JAD do projeto. Inicialmente, a única parte preenchida com dados é a página de visão geral, conforme mostrado na Figura 8. Você pode modificar o nome do MIDlet, seu fornecedor e sua versão, se quiser. Um clique na guia Descritor do Aplicativo no editor revela a visualização textual do arquivo JAD.


Figura 8. O Editor Descritor de Aplicativo do MIDlet
O Editor Descritor de Aplicativo do MIDlet

A próxima etapa lógica no desenvolvimento é criar a classe MIDlet real. No Project Explorer, clique com o botão direito do mouse na pasta src no projeto MIDlet e escolha Novo > MIDlet do Java ME no menu de contexto. Insira o nome do pacote desejado (por exemplo, sudoku) e especifique o nome de classe do MIDlet (por exemplo, Sudoku). A Figura 9 mostra a página do assistente preenchida. Quando você clica em Concluir, o assistente cria uma implementação do esqueleto do MIDlet para você. Além disso, o MIDlet recém-criado é incluído no Descritor de Aplicativo. Você pode verificar isso na página MIDlets do editor.


Figura 9. Assistente Novo MIDlet
Assistente Novo MIDlet

Antes de ir direto ao eSWT, você deve gravar um código para implementar a lógica do jogo Sudoku. Utilizando o exemplo do Sudoku do eRCP como ponto de partida, você precisa de classes que modelem o estado do jogo. Por exemplo, uma Célula representa uma célula de grade única. Ela possui um valor atual (isto é, entre 1 e 9, inclusivo); uma célula com o valor de 0 é considerada vazia. Uma Célula pode ser "fornecida", o que significa que ela vem pré-preenchida quando o jogo começa e não pode ser alterada pelo usuário. Uma Célula é considerada "válida" quando a linha, a coluna e a caixa às quais ela pertence são válidas. Por fim, uma Célula também pode fornecer uma lista de valores válidos, que são, essencialmente, qualquer valor que ainda não esteja na linha, na coluna ou na caixa da célula.

Outras classes de modelo incluem Coluna, Linhae Caixa, sendo que cada uma é uma representação direta do que elas significam. Uma Coluna representa nove células verticais na grade. Ela é válida quando não contém valores duplicados. Da mesma forma, uma Linha representa nove células horizontais na grade e é válida quando não contém valores duplicados. Uma Caixa representa uma região de células 3x3 sem sobreposição no painel. Ela também é válida quando contém somente valores exclusivos.

A classe SudokuBoard serve de contêiner da Célula, da Coluna e da Linha. Ela monitora Colunas, Linhas e Caixas inválidas (o painel é válido quando nenhuma delas é inválida). Ele é concluído quando não existem mais Células vazias. SudokuBoard também fornece métodos para se acessar várias partes de um painel, como a Célula por seu número de coluna e linha, bem como para determinar à qual Caixa uma Célula pertence. A classe SudokuBoard também suporta um mecanismo notificação simples: Objetos interessados em ser notificados sobre mudanças no painel podem se registrar no painel como listeners, contanto que implementem a interface SudokuBoardStateListener. Esse mecanismo é útil para separar a lógica do jogo de sua UI.

Finalmente, SudokuGame serve de ponto de entrada para o modelo de jogo. Ele fornece os meios para se criar novos painéis, bem como para se obter board factories e solucionadores disponíveis. Um SudokuBoardFactory é uma abstração de um algoritmo gerador de painel. Da mesma forma, um SudokuBoardSolver é uma abstração de um algoritmo solucionador do Sudoku. Uma simples implementação de um solucionador de backtracking é fornecida como gerador e solucionador. SudokuBoard também suporta um mecanismo de notificação (através de SudokuBoardChangeListener registrado) para notificar os clientes sobre mudanças no jogo, como, por exemplo, quando um novo painel for criado.

Ao comparar o código adaptado do exemplo do eRCP com a implementação original, você vai observar que alguns deles devem ser refatorados para a CLDC. Especificamente, todos os usos das classes de Coleta Java tiveram que ser refatorados para o uso de uma classe Vector muito mais simples. Da mesma forma, todos os usos de Agente Iterativo tiveram que ser convertidos em Enumeração.



Voltar para parte superior


Codificando a UI com o eSWT

Até aqui, você não encontrou nenhuma referência ao eSWT. O código de exemplo mantém uma boa separação de interesses. Você já aprendeu que todos os widgets do eSWT existem no contexto de uma exibição. O encadeamento que cria a instância Display do singleton se torna o encadeamento de exibição, e todas as chamadas de API do eSWT devem ser feitas de dentro dele. Além de criar widgets e controles, a tarefa do encadeamento de exibição é processar quaisquer eventos e mensagens postados nele. Quando a exibição é descartada, o encadeamento é finalizado e o aplicativo pára.

Como o tempo de vida do MIDlet é gerenciado pela estrutura, você não pode criar sua exibição e implementar seu loop de eventos da UI no método startApp() do MIDlet. Isso interceptaria efetivamente o encadeamento de chamada, que pertence ao gerenciador de aplicativos, e o bloquearia até depois do encerramento do aplicativo. Além disso, seu MIDlet deve criar um encadeamento separado e utilizá-lo como o encadeamento de exibição designado. Retornando à classe do MIDlet, você deve criar e iniciar uma nova instância de Encadeamento na primeira vez que o método startApp() for chamado. O método de execução do encadeamento é o lugar certo para você criar sua exibição e implementar o loop de eventos da interface com o usuário. A Listagem 2 mostra como isso é feito.


Listagem 2. Implementação do MIDlet Inicial com Encadeamento de Exibição do eSWT e Loop de Eventos da Interface com o Usuário

public class Sudoku extends MIDlet implements Runnable {

    private Thread thread;
    private Display display;
    private MobileShell shell;

    protected void startApp() throws MIDletStateChangeException {
        if (thread == null) {
            thread = new Thread(this);
            thread.start();
        }
    }

    public void run() {
        display = new Display();
        shell = new MobileShell(display);
        shell.setLayout(new FillLayout());
        shell.setText("Sudoku");
        shell.open();

        try {
            while (!shell.isDisposed()) {
                if (!display.readAndDispatch())
                    display.sleep();
            }
        } finally {
            display.dispose();
            notifyDestroyed();
        }
    }

    protected void pauseApp() {
        // não faz nada
    }

    protected void destroyApp(boolean unconditional)
            throws MIDletStateChangeException {
        display.asyncExec(new Runnable() {
            public void run() {
                if (!shell.isDisposed())
                    shell.close();
            }
        });
    }
}

O método run do encadeamento de exibição começa criando a Exibição e seu shell de nível superior. A classe MobileShell pode ser utilizada no lugar do shell padrão do SWT porque fornece recursos específicos para telefone móvel, como texto de status, modo tela cheia e mudanças de borda dinâmicas. O shell recém-criado pode ser utilizado como contêiner para todos os outros widgets e gráficos utilizados em todo o aplicativo. E o mais notável é que a interface com o usuário do jogo é implementada na classe SudokuView, que cuida da criação de widgets necessários na inicialização e no descarte dos recursos após a finalização. Além disso, você deve instanciar essa classe e permitir que ela crie seus widgets chamando seu método createPartControl(Composite), passando o shell de nível superior como seu contêiner. A Listagem 3 demonstra como fazer isso.


Listagem 3. Instanciando SudokuView e Descartando-o Quando o Shell For Descartado

final SudokuView view = new SudokuView();
view.createPartControl(shell);
view.setFocus();
shell.addDisposeListener(new DisposeListener() {
    public void widgetDisposed(DisposeEvent event) {
        view.dispose();
    }
});

Com os widgets necessários, você deve fornecer ao usuário uma maneira de encerrar o jogo. A classe Command fornece apenas esse tipo de funcionalidade; ela permite que você anexe um listener de seleção a um dos botões padrão do telefone móvel. O botão Sair é o lugar perfeito para o comando exit. Quando esse comando for invocado (ou seja, o botão for pressionado), o shell de nível superior deve ser simplesmente fechado. A Listagem 4 mostra o trecho de código para a criação do comando Exit.


Listagem 4. Criando o Comando Exit

Command exitCmd = new Command(shell, Command.EXIT, 0);
exitCmd.setText("Exit");
exitCmd.addSelectionListener(new SelectionListener() {
    public void widgetDefaultSelected(SelectionEvent event) {
        // não faz nada
    }

    public void widgetSelected(SelectionEvent event) {
        shell.close();
    }
});

Depois que tiver cuidado da criação do widget e da configuração do comando, você poderá abrir o shell e executar o loop de eventos da interface com o usuário padrão. Contanto que o shell não seja descartado (isto é, como resultado de um fechamento), você deverá ler e despachar eventos da interface com o usuário (chamando o método readAndDisplay()). Quando esse método retornar false, você poderá colocar a exibição no modo suspenso (chamando sleep()), que a faz aguardar até ser reativada por um evento recém-chegado. Quando o shell de nível superior for descartado, você deverá descartar a exibição e sair do encadeamento.

Normalmente, o aplicativo é encerrado quando o usuário opta por fazer isso. Entretanto, quando é executado como um MIDlet, pode ser solicitado que o aplicativo seja encerrado pelo gerenciador de aplicativos (por exemplo, em um esforço para liberar alguns recursos, etc). Além disso, o método destroyApp(boolean) do MIDlet deve sinalizar para o encadeamento de exibição que ele deve ser encerrado. Uma maneira de se fazer isso seria postar uma mensagem para o encadeamento de exibição pedindo ao shell de nível superior para fechar, parando assim o loop de eventos, descartando a exibição e encerrando o encadeamento.

Como você pode ver na Listagem 2, esse tipo de implementação de MIDlet é mais genérica e poderia suportar um aplicativo completamente diferente, contanto que toda a construção da sua UI e o processamento de eventos do usuário estejam encapsulados em uma classe do tipo SudokuView. De fato, a classe SudokuView trata da criação dos widgets necessários para desenhar o painel do jogo Sudoku, criando comandos adicionais para permitir que o usuário controle o jogo e conectando o listener do evento para tratar de eventos do teclado, bem como redimensionando e repintando. Esse padrão é resultado da refatoração do exemplo de Sudoku do eRCP, que exibe o jogo em uma visualização do eRCP, e não em um shell de nível superior (embora o conceito de contêiner separado seja aplicável).

Como o Sudoku tem natureza gráfica, sua interface com o usuário é desenhada através de primitivas de Graphical Context (GC), como linhas, planos de fundo e texto. A implementação do eRCP já fornece três classes "desenhistas" — a CellDrawer para desenhar células individuais, a BoxDrawer para desenhar caixas e BoardDrawer para desenhar o painel do jogo Sudoku inteiro. A classe DrawingContext é usada para gerenciar recursos gráficos, como cores e fontes utilizados pelas operações de desenho. O desenho do painel consiste em desenhar cada uma de suas nove caixas e todas as suas 81 células. O painel também acompanha e desenha a célula em destaque — aquela com que o usuário está interagindo no momento. Desenhar a caixa envolve o preenchimento de seu retângulo com uma cor do plano de fundo (utilizando setBackground(Color) e fillRectangle(int, int, int, int de GC), respectivamente).

Por fim, desenhar uma célula consiste em desenhar seu quadro utilizando drawRectangle(int, int, int, int), bem como seu valor atual utilizando drawText(String, int, int, int). A fonte do texto é configurada através de setFont(Font) e a cor de primeiro plano através de setForeground(Color). As células em destaque (com o mouse sobre elas) são desenhadas com uma cor diferente e com uma linha mais grossa, e seus valores disponíveis são impressos em texto pequeno junto com o quadro da célula. A Listagem 5 mostra o corpo do método drawCell de CellDrawer.


Listagem 5. Método drawCell de CellDrawer Utilizando a API de GC para Desenhar a Célula

public void drawCell(GC gc, DrawingContext context) {
    if (cell.isMarked()) {
        gc.setBackground(context.getMarkedCellColor());
        gc.fillRectangle(left, top, cellSize, cellSize);
    } else if (!cell.isValid()) {
        gc.setBackground(context.getInvalidCellColor());
        gc.fillRectangle(left, top, cellSize, cellSize);
    }

    gc.setLineWidth(1);
    gc.setForeground(context.getFrameColor());
    gc.drawRectangle(left, top, cellSize, cellSize);

    if (!cell.isEmpty()) {
        if (cell.isGiven())
            gc.setFont(context.getStaticCellFont(cellSize / 3));
        else
            gc.setFont(context.getCellFont(cellSize / 3));

        String text = String.valueOf(cell.getValue());
        Point textExtent = gc.textExtent(text);

        try {
            gc.drawText(text, left + (cellSize - textExtent.x) / 2,
                top + (cellSize - textExtent.y) / 2,
                SWT.DRAW_TRANSPARENT);
        } finally {
            gc.setFont(null);
        }
    }
}

O GC utilizado para desenhar o painel é obtido através da implementação de PaintListener em um widget de tela. A classe SudokuView cria esse controle como um filho direto do shell de nível superior. Ela também conecta um PaintListener a ele. O painel é então desenhado durante o processamento do evento de pintura da tela, que fornece acesso a seu contexto gráfico.

A interação do usuário com o jogo é manipulada através de uma combinação de KeyListener e outro comando. O KeyListener é usado para navegação no painel quando o usuário pressiona um botão de navegação direcional (esquerda, direita, para cima e para baixo). Ele também manipula o teclado numérico — o pressionamento de um número configura a célula atualmente em destaque para esse valor. Um comando select é utilizado quando o usuário pressiona o botão Selecionar (no meio do teclado de navegação). Isso aumenta em um o valor da célula atualmente em destaque. A Listagem 6 ilustra como conectar um listener de evento principal para tratar da entrada do teclado numérico.


Listagem 6. Snippet KeyListener Mostrando como Responder à Navegação com Seta

canvas.addKeyListener(new KeyListener() {
    public void keyPressed(KeyEvent e) {
        Cell hoverCell = drawer.getHighlightedCell();
        if (hoverCell == null)
            return;

        if (e.character == SWT.BS || e.character == SWT.DEL) {
            hoverCell.clear();
        } else if (Character.isDigit(e.character)) {
            hoverCell.setValue(e.character - '1' + 1);
        } else if (e.keyCode == SWT.SHIFT) {
            hoverCell.mark();
        } else if (e.keyCode == SWT.ARROW_LEFT) {
            Cell cell = hoverCell;
            do {
                int column = cell.column==0 ? 8 : cell.column-1;
                cell = board.getCell(cell.row, column);
            } while (cell.isGiven());

            drawer.highlightCell(cell);
            canvas.redraw();
...



Voltar para parte superior



Ir para a página anteriorPágina 4 de 10 Ir para a próxima página