Board.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. import { Theme } from "../Theme.js"
  2. import Tile2 from "./Tile2.js"
  3. import AsyncTween from "../../AsyncTween.js"
  4. import { Easing } from "../../Easing.js"
  5. import { Point } from "../../spatial/Point.js"
  6. import { ActionQueue, MatchTilesAction, SpawnTileAction, SwapTileAction, TileFallAction } from "../../ActionQueue.js"
  7. class Board {
  8. constructor() {
  9. this.tiles = []
  10. this.position = new Point(0, 0)
  11. this.actionQueue = new ActionQueue()
  12. this.selectedTiles = []
  13. }
  14. init(canvasBounds) {
  15. this.boardSize = { x: 7, y: 7 }
  16. for (let y = 0; y < this.boardSize.y; y++) {
  17. for (let x = 0; x < this.boardSize.x; x++) {
  18. this.spawnTile(x, y)
  19. }
  20. }
  21. this.onResize(canvasBounds)
  22. // setTimeout(() => {
  23. // console.log("checking for matches on load")
  24. this.checkForMatches()
  25. // }, 2000)
  26. }
  27. spawnTile(x, y) {
  28. const tileType = Math.floor(Math.random() * Object.values(Theme.Colors.TileColors).length)
  29. const newTile = new Tile2(this, x, y, tileType)
  30. this.tiles.push(newTile)
  31. return newTile
  32. }
  33. draw(ctx) {
  34. ctx.save()
  35. ctx.translate(-this.position.x, -this.position.y)
  36. ctx.fillStyle = Theme.Colors.umber
  37. ctx.beginPath()
  38. ctx.rect(0, 0,
  39. this.boardSize.x * (Board.TILE_PADDING + Board.TILE_SIZE),
  40. this.boardSize.y * (Board.TILE_PADDING + Board.TILE_SIZE))
  41. ctx.fill()
  42. this.tiles.forEach((tile) => {
  43. tile.draw(ctx)
  44. })
  45. ctx.restore()
  46. }
  47. update(delta) {
  48. }
  49. onResize(canvasBounds) {
  50. let narrowest = canvasBounds.width
  51. let isVertical = true
  52. if (canvasBounds.width > canvasBounds.height) {
  53. narrowest = canvasBounds.height
  54. isVertical = false
  55. }
  56. Board.TILE_SIZE = Math.min(64, Math.max(40, Math.floor((narrowest - this.boardSize.x * 4) / this.boardSize.x)))
  57. const offset = new Point(
  58. 0,
  59. -(canvasBounds.height / 2) + (10 * (Board.TILE_PADDING + Board.TILE_SIZE)) / 2
  60. )
  61. if (!isVertical) {
  62. offset.x = (7 * (Board.TILE_PADDING + Board.TILE_SIZE)) / 2
  63. offset.y = 0
  64. }
  65. this.position.x = this.boardSize.x * (Board.TILE_PADDING + Board.TILE_SIZE) / 2
  66. this.position.y = this.boardSize.y * (Board.TILE_PADDING + Board.TILE_SIZE) / 2
  67. this.position.offset(offset)
  68. }
  69. onInputMove(position, isDown) {
  70. const offsetPosition = position.addition(this.position)
  71. this.tiles.forEach((tile) => {
  72. tile.onInputMove(offsetPosition, isDown)
  73. })
  74. }
  75. onInputDown(position) {
  76. const offsetPosition = position.addition(this.position)
  77. this.tiles.forEach((tile) => {
  78. tile.onInputDown(offsetPosition)
  79. })
  80. }
  81. onInputUp(position) {
  82. const offsetPosition = position.addition(this.position)
  83. this.tiles.forEach((tile) => {
  84. tile.onInputUp(offsetPosition)
  85. })
  86. }
  87. selectTile(tile) {
  88. if (this.selectedTiles.includes(tile) || this.selectedTiles.length == 2) {
  89. tile.isSelected = false
  90. return
  91. }
  92. this.selectedTiles.push(tile)
  93. tile.isSelected = true
  94. if (this.selectedTiles.length == 2) {
  95. this.actionQueue.push(new SwapTileAction(this.selectedTiles[0], this.selectedTiles[1]))
  96. this.actionQueue.execute(SwapTileAction, () => this.validToSwap(this.selectedTiles[0], this.selectedTiles[1])).then(() => {
  97. this.tiles.forEach((tile) => tile.isSelected = false)
  98. this.selectedTiles = []
  99. this.checkForMatches()
  100. }, () => {
  101. this.selectedTiles.forEach((tile) => tile.isSelected = false)
  102. this.selectedTiles.length = 0
  103. })
  104. }
  105. }
  106. validToSwap(firstTile, otherTile) {
  107. if (firstTile.position.x + 1 == otherTile.position.x && firstTile.position.y == otherTile.position.y) {
  108. return true
  109. }
  110. if (firstTile.position.x - 1 == otherTile.position.x && firstTile.position.y == otherTile.position.y) {
  111. return true
  112. }
  113. if (firstTile.position.x == otherTile.position.x && firstTile.position.y + 1 == otherTile.position.y) {
  114. return true
  115. }
  116. if (firstTile.position.x == otherTile.position.x && firstTile.position.y - 1 == otherTile.position.y) {
  117. return true
  118. }
  119. return false
  120. }
  121. deselectTile(tileToDeselect) {
  122. tileToDeselect.isSelected = false
  123. this.selectedTiles = this.selectedTiles.filter((tile) => tile != tileToDeselect)
  124. }
  125. checkForMatches() {
  126. const tilesToPop = []
  127. //five in a row horizontal
  128. //five in a row vertical
  129. //four in a row horizontal
  130. //four in a row vertical
  131. //5 in an L shape
  132. //three in a row horizontal
  133. this.tiles.forEach((tile, index, array) => {
  134. if (tilesToPop.includes(tile)) {
  135. return
  136. }
  137. const validTiles = this.tiles.filter((checkedTile) => tile != checkedTile && tile.type == checkedTile.type && checkedTile.position.y == tile.position.y && (checkedTile.position.x == tile.position.x + 1 || checkedTile.position.x == tile.position.x - 1))
  138. if (validTiles.length == 2) {
  139. tilesToPop.push(tile, ...validTiles)
  140. this.actionQueue.push(new MatchTilesAction([tile, ...validTiles]))
  141. return
  142. }
  143. })
  144. //three in a row vertical
  145. this.tiles.forEach((tile, index, array) => {
  146. if (tilesToPop.includes(tile)) {
  147. return
  148. }
  149. const validTiles = this.tiles.filter((checkedTile) => tile != checkedTile && tile.type == checkedTile.type && checkedTile.position.x == tile.position.x && (checkedTile.position.y == tile.position.y + 1 || checkedTile.position.y == tile.position.y - 1))
  150. if (validTiles.length == 2) {
  151. tilesToPop.push(tile, ...validTiles)
  152. this.actionQueue.push(new MatchTilesAction([tile, ...validTiles]))
  153. return
  154. }
  155. })
  156. tilesToPop.forEach((tile) => {
  157. tile.isSelected = true
  158. })
  159. if (tilesToPop.length == 0) {
  160. return
  161. }
  162. this.actionQueue.execute(MatchTilesAction).then(() => {
  163. this.tiles = this.tiles.filter((tile) => !tilesToPop.includes(tile))
  164. this.spawnAndCascade()
  165. })
  166. }
  167. spawnAndCascade() {
  168. for (let x = 0; x < this.boardSize.x; x++) {
  169. const columnTiles = this.tiles.filter((tile) => tile.position.x == x)
  170. if (columnTiles.length == this.boardSize.y) {
  171. continue
  172. }
  173. if (columnTiles.length != this.boardSize.y) {
  174. this.actionQueue.push(new SpawnTileAction(x, this.boardSize.y - columnTiles.length))
  175. }
  176. }
  177. this.collapse()
  178. this.actionQueue.execute(SpawnTileAction, (x, y) => this.spawnTile(x, y)).then(() => {
  179. this.collapse().then(() => {
  180. this.checkForMatches()
  181. })
  182. })
  183. }
  184. collapse() {
  185. for (let x = 0; x < this.boardSize.x; x++) {
  186. const columnTiles = this.tiles.filter((tile) => tile.position.x == x).sort((tileA, tileB) => tileA.position.y < tileB.position.y)
  187. let bottom = this.boardSize.y
  188. columnTiles.forEach((tile, index) => {
  189. if (tile.position.y < bottom) {
  190. bottom -= 1
  191. this.actionQueue.push(new TileFallAction(tile, bottom))
  192. }
  193. })
  194. }
  195. return this.actionQueue.execute(TileFallAction)
  196. }
  197. }
  198. Board.TILE_PADDING = 4
  199. Board.TILE_SIZE = 64
  200. export default Board