在上次拆分组件的前提下,现在引入了pinia状态管理
在此过程中,发现了一个问题,后改用了其他方式,比较简单粗暴。
生产环境报:Missing types in subscribe's mutation.events
具体主要依托store来实现,也规范了几个状态变量
chess.js
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
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
<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
<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
<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>