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)>127 THEN GET F$
    130 IF F$="" OR N>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)>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>-1 AND X<15 AND Y>-9 AND Y<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)>127 THEN GET F$
    # Se algo foi pressionado ou se N>3 desvia para 160
    130 IF F$="" OR N>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]>145 desvia para 230
    180 IF F(J)=0 OR Y(J)>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>-1 e X<15 e Y>-9 e Y<5 desvia para 270
    220 IF X>-1 AND X<15 AND Y>-9 AND Y<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)>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 > 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>3 desvia para 160
            # 130 IF F$="" OR N>3 THEN 160
            if self.pressionado and self.N < 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]>145 desvia para 230
                # 180 IF F(J)=0 OR Y(J)>145 THEN 230
                if self.F[self.J] == 0 or self.Y[self.J] > 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>-1 e X<15 e Y>-9 e Y<5 desvia para 270
                # 220 IF X>-1 AND X<15 AND Y>-9 AND Y<5 THEN 270
                if self.X > -1 and self.X < 15 and self.y > -9 and self.y < 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: {} --> {}".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 > 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 < 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] > 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 > -1 and self.X < 15 and self.y > -9 and self.y < 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: {} --> {}".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 > 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 < 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] > 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 > -1 and self.X < 15 and self.y > -9 and self.y < 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: {} --> {}".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.