This is part 2 of a series originally published on the Coder One blog. Check out Part 1 here: Building an AI game bot for Bomberman. In this tutorial series, we'll build a bot in Python that can play in a variation of the classic Bomberman game called Dungeons and Data Structures. We'll start off simple, and add advanced strategies in the later parts of the series.
Recap
Make sure you check out part 1 of this bot programming tutorial series first. So far we've covered:
- Setting up the game environment Dungeons and Data Structures
- Working with a starter Python agent that makes random moves
- Getting familiar with the game environment's objects and methods
In part 2, we'll show you how to build a simple agent called the 'Wanderer Agent' that implements some scripted logic to explore and navigate the world. More specifically, it:
- Looks at its immediate surroundings
- Checks which directions are valid ones to move in
- Chooses a random valid direction to take
If you're more interested in the topic of reinforcement learning, check out my other introductory tutorial to Reinforcement Learning With OpenAI Gym’s ‘Taxi’.
Step 1: Helper methods
In this section, we'll walk through creating 3 helper methods that will be useful for our agent:
-
get_surrounding_tiles()
: Returns a list of our surrounding tiles -
get_empty_tiles()
: Returns tiles that are valid for our agent to move into -
move_to_tile()
: Returns the corresponding action the agent should take to move to a tile
You should be familiar with the game_state
and player_state
objects from part 1 of the tutorial.
get_surrounding_tiles()
Our first helper method get_surrounding_tiles()
will return us a list of tiles surrounding our agent's current location as an (x,y) tuple of the game map.
We'll take advantage of the coordinate-representation of the map:
Below is the skeleton code for our get_surrounding_tiles()
method*.* We've left you some gaps to fill out. If you get stuck, check out the solution. (💡 Hint: check the game state documentation for useful methods).
# given our current location as an (x,y) tuple, return the surrounding tiles as a list
# (i.e. [(x1,y1), (x2,y2),...])
def get_surrounding_tiles(self, location):
# location[0] = x-index; location[1] = y-index
tile_north = (location[0], location[1]+1)
tile_south = None ################ FILL THIS ###################
tile_west = None ################ FILL THIS ###################
tile_east = (location[0]+1, location[1])
surrounding_tiles = [tile_north, tile_south, tile_west, tile_east]
for tile in surrounding_tiles:
# check if the tile is within the boundaries of the game
if None: ################ CHANGE 'NONE' ###################
# remove invalid tiles from our list
surrounding_tiles.remove(tile)
return surrounding_tiles
Next, add your get_surrounding_tiles()
method to your Agent
class in my_agent.py
.
class Agent:
def __init__(self):
pass
def next_move(self, game_state, player_state):
'''
This method is called each time your Agent is required to choose an action
'''
pass
########################
### HELPERS ###
########################
def get_surrounding_tiles(self, location):
'''
Your code here
'''
return surrounding_tiles
get_empty_tiles()
In order for our agent to move effectively, it will also need to know which of its surrounding tiles are actually empty (i.e. not containing a block or other player). Here's a get_empty_tiles
method with some blanks for you to fill out:
# given a list of tiles, return only those that are empty/free
def get_empty_tiles(self, tiles):
empty_tiles = []
for tile in tiles:
if None: ################ CHANGE 'NONE' ###################
# add empty tiles to list
empty_tiles.append(tile)
return empty_tiles
move_to_tile()
Given an adjacent surrounding tile and our current location, move_to_tile()
will return the action (i.e. u
, d
, l
, r
) that will get us there. E.g. if the tile we want to move to is directly north of us, this method will return u
.
# given an adjacent tile location, move us there
def move_to_tile(self, location, tile):
# see where the tile is relative to our current location
diff = tuple(x-y for x, y in zip(tile, self.location))
# return the action that moves in the direction of the tile
if diff == (0,1):
action = 'u'
elif diff == (0,-1):
action = None ################ FILL THIS ###################
elif diff == (1,0):
action = None ################ FILL THIS ###################
elif diff == (-1,0):
action = 'l'
else:
action = ''
return action
Step 2: Agent logic
With our helper methods in place, we'll be able to implement some simple logic to control our agent to navigate the game world.
Here's some sample skeleton code to help you piece together your agent. Here's also a link to our own version of Wanderer Agent.
import random
class Agent:
def __init__(self):
'''
Place any initialization code for your agent here (if any)
'''
pass
def next_move(self, game_state, player_state):
'''
This method is called each time your Agent is required to choose an action
'''
########################
### VARIABLES ###
########################
# game map is represented in the form (x,y)
self.cols = game_state.size[0]
self.rows = None ################ FILL THIS ###################
# useful for later
self.game_state = game_state
self.location = player_state.location
########################
### AGENT ###
########################
# get our surrounding tiles
surrounding_tiles = self.get_surrounding_tiles(self.location)
# get list of empty tiles around us
empty_tiles = None ################ FILL THIS ###################
if empty_tiles:
# choose an empty tile to walk to
random_tile = random.choice(empty_tiles)
action = None ################ FILL THIS ###################
else:
# we're trapped
action = ''
return action
########################
### HELPERS ###
########################
# given our current location as an (x,y) tuple, return the surrounding tiles as a list
# (i.e. [(x1,y1), (x2,y2),...])
def get_surrounding_tiles(self, location):
# location[0] = x-index; location[1] = y-index
tile_north = (location[0], location[1]+1)
tile_south = None ################ FILL THIS ###################
tile_west = None ################ FILL THIS ###################
tile_east = (location[0]+1, location[1])
surrounding_tiles = [tile_north, tile_south, tile_west, tile_east]
for tile in surrounding_tiles:
# check if the tile is within the boundaries of the game
if None: ################ CHANGE 'NONE' ###################
# remove invalid tiles from our list
surrounding_tiles.remove(tile)
return surrounding_tiles
# given a list of tiles, return only those that are empty/free
def get_empty_tiles(self, tiles):
empty_tiles = []
for tile in tiles:
if None: ################ CHANGE 'NONE' ###################
# add empty tiles to list
empty_tiles.append(tile)
return empty_tiles
# given an adjacent tile location, move us there
def move_to_tile(self, location, tile):
# see where the tile is relative to our current location
diff = tuple(x-y for x, y in zip(tile, self.location))
# return the action that moves in the direction of the tile
if diff == (0,1):
action = 'u'
elif diff == (0,-1):
action = None ################ FILL THIS ###################
elif diff == (1,0):
action = None ################ FILL THIS ###################
elif diff == (-1,0):
action = 'l'
else:
action = ''
return action
Save your agent (my_agent.py
) then run the following command in your terminal to watch your new bot go up against itself:
coderone-dungeon --watch my_agent my_agent
Step 3: Bombs away
To win at Dungeons and Data Structures, your agent will need to do more than roam around the map. It will need to know how to place bombs strategically in order to blow up crates for points or take down your opponent.
Now that you've got a better grasp of the environment, have a go at implementing some logic around bomb placements.
If you're interested, check out our implementation of a very simple 'Flee Agent'. It uses the Manhattan Distance formula to decide whether to focus on running away from nearby bombs or placing more of them.
Next steps
In this part, we went from a random agent that selects its moves at random, to one that can interpret its surrounding environment and explore it.
In the next part of the series, we'll implement a pathfinding algorithm to help it navigate to useful objects around the map. You can follow me here on DEV or on Medium to get notified when Part 3 is out.
P.S. If you're interested in supporting this project, please follow us on Product Hunt!
Thanks for reading 🙌
Top comments (0)