# CS422: Code Explanation Report ## system.py ### Directory: /Sources/system.py ### Overview * This is the main program for the project to navigate between frontend, backend and other class. ### Code Explanation The main class of this code is **SystemController** class with the following main attributes and methods: * The attribute consists a dictionary with format: {map's name: map's content (3D List)} ```python def __init__(self): self.mapLists = {} ``` * This function uses to write the solution to json file for frontend to show the algorithms's path: ```python def writeSolutionJsonFile(self, mapName, agentPath, algorithm): ``` * The usage of this function is to solve a choosed map with name: **mapName** parameter with corresponding algorithm by **algorithm** parameter: ```python def solving(self, mapName, algorithm): ``` * In this function, the system solve all maps with **algorithm** parameter choosed by users (For example: **bfs, dfs**) ```python def solvingAllMap(self, algorithm): for mapName in self.mapLists: self.solving(mapName, algorithm) ``` ### How to use (Flow) * Declare an object for **SystemController** class ```python system = SystemController() ``` * Put your map's file in .txt format to Map folder on directory /Map * Call the following code for the system to read all existent map in Map folder ```python system.readFolderMap('Map') ``` * Choosing the algorithm to solve all maps by the following code (**Note:** The **algorithm** **parameter** is one of four algorithms: **dfs**, **bfs**, **ucs**, **a***): ```python system.solvingAllMap('dfs') ``` * Run the following code in command line ```cmd python system.py ``` ## algorithms.py ### Directory: /Sources/level1/algorithms.py ### Overview * This is the main **algorithm** program for the project to solve each level with corresponding map. ### Algorithm Explanation In first 3 levels, the algorithm use the following basic idea: * Frontier firstly add the initial state. * In each step, the algorithm check the current state is goal state or not. If it is goal state, return the results and stop the algorithm. * Then find the successor function for next state and check if it is possible for next step or not. In detail, there are 2 impossible cases: the position of agent is not inside the map, the agent do not have key to open door. * Add all possible state in the succesor function to frontier. * Continue until get to the goal state or print **No solution** if the map do not have any path from initial state to goal state. **Note:** Our algorithm use a better way to save key state of the agent in each state instead of saving a list of all keys for that agent. I will explain in detail in the following paragraph: * Assume there are **N** keys. * To save all keys' state of the agent, we use **bitmask** technical. In other word, key can be save in binary representation in order to reduce memory and complexity. * Denote **keyState** be the key state in binary representation. * Denote **keyStateDec** be the key state in decimal representation. For example: N = 5, the current agent has lists of key: [1, 2, 5], then **keyState = 10011** and **keyStateDec = 19** (from **keyState** convert to decimal). * Hence, for a state, we use variable **self.key** to save key state in decimal representation which has the same meaning as **keyStateDec** mention above. * To check if the agent has key **X**, we can use the following bitwise operation (**Note:** key index start from 0): ```python # Return true if agent has key X def isKey(self, X, keyState): return keyState & (1 << X) != 0 ``` * To add a key **X** to a new key state, we use the following function to update key **X** to new key state: ```python keyNext = self.key # This mean update bit X to 1 keyNext |= (1 << X) ``` * In summary, we use the traditional search algorithm such as **BFS** or **DFS**, but add **self.key** attribute and related function to control the current key state. To understand more deeply, read the next section, especially **Constructor** and **Successor** function. ### Code Explanation The main class of this code is **MazerSolverLevel1** class with the following main attributes and methods: * The constructor of the class (**I will explain each parameters in the following code**): ```python def __init__(self, mazer, nRow, mCol, nLayer = 1): # 3D Matrix of map state (X, Y, Z) self.mazer = mazer # Number of row in map state (Y) self.nRow = nRow # Number of column in map state (Z) self.mCol = mCol # Number of layer in map state (X) self.nLayer = nLayer # Key bitmask state of map state # (Detail in algorithm explanation) self.key = 0 # The direction of an agent's move which consists 8 # directions corresponding to UP, RIGHT, DOWN, LEFT, UP LEFT, # UP RIGHT, DOWN LEFT, DOWN RIGHT self.dx = [-1, 0, 1, 0, -1, -1, 1, 1] self.dy = [0, 1, 0, -1, -1, 1, -1, 1] ``` * The following function is to get the agent in current map state with **agentName** parameter. This function returns a tuple **(X, Y, Z)** corresponding to floor Z, row X, column Y: ```python def agentPosition(self, agentName): ``` * To check an agent's state is inside the map or not, use this function. ```python # Return true if inside the map # Else return false def inside(self, xCor, yCor, layer): ``` * To check an agent can move to another state or not ```python def canMove(self, xCor, yCor, layer, key): if not self.inside(xCor, yCor, layer) or not self.isValid(xCor, yCor, layer, key): return False return True ``` * The successor function and its explanation: ```python # successor function return a list of possible state in next step def succesor(self, xCor, yCor, layer, key): # successor list succ = [] # Try 8 direction from current position :(xCor, yCor, layer) for i in range(8): xNext = xCor + self.dx[i] yNext = yCor + self.dy[i] layerNext = layer keyNext = key # If next position is Up Stair if self.mazer[layer][xCor][yCor] == 'U': layerNext += 1 elif self.mazer[layer][xCor][yCor] == 'D': # Similar to Down start layerNext -= 1 # If next position is Key X, the bit X in binary representation of keyNext will update to 1 if self.mazer[layer][xCor][yCor][0] == 'K': keyNext |= (1 << int(self.mazer[layer][xCor][yCor][1:])) # If next position is possible for agent to move, push it to successor list if self.canMove(xNext, yNext, layerNext, keyNext) and self.isValid(xCor, yNext, layer, key) and self.isValid(xNext, yCor, layer, key): succ.append((xNext, yNext, layerNext, keyNext)) return succ ``` * To call DFS algorithm, use the following function: ```python # dfs algorithm to solve mazer # start parameter: tuple of position of agent 1. # For example: (1, 1, 0) # goal parameter: tuple of position of goal 1 # For example: (m, n, 0) def dfs(self, start, goal, key = 0): ``` * Similarly, BFS algorithm can be used: ```python def bfs(self, start, goal, key = 0): ```