GameState.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. import { Point } from "../../libraries/spatial/Point.js";
  2. import { Rectangle } from "../../libraries/spatial/Rectangle.js";
  3. import { Board } from "../Board.js";
  4. import {Slot} from "../Slot.js";
  5. export class GameState {
  6. constructor(fsm) {
  7. this.stateMachine = fsm;
  8. this.board = null;
  9. this.playerData = {current: 1, level: 1};
  10. this.canvasBounds = null;
  11. this.showNextButton = false;
  12. this.showUndoButton = false;
  13. this.movelist = [];
  14. this.boundMouseEvent = {};
  15. this.levelData = [];
  16. }
  17. init() {
  18. this.board = new Board();
  19. this.board.init(this);
  20. this.boundMouseEvent['mousedown'] = this.mouseDown.bind(this);
  21. this.boundMouseEvent['mousemove'] = this.mouseMove.bind(this);
  22. for(let key in this.boundMouseEvent) {
  23. window.addEventListener(key, this.boundMouseEvent[key]);
  24. }
  25. //TODO: replace next button
  26. //TODO: replace undo button
  27. }
  28. setLevelData(data) {
  29. this.levelData = data;
  30. }
  31. getLevelData() {
  32. return this.levelData;
  33. }
  34. setPlayerData(data) {
  35. this.playerData = data;
  36. }
  37. getPlayerData() {
  38. return this.playerData;
  39. }
  40. getLevel(levelId) {
  41. return this.getLevelData()[levelId];
  42. }
  43. draw(ctx, scaledCanvas) {
  44. this.canvasBounds = scaledCanvas.bounds;
  45. try {
  46. ctx.fillStyle = "white";
  47. ctx.strokeStyle = "black";
  48. ctx.font = "48px Luckiest Guy";
  49. ctx.textAlign = "center";
  50. ctx.lineWidth = 4;
  51. ctx.fillText(`Level ${this.playerData.current}`, scaledCanvas.center.x, 48);
  52. ctx.strokeText(`Level ${this.playerData.current}`, scaledCanvas.center.x, 48);
  53. if(this.showNextButton) {
  54. this.drawNextButton(ctx, scaledCanvas);
  55. }
  56. if(this.showUndoButton) {
  57. this.drawUndoButton(ctx, scaledCanvas);
  58. }
  59. this.board.draw(ctx, scaledCanvas);
  60. } catch(e) {
  61. ctx.setTransform(1, 0, 0, 1, 0, 0);
  62. ctx.fillStyle = 'crimson';
  63. ctx.font = "32px Arial";
  64. ctx.fontAlign = "left";
  65. ctx.fillText(`error: ${e.message}`, 4, 32);
  66. console.error(e);
  67. }
  68. if(this.showWinDialog) {
  69. this.drawWinDialog(ctx, scaledCanvas);
  70. }
  71. }
  72. update(delta) {
  73. this.board.update(delta);
  74. }
  75. drawNextButton(ctx, scaledCanvas) {
  76. let bounds = this.getNextButtonBounds();
  77. ctx.fillStyle = "rgba(255,255,255,0.5)";
  78. ctx.save();
  79. ctx.translate(bounds.x, bounds.y);
  80. ctx.beginPath();
  81. ctx.moveTo(bounds.width, bounds.height / 2);
  82. ctx.lineTo(bounds.width / 2, bounds.height);
  83. ctx.lineTo(bounds.width / 2, 0);
  84. ctx.closePath();
  85. ctx.fill();
  86. ctx.fillStyle = "white";
  87. ctx.font = "24px Arial";
  88. ctx.textAlign = "left";
  89. ctx.fillText(`next`, 0, bounds.height/2 + 8);
  90. ctx.restore();
  91. }
  92. drawUndoButton(ctx, scaledCanvas) {
  93. let bounds = this.getUndoButtonBounds();
  94. ctx.fillStyle = "rgba(255,255,255,0.5)";
  95. ctx.save();
  96. ctx.translate(bounds.x, bounds.y);
  97. ctx.beginPath();
  98. ctx.rect(0, 0, bounds.width, bounds.height);
  99. ctx.closePath();
  100. ctx.fill();
  101. ctx.fillStyle = "white";
  102. ctx.font = "24px Arial";
  103. ctx.textAlign = "center";
  104. let undoText = this.movelist.length < 9 ? `undo (${this.movelist.length})` : `undo`;
  105. ctx.fillText(undoText, bounds.width / 2, bounds.height - 20);
  106. ctx.restore();
  107. }
  108. drawWinDialog(ctx, scaledCanvas) {
  109. let bounds = scaledCanvas.bounds;
  110. ctx.fillStyle = "rgba(255, 255, 255, 0.5)";
  111. ctx.beginPath();
  112. ctx.rect(bounds.width / 4, bounds.height / 4 + bounds.height / 16, bounds.width / 2, bounds.height / 8);
  113. ctx.closePath();
  114. ctx.fill();
  115. ctx.fillStyle = "white";
  116. ctx.strokeStyle = "black";
  117. ctx.font = "48px Luckiest Guy";
  118. ctx.textAlign = "center";
  119. ctx.lineWidth = 4;
  120. ctx.fillText(`You Win!`, bounds.center.x, bounds.height / 2 - bounds.height / 8 + 12);
  121. ctx.strokeText(`You Win!`, bounds.center.x, bounds.height / 2 - bounds.height / 8 + 12);
  122. }
  123. mouseDown(event) {
  124. let mousePoint = new Point(event.clientX, event.clientY);
  125. let nextBounds = this.getNextButtonBounds();
  126. if(this.showNextButton && nextBounds.pointWithin(mousePoint)) {
  127. if(this.playerData.level == this.playerData.current) {
  128. this.playerData.level++;
  129. }
  130. this.playerData.current++;
  131. localStorage.setItem('ballsort-playerdata', JSON.stringify(this.playerData));
  132. this.board.reset();
  133. this.showNextButton = false;
  134. this.showWinDialog = false;
  135. }
  136. if(this.showUndoButton) {
  137. let undoBounds = this.getUndoButtonBounds();
  138. if(undoBounds.pointWithin(mousePoint)) {
  139. this.undo();
  140. }
  141. }
  142. if(!this.showWinDialog) {
  143. this.board.mouseDown(event);
  144. }
  145. }
  146. mouseMove(event) {
  147. document.body.style.cursor = "default";
  148. let mousePoint = new Point(event.clientX, event.clientY);
  149. if(this.showNextButton) {
  150. let nextBounds = this.getNextButtonBounds();
  151. if(nextBounds.pointWithin(mousePoint)) {
  152. document.body.style.cursor = "pointer";
  153. }
  154. }
  155. if(this.showUndoButton) {
  156. let undoBounds = this.getUndoButtonBounds();
  157. if(undoBounds.pointWithin(mousePoint)) {
  158. document.body.style.cursor = "pointer";
  159. }
  160. }
  161. if(!this.showWinDialog) {
  162. this.board.mouseMove(event);
  163. }
  164. }
  165. trackMove(fromSlot, toSlot) {
  166. let move = {from: fromSlot, to: toSlot};
  167. this.movelist.push(move);
  168. this.showUndoButton = this.movelist.length > 0;
  169. }
  170. undo() {
  171. if(this.board.heldBall) {
  172. this.board.placeHeldBallInSlot(this.board.removedFromSlot);
  173. return;
  174. }
  175. let move = this.movelist.pop();
  176. if(move) {
  177. let ball = move.to.removeBall();
  178. move.from.addBall(ball);
  179. }
  180. this.showUndoButton = this.movelist.length > 0;
  181. }
  182. getNextButtonBounds() {
  183. let boardBounds = this.board.getBounds();
  184. let position = {
  185. x: this.canvasBounds.center.x,
  186. y: boardBounds.y + boardBounds.height + 20,
  187. }
  188. if(this.canvasBounds.height < 660) {
  189. position.x = this.canvasBounds.width - 100;
  190. position.y = Slot.height + (Slot.heightPadding / 2) + boardBounds.y - 25;
  191. }
  192. return new Rectangle(position.x, position.y, 100, 50);
  193. }
  194. getUndoButtonBounds() {
  195. let boardBounds = this.board.getBounds();
  196. let position = {
  197. x: this.canvasBounds.center.x,
  198. y: boardBounds.y + boardBounds.height + 20,
  199. }
  200. if(this.canvasBounds.height < 660) {
  201. position.x = this.canvasBounds.width - 100;
  202. position.y = Slot.height + (Slot.heightPadding / 2) + boardBounds.y - 25;
  203. }
  204. return new Rectangle(position.x, position.y, 100, 50);
  205. }
  206. win() {
  207. setTimeout(() => {
  208. this.showWinDialog = true;
  209. this.showNextButton = true;
  210. this.showUndoButton = false;
  211. this.movelist = [];
  212. }, 400);
  213. }
  214. enter() {
  215. this.init(this.stateMachine.getState("game"));
  216. }
  217. leave() {
  218. this.board.destroy();
  219. for(let key in this.boundMouseEvent) {
  220. window.removeEventListener(key, this.boundMouseEvent[key]);
  221. }
  222. }
  223. }