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
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ô | Anda | Enxerga | Atira | Mira | Ideia nova que ele traz |
|---|---|---|---|---|---|
| RoboMinimo | não | não | não | não | O esqueleto obrigatório |
| RoboAndarilho | sim | não | não | não | move, turn, stop e reagir à parede |
| RoboVigia | ao levar tiro | sim | sim | radar travado na arma | Varredura de radar e disparo |
| RoboCacador | patrulha | sim | sim | calculada | Má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ô.
#! /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
RoboMinimopelo nome do seu time (nas duas ocorrências: no nome do arquivo e depois declass). - Troque as cores em
setColore companhia. - Coloque
self.move(80)dentro dorun()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.
#! /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
PASSOeCURVAlá no topo e veja o desenho que ele faz na arena. - Use
CURVA = 120para ele andar em triângulo em vez de quadrado, o que é mais difícil de prever. - Copie o
onTargetSpotteddo 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.
#! /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
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
VARREDURApara 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.
#! /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
onTargetSpottede 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()) - 1para ficar mais agressivo quando sobrar só um adversário.
- 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?