Skip to content

Bot Development Tutorial

Pbatch edited this page Jul 30, 2024 · 3 revisions

Introduction

Hello and welcome to the advanced tutorial. If you haven't already, we recommend that you start with the basic tutorial.

We will be designing a bot that plays the standard deck (minions, archers, arrows, giant, minipekka, fireball, knight and musketeer). This is the deck that all players can play after creating a new account.

Our plan is as follows:

  • Design a score function for each of our units
  • At each time step
    • Score all possible actions
    • Play the action with the highest score

To do this we need to create 2 files:

  • A subclass of Bot (clashroyalebuildabot/bot/standard/standard_bot.py)
  • A subclass of Action (clashroyalebuildabot/bot/standard/standard_action.py)

Instructions

1.) Create subclasses of Bot (clashroyalebuildabot/bot/bot.py) and Action (clashroyalebuildabot/bot/action.py).

class StandardBot(Bot):
    pass
class StandardAction(Action):
   pass

2.) In the StandardAction class, design a score function for each unit. I.e.

def _calculate_minipekka_score(self, state):
    """
    Place minipekka on the bridge as high up as possible
    Try to target the lowest hp tower
    """
    left_hp, right_hp = [state['numbers'][f'{direction}_enemy_princess_hp']['number']
                         for direction in ['left', 'right']]
    score = [0]
    if self.tile_x == 3:
        score = [1, self.tile_y, left_hp != -1, left_hp <= right_hp]
    elif self.tile_x == 14:
        score = [1, self.tile_y, right_hp != -1, right_hp <= left_hp]
    return score

It is useful to use the state to score an action. In the example above, we use the HP of the enemy towers to decide which side to put minipekka on. You might want to print the state a few times to learn what metadata is available, and how reliable it is.

We use the tile map to work out that 3 and 14 are the x-coordinates of the bridges.

tile-map

See clashroyalebuildabot/bot/standard/standard_action.py for how I choose to score actions for each unit.

3.) In the StandardAction class, apply the score function to each action.

def calculate_score(self, state):
    name_to_score = {'knight': self._calculate_knight_score,
                     'minions': self._calculate_minions_score,
                     'fireball': self._calculate_fireball_score,
                     'giant': self._calculate_giant_score,
                     'minipekka': self._calculate_minipekka_score,
                     'musketeer': self._calculate_musketeer_score,
                     'arrows': self._calculate_arrows_score,
                     'archers': self._calculate_archers_score
                     }
    score_function = name_to_score[self.name]
    score = score_function(state)
    self.score = score
    return score

4.) In the StandardBot class, play the action with the highest score at each timestep.

def run(self):
    while True:
        # Set the state of the game
        self.set_state()
        # Obtain a list of playable actions
        actions = self.get_actions()
        if actions:
            # Get the best action
            action = max(actions, key=lambda x: x.calculate_score(self.state))
            # Play the best action
            self.play_action(action)

This is almost identical to RandomBot (clashroyalebuildabot/bot/random/random_bot.py), except that RandomBot uses action = random.choice(actions)

5.) Load the emulator, start a game, and watch your bot play!

from clashroyalebuildabot.bot.standard.standard_bot import StandardBot

card_names = ['minions', 'archers', 'arrows', 'giant',
              'minipekka', 'fireball', 'knight', 'musketeer']
bot = StandardBot(card_names, debug=True)
bot.run()

Clone this wiki locally