<template>
    <div style="position: relative;">
        <canvas id="tetris1" width="320" height="620" style="border: 1px #ccc solid" tabindex="0"/>
        <div style="position: absolute;top: 0;left: 350px;">
            <p>{{view?view.score:0}}</p>
            <button @click="reset">重新开始</button>
        </div>
    </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { SColor } from "sybotan-base";
import { SCanvas, SCanvasView, SPaint } from "sybotan-graph";
import { SPaintStyle } from "sybotan-graph/lib";

/**
 * 俄罗斯广场视图
 *
 * @author  庞利祥 <sybotan@126.com>
 */
class TetrisView extends SCanvasView {
    /** 小格画笔 */
    gridPaint = new SPaint();
    /** 文字画笔 */
    textPaint = new SPaint();
    /** 网格数组 (二维数组初始化） */
    gridMap = Array(20)
        .fill(null)
        .map(() => Array(10).fill(0));
    /** 方块类型索引 0-6 */
    boxType = Number(Math.floor(Math.random() * 7));
    /** 方块变形索引 0-3 */
    dir = Number(Math.floor(Math.random() * 4));
    /** 下落方块行坐标 */
    currRow = 0;
    /** 下落方块列坐标 */
    currCol = 3;
    /** 记录上次刷新时间 */
    lastTime = Date.now();
    /** 记分分数 */
    score = 0;
    /** 是否游戏结束 */
    isGameOver = false;

    /** 所有方块集合 */
    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],
        ]
    ];

    // /** 记分数组列表 */
    private _scoreList = [100, 300, 600, 1000];

    /**
     * 构造函数
     *
     * @param id canvas DOM id
     */
    constructor(id: string) {
        super(id);

        // 设置绘制网格的颜色
        this.gridPaint.fill = SColor.Red;
        this.gridPaint.style = SPaintStyle.Fill;

        this.textPaint.fill = SColor.Blue;
        this.textPaint.font.size = 40;

        // 初始化游戏
        this.init();
    }

    /**
     * 初始化游戏
     */
    init(): void {
        // 遍历网格的行
        for (let r = 0; r < 20; r++) {
            // 遍历网格的列
            for (let c = 0; c < 10; c++) {
                // 将网格所有数据填充为 0
                this.gridMap[r][c] = 0;
            }
        }

        // 生成新的下落box
        this.newFallBox();

        // 初始化分数
        this.score = 0;
        // 游戏结束标记为 false
        this.isGameOver = false;
    }

    /**
     * 键盘按下事件
     *
     * @param   event   事件对象
     */
    onKeyDown(event: KeyboardEvent): void {
        // 如果游戏结束, 不响应任何键
        if (this.isGameOver) {
            return;
        }

        if (event.code == "KeyW") {     // 用户按下 W 键，变形
            // 如果变形不会碰撞
            if (!this.checkCollide(this.boxType, (this.dir + 1) % 4, this.currRow, this.currCol)) {
                // 进行变形操作
                this.dir = (this.dir + 1) % 4;
            }
        } else if (event.code == "KeyA") {  // 用户按下 A 键，左移
            // 如果左移不会碰撞
            if (!this.checkCollide(this.boxType,this.dir, this.currRow, this.currCol - 1)) {
                // 左移
                this.currCol--;
            }
        } else if (event.code == "KeyD") {  // 用户按下 D 键，右移
            // 如果右移不会碰撞
            if (!this.checkCollide(this.boxType,this.dir, this.currRow, this.currCol + 1)) {
                // 右移
                this.currCol++;
            }
        } else if (event.code == "KeyS") {    // 用户按下 S 键，下移
            // box 下落
            this.fallBox();
        }

        // 更新视图
        this.update();
    }

    /**
     * 绘制游戏画面
     *
     * @param   canvas      画布
     */
    onDraw(canvas: SCanvas): void {
        // 清空画布
        this.clear();

        // 绘制网格
        this.drawGridMap(canvas);

        // 如果游戏结束
        if (this.isGameOver) {
            // 闪烁显示 “Game Over”
            if (Date.now() % 1000 > 500) {
                canvas.drawText("Game Over", 50, 300, this.textPaint);
            }

            // 刷新游戏画面
            this.update();

            return;
        }

        // 绘制下落的广场
        this.drawFallBox(canvas);

        // 判断是否到了 box 下落的时间（用时间间隔判断，不会受机器的性能影响）
        if (Date.now() - this.lastTime > 500) {
            // box 下落
            this.fallBox();
            this.lastTime = Date.now();
        }

        // 刷新游戏画面
        this.update();
    }

    /**
     * 绘制网格
     *
     * @param   canvas      画布
     */
    private drawGridMap(canvas: SCanvas): void {
        // 遍历行
        for (let r = 0; r < 20; r++) {
            // 遍历列
            for (let c = 0; c < 10; c++) {
                // 绘制小格
                this.drawGird(canvas, this.gridMap[r][c], r, c);
            }
        }
    }

    /**
     * 绘制下落的 Box
     *
     * @param   canvas      画布
     */
    private drawFallBox(canvas: SCanvas): void {
        // 遍历 box 的行
        for (let r = 0; r < 4; r++) {
            // 遍历 box 的列
            for (let c = 0; c < 4; c++) {
                // 绘制小格
                this.drawGird(canvas, this.box[this.boxType * 4 + this.dir][r][c], this.currRow + r, this.currCol + c);
            }
        }
    }

    /**
     * 执行 box 下落流程
     */
    private fallBox(): void {
        // 如果可以下移
        if (!this.checkCollide(this.boxType,this.dir, this.currRow + 1, this.currCol)) {
            // 下移并刷新游戏画面
            this.currRow++;
            this.update();
            return;
        }

        // 将 box 中的内容填充到网格
        this.boxIntoMap();

        // 如果 box 没有下移过（游戏结束条件）
        if (this.currRow <= -1) {
            // 游戏结束，并更新游戏画面
            this.isGameOver = true;
            this.update();
            return;
        }

        // 移除满行
        const rows = this.removeFullRow();
        // 如果移除大于 1 行
        if (rows > 0) {
            this.score += this._scoreList[rows - 1];
        }
        // 生成新的下落 box
        this.newFallBox();
    }

    /**
     * 画一个小格
     *
     * @param   canvas      画布
     * @param   type        小格的图类型
     * @param   row         行
     * @param   col         列
     */
    private drawGird(canvas: SCanvas, type: number, row: number, col: number): void {
        // 如果类型不为 0
        if (type != 0) {
            // 绘制小格
            canvas.drawRect(col * 30 + 11, row * 30 + 11, 28, 28, this.gridPaint);
        }
    }

    /**
     * 判断当前下落的方块是否会发生碰撞
     *
     * @param   type        下落广场的类型
     * @param   dir         方块的朝向
     * @param   row         下落方块的行坐标
     * @param   col         下落广场的列坐标
     * @return  碰撞返回 true，否则返回 false 。
     */
    private checkCollide(type: number, dir: number, row: number, col: number): boolean {
        // 遍历 box 的行
        for (let r = 0; r < 4; r++) {
            const r1 = row + r;
            // 遍历 box 的列
            for (let c = 0; c < 4; c++) {
                const c1 = col + c;
                if (this.box[this.boxType * 4 + dir][r][c] != 0) {
                    // 如果没有对应的网络格（下标越界），则为碰撞
                    if (c1 < 0 || c1 >= 10 || r1 >= 20) {
                        return true;
                    }

                    // 如果对应的网格不为 0，则为碰撞
                    if (r1 > 0 && this.gridMap[row + r][col + c] != 0) {
                        return true;
                    }
                }
            }
        }

        // 返回不发生碰撞
        return false;
    }

    /**
     * 当前下落 box 填充到网格
     */
    private boxIntoMap() {
        // 遍历 box 的行
        for (let r = 0; r < 4; r++) {
            const r1 = this.currRow + r;
            // 遍历 box 的列
            for (let c = 0; c < 4; c++) {
                const g = this.box[this.boxType * 4 + this.dir][r][c]
                const c1 = this.currCol + c;
                // 如果下落的 box 对应的格不为空
                if (g != 0) {
                    // 如果没有对应的网格（下标越界），则处理下一格
                    if (c1 < 0 || c1 >= 10 || r1 < 0 || r1 >= 20) {
                        continue;
                    }
                    // 将 box 填入到网格的对应位置
                    this.gridMap[r1][c1] = g;
                }
            }
        }
    }

    /**
     * 生成新的下落方块
     */
    private newFallBox() {
        // box 的初始位置
        this.currCol = 3;
        this.currRow = -3;

        // box 的类型与方向
        this.boxType = Number(Math.floor(Math.random() * 7));
        this.dir = Number(Math.floor(Math.random() * 4));
    }

    /**
     * 消除满行（即不存在空格的行）
     *
     * @return  消除的行数
     */
    private removeFullRow(): number {
        // 用于统计消除的行数
        let count = 0;

        // 遍历行
        for (let r = 0; r < 20; r++) {
            // 消除标记
            let isRemove = true;
            // 遍历列
            for (let c = 0; c < 10; c++) {
                // 有任意一格为空，则标识消除标记为 false，跳出循环
                if (this.gridMap[r][c] == 0) {
                    isRemove = false
                    break;
                }
            }

            // 如果消除标记为 true
            if (isRemove) {
                // 清除 row 行。
                this.gridMap.splice(r,1);
                this.gridMap.unshift(Array(10).fill(0));
                // 统计消除的行数
                count++;
            }
        }

        // 返回消除的行数
        return count
    }
}

/**
 * 俄罗斯方块 demo
 *
 * @author  庞利祥 <sybotan@126.com>
 */
@Component
export default class Tetris1 extends Vue {
    /** 实例化 view */
    view: TetrisView | null = null;

    /**
     * 页面挂载完成
     */
    mounted(): void {
        this.view = new TetrisView("tetris1");
        document.getElementById("tetris1")!!.focus();
    }

    /**
     * 重置操作
     */
    reset(): void {
        // 初始化游戏
        this.view!!.init();
    }
}
</script>
