在上次拆分组件的前提下,现在引入了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>
最后修改:2024 年 12 月 05 日
如果觉得我的文章对你有用,请随意赞赏