原文链接: vue 俄罗斯方块
下一篇: opencv 二值化的素描
技术难点主要在于旋转的处理
标定一个不变的点设为方块初始点
将方块坐标变为一个相对坐标
向下x正, 向右y正
原始方块坐标为[x, y] 旋转后的坐标为 [y, -x]
记录方块的初始点移动, 加上旋转后的坐标完成变换
判断消除时, 如果消除了一行后, 还需要在进行一次判定, 判定落下来的行是否需要消除
将相关操作设计为函数, 简化代码开发, 和模块调试
移动的时候, 向右移动时从从右边开始判断, 向左移动时, 由左边开始判断
<template>
<div class="main">
<div class="cells">
<div v-for="(row, index) in game" :key="index">
<div class="row">
<div
v-for="(cell, key) in row"
:key="key"
:class="classes[cell]"
class="cell"
></div>
</div>
</div>
</div>
</div>
</template>
<script>
// 向下x正 向右y正
const H = 25;
const W = 11;
const MID_W = 5;
// 初始砖块,以中心为原点的相对坐标偏移列表
const BLOCKS = [
[[0, 0], [0, 1], [1, 0], [1, 1]], //田
[[0, -1], [0, 0], [1, 0], [1, 1]],
[[0, 0], [0, -1], [0, 1], [0, 2]],
[[0, 0], [0, 1], [1, 1], [1, 0]], // z
[[0, 0], [0, -1], [-1, 0], [-1, 1]],
[[0, 0], [0, 1], [-1, -1], [-1, 0]],
[[0, 0], [0, 1], [1, 0], [0, -1]],
[[0, 0], [1, 0], [-1, 0], [1, -1]],
[[0, 0], [1, 0], [-1, 0], [1, 1]]
];
// 0 表示背景 白色
// 1 表示已经下落的砖块 浅灰
// 2 表示正在下落的砖块 浅蓝
export default {
name: "Game",
data() {
return {
classes: ["bg", "box", "move"],
game: Array.from(Array(H)).map(() => Array(W).fill(0)),
blockIndex: 0,
block: BLOCKS[this.blockIndex],
dx: 3,
dy: MID_W
};
},
methods: {
left() {
// 左移
console.log("左移");
this.dy--;
let canMove = true;
for (let x = 0; x < H; x++) {
for (let y = 0; y < W; y++) {
if (this.game[x][y] === 2) {
if (y === 0 || this.game[x][y - 1] === 1) {
canMove = false;
// console.log(x, y);
}
}
}
}
if (canMove) {
for (let x = 0; x < H; x++) {
for (let y = 0; y < W; y++) {
if (this.game[x][y] === 2 && y > 0) {
this.change(x, y, 0);
this.change(x, y - 1, 2);
}
}
}
}
},
down() {
console.log("向下");
this.dx++;
let pos = [];
for (let x = 0; x < H; x++) {
for (let y = 0; y < W; y++) {
if (this.game[x][y] === 2) pos.push([x, y]);
}
}
let new_pos = [];
for (let i = 0; i < pos.length; i++) {
let [x, y] = pos[i];
new_pos.push([x + 1, y]);
}
let canMove = new_pos.every(([x, y]) => {
if (x < 0 || y < 0 || x >= H || y >= W) {
// 越界
return false;
}
if (this.game[x][y] === 1) {
// 已经有了方块
return false;
}
return true;
});
if (canMove) {
for (let [x, y] of pos) this.change(x, y, 0);
for (let [x, y] of new_pos) this.change(x, y, 2);
} else {
for (let [x, y] of pos) this.change(x, y, 1);
this.init();
// 判断是否需要消除
for (let i = H - 1; i > 0; i--) {
// 从下到上
let isDel = this.game[i].every(x => x === 1);
console.log(i, isDel);
if (isDel) {
for (let j = i; j > 0; j--) {
this.game[j] = this.game[j - 1];
}
this.game[0] = Array(W).fill(0);
i++;
}
// 删除之后,判断落下来的行是否需要删除
}
}
},
transfer() {
console.log("变形");
let block = this.block;
let new_block = block.map(([x, y]) => {
let nx = y;
let ny = -x;
return [nx, ny];
});
let pos = [];
for (let x = 0; x < H; x++) {
for (let y = 0; y < W; y++) {
if (this.game[x][y] === 2) pos.push([x, y]);
}
}
let new_pos = [];
console.log("block", block);
console.log("dx,dy", this.dx, this.dy);
console.log("diff", new_block);
for (let i = 0; i < new_block.length; i++) {
let [x, y] = new_block[i];
let nx = x + this.dx;
let ny = y + this.dy;
new_pos.push([nx, ny]);
}
console.log("pos", pos);
console.log("new_pos", new_pos);
let canChange = new_pos.every(([x, y]) => {
if (x < 0 || y < 0 || x >= H || y >= W) {
// 越界
return false;
}
if (this.game[x][y] === 1) {
// 已经有了方块
return false;
}
return true;
});
console.log("canChange", canChange);
if (canChange) {
for (let [x, y] of pos) this.change(x, y, 0);
for (let [x, y] of new_pos) this.change(x, y, 2);
this.block = new_block;
}
},
right() {
console.log("右移");
let canMove = true;
this.dy++;
for (let x = 0; x < H; x++) {
for (let y = 0; y < W; y++) {
if (this.game[x][y] === 2) {
if (y === W - 1 || this.game[x][y + 1] === 1) {
canMove = false;
// console.log(x, y);
}
}
}
}
// 向哪边移动, 则由哪边遍历
if (canMove) {
for (let x = 0; x < H; x++) {
for (let y = W - 1; y >= 0; y--) {
if (this.game[x][y] === 2 && y < W - 1) {
// console.log("move", x, y);
this.change(x, y, 0);
this.change(x, y + 1, 2);
}
}
}
}
},
change(x, y, v) {
this.$set(this.game[x], `${y}`, v);
},
keyDown(e) {
console.log(e);
if (e.key === "a") {
this.left();
} else if (e.key === "w") {
this.transfer();
} else if (e.key === "s") {
this.down();
} else if (e.key === "d") {
this.right();
}
},
init() {
// 随机选择一个block, 放在正上方
this.dx = 2;
this.dy = MID_W;
this.blockIndex = parseInt(Math.random() * BLOCKS.length);
// console.log(this.blockIndex);
this.block = Array.from(BLOCKS[this.blockIndex]);
// 是否为0 表示可以放置初始物块
let canPut = this.block.every(([x, y]) => {
console.log("x,y", x, y);
x = x + this.dx;
y = y + this.dy;
return this.game[x][y] === 0;
});
if (!canPut) {
// 游戏结束
} else {
this.block.forEach(([x, y]) => {
x = x + this.dx;
y = y + this.dy;
return this.change(x, y, 2);
});
}
}
},
mounted() {
console.log(this.game.length, this.game[0].length);
this.init();
document.body.onkeydown = this.keyDown;
setInterval(() => this.down(), 300);
}
};
</script>
<style scoped>
.main {
display: flex;
justify-content: center;
align-items: center;
width: 100vw;
height: 100vh;
flex-direction: column;
}
.cells {
display: flex;
/*justify-content: center;*/
/*align-items: center;*/
flex-direction: column;
}
.row {
display: flex;
flex-direction: row;
}
.cell {
width: 25px;
height: 25px;
box-sizing: border-box;
border: 1px solid black;
}
.box {
background: lightgray;
}
.move {
background: deepskyblue;
}
.bg {
background: white;
}
</style>