利用下午的时间,对这个程序进行了优化,主要拆解成组件形式,大致分为2个部分。1个部分为棋盘主要内容;1个部分为棋盘控制部分。在此过程中不断的了解了:
1、父组件传递子组件值
2、子组件修改父组件值
3、监听某个值的变化,如重新开始restart
4、也对新旧VUE产生了一定的想法,后续在琢磨怎么实现的了,想引入状态管理
【GoBang.vue】:主要布局组件,该组件主要引入上面所说的两个组件,并提取部分公共变量至此
<script>
import MainBoard from './MainBoard.vue'
import OperateBoard from './OperateBoard.vue'
export default {
data() {
return {
player: 1,//1--白棋;2--黑棋
winner: 0,
restart: false,
boardDetail: {
width: 600,//棋盘大小
lineNumber: 20,//棋盘线数
space: 30,//间隙
}
}
},
components: {
MainBoard, OperateBoard
},
created() {
document.title = "五子棋";
},
watch: {
restart(nV, oV) {
if (nV) {
this.player = 1
this.winner = 0
setTimeout(() => {
//设置一个1秒延时,用于子组件捕获数据变化
this.restart = false
}, 1000)
}
}
}
}
</script>
<template>
<div class="root-board">
<MainBoard :boardDetail="boardDetail"
v-model:player="player"
v-model:winner="winner"
v-model:restart="restart"/>
<OperateBoard v-model:boardDetail="boardDetail"
:player="player"
:winner="winner"
v-model:restart="restart"/>
</div>
</template>
<style scoped>
.root-board {
display: flex;
justify-content: center;
align-content: center;
}
</style>
【MainBoard.vue】主要用于下棋的部分,逻辑和之前一样,数据传输形式变了,感觉变复杂了
<script>
export default {
props: {
boardDetail: {
type: Object,
default: [],
required: true
},
player: {
type: Number,
default: 1,
required: true
},
winner: {
type: Number,
default: 0,
required: true
},
restart: {
type: Boolean,
default: false,
required: true
},
},
data() {
return {
board: [],
};
},
watch: {
restart(nV, oV) {
//监听restart重绘命令,以便重新绘制
if (nV) {
this.restartGame()
}
}
},
// 实例创建完成,仅在首次加载时完成
mounted() {
let boardDetail = this.boardDetail
this.drawBoard(boardDetail.width,
boardDetail.lineNumber,
boardDetail.space);
},
methods: {
/**
* 画整体棋盘
* @param width 所需棋盘整体大小,上下左右预留一半space空间
* @param lineNumber 线条数,线条数*间距=width
* @param space 间距
*/
drawBoard(width, lineNumber, space) {
const halfSpace = space / 2;
const canvas = document.getElementById("board");
const ctx = canvas.getContext("2d");
// 设置线条颜色
ctx.strokeStyle = "black";
for (let i = 0; i < lineNumber; i++) {
// 绘制横线
ctx.beginPath();
ctx.moveTo(halfSpace, i * space + halfSpace);
ctx.lineTo(width - halfSpace, i * space + halfSpace);
ctx.stroke();
// 绘制竖线
ctx.beginPath();
ctx.moveTo(i * space + halfSpace, halfSpace);
ctx.lineTo(i * space + halfSpace, width - halfSpace);
ctx.stroke();
//填充数组,重置为0
this.board.push(new Array(lineNumber).fill(0))
}
},
/**
* 监听每步棋子,需要传入棋盘信息
* @param event
* @param detail 棋盘信息
*/
handleClickAndDraw(event, detail) {
//存在输赢以后,不允许在落子
if (this.winner !== 0) {
return;
}
const lineNumber = detail.lineNumber
const space = detail.space
const halfSpace = space / 2;
// let x = event.offsetX;
// let y = event.offsetY;
// console.log(x + ' ' + y)
// 计算棋子落在哪个方格中
const cellX = Math.floor((event.offsetX) / space);
const cellY = Math.floor((event.offsetY) / space);
// console.log(cellX, cellY)
// 判断该位置是否有棋子
if (this.board[cellX][cellY] !== 0) {
alert("该位置已有棋子")
return;
}
const canvas = document.getElementById("board");
const ctx = canvas.getContext("2d");
//画带渐变色的棋子,同心圆形式
//考虑起点为2,因半径为space一半,避免太大,截止1/3大小
let grd = ctx.createRadialGradient(
cellX * space + halfSpace,
cellY * space + halfSpace,
2,
cellX * space + halfSpace,
cellY * space + halfSpace,
space / 3
)
grd.addColorStop(0, this.player === 1 ? '#FFFFFF' : '#4C4C4C')
grd.addColorStop(1, this.player === 1 ? '#DADADA' : '#000000')
ctx.beginPath()
ctx.fillStyle = grd
//画圆,半径设置为space/3,同上r1参数一致
ctx.arc(
cellX * space + halfSpace,
cellY * space + halfSpace,
space / 3,
0,
2 * Math.PI,
false
);
ctx.fill();
ctx.closePath();
this.board[cellX][cellY] = this.player; //将黑白棋信息存储
const winner_current = this.checkWinner(this.board, lineNumber) //判断输赢
if (winner_current !== 0) {
//代表此次操作有胜负,更新结果
this.$emit("update:winner", winner_current)
alert(winner_current)
return;
}
//通知父组件,修改了选手内容值
this.$emit("update:player", this.player === 1 ? 2 : 1)//交换
},
/**
* 胜负检查
* @param board X*X 二维数组
* @param lineNumber 线条数
* @returns number
*/
checkWinner(board, lineNumber) {
// 检查横向是否有五子连线
for (let i = 0; i < lineNumber; i++) {
let count = 0;
for (let j = 0; j < lineNumber; j++) {
if (board[i][j] === this.player) {
count++;
} else {
count = 0;
}
if (count >= 5) return this.player;
}
}
// 检查纵向是否有五子连线
for (let j = 0; j < lineNumber; j++) {
let count = 0;
for (let i = 0; i < lineNumber; i++) {
if (board[i][j] === this.player) {
count++;
} else {
count = 0;
}
if (count >= 5) return this.player;
}
}
// 检查右斜线是否有五子连线
for (let i = 0; i < lineNumber - 5; i++) {
for (let j = 0; j < lineNumber - 5; j++) {
let count = 0;
for (let k = 0; k < 5; k++) {
if (board[i + k][j + k] === this.player) {
count++;
} else {
count = 0;
}
if (count >= 5) return this.player;
}
}
}
// 检查左斜线是否有五子连线
for (let i = 0; i < lineNumber - 5; i++) {
for (let j = 4; j < lineNumber; j++) {
let count = 0;
for (let k = 0; k < 5; k++) {
if (board[i + k][j - k] === this.player) {
count++;
} else {
count = 0;
}
if (count >= 5) return this.player;
}
}
}
// 如果没有五子连线,则游戏继续
return 0;
},
/**
* 重置游戏
*/
restartGame() {
//清空基础数据
this.board = []
//清空画布
const canvas = document.getElementById("board");
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height)
//重新绘制
this.drawBoard(this.boardDetail.width,
this.boardDetail.lineNumber,
this.boardDetail.space);
},
}
};
</script>
<template>
<div class="main-board">
<canvas id="board" class="board-chess" width="600" height="600"
@click="handleClickAndDraw($event,this.boardDetail)">
</canvas>
</div>
</template>
<style scoped>
.main-board {
padding: 0 0;
margin: 10px;
display: flex;
align-items: center;
justify-content: center;
}
.board-chess {
border: 3px solid black;
padding: 0 0;
margin: 0 0;
}
</style>
【OperateBoard.vue】主要用于控制棋盘,如重新开始,重新绘制等,主要是通过对父组件的变量修改实现
<script>
export default {
props: {
boardDetail: {
type: Object,
default: [],
required: true
},
player: {
type: Number,
default: 1,
required: true
},
winner: {
type: Number,
default: 0,
required: true
},
},
data() {
return {
inputBoardDetail: {
width: 600,//棋盘大小
lineNumber: 20,//棋盘线数
space: 30,//间隙
}
};
},
mounted() {
this.inputBoardDetail.width = this.boardDetail.width
this.inputBoardDetail.lineNumber = this.boardDetail.lineNumber
this.inputBoardDetail.space = this.boardDetail.space
},
methods: {
changeBoardDetail() {
const square = this.inputBoardDetail.width
const lineNumber = this.inputBoardDetail.lineNumber
//重新设置board大小
let canvas = document.getElementById("board")
canvas.width = square
canvas.height = square
//重新设置棋盘大小
this.$emit("update:boardDetail", {
width: square,
lineNumber: lineNumber,
space: square / lineNumber
})
//重新绘制游戏
this.handleRestart()
},
handleRestart() {
this.$emit("update:restart", true)//交换
}
}
};
</script>
<template>
<div class="operate-board">
<div>
<span>棋盘信息:</span><br>
<span>棋盘长宽:<input v-model="this.inputBoardDetail.width"/></span><br>
<span>棋盘线条数:<input v-model="this.inputBoardDetail.lineNumber"/></span><br>
<span>棋盘间距:<input v-model="this.inputBoardDetail.space" disabled/></span><br>
<button @click="changeBoardDetail()">修改
</button>
</div>
<button @click="handleRestart">重新开始</button>
<div>当前落子:{{ this.player === 1 ? "白" : "黑" }}</div>
<div>胜利方:{{ this.winner === 1 ? "白棋" : this.winner === 2 ? "黑棋" : "" }}</div>
</div>
</template>
<style scoped>
.operate-board {
margin: 0 20px;
}
.board-detail > div input {
width: 50px;
}
</style>