Pelita is an "Actor-based Toolkit for Interactive Language Education in Python". The game was created for an Advanced Scientific Python summer school, and we are going to use it as well.
Download and have a look at the presentation from last year to get you in the mood.
The rules are simple: you work in a team, and send me your players before the contest (the exact date will be announced via OLAT). The last day of the lecture we will see the bots compete and let the best team win!
Your final grade will not be based on the outcome of the contest: it will be based on code quality, clarity, and originality. The specific objectives of this exercise are:
As you are going to see, it is possible to write simple to (very) complex strategies for your players. You should not try to develop the "best" strategy from scratch but rather advance in smaller steps. Also, you should know when to stop: I don't expect you to create the best players ever, nor do I expect you to spend all your time on this. Try to avoid bugs, try to develop simple but clever players, and have fun!
Download the pelita package from the official repository. Unzip the package and install it with pip install -e /path/to/package
(don't forget the --user
if you are on a university computer).
run your first game with a simple call to $ pelita
!
To write your own players, download the template available here. Unpack it and follow the instructions:
$ make test
to test the template.$ make
for a quick game against a random teamFor more control, use pelita
as indicated by its documentation. For example, to run a game against a predefined team:
$ pelita team/ SmartEatingPlayer
To run a game against yourself:
$ pelita team/ team/
The best way to get started is to have a look at the documentation about writing a player and running & debugging.
You can test different simple strategies by having a look at the demo players that you will find in the pelita/pelita/player/
folder.
All this, however, might still leave you a little confused: take your time and don't rush into coding right away. The test environment available in the template package and therefore runnable from spyder or pycharm is probably the best way to explore the tools available to you.
from pelita import datamodel
from pelita.graph import Graph, NoPathException, diff_pos
from pelita.player import AbstractPlayer
class FastEatingPlayer(AbstractPlayer):
"""Like SmartEatingPlayer but without taking enemies into
account and seeking the closest food (always)."""
def set_initial(self):
# This is called only once \t the beginning of the game
# A graph is useful to help you find your way in the maze
self.graph = Graph(self.current_uni.reachable([self.initial_pos]))
def goto_pos(self, pos):
# Next move to go to the desired position(s)
return self.graph.bfs(self.current_pos, pos)[-1]
def get_move(self):
# Check the move towards the closest enemy food
try:
next_pos = self.goto_pos(self.enemy_food)
move = diff_pos(self.current_pos, next_pos)
except NoPathException:
move = datamodel.stop
# Check that it is one of all possible moves, else random
if move in self.legal_moves:
return move
else:
return self.rnd.choice(list(self.legal_moves.keys()))
class StoppingPlayer(AbstractPlayer):
def get_move(self):
return datamodel.stop
Mazes can be provided by the user using a simple ascii layout. Walls are represented by #
, food pellets with dots, and players by their number (0 and 2 belong to team 1, 1 and 3 to team 2). Here I defined a maze made to test the behavior of the players of team 1 (left hand side), and add one food pellet on the left hand side too (this is needed for the game to be valid):
test_layout = (
""" ##############################################################
#0 . 1#
# . #
# . #
# . #
# . . #
# . #
# . #
# ........................#
#2 3#
##############################################################
"""
)
from pelita.game_master import GameMaster
from pelita.player import SimpleTeam
# Make the team
player_0 = FastEatingPlayer()
player_1 = StoppingPlayer()
player_2 = StoppingPlayer()
player_3 = StoppingPlayer()
teams = [
SimpleTeam('One', player_0, player_2),
SimpleTeam('Two', player_1, player_3)
]
# Start the game
game_master = GameMaster(test_layout, teams, 4, 300)
game_master.set_initial()
Now we are ready to see what is happening. Lets see the state of the universe before and after a round:
print(game_master.universe.pretty)
game_master.play_round()
print(game_master.universe.pretty)
Our player moved one step. What exacly is our player seeing from here?
For example, the player sees the locations of the enemy food:
player_0.enemy_food
The player also knows the (noisy) position of the enemy bots:
player_0.enemy_bots
You can verify that it is noisy by checking that on the next round the positions might change although the players didn't move:
game_master.play_round()
player_0.enemy_bots
print(game_master.universe.pretty)
What is the graph actually useful for? Let's find out:
path = player_0.graph.bfs(player_0.current_pos, player_0.enemy_food)
path
This gives us the shortest path towards all enemy food! Taking the last one gives us the next position we'd like to go:
next_pos = path[-1]
move = diff_pos(player_0.current_pos, next_pos)
move
# east, west, north, south are nothing more than direction tuples
assert move == datamodel.east
# shoule be a legal move
assert move in player_0.legal_moves
test_layout = (
""" ##############################################################
#0 ## . 1#
# ## ## . #
# # # . #
# # # . #
# . # # . #
# # # . #
# # # . #
# # # ........................#
#2 # 3#
##############################################################
"""
)
# Start the game
game_master = GameMaster(test_layout, teams, 4, 300)
universe = game_master.universe
game_master.set_initial()
Will our bot find its way through it?
for _ in range(30):
game_master.play_round()
print(game_master.universe.pretty)
Looks like it! Let the game run until the end:
game_master.play()
print(game_master.universe.pretty)
Let's make the opponent team a little more active:
from pelita.graph import manhattan_dist
import numpy as np
class AggressivePlayer(AbstractPlayer):
def set_initial(self):
self.graph = Graph(self.current_uni.reachable([self.initial_pos]))
def goto_pos(self, pos):
return self.graph.a_star(self.current_pos, pos)[-1]
def get_move(self):
# Take the closest enemy
dis = []
for enemy in self.enemy_bots:
dis.append(manhattan_dist(self.current_pos, enemy.current_pos))
enemy = self.enemy_bots[np.argmin(dis)]
try:
next_pos = self.goto_pos(enemy.current_pos)
# Check if the next_pos is on the wrong side of the maze
if not self.team.in_zone(next_pos):
# whoops, better not move
move = datamodel.stop
else:
move = diff_pos(self.current_pos, next_pos)
# Check that it is one of all possible moves, else stop
if move in self.legal_moves:
return move
else:
return datamodel.stop
except NoPathException:
return datamodel.stop
# Make the team
player_0 = FastEatingPlayer()
player_1 = StoppingPlayer()
player_2 = StoppingPlayer()
player_3 = AggressivePlayer()
teams = [
SimpleTeam('One', player_0, player_2),
SimpleTeam('Two', player_1, player_3)
]
# Start the game
game_master = GameMaster(test_layout, teams, 4, 300)
universe = game_master.universe
game_master.set_initial()
But stalking enemies is dangerous! See what happens in this game:
for _ in range(40):
game_master.play_round()
print(game_master.universe.pretty)
for _ in range(8):
game_master.play_round()
print(game_master.universe.pretty)
for _ in range(8):
game_master.play_round()
print(game_master.universe.pretty)
The bot will never be able to catch it! Having completely deterministic moves might be contra-productive sometimes.
Now it's your turn! Try to use these players in real games to visualize the outcome, and start to define new strategies! The game designers write in their presentation:
Back to the table of contents.