Convertendo um jogo escrito em Basic para Python - Parte I
Autor: Nilo Menezes | Publicado: 2016-05-14T13:13:00-04:00

A nostalgia dos computadores da década de 80 é algo que nunca parei de ter. Quando criança, tive a sorte de utilzar vários computadores de 8-bits, como ZX-81, ZX-Spectrum, Apple II e MSX, ou melhor, seus clones nacionais (TK-85, TK-90X, TK2000), uma vez que o Brasil vivia a época da Reserva do Mercado de Informática.
Numa época que não havia Internet, nós passávamos o tempo a digitar programas. Uma série de livros sobre programação de jogos foi editada pela editora Lutécia no Brasil, mas os originais americanos foram liberados pela Usborne. Neste artigo, eu vou traduzir o jogo principal do Computer Battlegames, chamado de Missile, de Apple II Basic para Python com Pyglet. A listagem original está na página 34 do livro em inglês (ver pdf acima).

10 HOME
20 HGR
30 HCOLOR=3
40 DIM Y(3),F(3)
50 N=1 : MS=5
60 PS=INT(RND(1)*6+4)
70 P=INT(RND(1)*135+11)
80 GOSUB 400
90 FOR I=PS TO 265 STEP PS
100 X=I-PS : Y=159-P : C=0 : GOSUB 300
110 X=I : C=3: GOSUB 300
120 F$="" : IF PEEK(-16384)&gt;127 THEN GET F$
130 IF F$="" OR N&gt;3 THEN 160
140 F(N)=1
150 N=N+1
160 FOR J=1 TO 3
170 C=0 : GOSUB 350
180 IF F(J)=0 OR Y(J)&gt;145 THEN 230
190 Y(J)=Y(J)+MS
200 C=3 : GOSUB 350
210 X=J*70-I : Y=P-Y(J)
220 IF X&gt;-1 AND X&lt;15 AND Y&gt;-9 AND Y&lt;5 THEN 270
230 NEXT
240 NEXT
250 VTAB 22 : PRINT "MISSED"
260 END
270 VTAB 22 : PRINT "HIT!!!"
280 END
300 HCOLOR=C
310 HPLOT X,Y TO X,Y-8
320 HPLOT TO X+3,Y-2 : HPLOT TO X+12, Y-2
330 HPLOT TO X+14,Y : HPLOT TO X,Y
340 RETURN
350 HCOLOR=C
360 HPLOT 70*J,158-Y(J) TO 70*J,154-Y(J)
340 RETURN
350 HCOLOR=C
360 HPLOT 70*J, 158-Y(J) TO 70*J,154-Y(J)
370 RETURN
400 FOR J=1 TO 3
410 HPLOT 70*J-5,159 TO 70*J+5,159
420 NEXT
430 RETURN

Vejamos o jogo rodando em um emulador:





Versão comentada:

# Limpa a tela
10 HOME
# Entra no modo de alta resolução 280x192
20 HGR
# Seleciona a cor 3 (purpura/magenta)
30 HCOLOR=3
# Cria dois vetores com 3 elementos cada
40 DIM Y(3),F(3)
# Inicializa N igual a 1 e MS igual a 5
50 N=1 : MS=5
# Gera um número aleatório entre 0 e 6 + 4
60 PS=INT(RND(1)*6+4)
# Gera um número aleatório entre 0 e 135 + 11
70 P=INT(RND(1)*135+11)
# Desvia para subrotina
80 GOSUB 400
# Loop: repete I de PS até 265, incrementando de PS
90 FOR I=PS TO 265 STEP PS
# Calcula os valores de X, Y e C. Desvia para subrotina em 300
100 X=I-PS : Y=159-P : C=0 : GOSUB 300
# Define X e C. Desvia para subrotina 300
110 X=I : C=3: GOSUB 300
# Verifica se uma tecla foi pressionada. Se foi, guarda em F
120 F$="" : IF PEEK(-16384)&gt;127 THEN GET F$
# Se algo foi pressionado ou se N&gt;3 desvia para 160
130 IF F$="" OR N&gt;3 THEN 160
# F[N] = 1
140 F(N)=1
# N+=1
150 N=N+1
# Repete J de 1 até 3
160 FOR J=1 TO 3
# Zera C e desvia para subrotina da linha 350
170 C=0 : GOSUB 350
# Se F[J]==0 ou Y[J]&gt;145 desvia para 230
180 IF F(J)=0 OR Y(J)&gt;145 THEN 230
# Y[J]+=MS
190 Y(J)=Y(J)+MS
# C=3. Desvia para subrotina da linha 350
200 C=3 : GOSUB 350
# Calcula X e Y
210 X=J*70-I : Y=P-Y(J)
# Se X&gt;-1 e X&lt;15 e Y&gt;-9 e Y&lt;5 desvia para 270
220 IF X&gt;-1 AND X&lt;15 AND Y&gt;-9 AND Y&lt;5 THEN 270
# Fim do loop, volta para o for da linha 160
230 NEXT
# Fim do loop, volta para for da linha 90
240 NEXT
# Posiciona o cursor na linha 22 e imprime MISSED
250 VTAB 22 : PRINT "MISSED"
# Termina o programa
260 END
# Posiciona o cursor na linha 22 e imprime HIT!!!
270 VTAB 22 : PRINT "HIT!!!"
# Termina o programa
280 END
# Troca a cor de desenho para C
300 HCOLOR=C
# Traça uma linha de X,Y até X,Y-8
310 HPLOT X,Y TO X,Y-8
# Continua a linha
320 HPLOT TO X+3,Y-2 : HPLOT TO X+12, Y-2
330 HPLOT TO X+14,Y : HPLOT TO X,Y
# Retorno da subrotina
340 RETURN
# Troca a cor de desenho para C
350 HCOLOR=C
# Desenha linha
360 HPLOT 70*J,158-Y(J) TO 70*J,154-Y(J)
# Volta da subrotina
340 RETURN
# Troca a cor para C
350 HCOLOR=C
# Desenha linha
360 HPLOT 70*J, 158-Y(J) TO 70*J,154-Y(J)
# Volta da subrotina
370 RETURN
# Repete J de 1 à 3
400 FOR J=1 TO 3
# Desenha linha
410 HPLOT 70*J-5,159 TO 70*J+5,159
# Fim do loop, volta para linha 400
420 NEXT
# Retorna da subrotina
430 RETURN

Bom, como precisaremos desenhar, vamos instalar a Pyglet:

pip3 install pyglet


A primeira coisa a fazer é criar a janela da Pyglet e separar a janela do jogo em si.
No caso, a janela é responsável por receber os eventos do teclado.
A configuração do OpenGL também precisam ser feitas aqui.
Como a alta resolução do Apple II é muito pequena 280x192 pontos, eu estou usando uma escala 4.
Desta forma, as coordenadas permanecerão as mesmas, mas os gráficos serão 4 vezes maiores. 
Se ficar muito grande em seu monitor, você pode ajustar a escala.

Eu tentei manter os comentários originais para ficar mais fácil de relacionar o código novo com o antigo.

Outra mudança são as coordenadas Y. No Apple II, Y = 0 é a primeira linha e em OpenGL é a última.

Vejamos como ficou o código da janela:

class Missile(pyglet.window.Window):
    # 20 HGR
    def __init__(self):
        self.scale = 4    # Escala os gráficos 4x
        self.frames = 8   # Frames por segundo.
                          # Define quantas vezes vamos atualizar a tela por segundo
        super(Missile, self).__init__(280 * self.scale,
                                      192 * self.scale,
                                      caption="Missiles")
        self.set_mouse_visible(False)
        self.game = Game(self)
        self.schedule = pyglet.clock.schedule_interval(
            func=self.update, interval=1.0 / self.frames)

    def update(self, interval):
        pass

    def on_draw(self):
        window.clear()
        self.game.atualiza()

    def on_resize(self, width, height):
        # Inicializa a view
        glViewport(0, 0, width, height)
        glMatrixMode(gl.GL_PROJECTION)
        glLoadIdentity()
        # Inverte as coordenadas do eixo Y
        glOrtho(0, width, height, 0, -1, 1)
        # Aplica a escala
        glScalef(self.scale, self.scale, 1.0)
        glMatrixMode(gl.GL_MODELVIEW)

    def on_key_press(self, symbol, modifiers):
        # Verifica se uma tecla foi pressionada. Se foi, guarda em F
        # 120 F$="" : IF PEEK(-16384)&gt;127 THEN GET F$
        self.game.pressionado = True

    def crialabel(self, mensagem, x=None, y=None,
                  fonte='Times New Roman', tamanho=36):
        """Prepara uma mensagem de texto para ser exibida"""
        x = x or self.width // 2
        y = y or self.height // 2
        return pyglet.text.Label(
            mensagem, font_name=fonte, font_size=tamanho,
            x=x, y=y, anchor_x='center', anchor_y='center')


Além disso, as cores do Apple II precisam ser definidas:

def rgb_to_f(r, g, b):
    return(r / 255.0, g / 255.0, b / 255.0)

# Cores do modo de alta resolução HGR
# Fonte: https://github.com/AppleWin/AppleWin/issues/254
CORES = [(0.0, 0.0, 0.0),  # Preto 0
         rgb_to_f(20, 245, 60),  # Verde 1
         rgb_to_f(255, 68, 253),  # Magenta 2
         rgb_to_f(255, 255, 255),  # Branco 3
         rgb_to_f(255, 106, 60),  # Laranja 5
         rgb_to_f(20, 207, 253),  # Azul médio 6
         rgb_to_f(255, 255, 255),  # Branco 7
         ]


e finalmente a classe Game com o jogo em si. Observar que os índices em Python começam em 0 e em Basic começam com 1.
Primeiro passo da conversão:

class Game():
    def __init__(self, parent):
        self.cor = CORES
        self.window = parent
        # Cria dois vetores com 3 elementos cada
        # 40 DIM Y(3),F(3)
        self.Y = [0] * 3
        self.F = [0] * 3
        # Inicializa N igual a 1 e MS igual a 5
        # 50 N=1 : MS=5
        self.N = 0
        self.MS = 5
        # Gera um número aleatório entre 0 e 6 + 4
        # 60 PS=INT(RND(1)*6+4)
        self.PS = random.randint(0, 6) + 4
        # Gera um número aleatório entre 0 e 135 + 11
        # 70 P=INT(RND(1)*135+11)
        self.P = random.randint(0, 135) + 11
        self.estado = "jogando"
        self.I = self.PS
        self.pressionado = False
        self.label = None

    def atualiza(self):
        if self.estado == "jogando":
            self.jogue()
        else:
            if self.label:
                glMatrixMode(gl.GL_PROJECTION)
                glLoadIdentity()
                glOrtho(0, self.window.width, 0, self.window.height, -1, 1)
                glMatrixMode(gl.GL_MODELVIEW)
                self.label.draw()
            if self.pressionado:
                pyglet.app.event_loop.exit()

    def jogue(self):
        # Desvia para subrotina
        # 80 GOSUB 400
        self.sub_400()
        # Loop: repete I de PS até 265, incrementando de PS
        # 90 FOR I=PS TO 265 STEP PS
        if self.I &gt; 265:
            self.sub_250()
            return
        # Calcula os valores de X, Y e C. Desvia para subrotina em 300
        # 100 X=I-PS : Y=159-P : C=0 : GOSUB 300
        self.X = self.I - self.PS
        self.y = 159 - self.P
        self.C = 0
        self.sub_300()
        # Define X e C. Desvia para subrotina 300
        # 110 X=I : C=3: GOSUB 300
        self.X = self.I
        self.C = 3
        self.sub_300()
        # Se algo foi pressionado ou se N&gt;3 desvia para 160
        # 130 IF F$="" OR N&gt;3 THEN 160
        if self.pressionado and self.N &lt; 3:
            # F[N] = 1
            # 140 F(N)=1
            self.F[self.N] = 1
            # N+=1
            # 150 N=N+1
            self.N += 1
        self.pressionado = False
        # Repete J de 1 até 3
        # 160 FOR J=1 TO 3
        for self.J in range(0, 3):
            # Zera C e desvia para subrotina da linha 350
            # 170 C=0 : GOSUB 350
            self.C = 0
            self.sub_350()
            # Se F[J]==0 ou Y[J]&gt;145 desvia para 230
            # 180 IF F(J)=0 OR Y(J)&gt;145 THEN 230
            if self.F[self.J] == 0 or self.Y[self.J] &gt; 145:
                continue
            # Y[J]+=MS
            # 190 Y(J)=Y(J)+MS
            self.Y[self.J] += self.MS
            # C=3. Desvia para subrotina da linha 350
            # 200 C=3 : GOSUB 350
            self.C = 3
            self.sub_350()
            # Calcula X e Y
            # 210 X=J*70-I : Y=P-Y(J)
            self.X = (self.J + 1) * 70 - self.I
            self.y = self.P - self.Y[self.J]
            # Se X&gt;-1 e X&lt;15 e Y&gt;-9 e Y&lt;5 desvia para 270
            # 220 IF X&gt;-1 AND X&lt;15 AND Y&gt;-9 AND Y&lt;5 THEN 270
            if self.X &gt; -1 and self.X &lt; 15 and self.y &gt; -9 and self.y &lt; 5:
                self.sub_270()           
            # Fim do loop, volta para o for da linha 160
            # 230 NEXT
            # Fim do loop, volta para for da linha 90
            # 240 NEXT
        # 90 FOR I=PS TO 265 STEP PS
        self.I += self.PS

    def sub_250(self):
        # Posiciona o cursor na linha 22 e imprime MISSED
        # 250 VTAB 22 : PRINT "MISSED"
        print("MISSED")
        self.imprima("MISSED")
        # Termina o programa
        # 260 END
        self.muda_estado("fimdejogo")

    def sub_270(self):
        # Posiciona o cursor na linha 22 e imprime HIT!!!
        # 270 VTAB 22 : PRINT "HIT!!!"
        print("HIT")
        self.imprima("HIT!!!")
        # Termina o programa
        self.muda_estado("fimdejogo")

    def sub_300(self):
        # Troca a cor de desenho para C
        # 300 HCOLOR=C
        self.set_color(self.C)
        # Traça uma linha de X,Y até X,Y-8
        # 310 HPLOT X,Y TO X,Y-8
        # Continua a linha
        # 320 HPLOT TO X+3,Y-2 : HPLOT TO X+12, Y-2
        # 330 HPLOT TO X+14,Y : HPLOT TO X,Y
        pyglet.graphics.draw(6, pyglet.gl.GL_LINE_LOOP,
                             ('v2i', (self.X, self.y,
                                      self.X, self.y - 8,
                                      self.X + 3, self.y - 2,
                                      self.X + 12, self.y - 2,
                                      self.X + 14, self.y,
                                      self.X, self.y)))

        # Retorno da subrotina
        # 340 RETURN

    def sub_350(self):
        # Troca a cor para C
        # 350 HCOLOR=C
        self.set_color(self.C)
        # Desenha linha
        # 360 HPLOT 70*J, 158-Y(J) TO 70*J,154-Y(J)
        J = self.J + 1
        pyglet.graphics.draw(2, pyglet.gl.GL_LINES,
                             ('v2i', (70 * J, 158 - self.Y[self.J],
                                      70 * J, 154 - self.Y[self.J])))
        # Volta da subrotina
        # 370 RETURN

    def sub_400(self):
        self.set_color(3)
        # 400 FOR J=1 TO 3
        for J in range(1, 4):
            # Desenha linha
            # 410 HPLOT 70*J-5,159 TO 70*J+5,159
            pyglet.graphics.draw(2, pyglet.gl.GL_LINES,
                                 ('v2i', (70 * J - 5, 159,
                                          70 * J + 5, 159)))
            # Fim do loop, volta para linha 400
            # 420 NEXT
        # Retorna da subrotina
        # 430 RETURN

    def set_color(self, color):
        glColor3f(*self.cor[color])

    def muda_estado(self, estado):
        print("Mudança de Estado: {} --&gt; {}".format(self.estado, estado))
        self.estado = estado

    def imprima(self, mensagem):
        self.label = self.window.crialabel(mensagem)

Como em OpenGL limpamos a tela a cada frame, a rotina que apaga os objetos pode ser apagada. 
No caso, a chamada a sub_300() com C=0.

O nome dos métodos ainda não foram mudados, vamos renomear:

sub_250 para pedeu

sub_270 para acertou

sub_300 para desenha_aviao

sub_350 para desenha_tiro

sub_400 para desenha_bases

Apagando os comentários com o código em Basic, temos:

import pyglet
from pyglet.gl import *
import random


def rgb_to_f(r, g, b):
    return(r / 255.0, g / 255.0, b / 255.0)

# Cores do modo de alta resolução HGR
# Fonte: https://github.com/AppleWin/AppleWin/issues/254
CORES = [(0.0, 0.0, 0.0),  # Preto 0
         rgb_to_f(20, 245, 60),  # Verde 1
         rgb_to_f(255, 68, 253),  # Magenta 2
         rgb_to_f(255, 255, 255),  # Branco 3
         rgb_to_f(255, 106, 60),  # Laranja 5
         rgb_to_f(20, 207, 253),  # Azul médio 6
         rgb_to_f(255, 255, 255),  # Branco 7
         ]


class Game():
    def __init__(self, parent):
        self.cor = CORES
        self.window = parent
        # Cria dois vetores com 3 elementos cada
        self.Y = [0] * 3
        self.F = [0] * 3
        # Inicializa N igual a 1 e MS igual a 5
        self.N = 0
        self.MS = 5
        # Gera um número aleatório entre 0 e 6 + 4
        self.PS = random.randint(0, 6) + 4
        # Gera um número aleatório entre 0 e 135 + 11
        self.P = random.randint(0, 135) + 11
        self.estado = "jogando"
        self.I = self.PS
        self.pressionado = False
        self.label = None

    def atualiza(self):
        if self.estado == "jogando":
            self.jogue()
        else:
            if self.label:
                glMatrixMode(gl.GL_PROJECTION)
                glLoadIdentity()
                glOrtho(0, self.window.width, 0, self.window.height, -1, 1)
                glMatrixMode(gl.GL_MODELVIEW)
                self.label.draw()
            if self.pressionado:
                pyglet.app.event_loop.exit()

    def jogue(self):
        self.desenha_bases()
        if self.I &gt; 265:
            self.perdeu()
            return
        self.X = self.I - self.PS
        self.y = 159 - self.P
        self.C = 0
        self.desenha_aviao()
        self.X = self.I
        self.C = 3
        self.desenha_aviao()
        if self.pressionado and self.N &lt; 3:
            self.F[self.N] = 1
            self.N += 1
        self.pressionado = False
        for self.J in range(0, 3):
            if self.F[self.J] == 0 or self.Y[self.J] &gt; 145:
                continue
            self.Y[self.J] += self.MS
            self.C = 3
            self.desenha_tiro()
            self.X = (self.J + 1) * 70 - self.I
            self.y = self.P - self.Y[self.J]
            if self.X &gt; -1 and self.X &lt; 15 and self.y &gt; -9 and self.y &lt; 5:
                self.acertou()
        self.I += self.PS

    def perdeu(self):
        self.imprima("MISSED")
        self.muda_estado("fimdejogo")

    def acertou(self):
        self.imprima("HIT!!!")
        self.muda_estado("fimdejogo")

    def desenha_aviao(self):
        self.set_color(self.C)
        pyglet.graphics.draw(6, pyglet.gl.GL_LINE_LOOP,
                             ('v2i', (self.X, self.y,
                                      self.X, self.y - 8,
                                      self.X + 3, self.y - 2,
                                      self.X + 12, self.y - 2,
                                      self.X + 14, self.y,
                                      self.X, self.y)))

    def desenha_tiro(self):
        self.set_color(self.C)
        J = self.J + 1
        pyglet.graphics.draw(2, pyglet.gl.GL_LINES,
                             ('v2i', (70 * J, 158 - self.Y[self.J],
                                      70 * J, 154 - self.Y[self.J])))

    def desenha_bases(self):
        self.set_color(3)
        for J in range(1, 4):
            pyglet.graphics.draw(2, pyglet.gl.GL_LINES,
                                 ('v2i', (70 * J - 5, 159,
                                          70 * J + 5, 159)))

    def set_color(self, color):
        glColor3f(*self.cor[color])

    def muda_estado(self, estado):
        print("Mudança de Estado: {} --&gt; {}".format(self.estado, estado))
        self.estado = estado

    def imprima(self, mensagem):
        self.label = self.window.crialabel(mensagem)


class Missile(pyglet.window.Window):
    def __init__(self):
        self.scale = 4    # Escala os gráficos 4x
        self.frames = 8   # Frames por segundo.
                          # Define quantas vezes vamos atualizar a tela por segundo
        super(Missile, self).__init__(280 * self.scale,
                                      192 * self.scale,
                                      caption="Missiles")
        self.set_mouse_visible(False)
        self.game = Game(self)
        self.schedule = pyglet.clock.schedule_interval(
            func=self.update, interval=1.0 / self.frames)

    def update(self, interval):
        pass

    def on_draw(self):
        window.clear()
        self.game.atualiza()

    def on_resize(self, width, height):
        # Inicializa a view
        glViewport(0, 0, width, height)
        glMatrixMode(gl.GL_PROJECTION)
        glLoadIdentity()
        # Inverte as coordenadas do eixo Y
        glOrtho(0, width, height, 0, -1, 1)
        # Aplica a escala
        glScalef(self.scale, self.scale, 1.0)
        glMatrixMode(gl.GL_MODELVIEW)

    def on_key_press(self, symbol, modifiers):
        self.game.pressionado = True

    def crialabel(self, mensagem, x=None, y=None,
                  fonte='Times New Roman', tamanho=36):
        """Prepara uma mensagem de texto para ser exibida"""
        x = x or self.width // 2
        y = y or self.height // 2
        return pyglet.text.Label(
            mensagem, font_name=fonte, font_size=tamanho,
            x=x, y=y, anchor_x='center', anchor_y='center')


window = Missile()
pyglet.app.run()


Bem melhor, mas ainda guarda várias características do programa em Basic.
O nome das variáveis é uma catástrofe. Renomeando as variáveis e melhorando os comentários, o programa completo fica assim:

import pyglet
from pyglet.gl import *
import random


def rgb_to_f(r, g, b):
    return(r / 255.0, g / 255.0, b / 255.0)

# Cores do modo de alta resolução HGR
# Fonte: https://github.com/AppleWin/AppleWin/issues/254
CORES = [(0.0, 0.0, 0.0),  # Preto 0
         rgb_to_f(20, 245, 60),  # Verde 1
         rgb_to_f(255, 68, 253),  # Magenta 2
         rgb_to_f(255, 255, 255),  # Branco 3
         rgb_to_f(255, 106, 60),  # Laranja 5
         rgb_to_f(20, 207, 253),  # Azul médio 6
         rgb_to_f(255, 255, 255),  # Branco 7
         ]


class Game():
    def __init__(self, parent):
        self.cor = CORES
        self.window = parent
        # Cria dois vetores com 3 elementos cada
        self.Y = [0] * 3 # Altura do tiro
        self.F = [0] * 3 # Estado do tiro
        self.tiros_disparados = 0  # tiros já disparados
        self.velocidade_do_tiro = 5
        # Gera um número aleatório entre 0 e 6 + 4
        self.velocidade_aviao = random.randint(0, 6) + 4
        # Gera um número aleatório entre 0 e 135 + 11
        self.altura_do_aviao = random.randint(0, 135) + 11
        self.estado = "jogando"
        self.posicao_do_aviao = self.velocidade_aviao
        self.pressionado = False
        self.label = None

    def atualiza(self):
        if self.estado == "jogando":
            self.jogue()
        else:
            if self.label:
                glMatrixMode(gl.GL_PROJECTION)
                glLoadIdentity()
                glOrtho(0, self.window.width, 0, self.window.height, -1, 1)
                glMatrixMode(gl.GL_MODELVIEW)
                self.label.draw()
            if self.pressionado:
                pyglet.app.event_loop.exit()

    def jogue(self):
        self.desenha_bases()
        if self.posicao_do_aviao &gt; 265:
            self.perdeu()
            return
        self.y = 159 - self.altura_do_aviao
        self.X = self.posicao_do_aviao
        self.C = 3
        self.desenha_aviao()
        if self.pressionado and self.tiros_disparados &lt; 3:
            self.F[self.tiros_disparados] = 1
            self.tiros_disparados += 1
        self.pressionado = False
        self.processa_tiros()
        self.posicao_do_aviao += self.velocidade_aviao

    def processa_tiros(self):
        for self.J in range(0, 3):
            if self.F[self.J] == 0 or self.Y[self.J] &gt; 145:
                continue
            self.Y[self.J] += self.velocidade_do_tiro
            self.C = 3
            self.desenha_tiro()
            self.X = (self.J + 1) * 70 - self.posicao_do_aviao
            self.y = self.altura_do_aviao - self.Y[self.J]
            if self.X &gt; -1 and self.X &lt; 15 and self.y &gt; -9 and self.y &lt; 5:
                self.acertou()

    def perdeu(self):
        self.imprima("MISSED")
        self.muda_estado("fimdejogo")

    def acertou(self):
        self.imprima("HIT!!!")
        self.muda_estado("fimdejogo")

    def desenha_aviao(self):
        self.set_color(self.C)
        pyglet.graphics.draw(6, pyglet.gl.GL_LINE_LOOP,
                             ('v2i', (self.X, self.y,
                                      self.X, self.y - 8,
                                      self.X + 3, self.y - 2,
                                      self.X + 12, self.y - 2,
                                      self.X + 14, self.y,
                                      self.X, self.y)))

    def desenha_tiro(self):
        self.set_color(self.C)
        J = self.J + 1
        pyglet.graphics.draw(2, pyglet.gl.GL_LINES,
                             ('v2i', (70 * J, 158 - self.Y[self.J],
                                      70 * J, 154 - self.Y[self.J])))

    def desenha_bases(self):
        self.set_color(3)
        for J in range(1, 4):
            pyglet.graphics.draw(2, pyglet.gl.GL_LINES,
                                 ('v2i', (70 * J - 5, 159,
                                          70 * J + 5, 159)))

    def set_color(self, color):
        glColor3f(*self.cor[color])

    def muda_estado(self, estado):
        print("Mudança de Estado: {} --&gt; {}".format(self.estado, estado))
        self.estado = estado

    def imprima(self, mensagem):
        self.label = self.window.crialabel(mensagem)


class Missile(pyglet.window.Window):
    def __init__(self):
        self.scale = 4    # Escala os gráficos 4x
        self.frames = 8   # Frames por segundo.
                          # Define quantas vezes vamos atualizar a tela por segundo
        super(Missile, self).__init__(280 * self.scale,
                                      192 * self.scale,
                                      caption="Missiles")
        self.set_mouse_visible(False)
        self.game = Game(self)
        self.schedule = pyglet.clock.schedule_interval(
            func=self.update, interval=1.0 / self.frames)

    def update(self, interval):
        pass

    def on_draw(self):
        window.clear()
        self.game.atualiza()

    def on_resize(self, width, height):
        # Inicializa a view
        glViewport(0, 0, width, height)
        glMatrixMode(gl.GL_PROJECTION)
        glLoadIdentity()
        # Inverte as coordenadas do eixo Y
        glOrtho(0, width, height, 0, -1, 1)
        # Aplica a escala
        glScalef(self.scale, self.scale, 1.0)
        glMatrixMode(gl.GL_MODELVIEW)

    def on_key_press(self, symbol, modifiers):
        self.game.pressionado = True

    def crialabel(self, mensagem, x=None, y=None,
                  fonte='Times New Roman', tamanho=36):
        """Prepara uma mensagem de texto para ser exibida"""
        x = x or self.width // 2
        y = y or self.height // 2
        return pyglet.text.Label(
            mensagem, font_name=fonte, font_size=tamanho,
            x=x, y=y, anchor_x='center', anchor_y='center')


window = Missile()
pyglet.app.run()


Em um próximo artigo, vou continuar a refatorar o código. Tiro e Avião são claramente classes. Animação deixa a desejar. Mas é o divertido da programação, você sempre pode melhorar.