<template>
    <div style="position: relative;">
        <canvas :id="id" width="320" height="620" tabindex="0"/>
        <div style="position: absolute;top: 0;left: 350px;">
            <p>{{score}}</p>
            <el-button @click="reset">reset</el-button>
        </div>
    </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { v1 as uuid } from "uuid";
import { SCanvasView, SColor, SPainter, STextAlign } from "@persagy-web/draw/lib";

/**
 * 俄罗斯方块视图
 *
 * @author 郝建龙 <haojianlong@sagacloud.com>
 */
class TestView extends SCanvasView {
    /** 背景表示数组 */
    map: number[][] = [];
    /** 方块类型索引 0-6 */
    boxType: number = Number(Math.floor(Math.random() * 7));  
    /** 方块变形索引 0-3 */
    dir: number = Number(Math.floor(Math.random() * 4)); 
    /** 所有方块集合 */
    box: number[][][] = [
        [
            [0, 0, 0, 0],
            [0, 1, 1, 0],
            [0, 1, 1, 0],
            [0, 0, 0, 0],
        ],
        [
            [0, 0, 0, 0],
            [0, 1, 1, 0],
            [0, 1, 1, 0],
            [0, 0, 0, 0],
        ],
        [
            [0, 0, 0, 0],
            [0, 1, 1, 0],
            [0, 1, 1, 0],
            [0, 0, 0, 0],
        ],
        [
            [0, 0, 0, 0],
            [0, 1, 1, 0],
            [0, 1, 1, 0],
            [0, 0, 0, 0],
        ],
        [
            [0, 1, 0, 0],
            [0, 1, 0, 0],
            [0, 1, 0, 0],
            [0, 1, 0, 0],
        ],
        [
            [0, 0, 0, 0],
            [1, 1, 1, 1],
            [0, 0, 0, 0],
            [0, 0, 0, 0],
        ],
        [
            [0, 0, 1, 0],
            [0, 0, 1, 0],
            [0, 0, 1, 0],
            [0, 0, 1, 0],
        ],
        [
            [0, 0, 0, 0],
            [0, 0, 0, 0],
            [1, 1, 1, 1],
            [0, 0, 0, 0],
        ],
        [
            [0, 0, 0, 0],
            [0, 1, 1, 0],
            [1, 1, 0, 0],
            [0, 0, 0, 0],
        ],
        [
            [0, 1, 0, 0],
            [0, 1, 1, 0],
            [0, 0, 1, 0],
            [0, 0, 0, 0],
        ],
        [
            [0, 0, 0, 0],
            [0, 0, 1, 1],
            [0, 1, 1, 0],
            [0, 0, 0, 0],
        ],
        [
            [0, 0, 0, 0],
            [0, 1, 0, 0],
            [0, 1, 1, 0],
            [0, 0, 1, 0],
        ],
        [
            [0, 0, 0, 0],
            [0, 1, 1, 0],
            [0, 0, 1, 1],
            [0, 0, 0, 0],
        ],
        [
            [0, 0, 0, 0],
            [0, 0, 1, 0],
            [0, 1, 1, 0],
            [0, 1, 0, 0],
        ],
        [
            [0, 0, 0, 0],
            [1, 1, 0, 0],
            [0, 1, 1, 0],
            [0, 0, 0, 0],
        ],
        [
            [0, 0, 1, 0],
            [0, 1, 1, 0],
            [0, 1, 0, 0],
            [0, 0, 0, 0],
        ],
        [
            [0, 0, 0, 0],
            [0, 1, 1, 0],
            [0, 0, 1, 0],
            [0, 0, 1, 0],
        ],
        [
            [0, 0, 0, 0],
            [0, 0, 1, 0],
            [1, 1, 1, 0],
            [0, 0, 0, 0],
        ],
        [
            [0, 1, 0, 0],
            [0, 1, 0, 0],
            [0, 1, 1, 0],
            [0, 0, 0, 0],
        ],
        [
            [0, 0, 0, 0],
            [0, 1, 1, 1],
            [0, 1, 0, 0],
            [0, 0, 0, 0],
        ],
        [
            [0, 0, 0, 0],
            [0, 1, 1, 0],
            [0, 1, 0, 0],
            [0, 1, 0, 0],
        ],
        [
            [0, 0, 0, 0],
            [1, 1, 1, 0],
            [0, 0, 1, 0],
            [0, 0, 0, 0],
        ],
        [
            [0, 0, 1, 0],
            [0, 0, 1, 0],
            [0, 1, 1, 0],
            [0, 0, 0, 0],
        ],
        [
            [0, 0, 0, 0],
            [0, 1, 0, 0],
            [0, 1, 1, 1],
            [0, 0, 0, 0],
        ],
        [
            [0, 0, 0, 0],
            [0, 0, 1, 0],
            [0, 1, 1, 1],
            [0, 0, 0, 0],
        ],
        [
            [0, 0, 0, 0],
            [0, 1, 0, 0],
            [0, 1, 1, 0],
            [0, 1, 0, 0],
        ],
        [
            [0, 0, 0, 0],
            [1, 1, 1, 0],
            [0, 1, 0, 0],
            [0, 0, 0, 0],
        ],
        [
            [0, 0, 1, 0],
            [0, 1, 1, 0],
            [0, 0, 1, 0],
            [0, 0, 0, 0],
        ]
    ];
    /** 方块初始位置x坐标 */
    x = 5;
    /** 方块初始位置y坐标 */
    y = 0;
    /** 记录上次刷新时间 */
    t = Date.now();
    /** 是否游戏结束 */
    gameOver: boolean = false;
    /** 方块颜色 */
    boxColor: SColor = new SColor();

    /**
     * 构造函数
     *
     * @param id canvas DOM id
     */
    constructor(id: string) {
        super(id);
        this.initMap();
        this.initColor();
    }

    /**
     * 键盘按下事件
     *
     * @param event 事件对象
     */
    onKeyDown(event: KeyboardEvent): void {
        if (this.gameOver) {    // 游戏结束
            return;
        }

        if (event.code == "KeyW") { // 按键 w
            if (!this.checkKnocked(this.x, this.y, (this.dir + 1) % 4)) {    // 检查变形后是否碰撞
                this.dir = (this.dir + 1) % 4;
            }
        } else if (event.code == "KeyA") {  // 按键 a
            if (!this.checkKnocked(this.x - 1, this.y, this.dir)) { // 检查左移后是否碰撞
                this.x--;
            }
        } else if (event.code == "KeyD") { // 按键 d
            if (!this.checkKnocked(this.x + 1, this.y, this.dir)) { // 检查右移后是否碰撞
                this.x++;
            }
        } else if (event.code == "KeyS") { // 按键 s
            if (!this.checkKnocked(this.x, this.y + 1, this.dir)) { // 检查下移后是否碰撞
                this.y++;
            } else {
                this.fillMap();
            }
        }

        this.update();
    }

    /**
     * Item 绘制操作
     *
     * @param painter 绘制对象
     */
    onDraw(painter: SPainter): void {

        if (this.gameOver) {    // 游戏结束绘制文字
            painter.brush.color = SColor.Blue;
            painter.font.size = 20;
            painter.font.textAlign = STextAlign.Center;
            painter.drawText("GAME OVER", 180, 30);
            return;
        }

        painter.clearRect(0, 0, this.width, this.height);

        painter.pen.color = new SColor("#0bc0ff");
        painter.brush.color = new SColor("#2accc7");
        this.drawMap(painter);

        painter.pen.color = this.boxColor;
        painter.brush.color = this.boxColor;
        // 绘制图形
        this.drawBox(painter);


        if (Date.now() - this.t > 500) {    // 下落速度，下移一格
            if (!this.checkKnocked(this.x, this.y + 1, this.dir)) {  // 下移是否碰撞
                this.y++;
            } else {
                this.fillMap();
            }
            this.t = Date.now();
        }
        this.update();
    }

    /**
     * 初始化背景
     */
    initMap(): void {
        this.gameOver = false;
        this.map = [];
        for (let row = 0; row < 22; row++) {    // 循环行数
            const m1: number[] = [];
            for (let col = 0; col < 14; col++) {    // 循环列数
                if (row > 19 || col < 2 || col > 11) {  // 左侧，右侧，底部补充两个格
                    // -1 代表左右填充
                    m1.push(-1);
                } else {
                    m1.push(0);
                }
            }
            this.map.push(m1);
        }
    }

    /**
     * 初始化方块形状
     */
    initBox(): void {
        this.x = 5;
        this.y = 0;
        this.boxType = Number(Math.floor(Math.random() * 7));
        this.dir = Number(Math.floor(Math.random() * 4));
        this.initColor();
    }

    /**
     * 初始化方块颜色
     */
    initColor(): void {
        const a = Number(Math.floor(Math.random() * 128));
        const b = Number(Math.floor(Math.random() * 128));
        const c = Number(Math.floor(Math.random() * 128));
        this.boxColor = new SColor(a, b, c);
    }

    /**
     * 绘制背景
     *
     * @param painter 绘制对象
     */
    private drawMap(painter: SPainter): void {
        for (let row = 0; row < 22; row++) {    // 行数
            for (let col = 0; col < 14; col++) {  // 列数
                this.drawShape(painter, row, col, this.map[row][col]);
            }
        }
    }

    /**
     * 绘制实体图形
     *
     * @param painter   绘制对象
     * @param row       行
     * @param col       列
     * @param type      图类型
     */
    private drawShape(painter: SPainter, row: number, col: number, type: number): void {
        const x = col * 30 + 11 - 60;
        const y = row * 30 + 11;

        switch (type) { // 类型判断
            case 0:     // 空白
                break;

            case -1:    // 边界
                painter.drawRoundRect(x, y, 28, 28, 6);
                break;

            default:    // 填充
                painter.drawRoundRect(x, y, 28, 28, 6);
                break;
        }
    }

    /**
     * 绘制方块
     *
     * @param painter 绘制对象
     */
    private drawBox(painter: SPainter): void {
        for (let row = 0; row < 4; row++) {   // 绘制图形行数
            for (let col = 0; col < 4; col++) {  // 绘制图形列数
                this.drawShape(painter, row + this.y, col + this.x, this.box[this.boxType * 4 + this.dir][row][col]);
            }
        }
    }

    /**
     * 是否碰撞
     *
     * @param x     x 坐标
     * @param y     y 坐标
     * @param dir   方块变形索引
     * @return 是否碰撞
     */
    private checkKnocked(x: number, y: number, dir: number): boolean {
        for (let row = 0; row < 4; row++) { // 绘制图形行数
            for (let col = 0; col < 4; col++) { // 绘制图形列数
                // 判断是否重合，每种图形四个一组，四种变形的某种变形，不为零表示绘制（确定方块），坐标转换，背景图中绘制
                if (this.box[this.boxType * 4 + dir][row][col] != 0 && this.map[y + row][x + col] != 0) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 将方块合到背景中
     */
    private fillMap(): void {
        for (let row = 0; row < 4; row++) {  // 遍历图形的行数
            for (let col = 0; col < 4; col++) { // 遍历图形的列数
                if (this.box[this.boxType * 4 + this.dir][row][col] == 1) { // 判断图形绘制
                    // 填充至背景中
                    this.map[this.y + row][this.x + col] = 1;
                }
            }
        }

        this.initBox();
        if (this.checkKnocked(this.x, this.y, this.dir)) {  // 判断背景上的图形与初始化图形是否重合
            //游戏结束
            this.gameOver = true;
        }

        this.remove();
    }

    /**
     * 消除满行
     */
    private remove(): void {
        //消除行数
        let removeCount = 0;
        //循环行数，不包含底部填充2行
        for (let row = 0; row < 20; row++) {
            //消除标记，true 是
            let flag = true;
            //若当前行有一个空格，则不消除
            for (let col = 2; col < 12; col++) {
                if (this.map[row][col] == 0) {  // 存在空格
                    flag = false;
                    break;
                }
            }

            if (flag) { // 标记删除
                this.map.splice(row, 1);
                //顶部加一行空行
                this.map.unshift(this.randomRow(10, 0));
                removeCount++;
            }
        }

        if (removeCount > 0) {   // 记录消除行数
            this.$emit("score", removeCount);
            // 删除行
            this.map.shift();
            // 底部随机产生一行
            this.map.splice(19, 0, this.randomRow());
        }
    }

    /**
     * 随机数组生成
     *
     * @return 数组
     */
    private randomRow(len: number = 10, num: number = 2): number[] {
        let array = new Array(len + 4).fill(-1);
        // 生成随机数，循环不包含左右填充格
        for (let i = 2; i < array.length - 2; i++) {
            array[i] = Math.floor(num * Math.random());
        }

        return array;
    }
}

@Component
export default class ElsfkCanvas extends Vue {
    /** 图 id */
    id: string = uuid();
    /** 记分数组列表 */
    scoreList: number[] = [100, 300, 600, 1000];
    /** 记分分数 */
    score: number = 0;
    /** 实例化 view */
    view: TestView | undefined = undefined;

    /**
     * 页面挂载
     */
    mounted(): void {
        this.view = new TestView(this.id);
        this.view.connect("score", this, this.changeScore);
        document.getElementById(this.id)!!.focus();
    }

    /**
     * 分值展示
     *
     * @param v     canvas 视图对象
     * @param count 计数分值
     */
    changeScore(v: SCanvasView, count: number[]) {
        //根据消除行数判断分值
        this.score += this.scoreList[count[0] - 1];
    }

    /**
     * 重置操作
     */
    reset() {
        this.view!!.initMap();
        this.view!!.initBox();
        this.score = 0;
        this.view!!.update();
        document.getElementById(this.id)!!.focus();
    }
}
</script>
