There is the graphical representation of one maze on the right. It contains several rooms, represented by circles, and several oriented passages, represented by arrows. The blue rooms are the entrances and the green rooms the exits.
This maze does not contain loops. When dealing with maze with loops, the infinite paths become a concern.
This project is mostly about mazes with no loops. But also includes, at the end, a little taste of mazes with loops.
The module interface has already been fully written and you are not allowed to change it: Maze.mli. As you can see, the data representation is public and there is also a small number of public functions declared. All the other entities you might define in the module body will become private. The representation is public to allow Mooshak to check the code of your module.
Use this file as a starting point to write your module body: Maze.ml.
In this project, we will represent the mazes, and associated concepts, using the following OCaml types:
type room = int type rooms = room list type path = room list type island = room list type passage = room * room type passages = passage list type maze = { rooms: rooms; entrances: rooms; exits: rooms; passages: passages }Each room is represented by a non-negative integer and each passage is represented by a pair of two distinct rooms (the two rooms that passage connects).
Each maze if characterized by:
Furthermore, all the lists must be in the so called canonical form, that is they must be sorted and do not include any duplicated elements.
Here is the representation of our main example:
let myMaze = { rooms = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13]; entrances = [1;4;11]; exits = [6;12]; passages = [(1,2);(1,4);(1,5);(2,5);(3,6);(4,5);(4,7);(5,6);(5,8);(6,9);(7,8);(8,9);(10,11);(11,12)] }
next : maze -> room -> roomsTo exemplify, there is a function that determines, in a maze m without loops, the maximum distance from the room n to a leaf room. A leaf room is any room without any way-out passage.
Note the use of the usual auxiliary function that processes the children.
let rec depth m r = let rs = next m r in if rs = [] then 0 else 1 + ldepth m rs and ldepth m rs = match rs with | [] -> failwith "ldepth" | [r] -> depth m r | r::rs -> max (depth m r) (ldepth m rs)It is easy to describe this function. If the room is a leaf room, then the result is 0. Otherwise, the result is 1 plus the maximum depth of the various children.
In any case, note that some of the requested functions do not need to analyze mazes structurally, and they should be programmed using different styles.
Another observation. Some of the requested functions that analyze mazes structurally, happen to build and return a list. In most of these cases, it is more compact and legible (so, a better style) to replace the auxiliary function by a direct call to the provided function flatMap. This function flatMap also has the extra advantage of keeping all the lists sorted and without repetition, as required in the description of many of the requested functions.
In the descriptions below, the preconditions of each function are clearly stated. Please, do not check the preconditions in your code; simply assume them, as usual.
The task of each function is to calculate the result according to the specification and also to ensure the stated postconditions.
It is provided a simple example for each function.
isValid : maze -> bool (* pre: none *) (* post: none *) let isValid m = ...
# isValid myMaze;; - : bool = true
makeLineMaze : room -> room -> maze (* pre: a < b *) (* post: isValid result *) let makeLineMaze a b = ...
# makeLineMaze 1 9;; - : maze = { rooms = [1; 2; 3; 4; 5; 6; 7; 8; 9]; entrances = [1]; exits = [9]; passages = [(1, 2); (2, 3); (3, 4); (4, 5); (5, 6); (6, 7); (7, 8); (8, 9)] }
combine : maze -> maze -> maze (* pre: isValid m1, isValid m2 *) (* post: isValid result *) let combine m1 m2 = ...
# combine myMaze myMaze;; - : maze = { rooms = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13]; entrances = [1; 4; 11]; exits = [6; 12]; passages = [(1, 2); (1, 4); (1, 5); (2, 5); (3, 6); (4, 5); (4, 7); (5, 6); (5, 8); (6, 9); (7, 8); (8, 9); (10, 11); (11, 12)] }
next : maze -> room -> rooms (* pre: isValid m *) (* post: isCanonical result *) let next m r = ...
# next myMaze 5;; - : room list = [6; 8]
next2 : next2 : maze -> rooms -> rooms (* pre: isValid m *) (* post: isCanonical result *) let next2 m rs = ...
# next2 myMaze [5; 11; 13];; - : room list = [6; 8; 12]
prev : maze -> room -> rooms (* pre: isValid m *) (* post: isCanonical result *) let prev m r = ...
# prev myMaze 5;; - : room list = [1; 2; 4]
adjacent : maze -> room -> rooms (* pre: isValid m *) (* post: isCanonical result *) let adjacent m r = ...
# adjacent myMaze 5;; - : room list = [1; 2; 4; 6; 8]
reachable : maze -> rooms (* pre: isValid m, not (hasLoop m) *) (* post: isCanonical result *) let reachable m = ...
# reachable myMaze;; - : room list = [1; 2; 4; 5; 6; 7; 8; 9; 11; 12]
solitary : maze -> rooms (* pre: isValid m *) (* post: isCanonical result *) let solitary m = ...
# solitary myMaze;; - : room list = [13]
islands : maze -> island list (* pre: isValid m, not (hasLoop m) *) (* post: isCanonical result *) let islands m = ...
# islands myMaze;; - : room list list = [[1; 2; 3; 4; 5; 6; 7; 8; 9]; [10; 11; 12]; [13]]
shortest : maze -> path (* pre: isValid m, not (hasLoop m) *) (* post: none *) let shortest m = ...
# shortest myMaze;; - : room list = [11; 12]
paths : maze -> path list (* pre: isValid m, not (hasLoop m) *) (* post: none *) let paths m = ...
# paths myMaze;; - : room list list = [[1; 2; 5; 6]; [1; 2; 5; 6; 9]; [1; 2; 5; 8; 9]; [1; 4; 5; 6]; [1; 4; 5; 6; 9]; [1; 4; 5; 8; 9]; [1; 4; 7; 8; 9]; [1; 5; 6]; [1; 5; 6; 9]; [1; 5; 8; 9]; [4; 5; 6]; [4; 5; 6; 9]; [4; 5; 8; 9]; [4; 7; 8; 9]; [11; 12]]
hasLoop : maze -> bool (* pre: isValid m *) (* post: none *) let hasLoop m = ...
# hasLoop myMaze;; - : bool = false
shortest2 : maze -> path (* pre: isValid m *) (* post: none *) let shortest2 m = ...
# shortest2 myMaze;; - : room list = [11; 12]
Around 80% of the grade of your group is automatically assigned by Mooshak. The remaining 20% is assigned manually by the teachers, who will analyze the quality of your code.
A special case: In case of code of extremely bad quality, or code that uses the forbidden imperative mechanisms of OCaml, or code that constantly simulates imperative mechanisms and concepts, a special rule will be used so that the grade will be always below 50%, even if the program works well.
To develop the project, we strongly recommend you use the OCaml interpreter, probably inside the Eclipse IDE.
However, you should know that Mooshak will compile your module using the following command:
ocamlc -c Maze.mli Maze.mlAfter the compilation, Mooshak will test the module in the interpreter like this:
$ ocaml Objective Caml version 4.02.3 # #load "Maze.cmo";; # open Maze;; ... ...
Please, make a backup of the file "Maze.ml" once a while, because the file can disappear as result of human error or as result of a software/hardware malfunction.
Have fun!