O desenvolvimento de aplicações móveis tem crescido fortemente devido ao aumento do número de aparelhos móveis, os quais tem trazido novas funcionalidades, inovações e além de oferecer uma nova variedade de serviços. Essa tendência foi mais acentuada ainda com a chegada de novos aparelhos móveis: os smartphones, que unem o poder dos PDAs (Personal Digital Assistant ou Assistente Pessoal Digital) com os celulares. Esses dispositivos são embarcados com diversos sistemas operacionais: Linux, Symbian, Windows Mobile dentre outros. No que diz respeito a linguagens de programação, existe um universo muito maior, como exemplo as linguagens Brew, C++, JavaME, FlashLite e Python.

Um dos sistemas operacionais que tem predominado no mercado de smartphones é o Symbian OS. Ele foi desenvolvido para esse perfil de aparelhos celulares, que agrega mais recursos comparado aos comuns. Além de ser um sistema multitarefa com uma interface gráfica intuitiva, ele possui várias plataformas para desenvolvimento de aplicativos móveis. Dentre elas, vale destacar a plataforma Symbian C++, a qual é uma variação da linguagem C++ usada também no desenvolvimento do sistema operacional e tem acesso a diversos recursos do celular. Outro exemplo é a plataforma Java, que é representada por uma Java Virtual machine compatível com JavaME/MIDP. Todas essas plataformas de desenvolvimento de aplicações para um aparelho Symbian possuem várias vantagens e desvantagens relacionadas ao grau de rapidez, acesso a recursos do aparelho e complexidade da linguagem de programação.

A maior dificuldade encontrada foi a necessidade de um estudo prévio aprofundado dessas plataformas e suas particularidades. Isso se tornou dispendioso, já que o foco do desenvolvimento de aplicativos móveis deveria ser em aprendizado fácil e produção rápida de aplicativos. A Nokia, então, ciente dessa necessidade trouxe uma linguagem que oferecesse aos desenvolvedores um meio que pudesse implementar aplicações úteis sem a obrigação de gastar meses aprendendo Java ou C++,além dos detalhes técnicos envolvidos no sistema operacional Symbian. O resultado foi o Python para S60 (PyS60), uma linguagem de script portada para celulares Symbian OS.

A linguagem além de ser bastante simples, através da rápida construção de programas, oferece novas oportunidades ao desenvolvimento de aplicativos móveis. Não somente os iniciantes na plataforma irão se beneficiar dessa oportunidade, como também os desenvolvedores experientes que poderão usufruir desse meio para desenvolvimento ágil e poderoso de aplicativos móveis.

Neste artigo, que é o segundo de uma série que começou com a introdução à plataforma Python para Série 60 e a descrição de instalação e configuração do ambiente de desenvolvimento, iremos explorar alguns recursos disponibilizados pela plataforma PyS60 através de simples exemplos de aplicações a fim de ilustrar os conceitos aqui apresentados.

Construindo aplicativos com PyS60

Para ilustrar o desenvolvimento de uma aplicação Python para S60 iremos utilizar alguns exemplos bastante simples de aplicações que explorem os recursos avançados da plataforma PyS60. O primeiro exemplo consiste de uma aplicação que captura fotos e os envia via MMS para um número de telefone informado. O segundo exemplo consiste do clássico jogo da velha (tic-tac-toe) a fim de demonstrar a possibilidade de construção de aplicativos que utilizam gráficos e eventos de teclado suportados pela plataforma Python para S60. Porém, antes de explorarmos o código, é preciso introduzir alguns conceitos que serão utilizados no decorrer do desenvolvimento dos aplicativos.

Estrutura de um aplicativo

Diversos aplicativos disponíveis para o Symbian S60 compartilham do mesmo layout de interface gráfica. Basta abrir alguns aplicativos existentes no seu aparelho (inclusive o interpretador do PyS60),para perceber que eles possuem a mesma estrutura no quesito de interface gráfica. A Figura 1 ilustra a estrutura de um aplicativo utilizando o framework de interface gráfica do aparelho S60. Observando o diagrama na Figura 2, pode-se comparar como essa estrutura é mapeada em um aplicativo real desenvolvido com este framework.

Estrutura do aplicativo
Figura 1: Estrutura do aplicativo
Aplicativo com S60 UI
Figura 2: Aplicativo com S60 UI

No topo da tela, você pode observar o título do aplicativo. Esta é parte de nossa aplicação onde setamos o título do aplicativo. Em PyS60, podemos setar um título usando o comando: appuifw.app.title = u"First App!" . Abaixo do título, se encontra a barra de navegação, útil para quando se deseja utilizar abas em seu aplicativo. A área que compõe a maior parte da aplicação corresponde ao corpo do aplicativo além de ser considerado como a parte mais importante do mesmo. Nele, podem ser atribuídos diversos tipos de objeto de interface gráfica como:

  • Canvas que manipula a parte de gráficos na tela.
  • Formulários para construção de listas que abrigam diversos tipos de campos de texto.
  • Um objeto do tipo texto que corresponde ao texto puro escrito na tela.
  • Listas, caixas de diálogo, etc.

Na parte inferior, você pode observar dois itens que são ativados por botões dedicados (softkeys da esquerda e direita) do teclado do seu aparelho móvel. Se nenhuma caixa de diálogo estiver presente, o softkey da esquerda ativa o menu da aplicação ('Options'), enquanto o softkey da direita corresponde ao comando de sair ('Exit') da aplicação em execução. Se uma caixa de diálogo estiver em exibição, os softkeys correspondem ao "Aceitar" ('Accept') e "Rejeitar" ('Cancel') respectivamente.

EmPyS60, você pode acessar os elementos de interface gráfica através do objeto app que faz parte do módulo appuifw. Modificá-los é bastante simples: como cada elemento é uma variável dentro do objeto appuifw.app, basta atribuir a um valor desejado, da mesma maneira que uma atribuição de uma variável qualquer (Ex appuifw.app.exit_key_handler= sai ). Além do módulo appuifw, há o módulo e32 que é responsável pela manipulação de objetos e funções nativas relacionadas ao sistema operacional Symbian OS (Ex: locks, threads, etc.). No decorrer deste artigo, iremos utilizar de alguns objetos deste módulo, que serão descritos conforme forem aparecendo.

Eventos e callbacks

Para facilitar a vida do programador no desenvolvimento de aplicações que consumam poucos recursos os módulos Python específicos para a plataforma S60 usam e abusam do conceito de eventos e callbacks. Esse conceito é simples: a aplicação fica "dormindo" até que um evento ocorra (tecla pressionada, sinal de relógio, etc.) e dispare uma função pré-determinada (callback).

Funções Assíncronas

Além do conceito de eventos e callbacks descrito acima os módulos que acompanham o PyS60 também possuem várias funções que se comportam de maneira assíncrona, ou seja, elas retornam antes de terminar de executar a sua tarefa. Nesses casos é bastante comum que essas funções executem uma chamada à uma função callback para 'avisar' de que sua tarefa foi concluída.

  • 32: Este módulo fornece acesso às funções específicas do sistema operacional Symbian que não possuem relação direta com a interface com o usuário. Neste módulo você irá encontrar funções que retornam a versão do PyS60, se seu programa está rodando no emulador, a lista de todos os drives disponíveis e funções e objetos que lidam com locks, threads, etc.
  • sysinfo: Este módulo fornece funções que retornam dados do aparelho tais como qual o perfil escolhido (geral, reunião, silencioso, ...), o estado da carga da bateria, tamanho da tela, espaço livre em disco, IMEI, potência do sinal da rede telefônica, tipo de toque, informações sobre a memória do aparelho, versão do firmware, etc.
  • appuifw: Neste módulo você irá encontrar tudo o que tem relação com a interface gráfica com o usuário (GUI). É um dos módulos mais importantes do PyS60.
  • graphics: Módulo com funções gráficas para manipulação de imagens, desenho de primitivas, impressão de textos em imagens, funções para tirar screenshots, etc. Esse módulo tem total interoperabilidade com os módulos camera e appuifw.
  • camera: Um dos módulos mais interessantes do PyS60 por sua facilidade de uso. Este módulo disponibiliza funções para manipular a(s) câmera(s) do celular permitindo que se tire fotografias ou que se grave vídeos com elas.
  • gles: Biblioteca que fornece uma API compatível com OpenGL/ES para desenho de gráficos 3D com aceleração (alguns dispositivos da S60 possuem um chip para aceleração gráfica 3D).
  • sensor: Este módulo dá acesso aos sensores de aceleração, rotação e tapping (bater com o dedo na tela do celular aciona esse sensor). Vale lembrar que apenas alguns modelos de celulares S60 dispõem desses sensores.
  • audio: Esse módulo permite a manipulação total do sistema de áudio do aparelho. Com ele é possível manipular tanto o alto-falante externo (tocando um MP3, por exemplo) quanto o áudio de uma ligação telefônica (emitir um som no meio de uma conversa ou até mesmo gravá-la).
  • telephone: Funcionalidades de telefonia tais como fazer uma ligação ou atender à uma chamada estão neste módulo.
  • messaging: Esse módulo tem as funções responsáveis pelo envio de SMS e MMS.
  • inbox, contacts, calendar: Manipulam respectivamente a caixa de entrada de mensagens (SMS/MMS), os contatos da agenda e os eventos de calendário. Esses módulos são extremamente poderosos.
  • location, positioning: Módulos de localização que utilizam respectivamente os dados da rede GSM e dados do GPS (interno ou externo) do aparelho.
  • e32db: Mini banco de dados relacional que permite manipulação utilizando SQL (será substituído pelo SQLite em versões futuras do PyS60).
  • socket: Módulo que já acompanha o Python e recebeu adições para suportar conexões via Bluetooth.

Desenvolvendo o primeiro aplicativo - FotoPy

A filosofia do Python diz que a linguagem tem "batteries included" (baterias inclusas) e isso significa que a linguagem sempre deve vir acompanhada de uma biblioteca padrão bastante completa e poderosa. Isso não é diferente no Python para S60 onde temos alguns módulos da biblioteca padrão do Python (apenas uma parcela dos módulos padrões) e mais algumas bibliotecas específicas para o desenvolvimento para S60.

Obviamente, por questões de espaço, não irei descrever ou usar todos os módulos neste tutorial, mas se você deseja obter informações detalhadas sobre o que está disponível para essa plataforma é recomendável dar uma leitura na documentação oficial do PyS60 que pode ser baixada no site do projeto listado na primeira parte deste artigo(disponível em formato PDF).

Para ilustrar o desenvolvimento de uma aplicação Python para S60 iremos utilizar o primeiro exemplo bastante simples de uma aplicação que tira uma foto e a envia via MMS para um número de telefone informado. Todas as aplicações PyS60 podem usar a estrutura abaixo (Listagem 1) para ser desenvolvida:

Listagem 1. Estrutura da Aplicação

 import e32
 import appuifw 
 
 def sai():</p>
 #envia o sinal para o objeto "trava"
 trava.signal()

 #Aqui começa a nossa aplicação
 # ============================
 #Cria um objeto "trava" que irá "segurar"
 #a nossa aplicação rodando
 trava = e32.Ao_lock()</p>

 #Atribui uma chamada "callback" para
 # o método "sai()" quando o usuário 
 #escolher a opção "Sair" no celular.
 #Obs: Note que a chamada do método "sai()"
 #não tem parênteses pois a função não 
 #é executada imediatamente
 appuifw.app.exit_key_handler = self.sai

 #Aguarda e segura a execução até que o
 #objeto "trava" receba um sinal.
 .trava.wait()

Esta primeira aplicação não fará nada de útil. A única coisa que foi codificada até agora foi a implementação da função “.sai()” (Linha 4) onde programamos a opção “Sair” ('Exit') da aplicação utilizando um “ActiveObjectLock” do Symbian. Este objeto “trava” (“ActiveObjectLock” - Ao_lock' - Linha 12) é necessária, pois as aplicações no universo Symbian são desenvolvidas para trabalharem no modelo assíncrono, ou seja, as funções retornam imediatamente após serem chamadas antes mesmo de terem concluído as suas tarefas. Isso acarretaria na saída súbita da aplicação sem que o usuário consiga enxergar a aplicação sendo executada. Por isso, utiliza-se a chamada à função wait() (Linha 22) que coloca a aplicação em modo de espera (a aplicação continua em execução)até que o objeto "trava" receba um sinal pela chamada da função signal() (Linha 6). Essa função deve ser chamada quando o usuário deseja finalizar a aplicação, por isso a sua chamada apenas dentro da função sai(). Muitas funcionalidades do PyS60 também são implementadas usando o modelo de callback, ou seja, o programador associa funções a eventos e quando esses eventos ocorrem, a função apropriada é invocada. Na nossa aplicação o método “.sai()” é executado sempre que o evento exit_key (Linha 18) for disparado e o mesmo ocorre quando pressionamos o softkey direito do aparelho móvel.

Um dos módulos mais interessantes que acompanha o PyS60 é o módulo “camera”. Com ele podemos facilmente acionar a câmera do aparelho móvel, tirar fotografias, manipular fotos, gravá-las no cartão de memória ou até enviá-las para outros celulares. Para tirar uma foto com a câmera basta executar os comandos (Listagem 2):

Listagem 2. Ligando a câmera


 import camera
 foto = camera.take_photo()
 foto.save(("E:\\Images\\foto.jpg")

A função “.take_photo()” do módulo “camera" irá retornar um objeto do tipo “Image” contendo a imagem fotografada (Linha 2). Para gravar a imagem no cartão de memória basta chamar o método “.save()” deste objeto,passando como parâmetro o caminho do diretório onde a imagem capturada deve ser armazenada(Note que o caractere “\” precisa ser duplicado para ser reconhecido como “escaping”) (Linha 3).

Como se pode observar é extremamente simples tirar uma foto em Python, mas isso tem um inconveniente: a foto é capturada assim que a função camera.take_photo() é chamada e isso implica que o que está sendo fotografado não aparece na tela do celular, logo o usuário não conseguirá visualizar o que está sendo fotografado. Para que visualizar o que a câmera está fotografando, é necessário acionar o view finder (modo preview) da câmera e desligá-lo imediatamente antes de tirar a fotografia. Então, de volta ao esqueleto da aplicação, adicionaremos algumas linhas de código a mais (Listagem 3):

Listagem 3. Adicionando o View Finder


import e32
import appuifw
import camera

 def sai():
 #envia o sinal para o objeto "trava"
 trava.signal()

 def desenha_tela(self,imagem):
 #Pinta a imagem vista pela câmera na tela
 canvas.blit(imagem)

 #Aqui começa a nossa aplicação
 # ============================
 #Vamos criar um objeto Canvas.
 #Objeto Canvas permite a exibição de imagens 
 canvas = appuifw.Canvas()

 #Cria um objeto "trava" que irá "segurar"
 #a nossa aplicação rodando
 trava = e32.Ao_lock()

 #Define o titulo da aplicacao 
 #O "u" antes da string informa que
 #o texto está no formato unicode.
 appuifw.app.title = u"PyFoto"

 #Vamos definir que o corpo da aplicação
 #será o objeto Canvas criado acima.
 appuifw.app.body = canvas 

 #Atribui uma chamada "callback" para
 # o método "sai()" quando o usuário 
 #escolher a opção "Sair" no celular.
 #Obs: Note que a chamada do método "sai()"
 #não tem parênteses pois a função não 
 #é executada imediatamente
 appuifw.app.exit_key_handler = sai

 #Iniciamos o "view finder" que irá 
 #executar desenha_tela() 
 #constantemente onde iremos exibir
 #a imagem capturada pelo finder no Canvas
 camera.start_finder(desenha_tela)

 #Aguarda e segura a execução até que o
 #objeto "trava" receba um sinal.
 trava.wait()

Executando esse teste vamos obter a seguinte tela (Figura 3).

View Finder em execução
Figura 3: View Finder em execução

Agora vamos adicionar uma opção "Tirar foto" ao nosso menu "Opções". Para isso vamos adicionar esse pequeno trecho de código (Listagem 4):

Listagem 4. Adicionando a captura de foto

 import e32
 (...)


 def tira_foto():
 #Desliga o view finder
 camera.stop_finder()

 #Tira a foto e grava em E:\\Images\\foto.jpg
 foto = camera.take_photo()
 foto.save("E:\\Images\\foto.jpg")

 #Religa o view finder
 camera.start_finder(desenha_tela)

 #Aqui começa a nossa aplicação
 # ============================
 (...)
 #Após a chamada camera.start_finder()

 #Cria uma opção "Tirar foto" no menu
 #Opções do celular que invoca o método
 #tira_foto() quando acionado.
 appuifw.app.menu = [(u"Tira foto",tira_foto)]
 (...)

Agora a foto já pode ser capturada, que será gravada no arquivo E:\Images\foto.jpg (para futuramente enviá-la via MMS) (Figura 4):

View Finder em execução
Figura 4: Opção “Tirar foto”

Uma observação importante é que a aplicação pode ficar com uma tela branca durante alguns segundos, pois é o tempo necessário para que o interpretador Python grave a foto recém-tirada. Agora com a foto salva, iremos enviá-la via MMS para um número de celular informado. Para isso adicione o código abaixo ao nosso aplicativo (Listagem 5):

Listagem 5. Adicionando o envio de MMS

 (...)
 import messaging

 (...)
 def tira_foto():
 #logo depois de foto.save(...)

 #Solicita o numero do telefone
 numero_fone = appuifw.query("Numero do telefone","text")

 #Verifica se o numero foi informado e envia a mensagem 
 if numero_telefone:
 messaging.mms_send(numero_telefone,u"Foto 
 tirada pelo PyFoto", "E:\\Images\\foto.jpg")

 (...)

Executando novamente a aplicação e após capturada uma foto, podemos agora informar o número do telefone do destinatário (Figura 5) e visualizar a mensagem depois de entregue (Figura 6) (Lembrando apenas de ter cuidado ao testar o envio do MMS, pois isso poderá implicar em custos associados de acordo com a sua operadora telefônica).

Solicitando o número
Figura 5: Solicitando o número
Mensagem recebida
Figura 6: Mensagem recebida

Finalizamos aqui nossa primeira aplicação funcional em Python para celulares Symbian S60. Obviamente, podemos melhorar muitos aspectos da nossa aplicação como exemplo:

  • Reduzir o tamanho da imagem antes de enviá-la para economizar custos de tráfego de dados.
  • Buscar o número de telefone da nossa lista de contatos.
  • Girar a tela para aproveitarmos melhor o espaço para o View Finder.
  • Adicionar outras opções de envio (Flickr, Bluetooth, etc.)

Mas fica como exercício para os leitores.

Desenvolvendo o segundo aplicativo - TicTacToePy (Jogo da Velha)

O último exemplo de aplicativo deste artigo é o desenvolvimento de um jogo com o PyS60. Construiremos o clássico popular jogo da velha a fim de ilustrar algumas funcionalidades que o PyS60 suporta como desenho/pintura na tela e eventos de teclado do aparelho móvel.

Antes de iniciarmos a construção do aplicativo, é interessante apresentarmos alguns conceitos úteis que são bastante usados na construção de jogos:

  • Loops de controle a fim de controlar a aplicação
  • Tempo dinâmico
  • Double Buffering (técnica de animação de desenho/pintura de tela)
  • Módulo random (Gerador de números aleatórios)

Estrutura de um aplicativo jogo

Como vimos no desenvolvimento da aplicação FotoPy, utilizamos um objeto e32.Ao_lock (trava) para interrompemos a execução e iniciar no aplicativo a espera por entradas do usuário. Entretanto, em um aplicativo como um jogo, toda a lógica do jogo deve estar em constante execução, até mesmo quando o usuário não interage com o jogo. Então, em vez de uma "trava", para controlar a aplicação, utilizamos um loop de controle.

Um loop de controle é simplesmente um loop while que toma conta do tempo do jogo passo a passo. Como a trava, o loop previne que a aplicação saia de execução instantaneamente. Durante a execução do jogo, a aplicação também deve ser capaz de responder por quaisquer entradas do usuário. Isto é feito da mesma maneira que qualquer outra aplicação, usando os eventos callbacks (ler seção Eventos e callbacks ), que modificam os valores das variáveis globais que consequentemente alteram o estado jogo. Tipicamente, o loop de controle é estruturado conforme o algoritmo a seguir (Listagem 6):

Listagem 6. Loop de controle


Inicializar todos os eventos e callbacks
 while:
 Atualiza o estado do jogo
 Redesenha/Repinta a tela do jogo
 Pausa por algum tempo.

O último comando no loop de controle (Linha 5) força a execução pausar por um tempo. Isso é necessário pois queremos que o usuário possa perceber o que está acontecendo dentro do jogo, já que a percepção humana é bem mais lenta em comparação a um processador de um telefone móvel (muito mais rápido). E ainda precisamos dar a aplicação um certo tempo para que ela possa reagir aos eventos do usuário. Por estas razões, pausamos o jogo por um pequeno tempo a cada iteração do loop. O método responsável por esta pausa é o e32.ao_sleep() que interrompe a execução por um parâmetro de tempo pré-determinado. No nosso jogo, pausamos por um segundo, e32.ao_sleep(1), a cada iteração. Isto permite que o usuário possa visualizar se venceu ou perdeu o jogo, antes de iniciar um novo jogo.

Em alguns casos, nós queremos pausar o jogo em uma menor quantidade de tempo possível, 0 segundos, mas ao mesmo tempo permitir que a aplicação possa tratar quaisquer entradas/interações do usuário no jogo. Para este caso específico, há uma função especial e32.ao_yield(), que ao chamada, garante que a interface gráfica fique em estado de "escuta" para quaisquer eventos, mesmo que a aplicação esteja desempenhando outra tarefa.

Quando vamos fazer animações, surgem alguns problemas relacionados aos vários métodos que podem ser utilizados. O método mais simples que podemos imaginar é aquele em que limpamos a tela, desenhamos os objetos, limpamos a tela novamente, desenhamos os objetos nas novas posições, e assim por diante. Este método, porém, tem um grave problema: a tela pisca a cada limpeza.Para contornar este tipo de problema, existem várias técnicas de animação. O PyS60 suporta a mais popular delas, o double buffering.

Podemos concluir, a partir do nome, como funciona esta técnica. Em vez de pintarmos na tela diretamente com o objeto Canvas, dispomos de um objeto bitmap auxiliar (chamado de buf) que, normalmente, possui o tamanho da tela (ou o tamanho da região onde ocorre a animação). Desenhamos, neste buf, os objetos que devem ser apresentados na tela. Após isso, desenhamos o conteúdo do buf na tela, fazendo com que os objetos apareçam através da chamada da função canvas.blit(buf). Limpamos, então, o buf, desenhamos os objetos novamente em suas novas posições, passamos o conteúdo do buf para a tela, e assim por diante. Desta maneira, o usuário não precisará visualizar a operação de pintura de cada elemento individual na tela, e sim apenas o resultado final, reduzindo assim o problema de "piscadela" na tela.

Por fim, diversos jogos necessitam de um ingrediente "aleatório" a fim de deixar o jogo interessante. Python provê um módulo básico denominado random que contém métodos para geração de números aleatórios. Neste jogo, a função random.choice() é usada para escolha aleatória de um dos valores de uma lista passada como parâmetro.

O Código da Aplicação Jogo TicTacToePy

O código da aplicação do jogo está dividido em três partes que após combinados gerarão o jogo completo e funcional. Nas próximas seções iremos explorar estas partes uma por uma.

A primeira parte do código do jogo TicTacToe (Listagem 7) define as constantes necessárias para o jogo e alguns métodos utilitários. Nas linhas 01 a 02, importamos os módulos appuifw, e32, random , graphics, e key_codes que serão necessários para manipulação da interface, controle, lógica, pintura de tela e eventos do nosso aplicativos. As funções handle_redraw e handle_event são responsáveis pela pintura do jogo na tela e tratamento de eventos do teclado respectivamente. As linhas 07, 15 e 20 definem as variáveis utilizadas no jogo. O modificador global avisa que as variáveis são compartilhadas por todo o código, a fim de que não haja uma sobreposição no uso das mesmas. (Ex: O estado da variável deve ser o mesmo por quaisquer partes do código.) A linha 29 appuifw.app.screen="full" define que a tela do aplicativo deve ocupar toda a área disponível pelo o aparelho para pintura de tela.

A linha 33, nós instanciamos a classe Canvas existente no módulo appuifw. Ele é responsável pelo controle de toda pintura e desenho de objetos na tela do aparelho. Observe que ele recebe como parâmetros os callbacks responsáveis pelo tratamento de eventos (event_callback) e pela pintura de tela (redraw_callback). Estas funções devem ser providas pelo desenvolvedor, se ele deseja que sua aplicação suporte essas funcionalidades. Na linha 36 criamos um objeto buffer (buf), onde será responsável por receber os elementos a serem pintados na tela (posição, formato, etc.)comum tamanho pré-definido pelo desenvolvedor (Nesse exemplo: uma tupla com 240 pixels de largura e 320 de altura). Por fim, criamos algumas variáveis de controle e estado do jogo a fim de poder controlar a ação e execução do jogo (Linhas 46 a 69).

Listagem 7. Definindo as constantes do jogo


import appuifw, e32, random
import graphics, key_codes

def handle_redraw(rect):
 global buf, canvas
 canvas.blit(buf)
 
def handle_event(event):
 print 'Tecla pressionada.'


def quit():
 global running, trava
 running = False
 trava.signal()

def newgame():
 global canvas, buf, running, trava, xcoord, ycoord,game_state, dotx, doty

 #Obtem o objeto "lock" da aplicação (trava).
 trava = e32.Ao_lock()
 
 #Seta a função quit() como callback do evento ExitKey.
 appuifw.app.exit_key_handler=quit
 
 #Seta a aplicação para ocupar toda a tela.
 appuifw.app.screen="full"
 
 #Cria um novo objeto canvas e seta os respectivos 
 #callbacks (pintura e eventos)
 canvas=appuifw.Canvas(event_callback=handle_event, / 
 redraw_callback=handle_redraw)
 
 #Cria uma nova imagem (buffer) 
 buf=graphics.Image.new((240,320))

 #Seta corpo da aplicação para o objeto canvas.
 appuifw.app.body=canvas

 #A fim de manter na memória o estado do jogo, utilizamos uma matriz
 #preenchida de:0 se o quadrado estiver vazio, 1 se o quadrado for
 #ocupado por uma jogada do Fone, 2 se o quadrado for
 #ocupado por uma jogada do jogador.
 game_state=[[0,0,0],[0,0,0],[0,0,0]]
 

 #Escrever X ou O requer as coordenadas pré-definidas.
 xcoord=[32,112,192]
 ycoord=[65,172,279]
 
 #Para mostrar onde o cursor do jogador se posiciona,
 #Colocamos um ponto vermelho (No início do jogo, ele é 
 #inicializado no meio da matriz)
 dotx=doty=1
 
 #Para manter o registro de qual jogador irá jogar na 
 #rodada, utilizamos a variavel turn (1 para o telefone, 
 #2 para o jogador)
 #O jogador começa o jogo
 turn=2
 
 #Variável para verificar se a partida foi encerrada.
 gameover=False
 
 #Variavel que verifica se a aplicação está em execução 
 #(True para Em Execução , False para Finalizado)
 running = True

 #Inicia um novo jogo assim que a aplicação 
 #é inicializada.
 .newgame()

Nesta segunda parte do código (Listagem 8), adicionamos ao aplicativo funções relacionadas ao tratamento de eventos que registram ações como pressionamento de botões (direcionais) e softkeys. Isso corresponde às ações do jogo em relação a qual quadrado o jogador irá executar sua jogada através do movimento do cursor (ponto vermelho)pela tela através do teclado do celular. Primeiro definimos a função is_down (Linha 04) que dado um código identificador de tecla (scancode), retorna o estado da tecla atual da tecla. A segunda função pressed (Linha 08) será utilizada dentro do loop de controle e checa se o estado da tecla passado como parâmetro está pressionada ou não. Para isso ele checa a variável downs e retorna se o estado dela é pressionado (True) ou não-pressionado (False).

E por fim temos a função handle_event que funciona da seguinte maneira: Se a tecla estiver pressionada (evento do tipo appuifw.EEventKeyDown) então devemos verificar se a tecla já estava pressionada (is_down() retornando 'True' ) ou se foi apenas um clique (is_down() retornando 'False') . Caso a tecla seja solta (quando o usuário libera a tecla pressionada - appuifw.EEventKeyUp), é setado o estado da tecla para não pressionado (Linha 23). Declaramos também algumas variáveis de controle adicionais que armazenamos estado das teclas pressed e down (Linhas 23 e 27) e são utilizadas nas funções de tratamento de eventos descritos acima. Essas variáveis são do tipo especial denominado dicionário (representado pelas {}) em Python (Ver Nota 01).

Listagem 8. Adicionando o tratamento de eventos


import appuifw, e32, random
(...)

 def is_down(scancode):
 global keyboard_state
 return keyboard_state.get(scancode,0) 

 def pressed(scancode):
 global downs
 if downs.get(scancode,0):
 downs[scancode]-=1
 return True
 return False
 
 def handle_event(event):
 global downs, keyboard_state 
 if event['type'] == appuifw.EEventKeyDown:
 code = event['scancode']
 if not is_down(code):
 downs
= downs.get(code,0)+1
 keyboard_state
=1
 elif event['type']==appuifw.EEventKeyUp:
 keyboard_state[event['scancode']]=0 


(...)

 def newgame():
 global canvas, buf, running, trava, xcoord, ycoord, 
 game_state, dotx, doty, keyboard_state, downs

 #Conjunto de variaveis que armazenam o estado das
 #teclas (clique ou continuamente pressionada).
 downs={}
 
 #Conjunto de variaveis que armazenam o estado das 
 #teclas.
 keyboard_state={}
 
 (...)

Chegamos na última parte do código da aplicação do jogo da velha. Nesta parte adicionamos toda a estrutura relacionada à pintura de tela (desenho do cursor, matriz, etc.) e a lógica do jogo. Não entrarei muito em detalhes na lógica do jogo, pois o código está comentado e autoexplicativo. Adicionamos uma nova função drawgrid() (Linha 04), que é responsável pela limpeza de tela, pintura dos elementos na tela (linhas da matriz, jogadas dos jogadores e cursor). Adicionamos o loop de controle a partir da linha 36. O loop verifica se o aplicativo está ainda em execução (verifica a condição de parada- variável running). Caso ele esteja em execução, é verificado se é a turno do jogador2 jogar (Linha 44). Se sim, verifica o estado das teclas (se o cursor foi movimentado ou selecionado) para efetuar a jogada do jogador (Linhas 45 a 70). Após verificamos se o jogo foi finalizado, isto acontece se um dos jogadores completou a sequência contínua de 3 elementos ou se não há mais quadrados em branco para jogar (Linhas 71 a 77). Para exibirmos na tela que algum jogador venceu, nós adicionamos um pequeno trecho de código que faz sublinhar a sequência de símbolos com uma linha verde (Linhas 79 a 109). Por fim, após o jogador 02 ter efetuado a sua jogada, chegou o turno do jogador 1 (controlado pelo telefone). O trecho de código (Linhas 117 a 127) é responsável pela escolha e efetuação da jogada do jogador controlado pelo telefone. Na linha 129, adicionamos uma chamada à função especial e32.ao_yield(), que ao chamada, garante que a interface gráfica fique em estado de "escuta" para quaisquer eventos, mesmo que a aplicação esteja desempenhando outra tarefa. Finalizado este loop, uma nova iteração do loop de controle é adicionada onde o fluxo acima descrito inicia-se novamente, como mostra a Listagem 9.

Listagem 9. Adicionando a Pintura de tela e lógica do jogo


import appuifw, e32, random
(...)

 def drawgrid():
 global xcoord, ycoord, game_state, buf, dotx, doty
 buf.clear()
 buf.line((80,20,80,300), 0)
 buf.line((160,20,160,300), 0)
 buf.line((20,107,220,107), 0)
 buf.line((20,214,220,214), 0)
 for i in range(3):
 for j in range(3):
 if(game_state[i-1][j-1]==1):
 buf.text((xcoord[i-1],ycoord[j-1]), u"O", font="title")
 elif(game_state[i-1][j-1]==2):
 buf.text((xcoord[i-1],ycoord[j-1]), u"X", font="title")
 buf.point((xcoord[dotx]+7,ycoord[doty]-10), 0xff0000, width=15) 

(...)

 def newgame():
 (...) 
 #Após gameOver=false

 #Variável para verificar se a partida foi encerrada.
 gameover=False
 
 #Pinta a Matriz do Jogo
 drawgrid()
 
 #Variavel que verifica se a aplicação está em execução 
 #(True para Em 31, #Execução , False para Finalizado)
 running = True
 
 #Loop de controle que move o cursor vermelho na tela 
 #de acordo com os eventos de teclado
 while(running==1):
 #Inicializa o menu
 appuifw.app.menu=[(u"Novo jogo", newgame), (u"Sair", quit)]
 
 if(pressed(key_codes.EScancodeRightSoftkey)):
 quit()
 
 #Quando for a vez do jogador jogar
 if(turn==2):
 #Se o botão "Select" for pressionado<br>
 if(pressed(key_codes.EScancodeSelect)):<br>
 #Se o quadrado for disponível para ser jogado
 if(game_state[dotx][doty]==0): 
 buf.text((xcoord[dotx],ycoord[doty]), u"X", /
 font="title")
 game_state[dotx][doty]=2
 handle_redraw(())
 drawgrid()
 turn=1
 if((pressed(key_codes.EScancodeLeftArrow)) and (dotx>0)):
 dotx-=1
 handle_redraw(())
 drawgrid()
 if((pressed(key_codes.EScancodeRightArrow)) and (dotx<2)):
 dotx+=1
 handle_redraw(())
 drawgrid()
 if((pressed(key_codes.EScancodeUpArrow)) and (doty>0)):
 doty-=1
 handle_redraw(())
 drawgrid()
 if((pressed(key_codes.EScancodeDownArrow)) and (doty<2)):
 doty+=1
 handle_redraw(())
 drawgrid()
 #Checamos se o jogo foi finalizado. Isto acontece se um dos jogadores 
 #completou a sequência contínua de 3 símbolos ou se a matriz está cheia.
 gameover=True
 for i in range(3):
 for j in range(3):
 if(game_state[i-1][j-1]==0):
 gameover=False
 
 #Para mostrar que o jogador venceu, sublinha a sequência de símbolos 
 #com uma reta de cor verde (0x33C00).

 if(game_state[0][0]==game_state[0][1]==game_state[0][2]<>0):
 buf.line(((40,20),(40,300)), 0x33CC00)
 gameover=True
 e32.ao_sleep(1)
 if(game_state[1][0]==game_state[1][1]==game_state[1][2]<>0):
 buf.line(((120,20),(120,300)), 0x33CC00)
 gameover=True
 e32.ao_sleep(1)
 if(game_state[2][0]==game_state[2][1]==game_state[2][2]<>0):
 buf.line(((200,20),(200,300)), 0x33CC00)
 gameover=True
 e32.ao_sleep(1)
 if(game_state[0][0]==game_state[1][0]==game_state[2][0]<>0):
 buf.line(((20,53),(220,53)), 0x33CC00)
 gameover=True
 e32.ao_sleep(1)
 if(game_state[0][1]==game_state[1][1]==game_state[2][1]<>0):
 buf.line(((20,160),(220,160)), 0x33CC00)
 gameover=True
 e32.ao_sleep(1)
 if(game_state[0][2]==game_state[1][2]==game_state[2][2]<>0):
 buf.line(((20,267),(220,267)), 0x33CC00)
 gameover=True
 e32.ao_sleep(1)
 if(game_state[0][0]==game_state[1][1]==game_state[2][2]<>0):
 buf.line(((20,20),(220,300)), 0x33CC00)
 gameover=True
 e32.ao_sleep(1)
 if(game_state[2][0]==game_state[1][1]==game_state[0][2]<>0):
 buf.line(((20,300),(220,20)), 0x33CC00)
 gameover=True
 e32.ao_sleep(1)
 
 if(gameover==True):
 if(appuifw.query(u"Fim de jogo. Jogar de novo?", "query")):
 newgame()
 else:
 quit()
 
 #Se for o turno do jogador 'Telefone' e 
 #se o jogo ainda não estiver ainda finalizado...
 while((turn==1) and (gameover==False)):0. px=random.choice([0,1,2])
 py=random.choice([0,1,2])
 if(game_state[px][py]==0):
 buf.text((xcoord[px],ycoord[py]), u"O", font="title")
 game_state[px][py]=1
 handle_redraw(())
 drawgrid()
 turn=2

 e32.ao_yield()
 
 #Inicia um novo jogo assim que a aplicação 
 #é inicializada.
 .newgame()

Vejam os screenshots do aplicativo final acima em execução (Figuras 7 e 8):

Estrutura do aplicativo
Figura 7: Estrutura do aplicativo
Iniciando o jogo
Figura 8: Iniciando o jogo

Finalizamos aqui nossa última aplicação funcional em Python para celulares Symbian S60. Obviamente, podemos melhorar muitos aspectos da nossa aplicação como exemplo:

  • Adicionar sons de áudio e música no aplicativo
  • Criar uma tabela de recordes (vitórias e derrotas do Jogador 01 x Jogador 02).
  • Melhorar a inteligência artificial do jogador 02 (Deixá-lo mais desafiante!)

Mas fica como exercício para os leitores.

Podemos inferir a partir desta série de artigos que o Python para S60 é uma escolha ideal para começar a criar aplicações para dispositivos baseados na plataforma SymbianS60, porque o desenvolvimento é relativamente simples, fácil e rápido. Com algumas linhas de código em Python, conseguimos produzir aplicativos simples e funcionais sem se preocupar com estruturas e particularidades da linguagem de programação em si. É bem adequado para o desenvolvimento de protótipos ou para a construção de aplicações para fazer prova de conceito com uma linguagem simples e consistente.