Your first bot
Five small steps, each a change to the one before. Every clip is the step's own code playing in the arena; the lines that ran light up as it plays.
1. A bot that does nothing
A bot is a class with an on_tick method. The game calls it sixty times a second with the state, and it returns the buttons to hold: here, none.
Save it as mybot.py. ibf run mybot.py --pad 0 plays it at home; on this site it plays when you upload it.
"""Your first bot, step 1: a bot that does nothing. Each tick, the game asks for the buttons
to hold, and the answer is none."""
from ibf import Action, Bot, State, run
class MyBot(Bot):
name = "mybot"
def on_tick(self, state: State) -> Action:
return Action.idle()
if __name__ == "__main__":
run(MyBot, pad=0)Step 1: no buttons, every tick: the ship stands still.
2. Walk right
Return an Action with a direction to move. The ship walks right, tick after tick, until it reaches its own colour: a wall for it.
"""Your first bot, step 2: walk right. A ship walks only on tiles of the other colour, so it
stops where its own colour starts."""
from ibf import Action, Bot, Dir, State, run
class MyBot(Bot):
name = "mybot"
def on_tick(self, state: State) -> Action:
return Action(move=Dir.RIGHT)
if __name__ == "__main__":
run(MyBot, pad=0)Step 2: right, every tick: the ship walks until its colour stops it.
3. Wander
A bot is an object, so it can remember things between ticks. This one keeps a direction and a countdown, and picks a new direction when the countdown runs out.
"""Your first bot, step 3: wander. Every half a second or so, a new direction at random; the
bot remembers it between ticks."""
import random
from ibf import Action, Bot, Dir, State, run
WALKS = [Dir.UP, Dir.DOWN, Dir.LEFT, Dir.RIGHT]
class MyBot(Bot):
name = "mybot"
def __init__(self) -> None:
super().__init__()
self.walk = Dir.RIGHT
self.ticks = 0
def on_tick(self, state: State) -> Action:
self.ticks -= 1
if self.ticks <= 0:
self.walk = random.choice(WALKS)
self.ticks = random.randint(20, 40)
return Action(move=self.walk)
if __name__ == "__main__":
run(MyBot, pad=0)Step 3: a new direction now and then: the ship wanders.
4. Aim and shoot
Now read the state: your ship and the nearest enemy. When they are in a line with nothing between, tap a shot down it; otherwise walk toward the enemy.
self.input.tap(...) makes a quick shot: the button is pressed for a few ticks, then let go, so the next tap counts too.
"""Your first bot, step 4: aim. Walk toward the enemy; once in line with it, with nothing
between, shoot down the lane."""
from ibf import Action, Bot, Dir, State, run
class MyBot(Bot):
name = "mybot"
def on_tick(self, state: State) -> Action:
me, foe = state.me, state.nearest_enemy()
if me is None or foe is None or not me.alive:
return Action.idle()
lane = state.board.clear_lane(me.tile, foe.tile)
if lane is not None and me.ammo > 0 and not self.input.busy:
self.input.tap(lane)
return Action.idle()
dx, dy = foe.tile[0] - me.tile[0], foe.tile[1] - me.tile[1]
if abs(dx) >= abs(dy):
return Action(move=Dir.RIGHT if dx > 0 else Dir.LEFT)
return Action(move=Dir.UP if dy > 0 else Dir.DOWN)
if __name__ == "__main__":
run(MyBot, pad=0)Step 4: toward the enemy, and a shot once in line.
5. Hunter
The framework's own example, unchanged: it heads for the enemy's row, and when its colour blocks the way, it shoots a road open. It draws its aim over the game, too.
From here, it is yours: the documentation lists everything in the state.
"""hunter: lines up with the nearest enemy and shoots down the lane (design 10.3).
A ship walks on the other colour, and a shot turns the tiles it crosses into ones its
ship can walk on. So hunter heads for the enemy's row: it steps toward it while it can,
and when the way is blocked it shoots to open it. Once they share a row or column, it
holds still and fires. It draws its aim over the board (design 10.7); the Bots panel's
"show" turns that off.
ibf run examples\\hunter.py --pad 0
"""
from ibf import Action, Bot, Dir, Param, State, run
class Hunter(Bot):
name = "hunter"
show = Param.bool(True, help="draw the aim: a line to the enemy, red once in its lane")
def on_round_start(self, state: State, number: int) -> None:
self.log.info("round %d on a %dx%d board", number, state.board.width, state.board.height)
def shoot(self, direction: Dir, ammo: int) -> None:
if ammo > 0 and not self.input.busy:
self.input.tap(direction) # a quick shot; holding it would charge
def on_tick(self, state: State) -> Action:
me, foe = state.me, state.nearest_enemy()
if me is None or foe is None or not me.alive:
return Action.idle()
board = state.board
lane = board.clear_lane(me.tile, foe.tile)
self.stats["distance"] = round(me.pos.distance(foe.pos), 1)
if self.show:
self.draw.line(me.pos, foe.pos, "#ff3355" if lane is not None else "#ffcc00", width=2)
self.draw.tile(*foe.tile, (1, 0.2, 0.3, 0.35))
if lane is not None:
self.shoot(lane, me.ammo)
return Action.idle()
# Into the enemy's row: a step toward it, or a shot that opens the way.
toward = Dir.UP if foe.tile[1] > me.tile[1] else Dir.DOWN
ahead = (me.tile[0], me.tile[1] + toward.value[1])
if board.walkable(ahead, me.color):
return Action(move=toward)
self.shoot(toward, me.ammo)
return Action.idle()
if __name__ == "__main__":
run(Hunter, pad=0)Step 5: hunter opens its way with shots, then fires down the lane.