功能完备的井字棋——基于css3和vue

Posted hans774882968

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了功能完备的井字棋——基于css3和vue相关的知识,希望对你有一定的参考价值。

我在csdn上浏览过几个井字棋的代码,有的照抄 借鉴太多已有的react版本的井字棋项目,有的代码冗余多、耦合度高,总体质量也不算高。相信我这个版本的代码质量能胜过他们的QAQ

效果demo
在这里插入图片描述
被csdn搞🤮了,图片只能传5mb,先拿这个阉割的凑合看看吧……目前我没发现有bug,如果有神犇发现了可以评论区告诉我qwq

这个井字棋项目的功能基本上都在“游戏说明”里了。

  • 可以输入你喜欢的双方标识,左侧为先手~
  • 人机模式下不允许查看轮到AI落子的那些棋局~
  • 由于作者水平有限, 目前仅支持电脑先手~
  • 提供了“历史棋局”功能~

对象名为什么叫g1?可以看到我有两个对象g1和g2,g2全程没有使用。把它放着,意在展示可以通过复制html代码+修改变量名为g2,从而复制一个子游戏。

一些细节问题

  • 游戏的各个阶段的表示

用的是ES6的Symbol,在这里它的作用类似于c语言的enum,好处主要是方便,且能提高可读性。
不过下面的gameEnd()就没有用Symbol,直接用数字标识,因为我懒得起名字……

const NOTSTARTED = Symbol(),SELFGAME = Symbol(),AIGAME = Symbol(),ENDING_ANIME = Symbol();

求当下是否处于游戏阶段:

inGameProcess(){return this.state === SELFGAME || this.state === AIGAME;}
  • 电脑的决策

井字棋游戏在双方都是最优决策的情况下,是必然平局的。但是电脑的最优决策并不容易写出,这也是为什么我只做了电脑先手。电脑的决策不是我自己想的,参考的代码:决策
大致过程就是特判电脑的第1步和第2步,后续过程另外考虑。可以直接看代码,注释很详细了。我的主要工作就是把上面链接的电脑决策代码给简化了一些。

  • 判定游戏结束

之前的方案

gameEnd(){
    //行
    for(let i = 0;i < 3;++i){
        let c = [0,0];
        for(let j = 0;j < 3;++j){
            if(this.currentBoard[ID(i,j)] > 0) c[this.currentBoard[ID(i,j)]-1]++;
        }
        if(c[0] === 3) return 1;
        if(c[1] === 3) return 2;
    }
    //列
    for(let j = 0;j < 3;++j){
        let c = [0,0];
        for(let i = 0;i < 3;++i){
            if(this.currentBoard[ID(i,j)] > 0) c[this.currentBoard[ID(i,j)]-1]++;
        }
        if(c[0] === 3) return 1;
        if(c[1] === 3) return 2;
    }
    let c;
    //对角线
    c = [0,0];
    for(let i = 0;i < 3;++i) if(this.currentBoard[ID(i,i)] > 0) ++c[this.currentBoard[ID(i,i)]-1];
    if(c[0] === 3) return 1;
    if(c[1] === 3) return 2;
    //反对角线
    c = [0,0];
    for(let i = 0;i < 3;++i) if(this.currentBoard[ID(i,2-i)] > 0) ++c[this.currentBoard[ID(i,2-i)]-1];
    if(c[0] === 3) return 1;
    if(c[1] === 3) return 2;
    //无赢家的前提下再看棋盘是否已满
    if(this.currentBoard.indexOf(0) === -1) return 3;
    return 0;
}

现在新增一个需求,就是对形成3连子的格子染色突出。我们发现如果加上满足新需求的代码,耦合度将会太高。
因此我们改成用二维数组allPossible来存每种可能的胜利局面。这样修改颜色的耦合代码就只需要加到一处了。

gameEnd(){
    const allPossible = [
        [0,1,2],[3,4,5],[6,7,8],
        [0,3,6],[1,4,7],[2,5,8],
        [0,4,8],[2,4,6]
    ];
    for(let cur of allPossible){
        let c = [0,0];
        for(let idx of cur){
            if(this.currentBoard[idx] > 0) ++c[this.currentBoard[idx]-1];
        }
        if(c[0] === 3 || c[1] === 3){
            for(let idx of cur) this.boardColor.splice(idx,1,1);
        }
        if(c[0] === 3) return 1;
        if(c[1] === 3) return 2;
    }
    //无赢家的前提下再看棋盘是否已满
    if(this.currentBoard.indexOf(0) === -1) return 3;
    return 0;
}

代码

html

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8" />
        <title>井字棋</title>
        <link rel="stylesheet" type="text/css" href = "./tic.css" />
        <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
        <script src="https://cdn.staticfile.org/vue/2.5.2/vue.min.js"></script>
    </head>
    <body>
        <h1 class="title">TicTacToe</h1>
        <div id="game">
            <div class="subgame">
                <h1>
                    <input v-model="g1.fn" class="firstName" />
                    <span>VS</span>
                    <input v-model="g1.sn" class="secondName" />
                </h1>
                <div class="body">
                    <div class="board">
                        <div v-for="(val,idx) in g1.currentBoard" @click="g1.putPiece(idx)" v-bind:style="{
                            cursor: g1.inGameProcess() && val === 0 ? 'pointer' : 'auto',
                            'background-color': g1.boardColor[idx] ? '#877f6c' : 'white',
                        }" class="cell">
                            {{ g1.boardText(val) }}
                        </div>
                    </div>
                    <div class="menu">
                        <button @click="help_show = true;">游戏说明</button>
                        <template v-if="g1.state === NOTSTARTED">
                            <button @click="g1.startGame(SELFGAME)">自己玩</button>
                            <button @click="g1.startGame(AIGAME)">人机</button>
                        </template>
                        <template v-else>
                            <button @click="g1.returnToMenu()"
                                v-bind:style="{cursor: g1.inGameProcess() ? 'pointer' : 'not-allowed'}"
                                :disabled="!g1.inGameProcess()">
                                返回
                            </button>
                            <p>
                                请棋手
                                <span class="turn-info">{{ g1.boardText(g1.getCurTurn()) }}</span>
                                落子
                            </p>
                            <p><span class="turn-info">{{ g1.step }}</span></p>
                        </template>
                    </div>
                </div>
                <!-- 展示历史棋盘局面 -->
                <div class="history">
                    <!-- 人机模式下不允许查看轮到AI落子的那些棋局 -->
                    <div v-for="(board,idx1) in g1.boards"
                        @click="g1.showHistoryBoard(board,idx1)"
                        v-bind:style="{
                            display: g1.state !== NOTSTARTED ? 'grid' : 'none',
                            cursor: g1.canClickHistoryBoard(idx1) ? 'pointer' : 'not-allowed'
                        }"
                        class="history-board">
                        <!-- 这里复用了.cell样式,并重新设置字体大小 -->
                        <div v-for="(val,idx2) in board" class="cell history-cell">
                            {{ g1.boardText(val) }}
                        </div>
                    </div>
                </div>
                <!-- 展示赢家 -->
                <transition name="winner">
                    <div class="winner" v-if="g1.winnerShow">{{ g1.winnerText }}</div>
                </transition>
                <!-- 用scale(1)控制弹出框出现 -->
                <div class="help-container" v-bind:style="{transform: help_show ? 'scale(1)' : 'scale(0)'}">
                    <div class="help-head">
                        <span class="help-title">游戏说明</span>
                        <span class="close" @click="help_show = false;">X</span>
                    </div>
                    <p>作者:hans774882968</p>
                    <p>可以输入你喜欢的双方标识,左侧为先手~</p>
                    <p>人机模式下不允许查看轮到AI落子的那些棋局~</p>
                    <p>由于作者水平有限,目前仅支持电脑先手~</p>
                    <p>提供了“历史棋局”功能~</p>
                    <p>我怎么这么菜,哭哭~</p>
                </div>
            </div>
        </div>
        <script src="./tic.js"></script>
    </body>
</html>

css

body{
    margin: 0;
}

a{
    text-decoration: none;
}

input{
    outline: none;
}

:root{
    --gap: 2px;
    --cell-size: 120px;
    --board-size: calc(3 * var(--cell-size) + (3 - 1) * var(--gap));
    --history-gap: 1px;
    --history-csz: 40px;
    --history-bsz: calc(3 * var(--history-csz) + (3 - 1) * var(--history-gap));
}

.title{
    color: #cb4042;
    text-align: center;
}

#game{
    display: flex;
    justify-content: space-evenly;
}

.subgame{
    position: relative;
}

.subgame>h1{
    margin-bottom: 80px;
    display: flex;
    justify-content: center;
    color: #777;
}

.subgame>h1 input{
    width: 85px;
    height: 42.5px;
    padding-left: 15px;
    font-size: 20px;
    color: #777;
    border-radius: 10px;
    border: 1px solid #bbb;
}

.subgame>h1 span{
    margin: 0 10px;
}

.body{
    display: flex;
}

.board{
    display: grid;
    grid-template-rows: repeat(3,var(--cell-size));
    grid-template-columns: repeat(3,var(--cell-size));
    grid-gap: var(--gap);
    padding: var(--gap);
    width: var(--board-size);
    height: var(--board-size);
    background-color: #cb4042;
}

.cell{
    background-color: white;
    color: #cb4042;
    font-size: 35px;
    display: grid;
    place-items:center;/* 居于中心 */
    user-select: none;
    overflow: hidden;
}

.menu{
    margin-left: 70px;
    width: 200px;
    display: flex;
    flex-direction用React写井字棋游戏

(教你简单地C语言黑框框三子棋(井字棋)

C语言实现三子棋(井字棋)

基于C++的井字棋小游戏(使用数组搭建)

python井字棋算法及代码

react入门学习:井字棋游戏(官方文档教程)