VUE3+Canvas绘制五子棋(四)

在上次拆分组件的前提下,现在引入了pinia状态管理

在此过程中,发现了一个问题,后改用了其他方式,比较简单粗暴。

生产环境报:Missing types in subscribe’s mutation.events

具体主要依托store来实现,也规范了几个状态变量

chess.js

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
import {defineStore, acceptHMRUpdate} from 'pinia'
import {
PlayerStatus,
GameStatus,
DEFAULT_BOARD_WIDTH,
DEFAULT_BOARD_LINE_NUMBER,
DEFAULT_BOARD_SPACE
} from "@/stores/status.js";

export const useChessStore = defineStore({
id: 'chess',
state: () => ({
player: PlayerStatus.BLACK,
status: GameStatus.GAMING,
boardDetail: {
width: DEFAULT_BOARD_WIDTH,//棋盘大小
lineNumber: DEFAULT_BOARD_LINE_NUMBER,//棋盘线数
space: DEFAULT_BOARD_SPACE,//间隙
},
board: []
}),
actions: {
/**
* 交换选手
*/
reversePlayer() {
this.player = this.player === PlayerStatus.BLACK ?
PlayerStatus.WHITE : PlayerStatus.BLACK
console.log("==action:reversePlayer==交换选手==,新:" + this.player)
},
/**
* 改变当前棋手
* @param s
*/
changePlayer(s) {
switch (s) {
case PlayerStatus.BLACK:
this.player = PlayerStatus.BLACK;
break
case PlayerStatus.WHITE:
this.player = PlayerStatus.WHITE;
break;
default:
}
console.log("==action:changePlayer==改变选手==,新:" + s)
},
/**
* 改变游戏状态
* @param s
*/
changeGameStatus(s) {
switch (s) {
case GameStatus.GAMING:
this.status = GameStatus.GAMING;
break
case GameStatus.WINNING:
this.status = GameStatus.WINNING;
break
case GameStatus.RESTART:
this.status = GameStatus.RESTART;
break
default:
}
console.log("==action:changeGameStatus==改变游戏状态==" + this.status)
},
/**
* 改变棋盘大小
* @param w
* @param n
* @param s
*/
changeBoardDetail(w, n, s) {
this.boardDetail = {
width: w,//棋盘大小
lineNumber: n,//棋盘线数
space: s,//间隙
}
console.log("==action:changeBoardDetail==改变棋盘大小==" + this.boardDetail)
},
/**
* 初始化棋盘内容
*/
initBoard() {
this.board = []
for (let i = 0; i < this.boardDetail.lineNumber; i++) {
this.board.push(new Array(this.boardDetail.lineNumber).fill(0))
}
console.log("==action:initBoard==初始化棋盘内容==" + this.board)

},
/**
* 清空棋盘内容
*/
clearBoard() {
this.board = []
console.log("==action:clearBoard==清空棋盘内容==" + this.board)
}
}

})

if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useChessStore, import.meta.hot))
}

status.js

1
2
3
4
5
6
7
8
9
10
11
12
13
export const DEFAULT_BOARD_WIDTH = 600//默认宽度
export const DEFAULT_BOARD_LINE_NUMBER = 15//默认线条数
export const DEFAULT_BOARD_SPACE = 40 //默认间隙
export const GameStatus = {
GAMING: 'gaming', // 游戏中
WINNING: 'winning', // 已获胜
RESTART: 'restart', // 已获胜
}

export const PlayerStatus = {
BLACK: 'BLACK', // 黑棋
WHITE: 'WHITE', // 白棋
}

完整代码如下:

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
<script>
import MainBoard from './MainBoard.vue'
import OperateBoard from './OperateBoard.vue'

export default {
data() {
return {}
},
components: {
MainBoard, OperateBoard
},
created() {
document.title = "五子棋";
}
}
</script>
<template>
<div class="root-board">
<MainBoard/>
<OperateBoard/>
</div>
</template>

<style scoped>
.root-board {
margin: 0 auto;
}
</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
254
255
256
257
<script>
import {useChessStore} from "@/stores/chess.js";
import {GameStatus, PlayerStatus} from "@/stores/status.js";
import {storeToRefs} from 'pinia'
import {watch} from "vue";

export default {
props: {},
data() {
return {};
},
setup() {
const chessStore = useChessStore()
return {
chessStore,
}
},
created() {

//监视状态变化,以便监听重置等情况
this.chessStore.$subscribe((mutation, state) => {
console.log(mutation)// 传递给 cartStore.$patch() 的补丁对象。
//TODO 生产环境有问题,去掉了mutation.events.key === "status"
if (state.status === GameStatus.RESTART) {
this.restartGame()
}
})
},

// 实例创建完成,仅在首次加载时完成
mounted() {
let boardDetail = this.chessStore.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.chessStore.initBoard()
}
,
/**
* 监听每步棋子,需要传入棋盘信息
* @param event
*/
handleClickAndDraw(event) {
const detail = this.chessStore.boardDetail
const board = this.chessStore.board
const player = this.chessStore.player
//存在输赢以后,不允许在落子
if (this.chessStore.status !== GameStatus.GAMING) {
return;
}

const lineNumber = detail.lineNumber
const space = detail.space
const halfSpace = space / 2;

// 计算棋子落在哪个方格中
const cellX = Math.floor((event.offsetX) / space);
const cellY = Math.floor((event.offsetY) / space);
// console.log(event.offsetX + ' ' + event.offsetY + ' ' + space)
// 判断该位置是否有棋子
// console.log(board)
if (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,
player === PlayerStatus.WHITE ? '#FFFFFF' : '#4C4C4C')
grd.addColorStop(1,
player === PlayerStatus.WHITE ? '#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();
board[cellX][cellY] = player; //将黑白棋信息存储

const winner_current = this.checkWinner(board, lineNumber) //判断输赢
if (winner_current !== null) {
//代表此次操作有胜负,更新结果
this.chessStore.changeGameStatus(GameStatus.WINNING)
alert(winner_current)
return;
}
//通知父组件,修改了选手内容值
this.chessStore.reversePlayer()//交换
}
,
/**
* 胜负检查
* @param board X*X 二维数组
* @param lineNumber 线条数
* @returns {UnwrapRef<string>|null}
*/
checkWinner(board, lineNumber) {
const player = this.chessStore.player
// 检查横向是否有五子连线
for (let i = 0; i < lineNumber; i++) {
let count = 0;
for (let j = 0; j < lineNumber; j++) {
if (board[i][j] === player) {
count++;
} else {
count = 0;
}

if (count >= 5) return player;
}
}

// 检查纵向是否有五子连线
for (let j = 0; j < lineNumber; j++) {
let count = 0;
for (let i = 0; i < lineNumber; i++) {
if (board[i][j] === player) {
count++;
} else {
count = 0;
}

if (count >= 5) return 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] === player) {
count++;
} else {
count = 0;
}

if (count >= 5) return 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] === player) {
count++;
} else {
count = 0;
}

if (count >= 5) return player;
}
}
}

// 如果没有五子连线,则游戏继续
return null;
},
/**
* 重置游戏
*/
restartGame() {
const boardDetail = this.chessStore.boardDetail
//清空基础数据
this.chessStore.changePlayer(PlayerStatus.BLACK)
//清空画布
const canvas = document.getElementById("board");
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height)
//重新绘制,会初始化棋子信息
this.drawBoard(boardDetail.width,
boardDetail.lineNumber,
boardDetail.space);
this.chessStore.changeGameStatus(GameStatus.GAMING)
}
,
}
}
;
</script>

<template>
<div class="main-board">
<canvas id="board" class="board-chess" width="600" height="600"
@click="handleClickAndDraw($event)">
</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
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
<script>
import {useChessStore} from "@/stores/chess.js";
import {GameStatus, PlayerStatus} from "@/stores/status.js";

export default {
props: {},
data() {
return {};
},
setup() {
const chessStore = useChessStore()
return {
chessStore,
PlayerStatus,
GameStatus
}
},

methods: {
/**
* 改变棋盘大小
*/
changeBoardDetail() {
const square = this.chessStore.boardDetail.width
const newWidth = square
const newHeight = square
const newLineNumber = this.chessStore.boardDetail.lineNumber
const newSpace = newWidth / newLineNumber
// console.log(newWidth + ' ' + newLineNumber + ' ' + newSpace)
//重新设置board大小
let canvas = document.getElementById("board")
canvas.width = newWidth
canvas.height = newHeight
//重新设置棋盘大小
this.chessStore.changeBoardDetail(newWidth, newLineNumber, newSpace)
//重新绘制游戏
this.handleRestart()
},
/**
* 重新开始
*/
handleRestart() {
this.chessStore.changeGameStatus(GameStatus.RESTART)
}
}
};
</script>

<template>
<div class="operate-board">
<div class="detail">
<span>长宽:<input v-model="chessStore.boardDetail.width"/></span>
<span>线条数:<input v-model="chessStore.boardDetail.lineNumber"/></span>
<span>间距:<input v-model="chessStore.boardDetail.space" disabled/></span>
<button @click="changeBoardDetail()">修改</button>
</div>
<div class="operate">
<button @click="handleRestart">重新开始</button>
<span>当前落子:{{ chessStore.player === PlayerStatus.WHITE ? "白" : "黑" }}</span>
<span>胜利方:{{
chessStore.status === GameStatus.WINNING ?
(chessStore.player === PlayerStatus.WHITE ? "白棋" : "黑棋") : ""
}}</span>
</div>
</div>
</template>

<style scoped>
.operate-board {
margin: 0 10px;
text-align: center;
}

.operate-board .detail {
margin: 10px 0;
}

.operate-board .detail > span input {
width: 50px;
}

.operate-board .detail span {
margin: 5px 0;
}
</style>

<script>
import {useChessStore} from "@/stores/chess.js";
import {GameStatus, PlayerStatus} from "@/stores/status.js";

export default {
props: {},
data() {
return {};
},
setup() {
const chessStore = useChessStore()
return {
chessStore,
PlayerStatus,
GameStatus
}
},

methods: {
/**
* 改变棋盘大小
*/
changeBoardDetail() {
const square = this.chessStore.boardDetail.width
const newWidth = square
const newHeight = square
const newLineNumber = this.chessStore.boardDetail.lineNumber
const newSpace = newWidth / newLineNumber
// console.log(newWidth + ' ' + newLineNumber + ' ' + newSpace)
//重新设置board大小
let canvas = document.getElementById("board")
canvas.width = newWidth
canvas.height = newHeight
//重新设置棋盘大小
this.chessStore.changeBoardDetail(newWidth, newLineNumber, newSpace)
//重新绘制游戏
this.handleRestart()
},
/**
* 重新开始
*/
handleRestart() {
this.chessStore.changeGameStatus(GameStatus.RESTART)
}
}
};
</script>

<template>
<div class="operate-board">
<div class="detail">
<span>长宽:<input v-model="chessStore.boardDetail.width"/></span>
<span>线条数:<input v-model="chessStore.boardDetail.lineNumber"/></span>
<span>间距:<input v-model="chessStore.boardDetail.space" disabled/></span>
<button @click="changeBoardDetail()">修改</button>
</div>
<div class="operate">
<button @click="handleRestart">重新开始</button>
<span>当前落子:{{ chessStore.player === PlayerStatus.WHITE ? "白" : "黑" }}</span>
<span>胜利方:{{
chessStore.status === GameStatus.WINNING ?
(chessStore.player === PlayerStatus.WHITE ? "白棋" : "黑棋") : ""
}}</span>
</div>
</div>
</template>

<style scoped>
.operate-board {
margin: 0 10px;
text-align: center;
}

.operate-board .detail {
margin: 10px 0;
}

.operate-board .detail > span input {
width: 50px;
}

.operate-board .detail span {
margin: 5px 0;
}
</style>

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