VUE3+Canvas绘制五子棋(三)

利用下午的时间,对这个程序进行了优化,主要拆解成组件形式,大致分为2个部分。1个部分为棋盘主要内容;1个部分为棋盘控制部分。在此过程中不断的了解了:

1、父组件传递子组件值

2、子组件修改父组件值

3、监听某个值的变化,如重新开始restart

4、也对新旧VUE产生了一定的想法,后续在琢磨怎么实现的了,想引入状态管理

【GoBang.vue】:主要布局组件,该组件主要引入上面所说的两个组件,并提取部分公共变量至此

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<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】主要用于下棋的部分,逻辑和之前一样,数据传输形式变了,感觉变复杂了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
<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】主要用于控制棋盘,如重新开始,重新绘制等,主要是通过对父组件的变量修改实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<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>

VUE3+Canvas绘制五子棋(三)
http://060800.xyz/2024/11/29/VUE3-Canvas绘制五子棋(三)/
作者
砖头
发布于
2024年11月29日
许可协议