Sharing of my labs carried out during the TDDC17 course at Linköping University.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

myvacuumagent.py 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. from lab1.liuvacuum import *
  2. DEBUG_OPT_DENSEWORLDMAP = False
  3. AGENT_STATE_UNKNOWN = 0
  4. AGENT_STATE_WALL = 1
  5. AGENT_STATE_CLEAR = 2
  6. AGENT_STATE_DIRT = 3
  7. AGENT_STATE_HOME = 4
  8. AGENT_DIRECTION_NORTH = 0
  9. AGENT_DIRECTION_EAST = 1
  10. AGENT_DIRECTION_SOUTH = 2
  11. AGENT_DIRECTION_WEST = 3
  12. def direction_to_string(cdr):
  13. cdr %= 4
  14. return "NORTH" if cdr == AGENT_DIRECTION_NORTH else\
  15. "EAST" if cdr == AGENT_DIRECTION_EAST else\
  16. "SOUTH" if cdr == AGENT_DIRECTION_SOUTH else\
  17. "WEST" #if dir == AGENT_DIRECTION_WEST
  18. """
  19. Internal state of a vacuum agent
  20. """
  21. class MyAgentState:
  22. def __init__(self, width, height):
  23. # Initialize perceived world state
  24. self.world = [[AGENT_STATE_UNKNOWN for _ in range(height)] for _ in range(width)]
  25. self.world[1][1] = AGENT_STATE_HOME
  26. # Agent internal state
  27. self.last_action = ACTION_NOP
  28. self.direction = AGENT_DIRECTION_EAST
  29. self.pos_x = 1
  30. self.pos_y = 1
  31. # Metadata
  32. self.world_width = width
  33. self.world_height = height
  34. """
  35. Update perceived agent location and orientation
  36. """
  37. def update_position(self, bump):
  38. if not bump and self.last_action == ACTION_FORWARD:
  39. if self.direction == AGENT_DIRECTION_EAST:
  40. self.pos_x += 1
  41. elif self.direction == AGENT_DIRECTION_SOUTH:
  42. self.pos_y += 1
  43. elif self.direction == AGENT_DIRECTION_WEST:
  44. self.pos_x -= 1
  45. elif self.direction == AGENT_DIRECTION_NORTH:
  46. self.pos_y -= 1
  47. elif self.last_action == ACTION_TURN_LEFT:
  48. if self.direction == AGENT_DIRECTION_EAST:
  49. self.direction = AGENT_DIRECTION_NORTH
  50. elif self.direction == AGENT_DIRECTION_SOUTH:
  51. self.direction = AGENT_DIRECTION_EAST
  52. elif self.direction == AGENT_DIRECTION_WEST:
  53. self.direction = AGENT_DIRECTION_SOUTH
  54. elif self.direction == AGENT_DIRECTION_NORTH:
  55. self.direction = AGENT_DIRECTION_WEST
  56. elif self.last_action == ACTION_TURN_RIGHT:
  57. if self.direction == AGENT_DIRECTION_EAST:
  58. self.direction = AGENT_DIRECTION_SOUTH
  59. elif self.direction == AGENT_DIRECTION_SOUTH:
  60. self.direction = AGENT_DIRECTION_WEST
  61. elif self.direction == AGENT_DIRECTION_WEST:
  62. self.direction = AGENT_DIRECTION_NORTH
  63. elif self.direction == AGENT_DIRECTION_NORTH:
  64. self.direction = AGENT_DIRECTION_EAST
  65. """
  66. Update perceived or inferred information about a part of the world
  67. """
  68. def update_world(self, x, y, info):
  69. self.world[x][y] = info
  70. """
  71. Dumps a map of the world as the agent knows it
  72. """
  73. def print_world_debug(self):
  74. for y in range(self.world_height):
  75. for x in range(self.world_width):
  76. if self.world[x][y] == AGENT_STATE_UNKNOWN:
  77. print("?" if DEBUG_OPT_DENSEWORLDMAP else " ? ", end="")
  78. elif self.world[x][y] == AGENT_STATE_WALL:
  79. print("#" if DEBUG_OPT_DENSEWORLDMAP else " # ", end="")
  80. elif self.world[x][y] == AGENT_STATE_CLEAR:
  81. print("." if DEBUG_OPT_DENSEWORLDMAP else " . ", end="")
  82. elif self.world[x][y] == AGENT_STATE_DIRT:
  83. print("D" if DEBUG_OPT_DENSEWORLDMAP else " D ", end="")
  84. elif self.world[x][y] == AGENT_STATE_HOME:
  85. print("H" if DEBUG_OPT_DENSEWORLDMAP else " H ", end="")
  86. print() # Newline
  87. print() # Delimiter post-print
  88. """
  89. Vacuum agent
  90. """
  91. class MyVacuumAgent(Agent):
  92. """
  93. Init function, everything here is execute once at the beginning
  94. """
  95. def __init__(self, world_width, world_height, log):
  96. super().__init__(self.execute)
  97. self.initial_random_actions = 10
  98. self.iteration_counter = 1000
  99. self.state = MyAgentState(world_width, world_height)
  100. self.log = log
  101. # Arrived variable for the goto function
  102. self.arrived = True
  103. # Arrived variable for the go_to_with_pathfinder function
  104. self.arrived_destination = True
  105. # If the agent has finished the cleaning
  106. self.finished = False
  107. # Goto coordinates for the goto function
  108. self.goto_x = 1
  109. self.goto_y = 1
  110. # Goto coordinates for the go_to_with_pathfinder function
  111. self.destination_x = 1
  112. self.destination_y = 1
  113. # If we need to re-calculate our destination
  114. self.reset_destination = True
  115. # Different list in agent memory
  116. self.waypoint = [] # Memory for next waypoint coordinates
  117. self.slist = [] # List of waypoint to go at the agent destination
  118. # At the beginning, we assume that the zone is surrounded by wall
  119. for i in range(0, world_width):
  120. self.state.update_world(i, 0, AGENT_STATE_WALL)
  121. self.state.update_world(i, world_height - 1, AGENT_STATE_WALL)
  122. for j in range(1, world_height - 1):
  123. self.state.update_world(0, j, AGENT_STATE_WALL)
  124. self.state.update_world(world_width - 1, j, AGENT_STATE_WALL)
  125. """
  126. Function which move the agent to a ramdom start position
  127. """
  128. def move_to_random_start_position(self, bump):
  129. action = random()
  130. self.initial_random_actions -= 1
  131. self.state.update_position(bump)
  132. self.log("Actual direction: " + direction_to_string(self.state.direction))
  133. if action < 0.1666666: # 1/6 chance
  134. self.state.last_action = ACTION_TURN_LEFT
  135. return ACTION_TURN_LEFT
  136. elif action < 0.3333333: # 1/6 chance
  137. self.state.last_action = ACTION_TURN_RIGHT
  138. return ACTION_TURN_RIGHT
  139. else: # 4/6 chance
  140. self.state.last_action = ACTION_FORWARD
  141. return ACTION_FORWARD
  142. """
  143. A basic "go to" function which does not consider wall
  144. """
  145. def goto(self, bump, dirt):
  146. self.state.update_position(bump)
  147. self.log("Actual direction: " + direction_to_string(self.state.direction))
  148. self.log("Next waypoint: {}, {}".format(self.goto_x, self.goto_y))
  149. if dirt:
  150. self.state.update_world(self.state.pos_x, self.state.pos_y, AGENT_STATE_DIRT)
  151. self.log("DIRT -> choosing SUCK action!")
  152. self.state.last_action = ACTION_SUCK
  153. return ACTION_SUCK
  154. else:
  155. self.state.update_world(self.state.pos_x, self.state.pos_y, AGENT_STATE_CLEAR)
  156. if (self.goto_x == self.state.pos_x and self.goto_y == self.state.pos_y) or bump:
  157. self.arrived = True
  158. self.log("Arrived to the target point")
  159. self.state.last_action = ACTION_NOP
  160. return ACTION_NOP
  161. if abs(self.state.pos_x - self.goto_x) >= abs(self.state.pos_y - self.goto_y):
  162. if self.state.pos_x - self.goto_x > 0: # Need to go WEST
  163. if self.state.direction == AGENT_DIRECTION_WEST:
  164. return ACTION_FORWARD
  165. elif self.state.direction == AGENT_DIRECTION_SOUTH:
  166. return ACTION_TURN_RIGHT
  167. else:
  168. return ACTION_TURN_LEFT
  169. else: # Need to go EAST
  170. if self.state.direction == AGENT_DIRECTION_EAST:
  171. return ACTION_FORWARD
  172. elif self.state.direction == AGENT_DIRECTION_NORTH:
  173. return ACTION_TURN_RIGHT
  174. else:
  175. return ACTION_TURN_LEFT
  176. else:
  177. if self.state.pos_y - self.goto_y > 0: # Need to go NORTH
  178. if self.state.direction == AGENT_DIRECTION_NORTH:
  179. return ACTION_FORWARD
  180. elif self.state.direction == AGENT_DIRECTION_WEST:
  181. return ACTION_TURN_RIGHT
  182. else:
  183. return ACTION_TURN_LEFT
  184. else: # Need to go SOUTH
  185. if self.state.direction == AGENT_DIRECTION_SOUTH:
  186. return ACTION_FORWARD
  187. elif self.state.direction == AGENT_DIRECTION_EAST:
  188. return ACTION_TURN_RIGHT
  189. else:
  190. return ACTION_TURN_LEFT
  191. """
  192. A pathfinding algorithm, based on the BFS algorithm
  193. """
  194. def pathfinding(self, target_x, target_y):
  195. # Class for node used in the search
  196. class Node:
  197. def __init__(self, parent=None, x=-1, y=-1):
  198. self.x = x
  199. self.y = y
  200. self.parent = parent
  201. def __eq__(self, other):
  202. return self.x == other.x and self.y == other.y
  203. # Start and end nodes
  204. start = Node(None, self.state.pos_x, self.state.pos_y)
  205. end = Node(None, target_x, target_y)
  206. # List containing the nodes to explore (By order of priority)
  207. queue = [start]
  208. # List of explored nodes
  209. explored = []
  210. # List which will be returned by the program containing
  211. # the coordinates to follow to reach the targeted point
  212. slist = []
  213. # While there is node to explore
  214. while queue:
  215. # If the queue is to big, we assume that there is a problem
  216. if len(queue) > 200:
  217. break
  218. # We take the first node in the queue
  219. node = queue.pop(0)
  220. # If it is the destination
  221. if node == end:
  222. print("Trying to go at : {}; {}".format(target_x, target_y))
  223. print("Solution found : ")
  224. n = node
  225. # We return the path found
  226. while n.parent:
  227. print("x: {}, y: {}".format(n.x, n.y))
  228. slist.append([n.x, n.y])
  229. n = n.parent
  230. # and stop the processing
  231. break
  232. # If we never explored this node
  233. elif node not in explored:
  234. explored.append(node)
  235. # For each adjacent node, if it is not a wall,
  236. # we add it to the queue
  237. if self.state.world[node.x - 1][node.y] != AGENT_STATE_WALL:
  238. queue.append(Node(node, node.x-1, node.y))
  239. if self.state.world[node.x + 1][node.y] != AGENT_STATE_WALL:
  240. queue.append(Node(node, node.x+1, node.y))
  241. if self.state.world[node.x][node.y - 1] != AGENT_STATE_WALL:
  242. queue.append(Node(node, node.x, node.y - 1))
  243. if self.state.world[node.x][node.y + 1] != AGENT_STATE_WALL:
  244. queue.append(Node(node, node.x, node.y+1))
  245. # If we don't find any solution
  246. if not slist:
  247. # We consider that the place in inaccessible
  248. self.state.update_world(end.x, end.y, AGENT_STATE_WALL)
  249. self.log("Find a inaccessible place, marking as a wall ##")
  250. print("Overload! Inaccessible place!")
  251. return slist
  252. """
  253. A "go to" function which implement the pathfinding algorithm using the goto function
  254. """
  255. def go_to_with_pathfinder(self, bump, dirt):
  256. # If we start or if we need to reset the path
  257. if self.reset_destination:
  258. print("Launching the pathfinding algorithm.")
  259. self.slist = self.pathfinding(self.destination_x, self.destination_y)
  260. # If we have a path, we follow it
  261. if self.slist:
  262. # If the agent move correctly the previous time
  263. if not bump:
  264. self.waypoint = self.slist.pop()
  265. self.goto_x = self.waypoint[0]
  266. self.goto_y = self.waypoint[1]
  267. self.arrived = False
  268. action = self.goto(bump, dirt)
  269. self.state.last_action = action
  270. self.reset_destination = False
  271. return action
  272. # else (whether we arrived or we don't find a path), we assume
  273. # that we will not go further
  274. else:
  275. self.reset_destination = True
  276. self.arrived_destination = True
  277. self.state.last_action = ACTION_NOP
  278. return ACTION_NOP
  279. pass
  280. """
  281. The execute function, which is call at each turn
  282. """
  283. def execute(self, percept):
  284. # If the agent has finished, we do nothing
  285. if self.finished:
  286. return ACTION_NOP
  287. ###########################
  288. # DO NOT MODIFY THIS CODE #
  289. ###########################
  290. bump = percept.attributes["bump"]
  291. dirt = percept.attributes["dirt"]
  292. home = percept.attributes["home"]
  293. # Move agent to a randomly chosen initial position
  294. if self.initial_random_actions > 0:
  295. self.log("Moving to random start position ({} steps left)".format(self.initial_random_actions))
  296. return self.move_to_random_start_position(bump)
  297. # Finalize randomization by properly updating position (without subsequently changing it)
  298. elif self.initial_random_actions == 0:
  299. self.initial_random_actions -= 1
  300. self.state.update_position(bump)
  301. self.state.last_action = ACTION_SUCK
  302. self.log("Processing percepts after position randomization")
  303. return ACTION_SUCK
  304. ########################
  305. # START MODIFYING HERE #
  306. ########################
  307. # Logging position and orientation
  308. self.log("Position: ({}, {})\t\tDirection: {}".format(self.state.pos_x, self.state.pos_y,
  309. direction_to_string(self.state.direction)))
  310. if bump:
  311. # Not arrived, but this way we force our agent to re-calculate its path
  312. self.arrived = True
  313. self.reset_destination = True
  314. # Get an xy-offset pair based on where the agent is facing
  315. offset = [(0, -1), (1, 0), (0, 1), (-1, 0)][self.state.direction]
  316. # Mark the tile at the offset from the agent as a wall (since the agent bumped into it)
  317. self.state.update_world(self.state.pos_x + offset[0], self.state.pos_y + offset[1], AGENT_STATE_WALL)
  318. # While we aren't arrived, we refer to the goto function
  319. if not self.arrived:
  320. action = self.goto(bump, dirt)
  321. self.state.last_action = action
  322. return action
  323. # While we aren't arrived, we refer to the go_to_with_pathfinder function
  324. elif not self.arrived_destination:
  325. return self.go_to_with_pathfinder(bump, dirt)
  326. # Max iterations for the agent
  327. if self.iteration_counter < 1:
  328. if self.iteration_counter == 0:
  329. self.iteration_counter -= 1
  330. self.log("Iteration counter is now 0. Halting!")
  331. self.log("Performance: {}".format(self.performance))
  332. return ACTION_NOP
  333. self.iteration_counter -= 1
  334. # Track position of agent
  335. self.state.update_position(bump)
  336. # Update perceived state of current tile
  337. if dirt:
  338. self.state.update_world(self.state.pos_x, self.state.pos_y, AGENT_STATE_DIRT)
  339. else:
  340. self.state.update_world(self.state.pos_x, self.state.pos_y, AGENT_STATE_CLEAR)
  341. # Debug
  342. self.state.print_world_debug()
  343. # Variables used to determine the closest unknown place
  344. closest_unk_range = self.state.world_height + self.state.world_width + 1
  345. closest_x = 1
  346. closest_y = 1
  347. find_new = False
  348. # Determination of the nearest unknown location
  349. for i in range(self.state.world_width):
  350. for j in range(self.state.world_height):
  351. if self.state.world[i][j] == AGENT_STATE_UNKNOWN and (
  352. abs(self.state.pos_x - i) + abs(self.state.pos_y - j)) < closest_unk_range:
  353. find_new = True
  354. closest_unk_range = (abs(self.state.pos_x - i) + abs(self.state.pos_y - j))
  355. closest_x = i
  356. closest_y = j
  357. # If we find a new place to go
  358. if find_new:
  359. self.log("Going to unknown places : ({};{})".format(closest_x, closest_y))
  360. self.destination_x = closest_x
  361. self.destination_y = closest_y
  362. return self.go_to_with_pathfinder(bump, dirt)
  363. # If not, we have finished, we go home
  364. if home:
  365. self.log("Finished !")
  366. self.log("Performance: {}".format(self.performance))
  367. self.finished = True
  368. # Finally we are at home
  369. else:
  370. self.log("Job done, going back home.")
  371. self.destination_x = 1
  372. self.destination_y = 1
  373. return self.go_to_with_pathfinder(bump, dirt)
  374. # Useless
  375. self.state.last_action = ACTION_NOP
  376. return ACTION_NOP