sexta-feira, 19 de junho de 2009

Adicionando tela de fundo.

Olá! Continuando nosso pequeno joguinho, esta semana vamos fazer a tela de fundo um pouco mais interessante que apenas um fundo preto e opaco... Vamos adicionar estrelas, com movimento!

Abaixo está o código da classe "Background", que devemos salvar no arquivo "background.py".

# coding: utf8
# background.py

import pygame
import random

class Background(object):
    
    def __init__(self, max_stars=100):
        self.max_speed = 6
        screen = pygame.display.get_surface()
        size = screen.get_size()
        back = pygame.Surface(size).convert()
        back.fill( (0,0,0) )
        self.image = back
        self.stars = []
        for _ in range(max_stars):
            rand = random.randint
            self.stars.append( [rand(0, size[0]), rand(0, size[1]), rand(1, self.max_speed)])
    
    def update(self):
        size = pygame.display.get_surface().get_size()
        for star in self.stars:
            star[1] += star[2]
            if star[1] >= size[1]:
                star[1] = 0
                star[0] = random.randint(0, size[0])
                star[2] = random.randint(1, self.max_speed)
    
    def draw(self, screen):
        image = self.image
        image.fill( (0, 0, 0) )
        for star in self.stars:
            image.set_at(star[:2], (255, 255, 255))
        
        screen.blit(self.image, (0, 0))

Como vocês podem ver, a classe é bem simples. Inicializamos a o objeto background com um número máximo padrão de 100 estrelas, cada estrela irá se mover de cima para baixo na tela, dando a impressão que estamos percorrendo o espaço. Atribuímos também a velocidade máxima com que cada estrela poderá se movimentar.

Preenchemos a tela com o tradicional fundo preto, criamos uma "Surface" que será onde iremos desenhar nossas estrelas e salvamos essa "Surface" na variável "self.image".

Agora a parte interessante. A criação das estrelas. Para nosso exemplo, faremos da forma mais simples possível: uma array com 3 números, representando a posição da estrela e sua velocidade (x, y, velocidade).

O método "update(self)" é onde ocorre a atualização do estado do nosso background. O que faremos aqui é simplesmente adicionar a velocidade (star[2]) à coordenada y da posição (star[1]). Caso após adicionarmos a velocidade a estrela suma, ou seja, sua cordenada y passe a ser maior que o tamanho da nossa tela na vertical, reposicionamos esta estrela, colocando-a no topo da tela, com novos valores para a coordenada x e para a velocidade. Note que para a velocidade sempre iremos desejar um valor >= 1, pois caso contrário teríamos uma estrela estática.

O método "draw(self)" é mais simples ainda. Preenchemos a nossa imagem com preto e então percorremos a lista de estrelas, adicionando um ponto branco para cada uma delas em sua posição atual. Após isso chamamos "blit" na variável "screen". O que está função faz é copiar o conteúdo de uma "Surface" para outra. Assim, ao copiarmos o fundo para a "Surface" screen, estaremos substituindo a imagem que será mostrada na tela na próxima atualização.

O que resta agora é apenas alterar a classe "Game" para utilizar o nosso background animado. Abaixo está o código da classe com as devidas alterações:

# coding: utf8
# game.py

import pygame
import random

from pygame.locals import *
import background

class Game(object):
    
    def __init__(self, size):
        pygame.init()
        flags = DOUBLEBUF
        self.screen = pygame.display.set_mode(size, flags)
        self.screen_size = self.screen.get_size()
        
        pygame.mouse.set_visible(False)
        pygame.display.set_caption('Meu Primeiro Jogo em Python')
        
        self.run = True
        self.list = {
            'player': pygame.sprite.RenderPlain(),
            'enemy': pygame.sprite.RenderPlain(),
            'fire': pygame.sprite.RenderPlain(),
            'enemy_fire': pygame.sprite.RenderPlain(),
        }
        self.player = None
        self.background = background.Background()
    
    def update_actors(self):
        if self.background is not None:
            self.background.update()
        
        for actor in self.list.values():
            actor.update()
    
    def draw_actors(self):
        if self.background is not None:
            self.background.draw(self.screen)
        else:
            self.screen.fill(0)
        
        for actor in self.list.values():
            actor.draw(self.screen)
    
    def act_actors(self):
        pass
    
    def manage(self):
        pass
    
    def handle_events(self):
        player = self.player
        for event in pygame.event.get():
            t = event.type
            
            if t in (KEYDOWN, KEYUP):
                k = event.key
            
            if t == QUIT:
                self.run = False
            elif t == KEYDOWN and k == K_ESCAPE:
                self.run = False
            elif t == KEYUP:
                pass
    
    def loop(self):
        clock = pygame.time.Clock()
        
        while self.run:
            clock.tick(1000)
            
            self.handle_events()
            self.manage()
            
            self.act_actors()
            self.update_actors()
            self.draw_actors()
            
            pygame.display.flip()

if __name__ == '__main__':
    game = Game( (640, 480) )
    game.loop()

Como você pode observar, com poucas linhas de código criamos um fundo de tela animado bem legal. No próximo post vou adicionar algumas naves. Até!

sexta-feira, 29 de maio de 2009

Loop Básico com PyGame

Bom Dia! Vou postar hoje o código de um loop básico para o jogo com naves usando PyGame. Para instalar a biblioteca PyGame vá até o site www.pygame.org e siga as instruções.

Vejamos abaixo o código:

# coding: utf8
# game.py

import pygame
import random

from pygame.locals import *

class Game(object):
    
    def __init__(self, size):
        pygame.init()
        flags = DOUBLEBUF
        self.screen = pygame.display.set_mode(size, flags)
        self.screen_size = self.screen.get_size()
        
        pygame.mouse.set_visible(False)
        pygame.display.set_caption('Meu Primeiro Jogo em Python')
        
        self.run = True
        self.list = {
            'player': pygame.sprite.RenderPlain(),
            'enemy': pygame.sprite.RenderPlain(),
            'fire': pygame.sprite.RenderPlain(),
            'enemy_fire': pygame.sprite.RenderPlain(),
        }
        self.player = None
        self.background = None
        self.actors = None
    
    def update_actors(self):
        if self.background is not None:
            self.background.update()
        
        for actor in self.list.values():
            actor.update()
    
    def draw_actors(self):
        if self.background is not None:
            self.background.draw(self.screen)
        else:
            self.screen.fill(0)
        
        for actor in self.list.values():
            actor.draw(self.screen)
    
    def act_actors(self):
        pass
    
    def manage(self):
        pass
    
    def handle_events(self):
        player = self.player
        for event in pygame.event.get():
            t = event.type
            
            if t in (KEYDOWN, KEYUP):
                k = event.key
            
            if t == QUIT:
                self.run = False
            elif t == KEYDOWN and k == K_ESCAPE:
                self.run = False
            elif t == KEYUP:
                pass
    
    def loop(self):
        clock = pygame.time.Clock()
        
        while self.run:
            clock.tick(1000)
            
            self.handle_events()
            self.manage()
            
            self.act_actors()
            self.update_actors()
            self.draw_actors()
            
            pygame.display.flip()

if __name__ == '__main__':
    game = Game( (640, 480) )
    game.loop()

Este será o corpo de nossa classe principal do jogo. Salve este arquivo e o execute. Você verá que o PyGame abrirá uma janela com tamanho 640x480 preenchida com a cor preta. Ao pressionar a tecla ESC a aplicação é terminada.

Pode não parecer, mas com estas poucas linhas de código já temos a estrutura básica para o nosso jogo. No construtor da class "Game" nós inicializamos a tela do jogo, utilizando o parâmetro "DOUBLEBUF", mais a diante explico o motivo de se utilizar este parâmetro. Então informamos um título para a janela e que desejamos que o mouse não seja visível dentro da janela do jogo.

As variáveis "run" e "list" são então inicializadas. A primeira indica se o jogo deve continuar ou não. A segunda guarda lista de sprites em quatro categorias. A classe "pygame.sprite.RenderPlain" é responsável por guardar as lista com os sprites e ela também será responsável por desenhar esta lista na tela. Na categoria "player" teremos apenas o sprite da nossa nave. Em "enemy" teremos os sprites das naves inimigas. Em "fire" adicionaremos os sprites dos nossos tiros, e, finalmente, em "enemy_fire" adicionaremos os sprites dos tiros das naves inimigas.

Temos então as variáveis "player" e "background", que serão exatamente objetos que representam o jogador e o plano de fundo de nosso jogo.

O método "update_actors" é o responsável por atualizar o estado de nossos objetos do jogo. Primeiro nós atualizamos o estado do plano de fundo, se houver um, e então atualizamos os atores, que são os objetos na nossa lista de sprites.

No método "draw_actors" temos um comportamento semelhante ao método "update_actors", mas nesse caso iremos escrever os pixels dos nossos plano de fundo e sprites na tela.

Em "act_actors" por enquanto não iremos ter nenhum código, mas é aqui que nós iremos tratar a ação de nosso jogo. Esta parte será explicada em um próximo post. O método "manage" será responsável por controlar se adicionamos mais inimigos ou não dependendo do estado do jogo. Por enquanto não irei mostrar o código desta parte também.

Em "handle_events" nós perguntamos ao pygame quais eventos ocorreram e então os tratamos. Por enquanto só temos interesse nos eventos do tipo "QUIT" e "KEYDOWN". Caso o evento "QUIT" aconteça ou no caso de "KEYDOWN" com a tecla "ESC" sendo pressionada, nós ajustamos a variável "run" para "False", o que fará que o loop principal de nosso jogo termine.

Chegamos, então, ao loop principal do jogo. Aqui criamos a variável "clock". Esta variável irá controlar a velocidade do jogo, de forma que o jogo não fique demasiado lento em máquinas menos potentes nem demasiado rápido em máquinas mais potentes.

Então temos nosso loop. Nós primeiros tratamos os possíveis eventos, depois atualizamos o estado do jogo para só então desenharmos os objetos na tela.

A última linha deste loop: "pygame.display.flip()" é bem interessante. Lembram do parâmetro "DOUBLEBUF" que utilizamos anteriormente na inicialização da tela? Pois aquele parâmetro informa ao pygame que, quando escrevermos os pixels na tela, na verdade estaremos escrevendo pixels numa espécie de buffer, que não é mostrado imediatamente na tela. Sendo assim, quando chamamos a função "pygame.display.flip()" este buffer é inteiramente copiado para a tela e a superfícies que estava sendo mostrada se tornará o novo buffer. Esse é o motivo da função se chamar "flip", pois ela "troca" as superfície atual com a temporária.

O "double buffering" como chamamos esta técnica é utilizado para evitar que durante o processo de desenho dos pixels o usuário veja imagens aparecendo uma após a outra na tela, causando um efeito indeseável e até mesmo problemas como o de sprites que são desenhados sobre outros sprites.

Bom, é isso! Em breve trarei mais um post com a classe "Background" que irá mostrar estrelas no plano de fundo, movimentandos no estilo dos famosos "vertical scrollers".

quinta-feira, 28 de maio de 2009

Retomando os Trabalhos!

Olá pessoal! Faz algum tempo que não atualizo o blog, é que estive muiiittoooo ocupado com o trabalho...

Bom, hoje vou começar a entrar nos detalhes do nosso projeto, finalmente! Vou falar um pouco do pygame, a biblioteca Python utilizada no desenvolvimento de jogos. Esta biblioteca irá nos fornecer praticamente tudo o que precisamos para desenvolver um jogo em Python, no que se diz respeito a parte programática da coisa.

Como é sabido, na história do desenvolvimento de jogos quase sempre se utilizam linguagens de baixo nível, tais como C/C++. Isto ocorre devido ao fato da necessidade de desempenho que está implícita na programação de jogos.

No entanto, tal desempenho vem com um custo. O desenvolvedor passa a ter que tratar de detalhes que não têm relação com o desenvolvimento do jogo propriamente dito, tais como o gerenciamento de memória e o tratamento de erros. Outro detalhe é que no desenvolvimento de jogos sempre temos algumas características que estão sempre presentes, tais como os sprites - figuras animadas ou não, e as rotinas que manipulam estes sprites, utilização de sons, recursos de rede, etc. Tendo isto em mente, surgiram várias bibliotecas dedicadas a facilitar a vida dos desenvolvedores de jogos, oferecendo o suporte básico a tais funcionalidades. Este é o caso da biblioteca SDL, a qual é a base da biblioteca pygame.

Ou seja, temos a biblioteca SDL, totalmente otimizada para linguagens de baixo nível, e temos o pygame, que possibilita o acesso a SDL através da linguagem Python. Isto torna o desenvolvimento de jogos utilizando Python uma realidade, já que temos apenas que nos preocupar com a criação do jogo em si, e, uma vez que com Python temos uma forma simples de expressar nossas ideias, deixando os detalhes de baixo nível para o pygame, podemos focar apenas na parte criativa.

Conceitos Básicos

O loop principal de um jogo é bastante simples. Basicamente temos que tratar os eventos, tais como o movimento do mouse ou uma tecla ser pressionada ou a recepção de dados via rede, executar a inteligência artificial e então o desenho do plano de fundo e de personagens visíveis. Vejamos abaixo como isso é feito de um modo geral:

01. Inicializar estruturas internas
02. Carregar dados do jogo
03. Repetir:
04.    Capturar e tratar eventos
05.    Executar a inteligência artificial
06.    Desenhar plano de fundo
07.    Desenhar personagems
08.    Emitir sons se necessário
09. Terminar o loop se o usuário deseja terminar o jogo.

Este pseudo código resume o laço principal de qualquer jogo, por mais avançado que seja, sempre terá um loop parecido com este.

No próximo post pretendo mostrar os passo para a criação de um jogo bem simples, onde o jogador controla uma nave espacial e deve derrotar os inimigos, no estilo "vertical scroller", ou seja, os inimigos irão aparecer no topo da tela e seguirão para baixo tentando acertar a nave do jogador. Espero postar uma primeira parte ainda hoje, ou no máximo amanhã. Então fiquem ligado!

quinta-feira, 19 de março de 2009

Herança

Esta semana vou falar sobre mais um tópico relativo a orientação a objetos: herança.

Herança consiste em fazer com que uma classe "herde" os atributos e métodos de uma outra classe, podendo reescrevê-los ou adicionar funcionalidade à estes.

Vamos a um exemplo prático, a partir da classe que especificamos anteriormente:

# coding: utf-8
# mamifero.py

class Mamifero:
    
    def som(self):
        print 'emitir um som'

class Homem(Mamifero):
        
    def som(self):
        print 'Oi!'

class Cachorro(Mamifero):
    
    def som(self):
        print 'Wufff! Wufff!'

class Gato(Mamifero):
    
    def som(self):
        print 'Meawwwww!'

mamifero = Mamifero()
mamifero.som()

animais = [Homem(), Cachorro(), Gato()]

for animal in animais:
    animal.som()
    

No código acima, criamos 3 classes onde estas herdam os atributos da classe "Mamifero". Neste caso estou demonstrando como podemos reescrever os métodos de uma classe para alterar o funcionamento deste numa classe mais específica.

Ao executar o código acima, no "for" mais abaixo no código, percorreremos uma lista de animais, chamando o método "som()" para cada um deles. Como cada animal foi criado a partir de uma classe específica, o método desta classe mais específica é chamado, e não o da classe mais genérica, "Mamifero". Sendo assim, o som emitido será o que nós esperamos.

Estou indo devagar nesta introdução à classes por este ser um tópico que causa muita confusão nos programadores iniciantes, mas que após um certo tempo de prática se mostra um paradigma interessante e fácil de lidar no dia a dia, tornando a abstração de problemas reais em algoritmos mais simples.

domingo, 1 de março de 2009

De Volta Após o Carnaval!

Olá Pessoal!

Pois é, passado o carnaval, estamos de volta! Esta semana vou entrar num tema bastante interessante: orientação a objetos.

Um conceito fundamental de orientação a objetos é o conceito de classes. Uma classe é uma abstração de um modelo, incluindo dados sobre o modelo e ações que podem ser tomadas sobre este modelo.

Um exemplo clássico de classe apresentada nos cursos de programação é a classe Mamífero. Vou utilizá-la aqui também, por ser de simples entendimento.

Vamos pensar um pouco sobre um mamífero... O que os mamíferos tem em comum? Podemos citar a data de nascimento, por exemplo. Este, aliás, é um atributo comum a todos os seres vivos, não somente os mamíferos... ;-)

Então, como definimos uma classe em Python? Não poderia ser mais simples. Vejamos o exemplo abaixo:

# coding: utf-8
# mamifero.py

class Mamifero:
    def __init__(self):
        self.nascimento = '5 de julho de 1981'

mamifero = Mamifero()
print mamifero.nascimento

No código acima, definimos a classe "Mamifero" e criamos um método, que é uma função pertencente à classe, chamado "__init__". Este método é especial, servindo como inicializador. Ou seja, sempre que um objeto da classe "Mamifero" for criado, este método especial será chamado.

Um outro detalhe é a variável "self". Tal variável representa o objeto para o qual o método foi chamado. No caso do método "__init__", o self representa o objeto que está sendo criado naquele momento.

No método "__init__", criamos um atributo para o objeto, indicando a data do nascimento. Para isto, simplesmente atribuimos uma nova propriedade ao objeto "self". A sintaxe da atribuição é bem simples: objeto.atributo = valor. Tal atribuição fará com que o objeto referenciado tenha um novo atributo, ou, caso o objeto já tenha o atributo especificado, atribuirá um novo valor a ele.

Para criar um novo objeto, a sintaxe também é bem simples: novo_objeto = Classe(). E para acessar um atributo do objeto utilizamos a sintaxe: objeto.atributo. Como vocês podem notar, a linguagem Python faz com que a criação e a utilização de classes e objetos seja o mais simples possível. Eu particularmente gosto do paradigma de orientação a objetos, e aconselho sempre utilizar classes em seus programas.

Vou manter este post bem básico mesmo, apenas com esta definição de classes. Na próxima semana vou mostrar um exemplo mais elaborado da utilização destas. Até o próximo post!

domingo, 8 de fevereiro de 2009

Solução do problema 3n + 1

Olá pessoal! Hoje trago a solução do problema 3n + 1 proposto no início da semana. Então vamos direto a ela:

# coding: utf8
# seq_3n1.py

resolvidos = {1: 1}

def solucionar(a, b):
    maior = 0
    while a <= b:
        num = calc_seq(a)
        if num > maior:
            maior = num
        a += 1

    return maior

def calc_seq(a):
    if a in resolvidos:
        return resolvidos[a]
    if (a % 2) == 0:
        new_a = a / 2
    else:
        new_a = 3 * a + 1
        
    passos = 1 + calc_seq(new_a)
    resolvidos[a] = passos 
    return passos
    
if __name__ == '__main__':
    maior = solucionar(1, 10)
    print "A maior sequencia gerado por um número entre 1 e 10 tem tamanho:", maior
    
    maior = solucionar(100, 200)
    print "A maior sequencia gerado por um número entre 100 e 200 tem tamanho:", maior
    
    maior = solucionar(201, 210)
    print "A maior sequencia gerado por um número entre 201 e 210 tem tamanho:", maior
    
    maior = solucionar(900, 1000)
    print "A maior sequencia gerado por um número entre 900 e 1000 tem tamanho:", maior   

Note que, para calcular a sequencia, utilizo uma função recursiva. Esta função primeiro verifica se já sabemos o tamanho da cadeia para o número que estamos procurando no dicionário "resolvidos". Caso este valor não esteja presente, calculamos o próximo elemento da sequencia e atribuímos o tamanho da sequencia como sendo o tamanho da sequencia do proximo elemente acrescido de 1, salvando este tamanho no dicionário.

Você pode estar se perguntando qual a razão de utilizarmos este dicionário, mas a razão é de simplesmente evitar cálculos repetidos. Inevitavelmente, quando estivermos calculando todas as sequencias de 100 a 200, por exemplo, iremos nos encontrar recalculando a mesma sequencia inúmeras vezes. Mas, se já tivermos calculado uma sequencia anteriormente, podemos simplesmente consultar o tamanho da sequencia gerada no dicionário, o que nos salvará um tempo precioso.

Bom, é isto. Como vocês podem notar, a solução é bastante simples e, com a ajuda de um dicionário, uma estrutura de dados valiosa que temos presente na linguagem Python, foi possível evitar um problema que frequentemente nos deparamos ao utilizar funções recursivas, que é a repetição de cálculos.

Espero ter sido claro na minha explicação e, como sempre, se tiverem dúvidas fiquem a vontade para enviar comentários. Até o próximo post!

quarta-feira, 4 de fevereiro de 2009

Solucionando Problemas com Algoritmos

Olá! Esta semana vou tentar explicar um pouco sobre algoritmos. Um algoritmo nada mais é que uma sequencia de passos lógicos que, ao serem seguidos, levam à solução de um determinado problema. Nos posts anteriores nós já utilizamos alguns algoritmos para resolver os problemas da sequencia de Fibonacci e do fatorial. Estes são algoritmos simples, fáceis de entender e de se desenvolver. No entanto, alguns problemas exigem algoritmos bastante complexos para sua solução e, em alguns casos, não será possível encontrar um algoritmo para solucionar um problema em tempo hábil. Vou explicar esta última sentença mais adiante.

Para exemplificar, vou sugerir que o leitor faça um exercício. Abra o bloco de notas ou o seu editor favorito, ou mesmo apanhe um pedaço de papel e um lápis, e escreva em ordem numerada os passos que você efetua para ir ao trabalho pela manhã ao acordar, ou, se você é estudante, os passos que você efetua para chegar à escola ou universidade ou onde quer que você estude.

Isto que você fez também é um algoritmo, que soluciona o problema de ir a um local ao acordar pela manhã. Alguns irão escrever uma longa lista, bem detalhada. Outros irão escrever uma lista mais curta e simples. Cada lista é válida.

Para esta semana, não irei escrever nenhum código no post, mas irei propor um exercício, que consiste em desenvolver um algoritmo para solucionar um problema computacional, e então escrever um módulo Python que implemente tal algoritmo. A seguir descrevo o problema:

Considere que, para um determinado número inteiro n existe uma sequencia de números a partir de n que leva ao número 1. Tal sequencia será definida da seguinte forma:

  • se n for ímpar, multiplique n por 3 e some 1 para obter o próximo número da sequencia
  • caso contrário, ou seja, se n for par, divida o por 2 para obter o próximo número

Para determinar se um número é ímpar ou par utilize o operador "%". Este operador retorna o resto da divisão, logo, se "n % 2 == 1" significa que o número é ímpar, pois o resto da divisão por 2 foi igual a um.

Vejamos um exemplo, para n igual a 22 teremos a seguinte sequencia:

22 11 34 17 52 26 13 40 20 10 5 16 8 4 2 1

O tamanho da sequencia, neste caso, é 16. O problema que proponho é, dados dois números, a e b, determinar qual o tamanho da maior sequencia gerada por todos os números entre estes, incluindo os próprios a e b.

Para solucionar estes problema, escreva um módulo "seq_3n1.py" com terá uma função "solucionar(a, b)" que retornará o tamanho da maior cadeia gerada

Publicarei uma solução para o exercício mais tarde esta semana no blog.