Board.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  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 = (canvasBounds.width / 2) - (10 * (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. this.tiles.forEach((tile, index, array) => {
  129. if (tilesToPop.includes(tile)) {
  130. return
  131. }
  132. const validTiles = this.tiles.filter((checkedTile) => !tilesToPop.includes(checkedTile) && tile.type == checkedTile.type && checkedTile.position.y == tile.position.y && (checkedTile.position.x == tile.position.x + 2 || checkedTile.position.x == tile.position.x + 1 || checkedTile.position.x == tile.position.x - 1 || checkedTile.position.x == tile.position.x - 2))
  133. if (validTiles.length == 4) {
  134. tilesToPop.push(tile, ...validTiles)
  135. this.actionQueue.push(new MatchTilesAction([tile, ...validTiles]))
  136. return
  137. }
  138. })
  139. //five in a row vertical
  140. this.tiles.forEach((tile, index, array) => {
  141. if (tilesToPop.includes(tile)) {
  142. return
  143. }
  144. const validTiles = this.tiles.filter((checkedTile) => !tilesToPop.includes(checkedTile) && tile.type == checkedTile.type && checkedTile.position.x == tile.position.x && (checkedTile.position.y == tile.position.y + 2 || checkedTile.position.y == tile.position.y + 1 || checkedTile.position.y == tile.position.y - 1 || checkedTile.position.y == tile.position.y - 2))
  145. if (validTiles.length == 4) {
  146. tilesToPop.push(tile, ...validTiles)
  147. this.actionQueue.push(new MatchTilesAction([tile, ...validTiles]))
  148. return
  149. }
  150. })
  151. //four in a row horizontal
  152. this.tiles.forEach((tile, index, array) => {
  153. if (tilesToPop.includes(tile)) {
  154. return
  155. }
  156. const validTiles = this.tiles.filter((checkedTile) => !tilesToPop.includes(checkedTile) && tile.type == checkedTile.type && checkedTile.position.y == tile.position.y && (checkedTile.position.x == tile.position.x + 2 || checkedTile.position.x == tile.position.x + 1 || checkedTile.position.x == tile.position.x - 1))
  157. if (validTiles.length == 3) {
  158. tilesToPop.push(tile, ...validTiles)
  159. this.actionQueue.push(new MatchTilesAction([tile, ...validTiles]))
  160. return
  161. }
  162. })
  163. //four in a row vertical
  164. this.tiles.forEach((tile, index, array) => {
  165. if (tilesToPop.includes(tile)) {
  166. return
  167. }
  168. const validTiles = this.tiles.filter((checkedTile) => !tilesToPop.includes(checkedTile) && tile.type == checkedTile.type && checkedTile.position.x == tile.position.x && (checkedTile.position.y == tile.position.y + 2 || checkedTile.position.y == tile.position.y + 1 || checkedTile.position.y == tile.position.y - 1))
  169. if (validTiles.length == 3) {
  170. tilesToPop.push(tile, ...validTiles)
  171. this.actionQueue.push(new MatchTilesAction([tile, ...validTiles]))
  172. return
  173. }
  174. })
  175. //5 in an L shape
  176. //TODO ugh this one is hard
  177. //three in a row horizontal
  178. this.tiles.forEach((tile, index, array) => {
  179. if (tilesToPop.includes(tile)) {
  180. return
  181. }
  182. const validTiles = this.tiles.filter((checkedTile) => !tilesToPop.includes(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))
  183. if (validTiles.length == 2) {
  184. tilesToPop.push(tile, ...validTiles)
  185. this.actionQueue.push(new MatchTilesAction([tile, ...validTiles]))
  186. return
  187. }
  188. })
  189. //three in a row vertical
  190. this.tiles.forEach((tile, index, array) => {
  191. if (tilesToPop.includes(tile)) {
  192. return
  193. }
  194. const validTiles = this.tiles.filter((checkedTile) => !tilesToPop.includes(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))
  195. if (validTiles.length == 2) {
  196. tilesToPop.push(tile, ...validTiles)
  197. this.actionQueue.push(new MatchTilesAction([tile, ...validTiles]))
  198. return
  199. }
  200. })
  201. tilesToPop.forEach((tile) => {
  202. tile.isSelected = true
  203. })
  204. if (tilesToPop.length == 0) {
  205. return
  206. }
  207. this.actionQueue.execute(MatchTilesAction).then(() => {
  208. this.tiles = this.tiles.filter((tile) => !tilesToPop.includes(tile))
  209. this.spawnAndCascade()
  210. })
  211. }
  212. spawnAndCascade() {
  213. for (let x = 0; x < this.boardSize.x; x++) {
  214. const columnTiles = this.tiles.filter((tile) => tile.position.x == x)
  215. if (columnTiles.length == this.boardSize.y) {
  216. continue
  217. }
  218. if (columnTiles.length != this.boardSize.y) {
  219. this.actionQueue.push(new SpawnTileAction(x, this.boardSize.y - columnTiles.length))
  220. }
  221. }
  222. this.collapse()
  223. this.actionQueue.execute(SpawnTileAction, (x, y) => this.spawnTile(x, y)).then(() => {
  224. this.collapse().then(() => {
  225. this.checkForMatches()
  226. })
  227. })
  228. }
  229. collapse() {
  230. for (let x = 0; x < this.boardSize.x; x++) {
  231. const columnTiles = this.tiles.filter((tile) => tile.position.x == x).sort((tileA, tileB) => tileA.position.y < tileB.position.y)
  232. let bottom = this.boardSize.y
  233. columnTiles.forEach((tile, index) => {
  234. if (tile.position.y < bottom) {
  235. bottom -= 1
  236. this.actionQueue.push(new TileFallAction(tile, bottom))
  237. }
  238. })
  239. }
  240. return this.actionQueue.execute(TileFallAction)
  241. }
  242. }
  243. Board.TILE_PADDING = 4
  244. Board.TILE_SIZE = 64
  245. export default Board