Micromouse Maze Simulator - 0.1.4¶
What is Micromouse Maze Simulator?¶
Micromouse Maze Simulator is a micromouse search algorithm simulator. It acts as a server, so any client can connect to it and send requests to read walls and log the current exploration state. Watch it in action!
Contents¶
Usage¶
Installation¶
Installation is very straight-forward using Python’s pip
:
pip install mmsim
This will install all the required dependencies and will provide you with the
mmsim
command.
Launching¶
To run the simulator simply:
mmsim
This will, the first time, download a set of mazes from micromouseonline.
If you have a local maze files collection in text format you may use that instead:
mmsim your/local/collection/path/
Protocol¶
Communication¶
The Micromouse Maze Simulator acts as a server, which means any client can connect to it and communicate with it using message passing.
Using message passing for communication means you can use any programming language to implement the client, as long as you correctly implement the communication protocol.
Communication is implemented using ØMQ, which is available for almost every programming language and implements multiplatform inter-process communication in a much more convenient way than raw TCP sockets.
To see which interface/port the server binds to by default:
mmsim --help
Note, however, that you can specify your preferred interface/port when launching the server:
mmsim --host 127.0.0.1 --port 1234
The server binds a single REP socket, which means the client is expected to communicate with the server sending requests, and the server will always send a reply back. This is important, as you must remember to receive and process that reply from the client. ØMQ forces the request-reply communication pattern to be correct and complete.
Implementing a basic client¶
To better understand how this works, we will start by implementing a very simple client in Python, to make sure the connection is well established:
import zmq
ctx = zmq.Context()
req = ctx.socket(zmq.REQ)
req.connect('tcp://127.0.0.1:6574')
req.send(b'ping')
reply = req.recv()
print(reply)
This simple client will send a ping
request to the server and the server
will reply back with a pong
message.
Note
If you start the client before the server, it will wait for the server to be available.
In C it would look like this:
#include <stdio.h>
#include <zmq.h>
int main (void)
{
char buffer[4];
void *context = zmq_ctx_new();
void *requester = zmq_socket(context, ZMQ_REQ);
zmq_connect(requester, "tcp://127.0.0.1:6574");
zmq_send(requester, "ping", 4, 0);
zmq_recv(requester, buffer, 4, 0);
printf("%s\n", buffer);
zmq_close(requester);
zmq_ctx_destroy(context);
return 0;
}
Which can be compiled with:
gcc client.c -o client -lzmq
And should result in the same pong
reply being printed when executed.
Protocol¶
We have already seen part of the protocol implemented. In this section we will describe each of the requests the client can send to the server.
Ping¶
When the client sends 4 bytes with the word ping
to the server, the server
replies back with the word pong
(another 4 bytes).
This request is for testing purposes only, but should be useful if you are starting to implement a client from zero.
Reset¶
When the client sends 5 bytes with the word reset
the server will reset the
simulation, which means that any information related to the last or current
simulation will be deleted.
This request is useful to be executed always when starting the client, to make sure the server starts with a client state too.
The server always replies back with an ok
.
Here is a simple reset
example implemented in Python, which is almost the
same as with the ping
request:
import zmq
ctx = zmq.Context()
req = ctx.socket(zmq.REQ)
req.connect('tcp://127.0.0.1:6574')
req.send(b'reset')
reply = req.recv()
print(reply)
Reading walls¶
The client can read walls at the current position. In order to do so, it must send the current position to the server:
<W><x-position><y-position><orientation>
The request is formed with 4 bytes:
W
: is the W byte character, idicating a request to read walls at the current position.x-position
: is a byte number indicating the x-position of the mouse.y-position
: is a byte number indicating the y-position of the mouse.orientation
: a byte character, indicating the mouse orientation (N
for North,E
for East,S
for South andW
for West). Indicates where the mouse is heading to.
Positions are defined considering:
- Starting cell (x=0, y=0) is at the South-West.
- When going from West to East, the x-position increments.
- When going from South to North, the y-position increments.
The server replies with 3 bytes indicating the walls around the mouse.
- First byte (boolean byte) indicates whether there is a wall to the left.
- Second byte (boolean byte) indicates whether there is a wall to the front.
- Third byte (boolean byte) indicates whether there is a wall to the right.
Here is an example in Python to read walls just after exiting the starting cell, when we are at (x=0, y=1) position and heading north:
import struct
import zmq
ctx = zmq.Context()
req = ctx.socket(zmq.REQ)
req.connect('tcp://127.0.0.1:6574')
req.send(b'W' + struct.pack('2B', 0, 1) + b'N')
left, front, right = struct.unpack('3B', req.recv())
print(left, front, right)
Sending exploration state¶
This is probably the most important and complex request. It basically sends the current state of the client including the mouse current position, the discovered walls so far and all the weights assigned to each cell in the maze.
The request looks like this:
S
: is the S byte character, idicating we are sharing the state.x-position
: is a byte number indicating the x-position of the mouse.y-position
: is a byte number indicating the y-position of the mouse.orientation
: a byte character, indicating the mouse orientation (N
for North,E
for East,S
for South andW
for West). Indicates where the mouse is heading to.C
: a byte character indicating how the cell numbers matrix is being transmitted.C
means C-style. If that does not work well for you, tryF
, for Fortran-style.numbers
: a byte array of 256 bytes. Each byte represents a number.C
: a byte character indicating how the walls matrix is being transmitted.C
means C-style. If that does not work well for you, tryF
, for Fortran-style.walls
: a byte array of 256 bytes. Each byte represents a number.
Walls are defined with a bitmask:
- 20: less significant bit. Set it to 1 to mark the cell as visited.
- 21: Set it to 1 to specify East wall is present.
- 22: Set it to 1 to specify South wall is present.
- 23: Set it to 1 to specify West wall is present.
- 24: Set it to 1 to specify North wall is present.
Note
The simulation server will store every state received until a reset occurs. This allows you to execute the full simulation as fast as possible and then navigate through the state history using the graphical interface.
Here is an example in Python to send a state with fake walls and cell numbers. We set the same wall and an increasing number for all cells:
import struct
import zmq
ctx = zmq.Context()
req = ctx.socket(zmq.REQ)
req.connect('tcp://127.0.0.1:6574')
req.send(b'reset')
req.recv()
numbers = list(range(256))
walls = [2] * 256
state = b'S' + struct.pack('2B', 0, 1) + b'N'
state += b'C'
state += struct.pack('256B', *numbers)
state += b'C'
state += struct.pack('256B', *walls)
req.send(state)
reply = req.recv()
print(reply)
Now try and play a bit with that script:
- Change the mouse position and orientation.
- Change the walls sent.
- Change the numbers sent.
- Change
C
andF
in the numbers sent to understand the differences. - Remove the
reset
and see how the state history increases and how you can navigate through it.
A real, complete micromouse client¶
A real, complete micromouse client implemented in C can be found in the Bulebule micromouse project.
Developers¶
Installing dependencies¶
To install the required dependencies for developing Micromouse Maze Simulator,
you can make use of the provided requirements.txt
file:
pip install -r requirements.txt
Running tests¶
Running the tests locally is very simple, using Tox from the top level path of the project:
tox
That single command will run all the tests for all the supported Python versions available in your system or environment.
For faster results you may want to run all the tests just against a single Python version. This command will run all tests against Python 3.5 only:
tox -e py35
Note that those tests include style and static analysis checks. If you just want to run all the behavior tests (not recommended):
pytest -n 8
If you just want to run a handful of behavior tests (common when developing new functionality), just run:
pytest -k keyword
Note
Before submitting your changes for review, make sure all tests pass
with tox
, as the continuous integration system will run all those checks
as well.
Generating documentation¶
Documentation is generated with Sphinx. In order to generate the documentation
locally you need to run make
from the docs
directory:
make html