2048test.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. import {STextAlign} from "@persagy-web/draw/lib";
  2. <template>
  3. <div>
  4. <canvas id="canvas" width="410" height="410" style="background: #bbada0;border-radius: 10px" tabindex="0"></canvas>
  5. <el-button @click="reset" type="primary">重新开始</el-button>
  6. </div>
  7. </template>
  8. <script lang="ts">
  9. import {Component, Vue} from "vue-property-decorator"
  10. import {SCanvasView, SColor, SPainter, STextAlign, STextBaseLine} from "@persagy-web/draw/lib";
  11. /**
  12. * 2048
  13. *
  14. * @author 郝洁 <haojie@persagy.com>
  15. */
  16. class GameView extends SCanvasView {
  17. /** 格子宽度 */
  18. box_width: number = this.width * 0.8 * 0.25;
  19. margin_width: number = this.width * 0.2 * 0.20;
  20. /** */
  21. gridMap: number[][] = [];
  22. /** */
  23. score: number = 0;
  24. /** 游戏结束 */
  25. isGameOver: boolean = false;
  26. /** 颜色 */
  27. color = {
  28. 0:{
  29. font: "#ffffffff",
  30. background: "#eee4da59"
  31. },
  32. 2: {
  33. font: "#776e65",
  34. background: "#eee4da"
  35. },
  36. 4: {
  37. font: "#776e65",
  38. background: "#ede0c8"
  39. },
  40. 8: {
  41. font: "#f9f6f2",
  42. background: "#f2b179"
  43. },
  44. 16: {
  45. font: "#f9f6f2",
  46. background: "#f59563"
  47. },
  48. 32: {
  49. font: "#f9f6f2",
  50. background: "#f67c5f"
  51. },
  52. 64: {
  53. font: "#f9f6f2",
  54. background: "#f65e3b"
  55. },
  56. 128: {
  57. font: "#f9f6f2",
  58. background: "#edcf72"
  59. },
  60. 256: {
  61. font: "#f9f6f2",
  62. background: "#edcc61"
  63. },
  64. 512: {
  65. font: "#f9f6f2",
  66. background: "#edc850"
  67. },
  68. 1024: {
  69. font: "#f9f6f2",
  70. background: "#edc53f"
  71. },
  72. 2048: {
  73. font: "#f9f6f2",
  74. background: "#edc22e"
  75. },
  76. };
  77. /**
  78. * 构造函数
  79. *
  80. * @param id canvas DOM id
  81. */
  82. constructor(id: string) {
  83. super(id);
  84. this.init()
  85. }
  86. /**
  87. * 初始化游戏
  88. */
  89. init(): void {
  90. this.gridMap = [];
  91. for (let row = 0; row < 4; row++) { // 循环行数
  92. const m1: number[] = [];
  93. for (let col = 0; col < 4; col++) { // 循环列数
  94. m1.push(0);
  95. }
  96. this.gridMap.push(m1);
  97. }
  98. // 初始化分数
  99. this.score = 0;
  100. // 游戏结束标记为 false
  101. this.isGameOver = false;
  102. // 生成随机位置的数字
  103. this.generateOneNumber('left');
  104. }
  105. /**
  106. * 键盘按下事件
  107. *
  108. * @param event 事件对象
  109. */
  110. onKeyDown(event: KeyboardEvent): void {
  111. if (this.isGameOver) {
  112. return;
  113. }
  114. let moved, merged;
  115. // 按键判断
  116. switch (event.code) {
  117. case "KeyW": // 上移动作
  118. case "ArrowUp":
  119. moved = this.moveToFill('top');
  120. merged = this.mergeGrid('top');
  121. (moved || merged) && this.generateOneNumber('top');
  122. break;
  123. case "KeyA": // 左移动作
  124. case "ArrowLeft":
  125. moved = this.moveToFill('left');
  126. merged = this.mergeGrid('left');
  127. (moved || merged) && this.generateOneNumber('left');
  128. // this.update()
  129. break;
  130. case "KeyD": // 右移动作
  131. case "ArrowRight":
  132. // 判断游戏是否继续,左部产生随机位置随机数
  133. moved = this.moveToFill('right');
  134. merged = this.mergeGrid('right');
  135. (moved || merged) && this.generateOneNumber('right');
  136. break;
  137. // 按键 下
  138. case "KeyS": // 下移动作
  139. case "ArrowDown":
  140. // 判断游戏是否继续,上部产生随机位置随机数
  141. moved = this.moveToFill('bottom');
  142. merged = this.mergeGrid('bottom');
  143. (moved || merged) && this.generateOneNumber('bottom');
  144. break;
  145. default :
  146. // do nothing
  147. }
  148. this.isGameOver = this.checkIsOver();
  149. }
  150. /**
  151. * 生成随机的格子
  152. */
  153. generateOneNumber(direction: string): void {
  154. let temp = [];
  155. if (direction == "left") {
  156. for (let i = 0; i < 4; i++) {
  157. temp.push(this.gridMap[i][3])
  158. }
  159. } else if (direction == "top") {
  160. temp = this.gridMap[3];
  161. } else if (direction == "bottom") {
  162. temp = this.gridMap[0];
  163. } else {
  164. // (direction == "right")
  165. for (let i = 0; i < 4; i++) {
  166. temp.push(this.gridMap[i][0])
  167. }
  168. }
  169. let indexArr = [];
  170. for(let i = 0; i < temp.length; i++) {
  171. if(temp[i] == 0) {
  172. indexArr.push(i)
  173. }
  174. }
  175. const len = indexArr.length;
  176. if (len) {
  177. const random = Math.floor(len * Math.random());
  178. const index = indexArr[random];
  179. if (direction == "left") {
  180. this.gridMap[index][3] = 2
  181. } else if (direction == "top") {
  182. this.gridMap[3][index] = 2;
  183. } else if (direction == "bottom") {
  184. this.gridMap[0][index] = 2;
  185. } else {
  186. // (direction == "right")
  187. this.gridMap[index][0] = 2
  188. }
  189. this.update()
  190. } else {
  191. // this.isGameOver = true;
  192. this.update();
  193. }
  194. //随机一个位置,取新方块出现的位置
  195. //随机一个数字 2 或 4 ,在随机位置显示随机数字
  196. }
  197. /**
  198. * 移动数字,使移动方向的数字填满
  199. */
  200. moveToFill(direction: string): boolean{
  201. let flag = false;
  202. if (direction == "left") {
  203. for (let i = 0; i < 4; i++) {
  204. const tempJrr = new Array(4).fill(0);
  205. let index = 0;
  206. for (let j = 0; j < 4; j++) {
  207. if (this.gridMap[i][j]!=0) {
  208. tempJrr[index] = this.gridMap[i][j];
  209. index++;
  210. } else {
  211. flag = true;
  212. }
  213. }
  214. this.gridMap[i] = tempJrr;
  215. }
  216. } else if (direction == "top") {
  217. for (let j = 0; j < 4; j++) {
  218. const tempJrr = new Array(4).fill(0);
  219. let index = 0;
  220. for (let i = 0; i < 4; i++) {
  221. if (this.gridMap[i][j]!=0) {
  222. tempJrr[index] = this.gridMap[i][j];
  223. index++;
  224. } else {
  225. flag = true;
  226. }
  227. }
  228. for(let u = 0; u < 4; u++) {
  229. this.gridMap[u][j] = tempJrr[u];
  230. }
  231. }
  232. } else if (direction == "bottom") {
  233. for (let j = 0; j < 4; j++) {
  234. const tempJrr = new Array(4).fill(0);
  235. let index = 3;
  236. for (let i = 3; i >= 0; i--) {
  237. if (this.gridMap[i][j]!=0) {
  238. tempJrr[index] = this.gridMap[i][j];
  239. index--;
  240. } else {
  241. flag = true;
  242. }
  243. }
  244. for(let u = 3; u >= 0; u--) {
  245. this.gridMap[u][j] = tempJrr[u];
  246. }
  247. }
  248. } else {
  249. for (let i = 0; i < 4; i++) {
  250. const tempJrr = new Array(4).fill(0);
  251. let index = 3;
  252. for (let j = 3; j >= 0; j--) {
  253. if (this.gridMap[i][j]!=0) {
  254. tempJrr[index] = this.gridMap[i][j];
  255. index--;
  256. } else {
  257. flag = true;
  258. }
  259. }
  260. this.gridMap[i] = tempJrr;
  261. }
  262. }
  263. return flag;
  264. }
  265. /**
  266. * 合并数字
  267. *
  268. * @param direction 方向
  269. */
  270. mergeGrid(direction: string): boolean{
  271. let flag = false;
  272. if (direction == "left") {
  273. for (let i = 0; i < 4; i++) {
  274. for (let j = 0; j < 3; j++) {
  275. if (this.gridMap[i][j] == this.gridMap[i][j + 1]) {
  276. this.gridMap[i][j] = 2*this.gridMap[i][j];
  277. this.gridMap[i][j + 1] = 0;
  278. flag = true;
  279. }
  280. }
  281. }
  282. } else if (direction == "top") {
  283. for (let i = 0; i < 3; i++) {
  284. for (let j = 0; j < 4; j++) {
  285. if (this.gridMap[i][j] == this.gridMap[i + 1][j]) {
  286. this.gridMap[i][j] = 2*this.gridMap[i][j];
  287. this.gridMap[i + 1][j] = 0;
  288. flag = true;
  289. }
  290. }
  291. }
  292. } else if (direction == "bottom") {
  293. for (let i = 3; i >= 1; i--) {
  294. for (let j = 0; j < 4; j++) {
  295. if (this.gridMap[i][j] == this.gridMap[i - 1][j]) {
  296. this.gridMap[i][j] = 2*this.gridMap[i][j];
  297. this.gridMap[i - 1][j] = 0;
  298. flag = true;
  299. }
  300. }
  301. }
  302. } else {
  303. // (direction == "right")
  304. for (let i = 0; i < 4; i++) {
  305. for (let j = 3; j >= 1; j--) {
  306. if (this.gridMap[i][j] == this.gridMap[i][j - 1]) {
  307. this.gridMap[i][j] = 2*this.gridMap[i][j];
  308. this.gridMap[i][j - 1] = 0;
  309. flag = true;
  310. }
  311. }
  312. }
  313. }
  314. if (flag) {
  315. this.moveToFill(direction)
  316. }
  317. return flag;
  318. }
  319. /**
  320. * 绘制游戏画面
  321. *
  322. * @param painter 绘制对象
  323. */
  324. onDraw(painter: SPainter): void {
  325. painter.clearRect(0, 0, this.width, this.height);
  326. painter.pen.color = SColor.Transparent;
  327. this.drawMap(painter);
  328. this.drawNum(painter);
  329. // 如果游戏结束,绘制文字
  330. if (this.isGameOver) {
  331. painter.brush.color = SColor.Blue;
  332. painter.font.size = 20;
  333. painter.font.textAlign = STextAlign.Center;
  334. painter.drawText("GAME OVER", 180, 30);
  335. // return;
  336. }
  337. }
  338. /**
  339. * 绘制数字
  340. */
  341. drawNum(painter: SPainter): void {
  342. // 遍历行
  343. for (let row = 0; row < 4; row++) {
  344. // 遍历列
  345. for (let col = 0; col < 4; col++) {
  346. this.drawCellNum(painter, row, col, this.gridMap[row][col]);
  347. }
  348. }
  349. }
  350. /**
  351. * 绘制格子中的数字
  352. */
  353. drawCellNum(painter: SPainter, row: number, col: number, type: number): void {
  354. const x = col * 100 + 10;
  355. const y = row * 100 + 10;
  356. if (type != 0) {
  357. painter.font.textAlign = STextAlign.Center;
  358. painter.font.size = 40;
  359. // @ts-ignore
  360. painter.font.textBaseLine = STextBaseLine.Middle;
  361. painter.brush.color = new SColor(this.color[type].font);
  362. painter.drawText(type+'', x+45, y+45);
  363. }
  364. }
  365. /**
  366. * 绘制背景图
  367. */
  368. drawMap(painter: SPainter) {
  369. // 遍历行
  370. for (let row = 0; row < 4; row++) {
  371. // 遍历列
  372. for (let col = 0; col < 4; col++) {
  373. this.drawCell(painter, row, col, this.gridMap[row][col]);
  374. }
  375. }
  376. }
  377. /**
  378. * 绘制具体格子
  379. *
  380. * @param painter 绘制对象
  381. * @param row 行
  382. * @param col 列
  383. * @param type 图类型
  384. */
  385. private drawCell(painter: SPainter, row: number, col: number, type: number): void {
  386. const x = col * 100 + 10;
  387. const y = row * 100 + 10;
  388. painter.brush.color = new SColor(this.color[type].background);
  389. painter.drawRoundRect(x, y, 90, 90, 10);
  390. }
  391. /**
  392. * 检测结束
  393. */
  394. checkIsOver():boolean{
  395. for(let i = 0; i < 4; i++) {
  396. if (this.gridMap[i].indexOf(0) > -1) {
  397. return false;
  398. }
  399. for(let j = 0; j < 3; j++) {
  400. if (this.gridMap[i][j] == this.gridMap[i][j+1]) {
  401. return false
  402. }
  403. if (i < 3 && this.gridMap[i][j] == this.gridMap[i+1][j]){
  404. return false
  405. }
  406. }
  407. }
  408. return this.gridMap[3][3] != this.gridMap[2][3];
  409. }
  410. }
  411. @Component
  412. export default class Gcanvas extends Vue {
  413. /** 实例化 */
  414. view: GameView | null = null;
  415. /**
  416. * 页面挂载完成
  417. */
  418. mounted(): void {
  419. this.view = new GameView('canvas');
  420. document.getElementById('canvas')!!.focus()
  421. }
  422. /**
  423. * 重置操作
  424. */
  425. reset(): void {
  426. this.view!!.init()
  427. document.getElementById("canvas")!!.focus()
  428. }
  429. }
  430. </script>
  431. <style scoped>
  432. </style>