|
@@ -0,0 +1,462 @@
|
|
|
|
+import {STextAlign} from "@persagy-web/draw/lib";
|
|
|
|
+<template>
|
|
|
|
+ <div>
|
|
|
|
+ <canvas id="canvas" width="410" height="410" style="background: #bbada0;border-radius: 10px" tabindex="0"></canvas>
|
|
|
|
+ <el-button @click="reset" type="primary">重新开始</el-button>
|
|
|
|
+ </div>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<script lang="ts">
|
|
|
|
+ import {Component, Vue} from "vue-property-decorator"
|
|
|
|
+ import {SCanvasView, SColor, SPainter, STextAlign, STextBaseLine} from "@persagy-web/draw/lib";
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 2048
|
|
|
|
+ *
|
|
|
|
+ * @author 郝洁 <haojie@persagy.com>
|
|
|
|
+ */
|
|
|
|
+ class GameView extends SCanvasView {
|
|
|
|
+ /** 格子宽度 */
|
|
|
|
+ box_width: number = this.width * 0.8 * 0.25;
|
|
|
|
+ margin_width: number = this.width * 0.2 * 0.20;
|
|
|
|
+ /** */
|
|
|
|
+ gridMap: number[][] = [];
|
|
|
|
+ /** */
|
|
|
|
+ score: number = 0;
|
|
|
|
+ /** 游戏结束 */
|
|
|
|
+ isGameOver: boolean = false;
|
|
|
|
+ /** 颜色 */
|
|
|
|
+ color = {
|
|
|
|
+ 0:{
|
|
|
|
+ font: "#ffffffff",
|
|
|
|
+ background: "#eee4da59"
|
|
|
|
+ },
|
|
|
|
+ 2: {
|
|
|
|
+ font: "#776e65",
|
|
|
|
+ background: "#eee4da"
|
|
|
|
+ },
|
|
|
|
+ 4: {
|
|
|
|
+ font: "#776e65",
|
|
|
|
+ background: "#ede0c8"
|
|
|
|
+ },
|
|
|
|
+ 8: {
|
|
|
|
+ font: "#f9f6f2",
|
|
|
|
+ background: "#f2b179"
|
|
|
|
+ },
|
|
|
|
+ 16: {
|
|
|
|
+ font: "#f9f6f2",
|
|
|
|
+ background: "#f59563"
|
|
|
|
+ },
|
|
|
|
+ 32: {
|
|
|
|
+ font: "#f9f6f2",
|
|
|
|
+ background: "#f67c5f"
|
|
|
|
+ },
|
|
|
|
+ 64: {
|
|
|
|
+ font: "#f9f6f2",
|
|
|
|
+ background: "#f65e3b"
|
|
|
|
+ },
|
|
|
|
+ 128: {
|
|
|
|
+ font: "#f9f6f2",
|
|
|
|
+ background: "#edcf72"
|
|
|
|
+ },
|
|
|
|
+ 256: {
|
|
|
|
+ font: "#f9f6f2",
|
|
|
|
+ background: "#edcc61"
|
|
|
|
+ },
|
|
|
|
+ 512: {
|
|
|
|
+ font: "#f9f6f2",
|
|
|
|
+ background: "#edc850"
|
|
|
|
+ },
|
|
|
|
+ 1024: {
|
|
|
|
+ font: "#f9f6f2",
|
|
|
|
+ background: "#edc53f"
|
|
|
|
+ },
|
|
|
|
+ 2048: {
|
|
|
|
+ font: "#f9f6f2",
|
|
|
|
+ background: "#edc22e"
|
|
|
|
+ },
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 构造函数
|
|
|
|
+ *
|
|
|
|
+ * @param id canvas DOM id
|
|
|
|
+ */
|
|
|
|
+ constructor(id: string) {
|
|
|
|
+ super(id);
|
|
|
|
+ this.init()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 初始化游戏
|
|
|
|
+ */
|
|
|
|
+ init(): void {
|
|
|
|
+ this.gridMap = [];
|
|
|
|
+ for (let row = 0; row < 4; row++) { // 循环行数
|
|
|
|
+ const m1: number[] = [];
|
|
|
|
+ for (let col = 0; col < 4; col++) { // 循环列数
|
|
|
|
+ m1.push(0);
|
|
|
|
+ }
|
|
|
|
+ this.gridMap.push(m1);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 初始化分数
|
|
|
|
+ this.score = 0;
|
|
|
|
+ // 游戏结束标记为 false
|
|
|
|
+ this.isGameOver = false;
|
|
|
|
+ // 生成随机位置的数字
|
|
|
|
+ this.generateOneNumber('left');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 键盘按下事件
|
|
|
|
+ *
|
|
|
|
+ * @param event 事件对象
|
|
|
|
+ */
|
|
|
|
+ onKeyDown(event: KeyboardEvent): void {
|
|
|
|
+ if (this.isGameOver) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ let moved, merged;
|
|
|
|
+ // 按键判断
|
|
|
|
+ switch (event.code) {
|
|
|
|
+ case "KeyW": // 上移动作
|
|
|
|
+ case "ArrowUp":
|
|
|
|
+ moved = this.moveToFill('top');
|
|
|
|
+ merged = this.mergeGrid('top');
|
|
|
|
+ (moved || merged) && this.generateOneNumber('top');
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case "KeyA": // 左移动作
|
|
|
|
+ case "ArrowLeft":
|
|
|
|
+ moved = this.moveToFill('left');
|
|
|
|
+ merged = this.mergeGrid('left');
|
|
|
|
+ (moved || merged) && this.generateOneNumber('left');
|
|
|
|
+ // this.update()
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case "KeyD": // 右移动作
|
|
|
|
+ case "ArrowRight":
|
|
|
|
+ // 判断游戏是否继续,左部产生随机位置随机数
|
|
|
|
+ moved = this.moveToFill('right');
|
|
|
|
+ merged = this.mergeGrid('right');
|
|
|
|
+ (moved || merged) && this.generateOneNumber('right');
|
|
|
|
+ break;
|
|
|
|
+ // 按键 下
|
|
|
|
+ case "KeyS": // 下移动作
|
|
|
|
+ case "ArrowDown":
|
|
|
|
+ // 判断游戏是否继续,上部产生随机位置随机数
|
|
|
|
+ moved = this.moveToFill('bottom');
|
|
|
|
+ merged = this.mergeGrid('bottom');
|
|
|
|
+ (moved || merged) && this.generateOneNumber('bottom');
|
|
|
|
+ break;
|
|
|
|
+ default :
|
|
|
|
+ // do nothing
|
|
|
|
+ }
|
|
|
|
+ this.isGameOver = this.checkIsOver();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 生成随机的格子
|
|
|
|
+ */
|
|
|
|
+ generateOneNumber(direction: string): void {
|
|
|
|
+ let temp = [];
|
|
|
|
+ if (direction == "left") {
|
|
|
|
+ for (let i = 0; i < 4; i++) {
|
|
|
|
+ temp.push(this.gridMap[i][3])
|
|
|
|
+ }
|
|
|
|
+ } else if (direction == "top") {
|
|
|
|
+ temp = this.gridMap[3];
|
|
|
|
+ } else if (direction == "bottom") {
|
|
|
|
+ temp = this.gridMap[0];
|
|
|
|
+ } else {
|
|
|
|
+ // (direction == "right")
|
|
|
|
+ for (let i = 0; i < 4; i++) {
|
|
|
|
+ temp.push(this.gridMap[i][0])
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ let indexArr = [];
|
|
|
|
+ for(let i = 0; i < temp.length; i++) {
|
|
|
|
+ if(temp[i] == 0) {
|
|
|
|
+ indexArr.push(i)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ const len = indexArr.length;
|
|
|
|
+ if (len) {
|
|
|
|
+ const random = Math.floor(len * Math.random());
|
|
|
|
+ const index = indexArr[random];
|
|
|
|
+ if (direction == "left") {
|
|
|
|
+ this.gridMap[index][3] = 2
|
|
|
|
+ } else if (direction == "top") {
|
|
|
|
+ this.gridMap[3][index] = 2;
|
|
|
|
+ } else if (direction == "bottom") {
|
|
|
|
+ this.gridMap[0][index] = 2;
|
|
|
|
+ } else {
|
|
|
|
+ // (direction == "right")
|
|
|
|
+ this.gridMap[index][0] = 2
|
|
|
|
+ }
|
|
|
|
+ this.update()
|
|
|
|
+ } else {
|
|
|
|
+ // this.isGameOver = true;
|
|
|
|
+ this.update();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //随机一个位置,取新方块出现的位置
|
|
|
|
+
|
|
|
|
+ //随机一个数字 2 或 4 ,在随机位置显示随机数字
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 移动数字,使移动方向的数字填满
|
|
|
|
+ */
|
|
|
|
+ moveToFill(direction: string): boolean{
|
|
|
|
+ let flag = false;
|
|
|
|
+ if (direction == "left") {
|
|
|
|
+ for (let i = 0; i < 4; i++) {
|
|
|
|
+ const tempJrr = new Array(4).fill(0);
|
|
|
|
+ let index = 0;
|
|
|
|
+ for (let j = 0; j < 4; j++) {
|
|
|
|
+ if (this.gridMap[i][j]!=0) {
|
|
|
|
+ tempJrr[index] = this.gridMap[i][j];
|
|
|
|
+ index++;
|
|
|
|
+ } else {
|
|
|
|
+ flag = true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ this.gridMap[i] = tempJrr;
|
|
|
|
+ }
|
|
|
|
+ } else if (direction == "top") {
|
|
|
|
+ for (let j = 0; j < 4; j++) {
|
|
|
|
+ const tempJrr = new Array(4).fill(0);
|
|
|
|
+ let index = 0;
|
|
|
|
+ for (let i = 0; i < 4; i++) {
|
|
|
|
+ if (this.gridMap[i][j]!=0) {
|
|
|
|
+ tempJrr[index] = this.gridMap[i][j];
|
|
|
|
+ index++;
|
|
|
|
+ } else {
|
|
|
|
+ flag = true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ for(let u = 0; u < 4; u++) {
|
|
|
|
+ this.gridMap[u][j] = tempJrr[u];
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } else if (direction == "bottom") {
|
|
|
|
+ for (let j = 0; j < 4; j++) {
|
|
|
|
+ const tempJrr = new Array(4).fill(0);
|
|
|
|
+ let index = 3;
|
|
|
|
+ for (let i = 3; i >= 0; i--) {
|
|
|
|
+ if (this.gridMap[i][j]!=0) {
|
|
|
|
+ tempJrr[index] = this.gridMap[i][j];
|
|
|
|
+ index--;
|
|
|
|
+ } else {
|
|
|
|
+ flag = true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ for(let u = 3; u >= 0; u--) {
|
|
|
|
+ this.gridMap[u][j] = tempJrr[u];
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ for (let i = 0; i < 4; i++) {
|
|
|
|
+ const tempJrr = new Array(4).fill(0);
|
|
|
|
+ let index = 3;
|
|
|
|
+ for (let j = 3; j >= 0; j--) {
|
|
|
|
+ if (this.gridMap[i][j]!=0) {
|
|
|
|
+ tempJrr[index] = this.gridMap[i][j];
|
|
|
|
+ index--;
|
|
|
|
+ } else {
|
|
|
|
+ flag = true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ this.gridMap[i] = tempJrr;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return flag;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 合并数字
|
|
|
|
+ *
|
|
|
|
+ * @param direction 方向
|
|
|
|
+ */
|
|
|
|
+ mergeGrid(direction: string): boolean{
|
|
|
|
+ let flag = false;
|
|
|
|
+ if (direction == "left") {
|
|
|
|
+ for (let i = 0; i < 4; i++) {
|
|
|
|
+ for (let j = 0; j < 3; j++) {
|
|
|
|
+ if (this.gridMap[i][j] == this.gridMap[i][j + 1]) {
|
|
|
|
+ this.gridMap[i][j] = 2*this.gridMap[i][j];
|
|
|
|
+ this.gridMap[i][j + 1] = 0;
|
|
|
|
+ flag = true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } else if (direction == "top") {
|
|
|
|
+ for (let i = 0; i < 3; i++) {
|
|
|
|
+ for (let j = 0; j < 4; j++) {
|
|
|
|
+ if (this.gridMap[i][j] == this.gridMap[i + 1][j]) {
|
|
|
|
+ this.gridMap[i][j] = 2*this.gridMap[i][j];
|
|
|
|
+ this.gridMap[i + 1][j] = 0;
|
|
|
|
+ flag = true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } else if (direction == "bottom") {
|
|
|
|
+ for (let i = 3; i >= 1; i--) {
|
|
|
|
+ for (let j = 0; j < 4; j++) {
|
|
|
|
+ if (this.gridMap[i][j] == this.gridMap[i - 1][j]) {
|
|
|
|
+ this.gridMap[i][j] = 2*this.gridMap[i][j];
|
|
|
|
+ this.gridMap[i - 1][j] = 0;
|
|
|
|
+ flag = true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ // (direction == "right")
|
|
|
|
+ for (let i = 0; i < 4; i++) {
|
|
|
|
+ for (let j = 3; j >= 1; j--) {
|
|
|
|
+ if (this.gridMap[i][j] == this.gridMap[i][j - 1]) {
|
|
|
|
+ this.gridMap[i][j] = 2*this.gridMap[i][j];
|
|
|
|
+ this.gridMap[i][j - 1] = 0;
|
|
|
|
+ flag = true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if (flag) {
|
|
|
|
+ this.moveToFill(direction)
|
|
|
|
+ }
|
|
|
|
+ return flag;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 绘制游戏画面
|
|
|
|
+ *
|
|
|
|
+ * @param painter 绘制对象
|
|
|
|
+ */
|
|
|
|
+ onDraw(painter: SPainter): void {
|
|
|
|
+ painter.clearRect(0, 0, this.width, this.height);
|
|
|
|
+
|
|
|
|
+ painter.pen.color = SColor.Transparent;
|
|
|
|
+ this.drawMap(painter);
|
|
|
|
+
|
|
|
|
+ this.drawNum(painter);
|
|
|
|
+
|
|
|
|
+ // 如果游戏结束,绘制文字
|
|
|
|
+ if (this.isGameOver) {
|
|
|
|
+ painter.brush.color = SColor.Blue;
|
|
|
|
+ painter.font.size = 20;
|
|
|
|
+ painter.font.textAlign = STextAlign.Center;
|
|
|
|
+ painter.drawText("GAME OVER", 180, 30);
|
|
|
|
+ // return;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 绘制数字
|
|
|
|
+ */
|
|
|
|
+ drawNum(painter: SPainter): void {
|
|
|
|
+ // 遍历行
|
|
|
|
+ for (let row = 0; row < 4; row++) {
|
|
|
|
+ // 遍历列
|
|
|
|
+ for (let col = 0; col < 4; col++) {
|
|
|
|
+ this.drawCellNum(painter, row, col, this.gridMap[row][col]);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 绘制格子中的数字
|
|
|
|
+ */
|
|
|
|
+ drawCellNum(painter: SPainter, row: number, col: number, type: number): void {
|
|
|
|
+ const x = col * 100 + 10;
|
|
|
|
+ const y = row * 100 + 10;
|
|
|
|
+ if (type != 0) {
|
|
|
|
+ painter.font.textAlign = STextAlign.Center;
|
|
|
|
+ painter.font.size = 40;
|
|
|
|
+ // @ts-ignore
|
|
|
|
+ painter.font.textBaseLine = STextBaseLine.Middle;
|
|
|
|
+ painter.brush.color = new SColor(this.color[type].font);
|
|
|
|
+ painter.drawText(type+'', x+45, y+45);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 绘制背景图
|
|
|
|
+ */
|
|
|
|
+ drawMap(painter: SPainter) {
|
|
|
|
+ // 遍历行
|
|
|
|
+ for (let row = 0; row < 4; row++) {
|
|
|
|
+ // 遍历列
|
|
|
|
+ for (let col = 0; col < 4; col++) {
|
|
|
|
+ this.drawCell(painter, row, col, this.gridMap[row][col]);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 绘制具体格子
|
|
|
|
+ *
|
|
|
|
+ * @param painter 绘制对象
|
|
|
|
+ * @param row 行
|
|
|
|
+ * @param col 列
|
|
|
|
+ * @param type 图类型
|
|
|
|
+ */
|
|
|
|
+ private drawCell(painter: SPainter, row: number, col: number, type: number): void {
|
|
|
|
+ const x = col * 100 + 10;
|
|
|
|
+ const y = row * 100 + 10;
|
|
|
|
+ painter.brush.color = new SColor(this.color[type].background);
|
|
|
|
+ painter.drawRoundRect(x, y, 90, 90, 10);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 检测结束
|
|
|
|
+ */
|
|
|
|
+ checkIsOver():boolean{
|
|
|
|
+ for(let i = 0; i < 4; i++) {
|
|
|
|
+ if (this.gridMap[i].indexOf(0) > -1) {
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ for(let j = 0; j < 3; j++) {
|
|
|
|
+ if (this.gridMap[i][j] == this.gridMap[i][j+1]) {
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+ if (i < 3 && this.gridMap[i][j] == this.gridMap[i+1][j]){
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return this.gridMap[3][3] != this.gridMap[2][3];
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Component
|
|
|
|
+ export default class Gcanvas extends Vue {
|
|
|
|
+ /** 实例化 */
|
|
|
|
+ view: GameView | null = null;
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 页面挂载完成
|
|
|
|
+ */
|
|
|
|
+ mounted(): void {
|
|
|
|
+ this.view = new GameView('canvas');
|
|
|
|
+ document.getElementById('canvas')!!.focus()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 重置操作
|
|
|
|
+ */
|
|
|
|
+ reset(): void {
|
|
|
|
+ this.view!!.init()
|
|
|
|
+ document.getElementById("canvas")!!.focus()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<style scoped>
|
|
|
|
+
|
|
|
|
+</style>
|