PageRenderTime 47ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/qooxdoo/application/demobrowser/source/class/demobrowser/demo/util/FSMMaze.js

https://github.com/Wkasel/qooxdoo
JavaScript | 477 lines | 288 code | 55 blank | 134 comment | 23 complexity | d5d29b2790d022a0b922a8c3fd7a1435 MD5 | raw file
  1. /* ************************************************************************
  2. qooxdoo - the new era of web development
  3. http://qooxdoo.org
  4. Copyright:
  5. 2007 Derrell Lipman
  6. 2004-2010 1&1 Internet AG, Germany, http://www.1und1.de
  7. License:
  8. LGPL: http://www.gnu.org/licenses/lgpl.html
  9. EPL: http://www.eclipse.org/org/documents/epl-v10.php
  10. See the LICENSE file in the project's top-level directory for details.
  11. Authors:
  12. * Derrell Lipman (derrell)
  13. * Tristan Koch (tristankoch)
  14. ************************************************************************ */
  15. qx.Class.define("demobrowser.demo.util.FSMMaze",
  16. {
  17. extend : qx.ui.container.Composite,
  18. statics :
  19. {
  20. Direction :
  21. {
  22. WEST : 0x8,
  23. SOUTH : 0x4,
  24. EAST : 0x2,
  25. NORTH : 0x1
  26. }
  27. },
  28. construct : function(rows, columns, x, y, cellSize)
  29. {
  30. this.base(arguments);
  31. this.setLayout(new qx.ui.layout.Grid());
  32. this.numRows = (rows === undefined ? 10 : rows);
  33. this.numCols = (columns === undefined ? 10 : columns);
  34. this.x = (x === undefined ? 50 : x);
  35. this.y = (y === undefined ? 50 : y);
  36. this.cellSize = (cellSize === undefined ? 50 : cellSize);
  37. this.totalHeight = this.numRows * this.cellSize;
  38. this.totalWidth = this.numCols * this.cellSize;
  39. this.setWidth(this.totalWidth);
  40. this.setHeight(this.totalHeight);
  41. for (var row = 0; row < this.numRows; row++)
  42. {
  43. this.getLayout().setRowHeight(row, this.cellSize);
  44. }
  45. for (var col = 0; col < this.numCols; col++)
  46. {
  47. this.getLayout().setColumnWidth(col, this.cellSize);
  48. }
  49. // Initialize the cells and walls arrays. Walls are Border objects;
  50. // Cells are HorizontalBoxLayout objects.
  51. this.cells = [];
  52. // Each element of mazeInfo is a bitmap of walls
  53. // (demobrowser.demo.util.FSMMaze.Direction.*)
  54. this.mazeInfo = [];
  55. // Build a grid with all walls of all cells intact
  56. for (row = 0; row < this.numRows; row++)
  57. {
  58. this.cells[row] = [];
  59. this.mazeInfo[row] = [];
  60. for (col = 0; col < this.numCols; col++)
  61. {
  62. // Instantiate this cell
  63. this.cells[row][col] = new qx.ui.container.Composite();
  64. this.cells[row][col].setLayout(new qx.ui.layout.HBox());
  65. this.add(this.cells[row][col], {row: row, column: col});
  66. // Apply the border on the cell
  67. this.cells[row][col].setDecorator(new qx.ui.decoration.Single(1, "solid", "black"));
  68. // We're starting with all walls intact. Note that.
  69. // See // demobrowser.demo.util.FSMMaze.Direction.* for the bit field values.
  70. this.mazeInfo[row][col] = 0xf;
  71. }
  72. }
  73. //
  74. // Build a "perfect" maze using the depth-first search algorithm described
  75. // at http://www.mazeworks.com/mazegen/mazetut/index.htm
  76. //
  77. var cellStack = [];
  78. var visitedCells = 1;
  79. var totalCells = this.numRows * this.numCols;
  80. var currentCell;
  81. var neighbors;
  82. var neighbor;
  83. // Start with some random cell
  84. currentCell =
  85. {
  86. row : Math.floor(Math.random() * this.numRows),
  87. col : Math.floor(Math.random() * this.numCols)
  88. };
  89. while (visitedCells < totalCells)
  90. {
  91. // Initialize neighbors of current cell array
  92. neighbors = [];
  93. // See if there's a west neighbor with all walls intact
  94. if (currentCell.col > 0 &&
  95. (this.mazeInfo[currentCell.row][currentCell.col - 1] == 0xf))
  96. {
  97. neighbors.push(
  98. {
  99. row : currentCell.row,
  100. col : currentCell.col - 1,
  101. currentCellWall : demobrowser.demo.util.FSMMaze.Direction.WEST,
  102. neighborWall : demobrowser.demo.util.FSMMaze.Direction.EAST
  103. });
  104. }
  105. // See if there's a south neighbor with all walls intact
  106. if (currentCell.row < this.numRows - 1 &&
  107. (this.mazeInfo[currentCell.row + 1][currentCell.col] == 0xf))
  108. {
  109. neighbors.push(
  110. {
  111. row : currentCell.row + 1,
  112. col : currentCell.col,
  113. currentCellWall : demobrowser.demo.util.FSMMaze.Direction.SOUTH,
  114. neighborWall : demobrowser.demo.util.FSMMaze.Direction.NORTH
  115. });
  116. }
  117. // See if there's an east neighbor with all walls intact
  118. if (currentCell.col < this.numCols - 1 &&
  119. (this.mazeInfo[currentCell.row][currentCell.col + 1] == 0xf))
  120. {
  121. neighbors.push(
  122. {
  123. row : currentCell.row,
  124. col : currentCell.col + 1,
  125. currentCellWall : demobrowser.demo.util.FSMMaze.Direction.EAST,
  126. neighborWall : demobrowser.demo.util.FSMMaze.Direction.WEST
  127. });
  128. }
  129. // See if there's a north neighbor with all walls intact
  130. if (currentCell.row > 0 &&
  131. (this.mazeInfo[currentCell.row - 1][currentCell.col] == 0xf))
  132. {
  133. neighbors.push(
  134. {
  135. row : currentCell.row - 1,
  136. col : currentCell.col,
  137. currentCellWall : demobrowser.demo.util.FSMMaze.Direction.NORTH,
  138. neighborWall : demobrowser.demo.util.FSMMaze.Direction.SOUTH
  139. });
  140. }
  141. // Did we find any neighbors with all walls intact?
  142. if (neighbors.length > 0)
  143. {
  144. // Yup. Choose one at random
  145. var r = Math.floor(Math.random() * neighbors.length);
  146. neighbor = neighbors[r];
  147. // Knock down the wall between it and currentCell. This is a
  148. // multiple-step process:
  149. // Step 1: Remove the wall flag on the current cell
  150. this.mazeInfo[currentCell.row][currentCell.col] &=
  151. ~neighbor.currentCellWall;
  152. // Step 2; Remove the wall flag on the neighbor cell
  153. this.mazeInfo[neighbor.row][neighbor.col] &=
  154. ~neighbor.neighborWall;
  155. // Step 3: Actually remove the wall on the current cell
  156. var currentWall = new qx.ui.decoration.Single(1, "solid", "black");
  157. var previousWall = this.cells[currentCell.row][currentCell.col].getDecorator();
  158. currentWall.set({
  159. widthLeft: previousWall.getWidthLeft(),
  160. widthBottom: previousWall.getWidthBottom(),
  161. widthRight: previousWall.getWidthRight(),
  162. widthTop: previousWall.getWidthTop()
  163. });
  164. switch(neighbor.currentCellWall)
  165. {
  166. case demobrowser.demo.util.FSMMaze.Direction.WEST:
  167. currentWall.setWidthLeft(0);
  168. break;
  169. case demobrowser.demo.util.FSMMaze.Direction.SOUTH:
  170. currentWall.setWidthBottom(0);
  171. break;
  172. case demobrowser.demo.util.FSMMaze.Direction.EAST:
  173. currentWall.setWidthRight(0);
  174. break;
  175. case demobrowser.demo.util.FSMMaze.Direction.NORTH:
  176. currentWall.setWidthTop(0);
  177. break;
  178. }
  179. this.cells[currentCell.row][currentCell.col].setDecorator(currentWall);
  180. // Step 4: Actually remove the wall on the neighbor cell
  181. var neighborWall = new qx.ui.decoration.Single(1, "solid", "black");
  182. var previousNeighborWall = this.cells[neighbor.row][neighbor.col].getDecorator();
  183. neighborWall.set({
  184. widthLeft: previousNeighborWall.getWidthLeft(),
  185. widthBottom: previousNeighborWall.getWidthBottom(),
  186. widthRight: previousNeighborWall.getWidthRight(),
  187. widthTop: previousNeighborWall.getWidthTop()
  188. });
  189. switch(neighbor.neighborWall)
  190. {
  191. case demobrowser.demo.util.FSMMaze.Direction.WEST:
  192. neighborWall.setWidthLeft(0);
  193. break;
  194. case demobrowser.demo.util.FSMMaze.Direction.SOUTH:
  195. neighborWall.setWidthBottom(0);
  196. break;
  197. case demobrowser.demo.util.FSMMaze.Direction.EAST:
  198. neighborWall.setWidthRight(0);
  199. break;
  200. case demobrowser.demo.util.FSMMaze.Direction.NORTH:
  201. neighborWall.setWidthTop(0);
  202. break;
  203. }
  204. this.cells[neighbor.row][neighbor.col].setDecorator(neighborWall);
  205. // Push currentCell onto the cell stack
  206. cellStack.push({ row : currentCell.row, col : currentCell.col });
  207. // The neighbor becomes our new current cell
  208. currentCell = { row : neighbor.row, col : neighbor.col };
  209. // We've visited one more cell
  210. visitedCells++;
  211. }
  212. else
  213. {
  214. // Pop the most recent cell from the cell stack
  215. var cell = cellStack.pop();
  216. currentCell = { row : cell.row, col : cell.col };
  217. }
  218. }
  219. // Determine the starting cell
  220. this.startCell =
  221. {
  222. row : Math.floor(Math.random() * this.numRows),
  223. col : Math.floor(Math.random() * this.numCols)
  224. }
  225. // Show the starting cell
  226. var startCell = this.cells[this.startCell.row][this.startCell.col];
  227. startCell.setBackgroundColor("#b0ffb0");
  228. // Determine the ending cell, not too close to the starting cell
  229. do
  230. {
  231. this.endCell =
  232. {
  233. row : Math.floor(Math.random() * this.numRows),
  234. col : Math.floor(Math.random() * this.numCols)
  235. }
  236. } while ((Math.abs(this.startCell.row - this.endCell.row) <
  237. this.numRows / 2) ||
  238. (Math.abs(this.startCell.col - this.endCell.col) <
  239. this.numCols / 2));
  240. // Show the ending cell
  241. var endCell = this.cells[this.endCell.row][this.endCell.col];
  242. endCell.setBackgroundColor("#ffb0b0");
  243. },
  244. members :
  245. {
  246. /**
  247. * Get the size of each cell.
  248. */
  249. getCellSize : function()
  250. {
  251. return this.cellSize;
  252. },
  253. /**
  254. * Get the starting cell.
  255. *
  256. * @return {Object}
  257. * The returned object contains two members: row and col.
  258. */
  259. getStartCell : function()
  260. {
  261. return this.startCell;
  262. },
  263. /**
  264. * Get the ending cell.
  265. *
  266. * @return {Object}
  267. * The returned object contains two members: row and col.
  268. */
  269. getEndCell : function()
  270. {
  271. return this.endCell;
  272. },
  273. /**
  274. * Get the position of the specified cell.
  275. *
  276. * @param cell {Object}
  277. * The cell for which the position is desired. This object contains two
  278. * members: row and col.
  279. *
  280. * @return {Object}
  281. * The returned object contains two members: top and left.
  282. */
  283. getCellTopLeft : function(cell)
  284. {
  285. return(
  286. {
  287. top : this.y + (this.cellSize * cell.row),
  288. left : this.x + (this.cellSize * cell.col)
  289. });
  290. },
  291. /**
  292. * Get the neighbor cell to the specified cell's west.
  293. *
  294. * @param cell {Object}
  295. * The cell for which the neighbor is desired. This object contains two
  296. * members: row and col.
  297. *
  298. * @return {Object|null}
  299. * The returned object contains two members: row and col.
  300. * If there is no such neighbor, null is returned.
  301. */
  302. getWestCell : function(cell)
  303. {
  304. var dir = demobrowser.demo.util.FSMMaze.Direction.WEST;
  305. if (cell.col > 0 &&
  306. ((this.mazeInfo[cell.row][cell.col] & dir) == 0))
  307. {
  308. return(
  309. {
  310. row : cell.row,
  311. col : cell.col - 1
  312. });
  313. }
  314. return null;
  315. },
  316. /**
  317. * Get the neighbor cell to the specified cell's south.
  318. *
  319. * @param cell {Object}
  320. * The cell for which the neighbor is desired. This object contains two
  321. * members: row and col.
  322. *
  323. * @return {Object|null}
  324. * The returned object contains two members: row and col.
  325. * If there is no such neighbor, null is returned.
  326. */
  327. getSouthCell : function(cell)
  328. {
  329. var dir = demobrowser.demo.util.FSMMaze.Direction.SOUTH;
  330. if (cell.row < this.numRows - 1 &&
  331. ((this.mazeInfo[cell.row][cell.col] & dir) == 0))
  332. {
  333. return(
  334. {
  335. row : cell.row + 1,
  336. col : cell.col
  337. });
  338. }
  339. return null;
  340. },
  341. /**
  342. * Get the neighbor cell to the specified cell's east.
  343. *
  344. * @param cell {Object}
  345. * The cell for which the neighbor is desired. This object contains two
  346. * members: row and col.
  347. *
  348. * @return {Object|null}
  349. * The returned object contains two members: row and col.
  350. * If there is no such neighbor, null is returned.
  351. */
  352. getEastCell : function(cell)
  353. {
  354. var dir = demobrowser.demo.util.FSMMaze.Direction.EAST;
  355. if (cell.col < this.numCols - 1 &&
  356. ((this.mazeInfo[cell.row][cell.col] & dir) == 0))
  357. {
  358. return(
  359. {
  360. row : cell.row,
  361. col : cell.col + 1
  362. });
  363. }
  364. return null;
  365. },
  366. /**
  367. * Get the neighbor cell to the specified cell's north.
  368. *
  369. * @param cell {Object}
  370. * The cell for which the neighbor is desired. This object contains two
  371. * members: row and col.
  372. *
  373. * @return {Object|null}
  374. * The returned object contains two members: row and col.
  375. * If there is no such neighbor, null is returned.
  376. */
  377. getNorthCell : function(cell)
  378. {
  379. var dir = demobrowser.demo.util.FSMMaze.Direction.NORTH;
  380. if (cell.row > 0 &&
  381. ((this.mazeInfo[cell.row][cell.col] & dir) == 0))
  382. {
  383. return(
  384. {
  385. row : cell.row - 1,
  386. col : cell.col
  387. });
  388. }
  389. return null;
  390. },
  391. /**
  392. * Mark the specified cell as part of the final backtrace.
  393. *
  394. * @param cell {Object}
  395. * The cell to be marked. This object contains two members: row and
  396. * col.
  397. *
  398. * @return {Void}
  399. */
  400. markCell : function(cell)
  401. {
  402. var size = Math.ceil(this.cellSize / 5);
  403. var o = new qx.ui.basic.Label("&bull;", null, "html");
  404. o.set(
  405. {
  406. height : size,
  407. width : size,
  408. paddingTop : (this.cellSize - size) / 2,
  409. paddingLeft : (this.cellSize - size) / 2,
  410. rich : true
  411. });
  412. // {top: (this.cellSize - size) / 2, left: (this.cellSize - size) / 2}
  413. this.cells[cell.row][cell.col].add(o);
  414. }
  415. }
  416. });