Kit inicial

Quatro robôs para copiar

Estes robôs funcionam. Copie um, cole no seu arquivo dentro de Robots/, troque o nome da classe pelo nome do seu time e rode. Depois comece a mexer.

Atalhos: 1. RoboMinimo · 2. RoboAndarilho · 3. RoboVigia · 4. RoboCacador

Como usar esta página

Os quatro robôs estão em ordem crescente de dificuldade e cada um acrescenta uma ideia ao anterior. Se você tem pouco tempo, pule direto para o RoboVigia: ele já anda, enxerga e atira, e é um adversário razoável. Se quer disputar o primeiro lugar, comece pelo RoboCacador e melhore a partir dele.

RobôAndaEnxergaAtiraMiraIdeia nova que ele traz
RoboMinimonãonãonãonãoO esqueleto obrigatório
RoboAndarilhosimnãonãonãomove, turn, stop e reagir à parede
RoboVigiaao levar tirosimsimradar travado na armaVarredura de radar e disparo
RoboCacadorpatrulhasimsimcalculadaMáquina de estados e potência por distância

1. RoboMinimo esqueleto

Ele não faz nada, e esse é justamente o ponto. Tem os onze comandos que o jogo exige, todos vazios. É a base segura para começar: você preenche um de cada vez e testa.

O que ele ensina: a estrutura obrigatória de qualquer robô.

Robots/RoboMinimo.py
#! /usr/bin/python
# -*- coding: utf-8 -*-
#
# ROBO 1 de 4 - O ESQUELETO
#
# Este robo nao faz nada: ele so entra na arena e fica parado.
# O valor dele e outro: ele tem TODOS os metodos que o jogo exige.
# Se algum deles faltar, o jogo fecha sozinho no meio da batalha.
#
# Use este arquivo como ponto de partida e va preenchendo os metodos.

from robot import Robot


class RoboMinimo(Robot):

    def init(self):
        """Roda uma unica vez, quando o robo entra na arena."""
        # Cores em RGB: (vermelho, verde, azul), de 0 a 255
        self.setColor(66, 185, 193)          # o corpo do tanque
        self.setGunColor(255, 255, 255)      # o cano da arma
        self.setRadarColor(255, 204, 77)     # o radar
        self.setBulletsColor(66, 185, 193)   # os tiros

        # Deixa o campo de visao do radar visivel. Otimo para entender
        # o que o robo enxerga enquanto voce testa.
        self.radarVisible(True)

    def run(self):
        """O cerebro do robo. Escreva aqui o que ele deve fazer."""
        pass

    def sensors(self):
        """Roda a cada quadro. Use para observar a arena."""
        pass

    def onHitWall(self):
        """Bati numa parede."""
        pass

    def onRobotHit(self, robotId, robotName):
        """EU bati em outro robo."""
        pass

    def onHitByRobot(self, robotId, robotName):
        """OUTRO robo bateu em mim."""
        pass

    def onHitByBullet(self, bulletBotId, bulletBotName, bulletPower):
        """Levei um tiro."""
        pass

    def onBulletHit(self, botId, bulletId):
        """Meu tiro acertou alguem."""
        pass

    def onBulletMiss(self, bulletId):
        """Meu tiro saiu da arena sem acertar ninguem."""
        pass

    def onTargetSpotted(self, botId, botName, botPos):
        """Meu radar viu um inimigo."""
        pass

    def onRobotDeath(self):
        """Minha vida chegou a zero."""
        pass

O que mudar primeiro

  • Troque RoboMinimo pelo nome do seu time (nas duas ocorrências: no nome do arquivo e depois de class).
  • Troque as cores em setColor e companhia.
  • Coloque self.move(80) dentro do run() e rode. Pronto: seu robô anda.

2. RoboAndarilho ele anda

Anda em quadrado pela arena e, quando bate numa parede, dá ré e vira. Ainda não atira: a ideia aqui é entender como os comandos entram na fila e como o stop() separa uma ação da outra.

O que ele ensina: move, turn, stop, reset e o evento onHitWall.

Robots/RoboAndarilho.py
#! /usr/bin/python
# -*- coding: utf-8 -*-
#
# ROBO 2 de 4 - ELE ANDA
#
# Anda em quadrado pela arena e recua quando bate numa parede.
# Nao atira ainda: o objetivo aqui e entender move, turn e stop.

from robot import Robot

# Constantes no topo do arquivo: fica facil de ajustar sem cacar no codigo.
PASSO = 80      # quantos pixels ele anda por vez
CURVA = 90      # quantos graus ele vira ao fim de cada trecho


class RoboAndarilho(Robot):

    def init(self):
        self.setColor(110, 231, 160)
        self.setGunColor(255, 255, 255)
        self.setRadarColor(255, 204, 77)
        self.setBulletsColor(110, 231, 160)

        self.radarVisible(True)

    def run(self):
        # Bloco 1: anda para a frente.
        self.move(PASSO)
        self.stop()

        # Bloco 2: so depois de terminar de andar, vira.
        self.turn(CURVA)
        self.stop()

        # O stop() e o que separa um bloco do outro. Sem ele, o robo
        # andaria e viraria AO MESMO TEMPO, fazendo uma curva aberta.

    def sensors(self):
        pass

    def onHitWall(self):
        # Bateu na parede: custou 1 de vida. Cancela o que estava fazendo,
        # da re e vira, tudo no mesmo bloco.
        self.reset()
        self.move(-PASSO)
        self.turn(CURVA)

    def onRobotHit(self, robotId, robotName):
        self.rPrint("Bati no " + str(robotName))

    def onHitByRobot(self, robotId, robotName):
        self.rPrint("O " + str(robotName) + " bateu em mim")

    def onHitByBullet(self, bulletBotId, bulletBotName, bulletPower):
        self.rPrint("Levei tiro do " + str(bulletBotName))

    def onBulletHit(self, botId, bulletId):
        pass

    def onBulletMiss(self, bulletId):
        pass

    def onTargetSpotted(self, botId, botName, botPos):
        pass

    def onRobotDeath(self):
        self.rPrint("Fim de jogo para mim")

Repare no onHitWall

Ele chama self.reset() antes de qualquer coisa. Sem isso, o robô voltaria a executar o plano antigo, que era justamente andar contra a parede. O reset() joga o plano fora e permite decidir de novo.

Repare também que move(-PASSO) e turn(CURVA) estão no mesmo bloco, sem stop() entre eles: o robô dá ré enquanto vira, o que é mais rápido do que fazer uma coisa e depois a outra.

Como melhorar

  • Mude PASSO e CURVA lá no topo e veja o desenho que ele faz na arena.
  • Use CURVA = 120 para ele andar em triângulo em vez de quadrado, o que é mais difícil de prever.
  • Copie o onTargetSpotted do próximo robô e ele já começa a atirar.

3. RoboVigia ele atira

Fica varrendo a arena com o radar. Assim que enxerga alguém, atira. Se levar um tiro, sai do lugar, porque quem acertou você uma vez vai tentar de novo no mesmo ponto.

O que ele ensina: lockRadar, setRadarField, gunTurn, fire e os eventos onTargetSpotted e onHitByBullet.

Robots/RoboVigia.py
#! /usr/bin/python
# -*- coding: utf-8 -*-
#
# ROBO 3 de 4 - ELE ATIRA
#
# Fica varrendo a arena com o radar. Assim que ve alguem, atira.
# Se levar tiro, sai do lugar - ficar parado apanhando e o pior negocio.

from robot import Robot

VARREDURA = 10       # quantos graus o radar gira a cada rodada
POTENCIA = 3         # forca do tiro (de 0.5 ate 10)
FUGA = 100           # quantos pixels ele corre ao levar tiro


class RoboVigia(Robot):

    def init(self):
        self.setColor(255, 204, 77)
        self.setGunColor(255, 255, 255)
        self.setRadarColor(255, 122, 107)
        self.setBulletsColor(255, 204, 77)

        # O radar passa a acompanhar a arma. Assim, tudo que o radar
        # enxerga ja esta na mira - nao precisa mirar depois.
        self.lockRadar("gun")

        # Radar estreito: enxerga longe, mas so bem na frente.
        self.setRadarField("thin")

        self.radarVisible(True)

    def run(self):
        # Gira a arma (e o radar junto) procurando alguem.
        self.gunTurn(VARREDURA)
        self.stop()

    def sensors(self):
        pass

    def onTargetSpotted(self, botId, botName, botPos):
        # O radar viu alguem bem na frente da arma. Atira.
        self.rPrint("Vi o " + str(botName))
        self.fire(POTENCIA)

    def onHitByBullet(self, bulletBotId, bulletBotName, bulletPower):
        # Alguem me acertou: sair do lugar dificulta o proximo tiro dele.
        self.reset()
        self.move(FUGA)
        self.turn(45)

    def onHitWall(self):
        self.reset()
        self.move(-FUGA)

    def onRobotHit(self, robotId, robotName):
        pass

    def onHitByRobot(self, robotId, robotName):
        pass

    def onBulletHit(self, botId, bulletId):
        self.rPrint("Acertei!")

    def onBulletMiss(self, bulletId):
        pass

    def onRobotDeath(self):
        pass

O truque que faz ele funcionar

Radar travado na arma

A linha self.lockRadar("gun") no init() faz o radar apontar sempre para onde a arma aponta. Consequência: tudo que o radar enxerga já está na mira. Por isso o onTargetSpotted deste robô é uma linha só: não precisa calcular ângulo nenhum, é só atirar.

A outra peça é setRadarField("thin"): um cone estreito e comprido, que enxerga do outro lado da arena. Combinado com a arma girando devagar, vira um franco-atirador que varre o mapa inteiro.

Como melhorar

  • Ajuste POTENCIA. Valores altos matam rápido mas custam vida quando erram (veja a matemática do tiro).
  • Diminua VARREDURA para ele varrer mais devagar e ter mais chance de enxergar quem passa.
  • Faça ele andar também: um alvo parado é um alvo fácil, e ficar parado ainda gera penalidade.
  • Atire mais forte quando estiver com vida alta e mais fraco quando estiver quase morrendo, usando self.getHealth().

4. RoboCacador competidor

Este já é um adversário de verdade. Ele patrulha as bordas da arena sem encostar nas paredes, varre o radar enquanto anda, calcula o ângulo exato até quem aparece e escolhe a potência do tiro conforme a distância.

O que ele ensina: máquina de estados, mira com trigonometria e escolha de potência.

Robots/RoboCacador.py
#! /usr/bin/python
# -*- coding: utf-8 -*-
#
# ROBO 4 de 4 - ELE CACA
#
# Patrulha as bordas da arena sem encostar nas paredes, varre o radar
# enquanto anda, mira com precisao em quem aparece e escolhe a forca do
# tiro pela distancia.
#
# Este e um bom adversario. Use como base e melhore.

import math

from robot import Robot

MARGEM = 90          # distancia que ele mantem das paredes
PASSO = 15           # quanto ele anda por rodada
VARREDURA = 8        # quantos graus o radar gira por rodada
ALCANCE = 450        # so atira em quem estiver mais perto que isso

# Lembre da bussola do jogo: 0 aponta para BAIXO e o angulo cresce
# no sentido horario.
BAIXO = 0
ESQUERDA = 90
CIMA = 180
DIREITA = 270

CIRCUITO = [BAIXO, ESQUERDA, CIMA, DIREITA]


class RoboCacador(Robot):

    def init(self):
        self.setColor(66, 185, 193)
        self.setGunColor(255, 255, 255)
        self.setRadarColor(255, 122, 107)
        self.setBulletsColor(127, 214, 219)

        self.arena = self.getMapSize()

        self.lockRadar("gun")
        self.setRadarField("thin")
        self.radarVisible(True)

        # Em qual trecho do circuito ele esta (indice da lista CIRCUITO).
        self.trecho = 0

    # ---------------------------------------------------------- ajudantes --

    def chegou_no_fim_do_trecho(self):
        """Diz se ja esta perto demais da parede deste trecho."""
        posicao = self.getPosition()
        direcao = CIRCUITO[self.trecho]

        if direcao == BAIXO:
            return posicao.y() > self.arena.height() - MARGEM
        if direcao == CIMA:
            return posicao.y() < MARGEM
        if direcao == ESQUERDA:
            return posicao.x() < MARGEM
        return posicao.x() > self.arena.width() - MARGEM

    def menor_giro(self, de, para):
        """Menor giro (entre -180 e 180) para sair de um angulo e chegar noutro."""
        erro = para - de
        while erro < -180:
            erro = erro + 360
        while erro > 180:
            erro = erro - 360
        return erro

    # ------------------------------------------------------------ cerebro --

    def run(self):
        if self.chegou_no_fim_do_trecho():
            self.trecho = (self.trecho + 1) % len(CIRCUITO)

        erro = self.menor_giro(self.getHeading() % 360, CIRCUITO[self.trecho])

        if abs(erro) > 5:
            self.turn(erro)      # ainda torto: primeiro se alinha
        else:
            self.move(PASSO)     # alinhado: anda

        # A arma (e o radar junto com ela) varre enquanto o corpo anda.
        self.gunTurn(VARREDURA)
        self.stop()

    def sensors(self):
        pass

    def onTargetSpotted(self, botId, botName, botPos):
        eu = self.getPosition()
        dx = botPos.x() - eu.x()
        dy = botPos.y() - eu.y()

        # Angulo ate o inimigo, ja convertido para a bussola do jogo.
        angulo_do_inimigo = math.degrees(math.atan2(dy, dx)) - 90
        erro = self.menor_giro(self.getGunHeading() % 360, angulo_do_inimigo)

        self.gunTurn(erro)
        self.stop()

        distancia = math.sqrt(dx * dx + dy * dy)

        if distancia < ALCANCE:
            if distancia < 150:
                potencia = 5     # perto: vale a pena o tiro caro
            elif distancia < 300:
                potencia = 3
            else:
                potencia = 1     # longe: tiro barato, chance maior de errar
            self.fire(potencia)

    def onHitWall(self):
        # Encostou na parede: perdeu vida. Volta e pula para o proximo trecho.
        self.reset()
        self.trecho = (self.trecho + 1) % len(CIRCUITO)
        self.move(-PASSO * 2)

    def onHitByBullet(self, bulletBotId, bulletBotName, bulletPower):
        self.rPrint("Tomei " + str(3 * bulletPower) + " de dano do " + str(bulletBotName))

    def onRobotHit(self, robotId, robotName):
        self.reset()
        self.move(-PASSO * 2)

    def onHitByRobot(self, robotId, robotName):
        pass

    def onBulletHit(self, botId, bulletId):
        pass

    def onBulletMiss(self, bulletId):
        pass

    def onRobotDeath(self):
        pass

As três ideias dentro dele

Patrulha por trechos

A variável self.trecho guarda em que lado da arena ele está andando. Quando chega perto da parede, passa para o próximo trecho. Isso se chama máquina de estados: em vez de um plano fixo, o robô tem uma situação atual e regras para trocar de situação.

Mira calculada

Com a posição do inimigo e a sua, dá para descobrir o ângulo exato entre os dois. É o que faz a linha com atan2. O menor_giro depois garante que ele vire pelo caminho mais curto, em vez de dar a volta inteira.

Potência por distância

Perto, o tiro dificilmente erra: vale o disparo caro. Longe, a chance de errar é alta: melhor gastar pouco. É a aplicação direta da matemática do tiro.

Como melhorar

  • Mexa em MARGEM: mais longe das paredes é mais seguro, mas dá menos espaço de manobra.
  • Faça o circuito ser sorteado em vez de sempre no mesmo sentido, assim ninguém prevê para onde ele vai.
  • Guarde a última posição de cada inimigo no onTargetSpotted e mire onde ele vai estar, não onde estava.
  • Use self.getHealth() para mudar de comportamento: caçar com vida alta, fugir com vida baixa.
  • Use len(self.getEnemiesLeft()) - 1 para ficar mais agressivo quando sobrar só um adversário.

Antes de entregar o seu robô
  • O arquivo está dentro de Robots/ e foi salvo?
  • O nome da classe é o nome que você quer ver na arena?
  • Os onze comandos obrigatórios estão todos lá?
  • Você rodou uma batalha inteira sem o jogo fechar sozinho?
  • Ele se mexe o tempo todo, sem ficar parado?