Contents of the page


The PyRat software

General principles

The PyRat software works essentially as follows. The core of the program is managed by the pyrat.py file, which takes care of all the fundamental elements: maze creation, player management, display management, statistics generation, etc.

When executed, this program starts a thread, i.e., a parallel process that takes care of the display. It communicates with him through lines of communication. Therefore, the display has no direct impact on the core of PyRat: if it is slowed down, it does not affect the main program (unless of course the machine resources are insufficient).

It also starts two processes, one for each player. These programs are more autonomous than threads and allow to manage both players almost independently. Communications between the main program and the processes managing the players are also done through communication queues. Again, everything is done to ensure that one player’s program does not affect the other’s.

Important: It is the PyRat main program that handles everything. As a consequence, running your AI directly from the terminal will result in nothing interesting.

Important: Similarly, pressing the play button in your favorite code editor (say, Spyder) to run PyRat will not work, as pyrat.py needs some arguments, such as which program to associate with the rat, etc.

A game of PyRat

When the game starts, the main program calls the preprocessing function of the players’ files. The PyRat core program then waits for some time (argument time_allowed of function preprocessing), in order to let the players make some computations.

After the proprocessing step is over, the PyRat core program calls the turn functions of both players to find out their movements. Again, it then waits for some time (argument time_allowed of function turn), in order to let the players decide which move to return.

After this time is over, the PyRat program applies the players’ decisions and updates the graphical interface. If one player is too late, they don’t move for the turn.

Note: It is possible to configure a game so that the PyRat core program waits for both players to return a decision, instead of using a fixed turn duration. This is done through PyRat’s option --synchronous.

Turns then continue until the game is over.

Start a game

Once PyRat is installed, the following command starts a game where the rat tries to pick up all the pieces of cheese by moving randomly:

python pyrat.py --rat AIs/random.py

After the game

Once the game is finished, a string of characters appears in the terminal, summarizing the game:

{
	"miss_python": 0.0
	"miss_rat": 114.0
	"moves_python": 123.0
	"moves_rat": 21.0
	"prep_time_python": 3.0994415283203125e-06
	"prep_time_rat": 0.0017397403717041016
	"score_python": 21.0
	"score_rat": 5.0
	"stucks_python": 17.0
	"stucks_rat": 5.0
	"turn_time_python": 0.0019042918352576775
	"turn_time_rat": 4.34830075218564e-06
	"win_python": 1.0
	"win_rat": 0.0
}

It contains a certain amount of information for each player, among which :

  • miss_xxx: The number of movements missed due to a too long calculation or moving toward a wall;
  • moves_xxx: The number of moves performed;
  • prep_time_xxx: The time taken by function preprocessing before completing;
  • score_xxx: The number of pieces of cheese collected;
  • stucks_xxx: The number of additional movements caused by mud;
  • turn_time_xxx: The time taken by function turn before returning a move;
  • win_xxx: 1 if the game is won, 0 if lost, 0.5 if tied.

To obtain average statistics on several games (which is interesting, especially if they contain some random factor), use the --tests parameter. The PyRat output will indicate the average obtained for each of the criteria mentioned above.

You should use it in combination with --nodrawing --synchronous for faster computations.

PyRat options

There are many options in PyRat that allow you to customize the dimensions of the maze, add a second player, allow more time for each turn, disable graphical interface, etc. The complete list can be accessed via this command:

python pyrat.py -h

Details on PyRat player programs

The template contents

To make it easier to learn the PyRat game, we provide the teachers and the students with a Python source file skeleton. This is located in the AIs folder, and is called template.py.

Reading the code contained in this file, you will realize that it is subdivided into subsections:

##############################################################
# The turn function should always return a move to indicate where to go
# The four possibilities are defined here
##############################################################

MOVE_DOWN = 'D'
MOVE_LEFT = 'L'
MOVE_RIGHT = 'R'
MOVE_UP = 'U'

The code above defines four constants that correspond to what the turn function should return, i.e., a decision of a move to perform in the maze.

##############################################################
# Please put your code here (imports, variables, functions...)
##############################################################

This area delimits where you should write your codes. For sure it would work if putting them farther in the code, but keeping things organized is always better, especially when asking help to your teacher 🙂

##############################################################
# The preprocessing function is called at the start of a game
# It can be used to perform intensive computations that can be
# used later to move the player in the maze.
# ------------------------------------------------------------
# maze_map : dict(pair(int, int), dict(pair(int, int), int))
# maze_width : int
# maze_height : int
# player_location : pair(int, int)
# opponent_location : pair(int,int)
# pieces_of_cheese : list(pair(int, int))
# time_allowed : float
##############################################################

def preprocessing (maze_map, maze_width, maze_height, player_location, opponent_location, pieces_of_cheese, time_allowed) :
    
    # Example prints that appear in the shell only at the beginning of the game
    # Remove them when you write your own program
    print("maze_map", type(maze_map), maze_map)
    print("maze_width", type(maze_width), maze_width)
    print("maze_height", type(maze_height), maze_height)
    print("player_location", type(player_location), player_location)
    print("opponent_location", type(opponent_location), opponent_location)
    print("pieces_of_cheese", type(pieces_of_cheese), pieces_of_cheese)
    print("time_allowed", type(time_allowed), time_allowed)

The preprocessing function is called exactly once before the PyRat game start. It gives you the possibility to make some time-consuming computations, and use their results in the turn function later.

Here, we just print to the terminal the various arguments given to the function. You can remove these prints and write pass instead to make the function do nothing during the preprocessing step.

##############################################################
# The turn function is called each time the game is waiting
# for the player to make a decision (a move).
# ------------------------------------------------------------
# maze_map : dict(pair(int, int), dict(pair(int, int), int))
# maze_width : int
# maze_height : int
# player_location : pair(int, int)
# opponent_location : pair(int,int)
# player_score : float
# opponent_score : float
# pieces_of_cheese : list(pair(int, int))
# time_allowed : float
##############################################################

def turn (maze_map, maze_width, maze_height, player_location, opponent_location, player_score, opponent_score, pieces_of_cheese, time_allowed) :

    # We go up at every turn
    # You should replace this with more intelligent decisions
    return MOVE_UP


The final function of this program is turn, and is supposed to return a move among those we have defined at the beginning of the file.

In this template file, we always go up. Your goal in this course is to make things that are better than that!

The data structures used

Locations in the maze

Locations in the maze are encoded as pairs (x, y) of integers, representing their horizontal and vertical coordinates. In this coordinates system, (0, 0) is at the bottom left.

In PyRat, player locations, as well as the cheese locations, are represented like that. Indeed, the comments above functions preprocessing and turn indicate:

# player_location : pair(int, int)
# opponent_location : pair(int,int)
# pieces_of_cheese : list(pair(int, int))

The maze map

In PyRat, the maze map is encoded as a dictionary, that associates to each location (x, y) another dictionary of neighbors of (x, y). This second dictionary represents the locations accessible (i.e., not separated by a wall) from (x, y) in the maze, as well as the weight to reach them.

The comments above functions preprocessing and turn indicate:

# maze_map : dict(pair(int, int), dict(pair(int, int), int))

In Python, you can access the element of a list l at index i with the notation l[i]. For this code to work, i must be an integer between 0 and len(l)-1. In short, you can understand dictionaries as a generalization of lists, where the index is not necessarily an integer.

So let’s come back to maze_map. It is a dictionary in which keys (analogous to the indices in lists) are the possible locations in the maze, and values (i.e., what we associate with the keys) are the adjacent locations which can be reached, along with the number of moves to reach it.

As an example, let us consider the following small maze:

maze_map = {(0, 0): {(1, 0): 1}, (0, 1): {(1, 1): 1}, (1, 0): {(1, 1): 6, (0, 0): 1}, (1, 1): {(1, 0): 6, (0, 1): 1}}

In this maze, location (0, 0) is at the bottom left. When writing maze_map[(0, 0)], we obtain {(1, 0): 1}, indicating that (0, 0) can access (1, 0) with a weight of maze_map[(0, 0)][(1, 0)] = 1.

Similarly, when writing maze_map[(1, 0)], we obtain {(1, 1): 6, (0, 0): 1}, indicating that (1, 0) can come back to (0, 0) with a weight of maze_map[(1, 0)][(0, 0)] = 1, and that (1, 0) can reach (1, 1) with a weight of maze_map[(1, 0)][(1, 1)] = 6 due to the presence of mud in the maze.