Jogo da Memória com Apache Pivot

É grande com satisfação que posto este pequeno tutorial!

Tenho percebido, pelas estatísticas do blog, que o “Jogo da Memória em Java”, um de meus primeiros posts neste blog, é o tutorial mais acessado.

A grande maioria da galera que acessa este tutorial está buscando uma solução fácil para algum trabalho de faculdade, mas no fundo, este tutorial representou muito mais pra mim. Foi meu primeiro desafio pessoal em Java, algo que eu quis fazer para realmente me desafiar, e aprender a programar em Swing.

Agora, tenho o prazer de postar aqui o mesmo jogo, com algumas pequenas modificações  funcionais, escrito na plataforma Pivot.

Sim! Apesar de ser escrito em Java, o Apache Pivot é uma plataforma RIA completa.

Não vou entrar em detalhes para explicar o código. Ao invés disso, vou disponibilizar aqui o código do jogo, e deixar o desafio de executar o jogo para aqueles que se interessarem em aprender mais sobre esta fascinante plataforma RIA.

Este jogo, apesar de pequeno, demonstra muitos dos recursos disponíveis no Pivot.

Visitem o site do projeto, e leiam os tutoriais http://pivot.apache.org/.

A versão 2.0 estará saindo em breve (tenho acompanhado a lista de desenvolvimento e de usuários, e recomendo).

 

 

 

Definição da interface através do arquivo memgame.wtkx:

 


<Window title="Pivot's Memory Game" maximized="true"
    xmlns:wtkx="http://pivot.apache.org/wtkx"
    xmlns:content="org.apache.pivot.wtk.content"
    xmlns="org.apache.pivot.wtk">
    <content>
        <Border>
            <content>
	            <GridPane columnCount="6" styles="{horizontalSpacing:1, verticalSpacing:1,
                    showHorizontalGridLines:true, showVerticalGridLines:true}">
                    <rows>
                        <GridPane.Row>
                            <PushButton toggleButton="true" wtkx:id="1">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
                            <PushButton toggleButton="true" wtkx:id="2">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
                            <PushButton toggleButton="true" wtkx:id="3">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
                            <PushButton toggleButton="true" wtkx:id="4">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
				            <PushButton toggleButton="true" wtkx:id="5">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
				            <PushButton toggleButton="true" wtkx:id="6">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
                        </GridPane.Row>
                        <GridPane.Row>
                            <PushButton toggleButton="true" wtkx:id="7">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
                            <PushButton toggleButton="true" wtkx:id="8">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
                            <PushButton toggleButton="true" wtkx:id="9">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
                            <PushButton toggleButton="true" wtkx:id="10">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
				            <PushButton toggleButton="true" wtkx:id="11">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
				            <PushButton toggleButton="true" wtkx:id="12">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
                        </GridPane.Row>
                        <GridPane.Row>
                            <PushButton toggleButton="true" wtkx:id="13">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
                            <PushButton toggleButton="true" wtkx:id="14">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
                            <PushButton toggleButton="true" wtkx:id="15">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
                            <PushButton toggleButton="true" wtkx:id="16">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
				            <PushButton toggleButton="true" wtkx:id="17">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
				            <PushButton toggleButton="true" wtkx:id="18">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
                        </GridPane.Row>
                        <GridPane.Row>
                            <PushButton toggleButton="true" wtkx:id="19">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
                            <PushButton toggleButton="true" wtkx:id="20">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
                            <PushButton toggleButton="true" wtkx:id="21">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
                            <PushButton toggleButton="true" wtkx:id="22">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
				            <PushButton toggleButton="true" wtkx:id="23">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
				            <PushButton toggleButton="true" wtkx:id="24">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
                        </GridPane.Row>
                        <GridPane.Row>
                            <PushButton toggleButton="true" wtkx:id="25">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
                            <PushButton toggleButton="true" wtkx:id="26">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
                            <PushButton toggleButton="true" wtkx:id="27">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
                            <PushButton toggleButton="true" wtkx:id="28">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
				            <PushButton toggleButton="true" wtkx:id="29">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
				            <PushButton toggleButton="true" wtkx:id="30">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
                        </GridPane.Row>
                        <GridPane.Row>
                            <PushButton toggleButton="true" wtkx:id="31">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
                            <PushButton toggleButton="true" wtkx:id="32">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
                            <PushButton toggleButton="true" wtkx:id="33">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
                            <PushButton toggleButton="true" wtkx:id="34">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
				            <PushButton toggleButton="true" wtkx:id="35">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
				            <PushButton toggleButton="true" wtkx:id="36">
				                <buttonData>
				                    <content:ButtonData text=""/>
				                </buttonData>
				            </PushButton>
                        </GridPane.Row>
                    </rows>
                </GridPane>
            </content>
        </Border>
    </content>
</Window>

Classe que manipula os componentes de tela:


package votti.pivot.memgame;

import org.apache.pivot.collections.Map;
import org.apache.pivot.wtk.Alert;
import org.apache.pivot.wtk.Application;
import org.apache.pivot.wtk.Button;
import org.apache.pivot.wtk.ButtonPressListener;
import org.apache.pivot.wtk.DesktopApplicationContext;
import org.apache.pivot.wtk.Display;
import org.apache.pivot.wtk.MessageType;
import org.apache.pivot.wtk.PushButton;
import org.apache.pivot.wtk.Window;
import org.apache.pivot.wtkx.WTKXSerializer;

import votti.pivot.memgame.component.MemGameButtonData;

public class Main implements Application, ButtonPressListener {

	private String defaultImage = "img/default.gif";
	private boolean firstClick = true;
	private boolean right = true;
	private PushButton buttonOne;
	private PushButton buttonTwo;
	private Button clickedButtonOne;
	private Button clickedButtonTwo;

    private Window window = null;
    private String[] images18;
    private String[] images36;
    private PushButton[] buttons;

    @Override
    public void startup(Display display, Map<String, String> properties)

        throws Exception {
        WTKXSerializer wtkxSerializer = new WTKXSerializer();
        window = (Window) wtkxSerializer.readObject(this, "memgame.wtkx");

        prepareImagesArray();

        buttons = new PushButton[36];

        for(int aux = 0; aux < 36; aux++){
        	buttons[aux] = (PushButton) wtkxSerializer.get(String.valueOf(aux+1));
        	buttons[aux].setButtonData(new MemGameButtonData(defaultImage, images36[aux]));
        	buttons[aux].getButtonPressListeners().add(this);
        }

        window.open(display);
        window.setWidth(500);
        window.setHeight(520);
    }

    @Override
    public boolean shutdown(boolean optional) {
        if (window != null) {
            window.close();
        }

        return false;
    }

    @Override
    public void suspend() {
    }

    @Override
    public void resume() {
    }

    public static void main(String[] args) {
        DesktopApplicationContext.main(Main.class, args);
    }

    private void prepareImagesArray(){

		int posicaoNoArray, x, y;
		this.images18 = new String[18];

		for ( x = 0; x < (18); x++ ){
			this.images18[ x ] = new String("img/" + ( x + 1 ) + ".gif");
		}

		this.images36 = new String[ (36) ];

		for ( x = 0; x < 2; x++ ){
			for ( y = 0; y < (18); y++ ){
				do{
					posicaoNoArray = ( int ) ( Math.random() * (36) );
				}while( this.images36[ posicaoNoArray ] != null );
				this.images36[ posicaoNoArray ] = images18[ y ];
			}
		}
	}

	@Override
	public void buttonPressed(Button button) {

		if( firstClick ){

			if ( !right ){

				buttonOne = (PushButton) clickedButtonOne;
				buttonTwo = (PushButton) clickedButtonTwo;

				((MemGameButtonData)buttonOne.getButtonData()).setDefaultURL();
				((MemGameButtonData)buttonTwo.getButtonData()).setDefaultURL();

				window.repaint();
			}

			clickedButtonOne = button;

			buttonOne = (PushButton) clickedButtonOne;
			((MemGameButtonData)buttonOne.getButtonData()).setButtonURL();

			firstClick = !firstClick;
		}
		else{

			clickedButtonTwo = button;
			buttonTwo = (PushButton) clickedButtonTwo;

			if ( clickedButtonTwo == clickedButtonOne ){

				right = false;

				Alert.alert(MessageType.WARNING, "Not permited action!", window);
			}
			else{

				((MemGameButtonData)buttonTwo.getButtonData()).setButtonURL();

				if ( ((MemGameButtonData)buttonOne.getButtonData()).getButtonURL().equals(
						((MemGameButtonData)buttonTwo.getButtonData()).getButtonURL())){

					right = true;

					buttonOne.setEnabled( false );
					buttonTwo.setEnabled( false );
				}

				else{
					right = false;
				}

				firstClick = !firstClick;
			}
		}

	}
}

Mensagens dinâmicas com MessageFormat

Esta é uma rápida dica sobre uma classe muito útil da API Java, a classe MessageFormat. Não vou dar muitas explicações, e mais detalhes podem ser encontrados na própria API: http://download.oracle.com/javase/1.4.2/docs/api/java/text/MessageFormat.html

Dentre as muitas coisas legais que esta classe permite fazer (formatação de valores monetários, data, hora), temos uma maneira de tratar mensagens internacionalizadas (ou não) cujo conteúdo muda de acordo com o valor específico de um parâmetro. Explico melhor:

Digamos que você precise internacionalizar, ou apenas armazenar em um properties, a seguinte frase:

“Duke, você nos visitou uma vez.”

Digamos que você precise tratar esta frase, de maneira que quando a quantidade de visitas for maior, ela mude para:

“Duke, você nos visitou (n) vezes”

Certo! O tal (n) alí pode ser qualquer número, então o que muita gente faz são concatenações para conseguir o resultado esperado, por exemplo:

“Duke, você nos visitou” + n + “vezes”;

Vamos Piorar a situação? E se o Duke nunca lhe visitou, ou seja, n é igual a zero? E é lógico que não é somente o Duke que lhe visita, então você ainda tem o seguinte:

m*, você nos visitou uma vez. (quando n = 1)

m* + você nos visitou + n + vezes. (quando n > 1)

m*, você nunca nos visitou. (quando n = 0)

*onde m pode ser o Duke ou qualquer pessoa.

Isto ainda pode ficar mais complicado quando você precisa passar a mensagem para mais de um idioma, pois a posição das partes variantes (m e n) pode ser diferente na mensagem. Este cenário é comum em sistemas internacionalizados.

Apesar de a maioria dos frameworks web com foco em componentes de apresentação apresentarem algum suporte a este tipo de situação, algumas vezes será necessário tratar mensagens assim no código Java.

Para estas situações a classe MessageFormat é ideal.

Em primeiro lugar, podemos ter apenas uma mensagem que trata todas as possibilidades, acredite. Basta colocarmos, para cada parte variável da mensagem, um placeholder, que identificará a presença de um valor dinâmico. Como no exemplo temos dois valores variáveis (n e m) teremos dois placeholders na mensagem. A sintaxe de um placeholder é um numérico entre chaves: {0} e {1}. Os valores indicam a posição dos parâmetros passados para o método que formatará a mensagem.

Nossa mensagem então ficaria assim:

{1}, você nos visitou {0} vezes.

Mas espere! Isto ainda não nos atende. Apenas uma das variações da mensagem está contemplada; precisamos do tipo de formato choice: este tipo de formato nos permite escolher dinamicamente uma parte da mensagem de acordo com o valor passado a um placeholder:

{1}, você {0,choice,0# nunca nos visitou.|1# nos visitou uma vez.|1<nos visitou {0,number,integer} vezes}.

Na mensagem acima, está em destaque a sintaxe do tipo de formatação choice; ele é colocado após o número do placeholder, depois de uma vírgula.

Em vermelho está o texto que será apresentado caso o valor passado ao placeholder seja zero. Em verde está o texto que será apresentado caso o valor passado ao placeholder seja um. Em azul está o texto que será apresentado caso o valor passado ao placeholder seja maior que um.

OK, mas como fazer a mágica da transformação? Basta passar a mensagem e os parâmetros para o método format, da classe MessageFormat. Abaixo coloco uma classe de exemplo, que mostra ainda a mesma mensagem traduzida em inglês e espanhol, para demonstrar que a localização dos placeholders pode mudar sem afetar o resultado esperado.


import java.text.MessageFormat;

public class FormatacaoMensagem{

private static String ingles = "You have {0,choice,0#never visited us|1#visited us once|1<visited us {0,number,integer} times}, {1}.";
private static String portugues = "{1}, você {0,choice,0#nunca nos visitou.|1#nos visitou uma vez.|1<nos visitou {0,number,integer} vezes}.";
private static String espanhol = "Usted {0,choice,0#nunca nos ha visitado, {1}|1#nos ha visitado una vez, {1}|1<nos ha visitado {0,number,integer} veces, {1}}.";

public static void main(String[] args){

Object[] nenhum = {
new Integer(0),
"Duke"

};

Object[] um = {
new Integer(1),
"Duke"

};

Object[] varios = {
new Integer(2),
"Duke"

};

System.out.println( MessageFormat.format(ingles, nenhum) );
System.out.println( MessageFormat.format(ingles, um) );
System.out.println( MessageFormat.format(ingles, varios) );

System.out.println( MessageFormat.format(portugues, nenhum) );
System.out.println( MessageFormat.format(portugues, um) );
System.out.println( MessageFormat.format(portugues, varios) );

System.out.println( MessageFormat.format(espanhol, nenhum) );
System.out.println( MessageFormat.format(espanhol, um) );
System.out.println( MessageFormat.format(espanhol, varios) );

}
}

Eis o resultado final:

Vale a pena conferir a API para as outras opções que a classe oferece.

Está dada a dica!

Dica: Prototipação com Balsamiq

Esta é uma dica rápida, de uma ferramenta que veio ao encontro de minhas necessidades, e pode ser o que você também procura.

Há algum tempo deixei de usar o iPlotz, e hoje uma colega de trabalho me indicou o Balsamiq.

Não vou explicar muito, apenas colocar uma imagem de uma tela e deixar o link para quem tiver interesse.

 

 

A instalação é fácil, mas é preciso ter instalada a plataforma AIR da Adobe (o que também é fácil conseguir – há um link na página de instalação para se chegar na instalação do AIR).

Eis a página do projeto: http://balsamiq.com/

 

Está dada a dica!