minigrid.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846
  1. import math
  2. import gym
  3. from enum import IntEnum
  4. import numpy as np
  5. from gym import error, spaces, utils
  6. from gym.utils import seeding
  7. from gym_minigrid.rendering import *
  8. # Size in pixels of a cell in the full-scale human view
  9. CELL_PIXELS = 32
  10. # Number of cells (width and height) in the agent view
  11. AGENT_VIEW_SIZE = 7
  12. # Size of the array given as an observation to the agent
  13. OBS_ARRAY_SIZE = (AGENT_VIEW_SIZE, AGENT_VIEW_SIZE, 3)
  14. COLORS = {
  15. 'red' : (255, 0, 0),
  16. 'green' : (0, 255, 0),
  17. 'blue' : (0, 0, 255),
  18. 'purple': (112, 39, 195),
  19. 'yellow': (255, 255, 0),
  20. 'grey' : (100, 100, 100)
  21. }
  22. # Used to map colors to integers
  23. COLOR_TO_IDX = {
  24. 'red' : 0,
  25. 'green' : 1,
  26. 'blue' : 2,
  27. 'purple': 3,
  28. 'yellow': 4,
  29. 'grey' : 5
  30. }
  31. IDX_TO_COLOR = dict(zip(COLOR_TO_IDX.values(), COLOR_TO_IDX.keys()))
  32. # Map of object type to integers
  33. OBJECT_TO_IDX = {
  34. 'empty' : 0,
  35. 'wall' : 1,
  36. 'door' : 2,
  37. 'locked_door' : 3,
  38. 'key' : 4,
  39. 'ball' : 5,
  40. 'box' : 6,
  41. 'goal' : 7
  42. }
  43. IDX_TO_OBJECT = dict(zip(OBJECT_TO_IDX.values(), OBJECT_TO_IDX.keys()))
  44. class WorldObj:
  45. """
  46. Base class for grid world objects
  47. """
  48. def __init__(self, type, color):
  49. assert type in OBJECT_TO_IDX, type
  50. assert color in COLOR_TO_IDX, color
  51. self.type = type
  52. self.color = color
  53. self.contains = None
  54. def canOverlap(self):
  55. """Can the agent overlap with this?"""
  56. return False
  57. def canPickup(self):
  58. """Can the agent pick this up?"""
  59. return False
  60. def canContain(self):
  61. """Can this contain another object?"""
  62. return False
  63. def toggle(self, env, pos):
  64. """Method to trigger/toggle an action this object performs"""
  65. return False
  66. def render(self, r):
  67. assert False
  68. def _setColor(self, r):
  69. c = COLORS[self.color]
  70. r.setLineColor(c[0], c[1], c[2])
  71. r.setColor(c[0], c[1], c[2])
  72. class Goal(WorldObj):
  73. def __init__(self):
  74. super(Goal, self).__init__('goal', 'green')
  75. def render(self, r):
  76. self._setColor(r)
  77. r.drawPolygon([
  78. (0 , CELL_PIXELS),
  79. (CELL_PIXELS, CELL_PIXELS),
  80. (CELL_PIXELS, 0),
  81. (0 , 0)
  82. ])
  83. class Wall(WorldObj):
  84. def __init__(self, color='grey'):
  85. super(Wall, self).__init__('wall', color)
  86. def render(self, r):
  87. self._setColor(r)
  88. r.drawPolygon([
  89. (0 , CELL_PIXELS),
  90. (CELL_PIXELS, CELL_PIXELS),
  91. (CELL_PIXELS, 0),
  92. (0 , 0)
  93. ])
  94. class Door(WorldObj):
  95. def __init__(self, color, isOpen=False):
  96. super(Door, self).__init__('door', color)
  97. self.isOpen = isOpen
  98. def render(self, r):
  99. c = COLORS[self.color]
  100. r.setLineColor(c[0], c[1], c[2])
  101. r.setColor(0, 0, 0)
  102. if self.isOpen:
  103. r.drawPolygon([
  104. (CELL_PIXELS-2, CELL_PIXELS),
  105. (CELL_PIXELS , CELL_PIXELS),
  106. (CELL_PIXELS , 0),
  107. (CELL_PIXELS-2, 0)
  108. ])
  109. return
  110. r.drawPolygon([
  111. (0 , CELL_PIXELS),
  112. (CELL_PIXELS, CELL_PIXELS),
  113. (CELL_PIXELS, 0),
  114. (0 , 0)
  115. ])
  116. r.drawPolygon([
  117. (2 , CELL_PIXELS-2),
  118. (CELL_PIXELS-2, CELL_PIXELS-2),
  119. (CELL_PIXELS-2, 2),
  120. (2 , 2)
  121. ])
  122. r.drawCircle(CELL_PIXELS * 0.75, CELL_PIXELS * 0.5, 2)
  123. def toggle(self, env, pos):
  124. if not self.isOpen:
  125. self.isOpen = True
  126. return True
  127. return False
  128. def canOverlap(self):
  129. """The agent can only walk over this cell when the door is open"""
  130. return self.isOpen
  131. class LockedDoor(WorldObj):
  132. def __init__(self, color, isOpen=False):
  133. super(LockedDoor, self).__init__('locked_door', color)
  134. self.isOpen = isOpen
  135. def render(self, r):
  136. c = COLORS[self.color]
  137. r.setLineColor(c[0], c[1], c[2])
  138. r.setColor(0, 0, 0)
  139. if self.isOpen:
  140. r.drawPolygon([
  141. (CELL_PIXELS-2, CELL_PIXELS),
  142. (CELL_PIXELS , CELL_PIXELS),
  143. (CELL_PIXELS , 0),
  144. (CELL_PIXELS-2, 0)
  145. ])
  146. return
  147. r.drawPolygon([
  148. (0 , CELL_PIXELS),
  149. (CELL_PIXELS, CELL_PIXELS),
  150. (CELL_PIXELS, 0),
  151. (0 , 0)
  152. ])
  153. r.drawPolygon([
  154. (2 , CELL_PIXELS-2),
  155. (CELL_PIXELS-2, CELL_PIXELS-2),
  156. (CELL_PIXELS-2, 2),
  157. (2 , 2)
  158. ])
  159. r.drawLine(
  160. CELL_PIXELS * 0.75,
  161. CELL_PIXELS * 0.45,
  162. CELL_PIXELS * 0.75,
  163. CELL_PIXELS * 0.60
  164. )
  165. def toggle(self, env, pos):
  166. # If the player has the right key to open the door
  167. if isinstance(env.carrying, Key) and env.carrying.color == self.color:
  168. self.isOpen = True
  169. # The key has been used, remove it from the agent
  170. env.carrying = None
  171. return True
  172. return False
  173. def canOverlap(self):
  174. """The agent can only walk over this cell when the door is open"""
  175. return self.isOpen
  176. class Key(WorldObj):
  177. def __init__(self, color='blue'):
  178. super(Key, self).__init__('key', color)
  179. def canPickup(self):
  180. return True
  181. def render(self, r):
  182. self._setColor(r)
  183. # Vertical quad
  184. r.drawPolygon([
  185. (16, 10),
  186. (20, 10),
  187. (20, 28),
  188. (16, 28)
  189. ])
  190. # Teeth
  191. r.drawPolygon([
  192. (12, 19),
  193. (16, 19),
  194. (16, 21),
  195. (12, 21)
  196. ])
  197. r.drawPolygon([
  198. (12, 26),
  199. (16, 26),
  200. (16, 28),
  201. (12, 28)
  202. ])
  203. r.drawCircle(18, 9, 6)
  204. r.setLineColor(0, 0, 0)
  205. r.setColor(0, 0, 0)
  206. r.drawCircle(18, 9, 2)
  207. class Ball(WorldObj):
  208. def __init__(self, color='blue'):
  209. super(Ball, self).__init__('ball', color)
  210. def canPickup(self):
  211. return True
  212. def render(self, r):
  213. self._setColor(r)
  214. r.drawCircle(CELL_PIXELS * 0.5, CELL_PIXELS * 0.5, 10)
  215. class Box(WorldObj):
  216. def __init__(self, color, contains=None):
  217. super(Box, self).__init__('box', color)
  218. self.contains = contains
  219. def render(self, r):
  220. c = COLORS[self.color]
  221. r.setLineColor(c[0], c[1], c[2])
  222. r.setColor(0, 0, 0)
  223. r.setLineWidth(2)
  224. r.drawPolygon([
  225. (4 , CELL_PIXELS-4),
  226. (CELL_PIXELS-4, CELL_PIXELS-4),
  227. (CELL_PIXELS-4, 4),
  228. (4 , 4)
  229. ])
  230. r.drawLine(
  231. 4,
  232. CELL_PIXELS / 2,
  233. CELL_PIXELS - 4,
  234. CELL_PIXELS / 2
  235. )
  236. r.setLineWidth(1)
  237. def toggle(self, env, pos):
  238. # Replace the box by its contents
  239. env.grid.set(*pos, self.contains)
  240. return True
  241. class Grid:
  242. """
  243. Represent a grid and operations on it
  244. """
  245. def __init__(self, width, height):
  246. assert width >= 4
  247. assert height >= 4
  248. self.width = width
  249. self.height = height
  250. self.grid = [None] * width * height
  251. def copy(self):
  252. from copy import deepcopy
  253. return deepcopy(self)
  254. def set(self, i, j, v):
  255. assert i >= 0 and i < self.width
  256. assert j >= 0 and j < self.height
  257. self.grid[j * self.width + i] = v
  258. def get(self, i, j):
  259. assert i >= 0 and i < self.width
  260. assert j >= 0 and j < self.height
  261. return self.grid[j * self.width + i]
  262. def rotateLeft(self):
  263. """
  264. Rotate the grid to the left (counter-clockwise)
  265. """
  266. grid = Grid(self.width, self.height)
  267. for j in range(0, self.height):
  268. for i in range(0, self.width):
  269. v = self.get(self.width - 1 - j, i)
  270. grid.set(i, j, v)
  271. return grid
  272. def slice(self, topX, topY, width, height):
  273. """
  274. Get a subset of the grid
  275. """
  276. grid = Grid(width, height)
  277. for j in range(0, height):
  278. for i in range(0, width):
  279. x = topX + i
  280. y = topY + j
  281. if x >= 0 and x < self.width and \
  282. y >= 0 and y < self.height:
  283. v = self.get(x, y)
  284. else:
  285. v = Wall()
  286. grid.set(i, j, v)
  287. return grid
  288. def render(self, r, tileSize):
  289. """
  290. Render this grid at a given scale
  291. :param r: target renderer object
  292. :param tileSize: tile size in pixels
  293. """
  294. assert r.width == self.width * tileSize
  295. assert r.height == self.height * tileSize
  296. # Total grid size at native scale
  297. widthPx = self.width * CELL_PIXELS
  298. heightPx = self.height * CELL_PIXELS
  299. # Draw background (out-of-world) tiles the same colors as walls
  300. # so the agent understands these areas are not reachable
  301. c = COLORS['grey']
  302. r.setLineColor(c[0], c[1], c[2])
  303. r.setColor(c[0], c[1], c[2])
  304. r.drawPolygon([
  305. (0 , heightPx),
  306. (widthPx, heightPx),
  307. (widthPx, 0),
  308. (0 , 0)
  309. ])
  310. r.push()
  311. # Internally, we draw at the "large" full-grid resolution, but we
  312. # use the renderer to scale back to the desired size
  313. r.scale(tileSize / CELL_PIXELS, tileSize / CELL_PIXELS)
  314. # Draw the background of the in-world cells black
  315. r.fillRect(
  316. 0,
  317. 0,
  318. widthPx,
  319. heightPx,
  320. 0, 0, 0
  321. )
  322. # Draw grid lines
  323. r.setLineColor(100, 100, 100)
  324. for rowIdx in range(0, self.height):
  325. y = CELL_PIXELS * rowIdx
  326. r.drawLine(0, y, widthPx, y)
  327. for colIdx in range(0, self.width):
  328. x = CELL_PIXELS * colIdx
  329. r.drawLine(x, 0, x, heightPx)
  330. # Render the grid
  331. for j in range(0, self.height):
  332. for i in range(0, self.width):
  333. cell = self.get(i, j)
  334. if cell == None:
  335. continue
  336. r.push()
  337. r.translate(i * CELL_PIXELS, j * CELL_PIXELS)
  338. cell.render(r)
  339. r.pop()
  340. r.pop()
  341. def encode(self):
  342. """
  343. Produce a compact numpy encoding of the grid
  344. """
  345. codeSize = self.width * self.height * 3
  346. array = np.zeros(shape=(self.width, self.height, 3), dtype='uint8')
  347. for j in range(0, self.height):
  348. for i in range(0, self.width):
  349. v = self.get(i, j)
  350. if v == None:
  351. continue
  352. array[i, j, 0] = OBJECT_TO_IDX[v.type]
  353. array[i, j, 1] = COLOR_TO_IDX[v.color]
  354. if hasattr(v, 'isOpen') and v.isOpen:
  355. array[i, j, 2] = 1
  356. return array
  357. def decode(array):
  358. """
  359. Decode an array grid encoding back into a grid
  360. """
  361. width = array.shape[0]
  362. height = array.shape[1]
  363. assert array.shape[2] == 3
  364. grid = Grid(width, height)
  365. for j in range(0, height):
  366. for i in range(0, width):
  367. typeIdx = array[i, j, 0]
  368. colorIdx = array[i, j, 1]
  369. openIdx = array[i, j, 2]
  370. if typeIdx == 0:
  371. continue
  372. objType = IDX_TO_OBJECT[typeIdx]
  373. color = IDX_TO_COLOR[colorIdx]
  374. isOpen = True if openIdx == 1 else 0
  375. if objType == 'wall':
  376. v = Wall(color)
  377. elif objType == 'ball':
  378. v = Ball(color)
  379. elif objType == 'key':
  380. v = Key(color)
  381. elif objType == 'box':
  382. v = Box(color)
  383. elif objType == 'door':
  384. v = Door(color, isOpen)
  385. elif objType == 'locked_door':
  386. v = LockedDoor(color, isOpen)
  387. elif objType == 'goal':
  388. v = Goal()
  389. else:
  390. assert False, "unknown obj type in decode '%s'" % objType
  391. grid.set(i, j, v)
  392. return grid
  393. class MiniGridEnv(gym.Env):
  394. """
  395. 2D grid world game environment
  396. """
  397. metadata = {
  398. 'render.modes': ['human', 'rgb_array', 'pixmap'],
  399. 'video.frames_per_second' : 10
  400. }
  401. # Enumeration of possible actions
  402. class Actions(IntEnum):
  403. left = 0
  404. right = 1
  405. forward = 2
  406. # Toggle/pick up/activate object
  407. toggle = 3
  408. # Wait/stay put/do nothing
  409. wait = 4
  410. def __init__(self, gridSize=16, maxSteps=100):
  411. # Action enumeration for this environment
  412. self.actions = MiniGridEnv.Actions
  413. # Actions are discrete integer values
  414. self.action_space = spaces.Discrete(len(self.actions))
  415. # The observations are RGB images
  416. self.observation_space = spaces.Box(
  417. low=0,
  418. high=255,
  419. shape=OBS_ARRAY_SIZE
  420. )
  421. # Range of possible rewards
  422. self.reward_range = (-1, 1000)
  423. # Renderer object used to render the whole grid (full-scale)
  424. self.gridRender = None
  425. # Renderer used to render observations (small-scale agent view)
  426. self.obsRender = None
  427. # Environment configuration
  428. self.gridSize = gridSize
  429. self.maxSteps = maxSteps
  430. self.startPos = (1, 1)
  431. self.startDir = 0
  432. # Initialize the state
  433. self.seed()
  434. self.reset()
  435. def _genGrid(self, width, height):
  436. """
  437. Generate a new grid
  438. """
  439. # Initialize the grid
  440. grid = Grid(width, height)
  441. # Place walls around the edges
  442. for i in range(0, width):
  443. grid.set(i, 0, Wall())
  444. grid.set(i, height - 1, Wall())
  445. for j in range(0, height):
  446. grid.set(0, j, Wall())
  447. grid.set(height - 1, j, Wall())
  448. # Place a goal in the bottom-left corner
  449. grid.set(width - 2, height - 2, Goal())
  450. return grid
  451. def _reset(self):
  452. # Generate a new random grid at the start of each episode
  453. # To prevent this behavior, call env.seed with the same
  454. # seed before env.reset
  455. self.grid = self._genGrid(self.gridSize, self.gridSize)
  456. # Place the agent in the starting position and direction
  457. self.agentPos = self.startPos
  458. self.agentDir = self.startDir
  459. # Item picked up, being carried, initially nothing
  460. self.carrying = None
  461. # Step count since episode start
  462. self.stepCount = 0
  463. # Return first observation
  464. obs = self._genObs()
  465. return obs
  466. def _seed(self, seed=1337):
  467. """
  468. The seed function sets the random elements of the environment,
  469. and initializes the world.
  470. """
  471. # Seed the random number generator
  472. self.np_random, _ = seeding.np_random(seed)
  473. return [seed]
  474. def _randInt(self, low, high):
  475. """
  476. Generate random integer in [low,high[
  477. """
  478. return self.np_random.randint(low, high)
  479. def _randPos(self, xLow, xHigh, yLow, yHigh):
  480. """
  481. Generate a random (x,y) position tuple
  482. """
  483. return (
  484. self.np_random.randint(xLow, xHigh),
  485. self.np_random.randint(yLow, yHigh)
  486. )
  487. def _randElem(self, iterable):
  488. lst = list(iterable)
  489. idx = self._randInt(0, len(lst))
  490. return lst[idx]
  491. def getStepsRemaining(self):
  492. return self.maxSteps - self.stepCount
  493. def getDirVec(self):
  494. """
  495. Get the direction vector for the agent, pointing in the direction
  496. of forward movement.
  497. """
  498. # Pointing right
  499. if self.agentDir == 0:
  500. return (1, 0)
  501. # Down (positive Y)
  502. elif self.agentDir == 1:
  503. return (0, 1)
  504. # Pointing left
  505. elif self.agentDir == 2:
  506. return (-1, 0)
  507. # Up (negative Y)
  508. elif self.agentDir == 3:
  509. return (0, -1)
  510. else:
  511. assert False
  512. def getViewExts(self):
  513. """
  514. Get the extents of the square set of tiles visible to the agent
  515. Note: the bottom extent indices are not included in the set
  516. """
  517. # Facing right
  518. if self.agentDir == 0:
  519. topX = self.agentPos[0]
  520. topY = self.agentPos[1] - AGENT_VIEW_SIZE // 2
  521. # Facing down
  522. elif self.agentDir == 1:
  523. topX = self.agentPos[0] - AGENT_VIEW_SIZE // 2
  524. topY = self.agentPos[1]
  525. # Facing right
  526. elif self.agentDir == 2:
  527. topX = self.agentPos[0] - AGENT_VIEW_SIZE + 1
  528. topY = self.agentPos[1] - AGENT_VIEW_SIZE // 2
  529. # Facing up
  530. elif self.agentDir == 3:
  531. topX = self.agentPos[0] - AGENT_VIEW_SIZE // 2
  532. topY = self.agentPos[1] - AGENT_VIEW_SIZE + 1
  533. else:
  534. assert False
  535. botX = topX + AGENT_VIEW_SIZE
  536. botY = topY + AGENT_VIEW_SIZE
  537. return (topX, topY, botX, botY)
  538. def _step(self, action):
  539. self.stepCount += 1
  540. reward = 0
  541. done = False
  542. # Rotate left
  543. if action == self.actions.left:
  544. self.agentDir -= 1
  545. if self.agentDir < 0:
  546. self.agentDir += 4
  547. # Rotate right
  548. elif action == self.actions.right:
  549. self.agentDir = (self.agentDir + 1) % 4
  550. # Move forward
  551. elif action == self.actions.forward:
  552. u, v = self.getDirVec()
  553. newPos = (self.agentPos[0] + u, self.agentPos[1] + v)
  554. targetCell = self.grid.get(newPos[0], newPos[1])
  555. if targetCell == None or targetCell.canOverlap():
  556. self.agentPos = newPos
  557. elif targetCell.type == 'goal':
  558. done = True
  559. reward = 1000 - self.stepCount
  560. # Pick up or trigger/activate an item
  561. elif action == self.actions.toggle:
  562. u, v = self.getDirVec()
  563. objPos = (self.agentPos[0] + u, self.agentPos[1] + v)
  564. cell = self.grid.get(*objPos)
  565. if cell and cell.canPickup():
  566. if self.carrying is None:
  567. self.carrying = cell
  568. self.grid.set(*objPos, None)
  569. elif cell:
  570. cell.toggle(self, objPos)
  571. # Wait/do nothing
  572. elif action == self.actions.wait:
  573. pass
  574. else:
  575. assert False, "unknown action"
  576. if self.stepCount >= self.maxSteps:
  577. done = True
  578. obs = self._genObs()
  579. return obs, reward, done, {}
  580. def _genObs(self):
  581. """
  582. Generate the agent's view (partially observable, low-resolution encoding)
  583. """
  584. topX, topY, botX, botY = self.getViewExts()
  585. grid = self.grid.slice(topX, topY, AGENT_VIEW_SIZE, AGENT_VIEW_SIZE)
  586. for i in range(self.agentDir + 1):
  587. grid = grid.rotateLeft()
  588. # Make it so the agent sees what it's carrying
  589. # We do this by placing the carried object at the agent's position
  590. # in the agent's partially observable view
  591. agentPos = grid.width // 2, grid.height - 1
  592. if self.carrying:
  593. grid.set(*agentPos, self.carrying)
  594. else:
  595. grid.set(*agentPos, None)
  596. # Encode the partially observable view into a numpy array
  597. obs = grid.encode()
  598. return obs
  599. def getObsRender(self, obs):
  600. """
  601. Render an agent observation for visualization
  602. """
  603. if self.obsRender == None:
  604. self.obsRender = Renderer(
  605. AGENT_VIEW_SIZE * CELL_PIXELS // 2,
  606. AGENT_VIEW_SIZE * CELL_PIXELS // 2
  607. )
  608. r = self.obsRender
  609. r.beginFrame()
  610. grid = Grid.decode(obs)
  611. # Render the whole grid
  612. grid.render(r, CELL_PIXELS // 2)
  613. # Draw the agent
  614. r.push()
  615. r.scale(0.5, 0.5)
  616. r.translate(
  617. CELL_PIXELS * (0.5 + AGENT_VIEW_SIZE // 2),
  618. CELL_PIXELS * (AGENT_VIEW_SIZE - 0.5)
  619. )
  620. r.rotate(3 * 90)
  621. r.setLineColor(255, 0, 0)
  622. r.setColor(255, 0, 0)
  623. r.drawPolygon([
  624. (-12, 10),
  625. ( 12, 0),
  626. (-12, -10)
  627. ])
  628. r.pop()
  629. r.endFrame()
  630. return r.getPixmap()
  631. def _render(self, mode='human', close=False):
  632. """
  633. Render the whole-grid human view
  634. """
  635. if close:
  636. if self.gridRender:
  637. self.gridRender.close()
  638. return
  639. if self.gridRender is None:
  640. self.gridRender = Renderer(
  641. self.gridSize * CELL_PIXELS,
  642. self.gridSize * CELL_PIXELS,
  643. True if mode == 'human' else False
  644. )
  645. r = self.gridRender
  646. r.beginFrame()
  647. # Render the whole grid
  648. self.grid.render(r, CELL_PIXELS)
  649. # Draw the agent
  650. r.push()
  651. r.translate(
  652. CELL_PIXELS * (self.agentPos[0] + 0.5),
  653. CELL_PIXELS * (self.agentPos[1] + 0.5)
  654. )
  655. r.rotate(self.agentDir * 90)
  656. r.setLineColor(255, 0, 0)
  657. r.setColor(255, 0, 0)
  658. r.drawPolygon([
  659. (-12, 10),
  660. ( 12, 0),
  661. (-12, -10)
  662. ])
  663. r.pop()
  664. # Highlight what the agent can see
  665. topX, topY, botX, botY = self.getViewExts()
  666. r.fillRect(
  667. topX * CELL_PIXELS,
  668. topY * CELL_PIXELS,
  669. AGENT_VIEW_SIZE * CELL_PIXELS,
  670. AGENT_VIEW_SIZE * CELL_PIXELS,
  671. 200, 200, 200, 75
  672. )
  673. r.endFrame()
  674. if mode == 'rgb_array':
  675. return r.getArray()
  676. elif mode == 'pixmap':
  677. return r.getPixmap()
  678. return r