Skip to content

Latest commit

 

History

History
195 lines (131 loc) · 9.74 KB

robotgame_basic_strategy.md

File metadata and controls

195 lines (131 loc) · 9.74 KB

#Robotgame basic strategy by ramk13

##Starting from the example bot

Let's take a look at the example bot and what it does:

import rg

class Robot:
    def act(self, game):
        # if we're in the center, stay put
        if self.location == rg.CENTER_POINT:
            return ['guard']

        # if there are enemies around, attack them
        for loc, bot in game.robots.iteritems():
            if bot.player_id != self.player_id:
                if rg.dist(loc, self.location) <= 1:
                    return ['attack', loc]

        # move toward the center
        return ['move', rg.toward(self.location, rg.CENTER_POINT)]

It has three features that should be easy to pick out:

  • Stay put if at the center point
  • Attack an enemy if its adjacent to us
  • Move towards the center

This gets us started but we can add a lot to it. For the most part the features I'm going to discuss are improvements over versions without these features. You can pick and choose the ones you like to construct the bot you want.

##Individual Strategies:

So let's expand on the example bot. Here's a list of features that we can add:

  • Feature 1 - Leave spawn

Staying in spawn is bad. So let's check if we are in spawn, and if we are let's leave. We should leave even if there's someone to attack, because we don't want to get stuck/trapped in there.

  • Feature 2 - Flee if we're going to die

The example bot attacks until it dies. Since the score is only the number of bots you have and not their health, it's worth more to stay alive than to do 10 more damage to a bot. So how about we see if we are about to die, and if so we run for it instead of dying in vain.

  • Feature 3 - Attack towards enemies two steps away

If you watch any decent robots, you'll immediately notice that if you move into a space that someone else is attacking, you get hit. For that reason, if there's a chance someone might move into a spot next to us, we should attack towards them. That prevents anyone from getting next to us without taking a hit.

The combination of fleeing plus attacking towards enemies is really powerful. Any aggressive enemy has to run into your attack to get to next you before they can even attack you. If you move away after they close, you can start the process again. In gaming this tactic is called kiting and you can see how powerful it is here:

Kiting example

Look at how the weak 8 hp bot moves next to the stronger 50 hp bot, then flees. It attacks the square the 50 hp bot moves into, then flees again and restarts the process. This goes on until the formerly stronger bot suicides (not helpful in this case). All the while our 8 hp bot hasn't gotten hit.

  • Feature 4 - Only move to safe spots which are unoccupied

the example bot moves towards the center, but there are several cases where it makes sense to do something else. It's more important to move where its safe so that we don't put ourselves in danger with no benefit. So what's not safe? Spawn, moving into an enemy, moving next to an enemy. We know we can't move into obstacles. Also, we can cut down on collisions if we don't move into teammates.

  • Feature 5 - Move towards the enemy if there isn't one within two steps

If we have a bunch of safe options, why move to center? We know staying in spawn is bad, but that doesn't make the center good. A better idea is moving towards (but not into) the enemy. This combined with attacking towards enemies will give us better control of the board. Later we can find situations where it's worth moving somewhere that isn't explicitly safe, but for now let's stick with this.

Combining the features

Let's put these ideas together into pseudocode. We can put all the decision logic for our bot in a single if/else statement.

if we're in spawn:
    move somewhere safe (i.e. out of spawn)
else if there's an enemy next to us (1 step away):
    if we are in danger of dying:
        move somewhere safe
    else:
        attack an enemy
else if there's an enemy two steps away:
    attack towards an enemy
else if there's somewhere safe to move: (there's no one near us)
    move somewhere safe, but towards the closest enemy
else:
    guard, since there's nowhere to move or attack

Implementing it in code

To implement this in code, we need some data structures with our game info and a few functions. Note: There are a lot of ways to do the same thing in python. This is not optimal in any way, just a functional example.

Using sets instead of lists

To make things easier, we can use python sets instead of lists with the set() function or set comprehensions to start. This allows us to use the following set operators (more info here: https://docs.python.org/2/library/sets.html ). Also if you aren't familiar or comfortable with list/set comprehensions, check out this example of a list comprehension and its equivalent for loop from the python docs.

  • | or union - gives you a set with all elements in both sets
    • or difference - gives you a set with the second sets elements removed from the first
  • & or intersection - gives you a set that only has elements that are in both sets

Let's start by saying that we have lists of the following: teammates, enemies, spawn locations 'spawn', obstacle locations 'obstacle'

Base data structures

all_locs = {(x, y) for x in xrange(19) for y in xrange(19)}
spawn = {loc for loc in all_locs if 'spawn' in rg.loc_types(loc)}
obstacle = {loc for loc in all_locs if 'obstacle' in rg.loc_types(loc)}
team = {loc for loc in game.robots if game.robots[loc].player_id == self.player_id}
enemy = set(game.robots)-team

You can see here how we made a set of the enemy robots by taking all the robots and subtracting out our team.

Using the data structures to create useful sets/functions

For moving/attacking, there are only four possibilities for directions and the function rg.locs_around can give us those. We can exclude obstacle locations, since we'll never move or attack to those. adjacent & enemy gives us the the adjacent squares that also are in the set of enemies.

adjacent = set(rg.locs_around(self.location)) - obstacle
adjacent_enemy = adjacent & enemy

To figure out where there are enemies two steps away, let's look at adjacent squares with an enemy next to that square. We'll exclude an adjacent square if a teammate is in the square.

adjacent_enemy2 = {loc for loc in adjacent if (set(rg.locs_around(loc)) & enemy)} - team
team

Then we need to check whether each of those is safe. We'll remove options where there's an enemy one or two steps away. We'll also exclude spawn so that we don't go back into it. Also, to cut down on collisions we can exclude moving into teammates. When we get more complex we can remove the team restriction, but we'll need to add another check in its place. For now this is best.

safe = adjacent - adjacent_enemy - adjacent_enemy2 - spawn - team

We need a function that gives us the location from a set that is closest to what we specify. We can use this function find the closest enemy, but also to choose between our list of safe moves. We can pick the safe move that's closest to where we want to go.

def mindist(bots, loc):
    return min(bots, key=lambda x: rg.dist(x, loc))

We can use the pop() method of a set to get an arbitrary element of that set. We can use that to pick an enemy to attack. Also to tell whether we are in danger of dying or not, we can multiply the number of enemies adjacent to us by the average damage of an attack (9 hit points) and see if we have more health than that. Because of the way we wrote our minimum distance function, we need to be sure that we always put a set with objects in it. For that reason, we need to make sure that enemy isn't empty or we'll get an error from mindist.

Putting it all together

Let's put all this together in code (Note: this doesn't constitute a whole working bot):

all_locs = {(x, y) for x in xrange(19) for y in xrange(19)}
spawn = {loc for loc in all_locs if 'spawn' in rg.loc_types(loc)}
obstacle = {loc for loc in all_locs if 'obstacle' in rg.loc_types(loc)}
team = {loc for loc in game.robots if game.robots[loc].player_id == self.player_id}
enemy = set(game.robots)-team

adjacent = set(rg.locs_around(self.location)) - obstacle
adjacent_enemy = adjacent & enemy
adjacent_enemy2 = {loc for loc in adjacent if (set(rg.locs_around(loc)) & enemy)} - team
safe = adjacent - adjacent_enemy - adjacent_enemy2 - spawn - team

def mindist(bots, loc):
    return min(bots, key=lambda x: rg.dist(x, loc))

if enemy:
    closest_enemy = mindist(enemy,self.location)
else
    closest_enemy = rg.CENTER_POINT

# we'll overwrite this if there's something better to do
move = ['guard']

if self.location in spawn:
    if safe:
        move = ['move', mindist(safe, rg.CENTER_POINT)]
elif adjacent_enemy:
    if 9*len(adjacent_enemy) >= self.hp:
        if safe:
            move = ['move', mindist(safe, rg.CENTER_POINT)]
    else:
        move = ['attack', adjacent_enemy.pop()]
elif adjacent_enemy2:
    move = ['attack', adjacent_enemy2.pop()]
elif safe:
    move = ['move', mindist(safe, closest_enemy)]

If you'd like to see this as a working bot, you just need to add the class and function definition at the top and have the act function return move

Here's the basic strategy bot as a file.

If you are ready for more features move on to the intermediate strategy guide.