123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520 |
- <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>
|