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.

idle.py
"""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.

walk_right.py
"""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.

wander.py
"""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.

aim.py
"""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.py
"""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.