minigrid.py 22 KB

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