zoukankan      html  css  js  c++  java
  • 网页版井字游戏(TicTacToe)人机对战的制作(附思路和源码)

    井字游戏的规则是:在一个井字格子的棋盘里下棋,横竖斜一旦三子连子,则胜。而事实上,遵循一定的规则,该游戏便能保证不败,即至少是平局。
    若是两人对战,则仅需要判断“胜负平”三种状态即可,比较简单,而人机对战的难点便在于让机器立于不败之地的下棋规则。下面会重点讲解不败的思路。

    先放一张游戏截图,程序演示与源码下载可以去:戳我演示或下载代码
    井字游戏

    在此先规定电脑一定是先手,如果电脑不是先手的话算法需要另外设计,但方法类似,在此暂不讨论。先说说电脑先手时保持不败的设计思路:
    根据下图标出的编号来看:
    这里写图片描述
    前两子是有特别讲究的,不然可能会输。
    第一子:只能在正中间或者四个角随机选一个,即1、3、5、7、9中任选一个。
    第二子:分两种情况讨论。

    1. 若第一子在正中,即5号,那么如果玩家下在四个角(1、3、7、9),则第二子下其对角(9、7、3、1)。如果玩家下在某一行或某一列的中间格,即2、4、6、8的某一个,则第二子应下在靠近其的某个角。
      比如:

      电脑(第1子):5
      玩家(第1子):1
      电脑(第2子):9



      电脑(第1子):5
      玩家(第1子):2
      电脑(第2子):1或3(随机选择)

    2. 若第一子在四个角,即1、3、7、9其中之一,则也许根据玩家的下法来判断。
      若玩家下在正中央,即5号格子,那么第二步就需下第一步的对角,即9、7、3、1。 如果玩家下的不是正中央,那么第二步就下正中央。 举例:

      电脑(第1子):3
      玩家(第1子):5
      电脑(第2子):7



      电脑(第1子):3
      玩家(第1子):9
      电脑(第2子):5

    第二子以后,只需遵循一定规则即可。规则共三条,从第一条开始往下判断即可(注意一定要按顺序!!!)
    1、判断电脑下某处后是否能直接胜出。即电脑下的子是否有二连珠且第三个位置还没有棋子。如果有,就往该处下棋,即可胜出,如果没有,往下。
    2、判断玩家棋子是否有二连珠且对应连线的第三个位置为空位的,如果有,将该空位补上,如果没有,第三步。
    3、遍历所有还没下棋的格子,对每一个格子进行判断,并统计出如果下这一格,能让几条线有二连珠且第三个位置为空位。选择统计的数量最多的一格下棋。有点抽象,拿下图举个例子,图中编号为下棋的顺序:
    这里写图片描述
    看第五步棋,也就是电脑的第三步,如果该棋下在第一行中间位置,能成线的条数为0,如果下在第一列中间位置,成线条数为1(第一列),而下在现在这个位置,成线条数为2(第一列与第三行)。而成线数越多,便越有优势,当成两条线时,便赢了。

    下面放上源代码,核心代码有详细注释,可以直接去上面给的链接里切换编辑视图,点击右下角有地方下载。此处代码也可以直接复制即可运行:
    html

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <title>YinyouTicTacToe</title>
        <link href="https://fonts.googleapis.com/css?family=Itim" rel="stylesheet">
        <link rel="stylesheet" type="text/css" href="css/style.css">
    </head>
    <body>
        <h1>Yinyou TicTacToe</h1>
        <div class="main">
                <div id="tic-1" class="tic">
                    <span id="span-1" class="tic-span"></span>
                </div>
                <div id="tic-2" class="tic">
                    <span id="span-2" class="tic-span"></span>
                </div>
                <div id="tic-3" class="tic">
                    <span id="span-3" class="tic-span"></span>
                </div>
                <div id="tic-4" class="tic">
                    <span id="span-4" class="tic-span"></span>
                </div>
                <div id="tic-5" class="tic">
                    <span id="span-5" class="tic-span"></span>
                </div>
                <div id="tic-6" class="tic">
                    <span id="span-6" class="tic-span"></span>
                </div>
                <div id="tic-7" class="tic">
                    <span id="span-7" class="tic-span"></span>
                </div>
                <div id="tic-8" class="tic">
                    <span id="span-8" class="tic-span"></span>
                </div>
                <div id="tic-9" class="tic">
                    <span id="span-9" class="tic-span"></span>
                </div>  
        </div>
    
        <div class="back"></div>
        <div class="choose">
            <div class="choose-title">
                <h2>TicTacToe</h2>
                <hr>
                <p>Please choose whether you wanna use? × OR O</p>
            </div>
            <div class="choose-bt">
                <button id="cha">×</button>
                <button id="O">o</button>
            </div>
        </div>
    
        <div class="loser">
            Tie!
        </div>
    
        <script src='http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
        <script type="text/javascript" src="js/tic.js"></script>
    </body>
    </html>

    css

    *{
        padding: 0;
        margin: 0;
        font-family: 'Itim', cursive;
    }
    html,body{
        background-color: #cb4042;
        width: 100%;
        height: 96%;
        background-position: fixed;
    }
    
    h1{
        text-align: center;
        color: #fff;
        font-size: 2em;
        margin-top: 3%;
    }
    .main{
        width: 486px;
        height: 486px;
        margin: auto;
        margin-top: 3%;
        background-color: #fff;
        text-align: center;
        z-index: 5;
    }
    
    .tic{
        display: inline-block;
        width: 160px;
        height: 160px;
        font-size: 1em;
        -webkit-text-size-adjust:100%;
        border:1px solid #cb4042;
        cursor: pointer;
        float: left;
        color: #000;
        position: relative;
    }
    .tic-span{
        font-size: 3em;
        color: #cb4042;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%,-50%);
    }
    
    .back{
        position: fixed;
        top: 0;
        width: 100%;
        height: 100%;
        background-color: #000;
        opacity: .5;
        z-index: 20;
    }
    
    .choose{
        z-index: 21;
        position: absolute;
        top: 20%;
        left: 0;
        right: 0;
        margin: auto;
        width: 700px;
        color: #fff;
        background-color: #db4d6d;
        border-radius: 10px;
        outline: none;
        display: none;
    }
    
    .choose-title{
        padding-top: 20px;
        text-align: center;
    }
    
    .choose-bt{
        margin-top: 20px;
        margin-bottom: 15px;
        /* text-align: right;
        margin-right: 15px; */
        text-align: center;
    }
    
    .choose-bt button{
        font-family: sans-serif;
        width: 40px;
        height: 40px;
        font-size: 1.5em;
        text-align: center;
        background:#eee;
        border:1px solid #cb4042;
        border-radius: 50%;
        outline: none;
        color: #cb4042;
        margin-left: 50px;
        margin-right: 50px;
        cursor: pointer;
    }
    
    .choose-bt button:hover{
        background-color: #fff;
    }
    
    .loser{
        position: absolute;
        top: 0;
        height: 100%;
        width: 100%;
        text-align: center;
        font-size: 25em;
        z-index: 1;
        color: red;
        display: none;
    }

    js

    $(document).ready(function(){
        var cmpt = "";  //电脑用的
        var user = "";  //用户用的
        var group = [0,0,0,0,0,0,0,0,0];    //记录九个棋格,0表示没下,1表示电脑,2表示玩家
    
        $(".choose").fadeIn(1000);
        $("#cha").on("click",function(){
            $(".choose").fadeOut(1);
            $(".back").fadeOut(1);
            cmpt = "O";
            user = "×";
            pcStep();
        });
        $("#O").on("click",function(){
            $(".choose").fadeOut(1);
            $(".back").fadeOut(1);
            cmpt="×";
            user="O";
            pcStep();
        });
    
        //电脑下棋,只下一步
        var pcStep = function(){
            var step = 0;       //记录当前是电脑下的第几步
            for(var i in group){
                if(group[i] !== 0){
                    //如果某格已经下过了,step++
                    step++;
                }
            }
            if(step %2 !== 0){
                //如果用户还没下,就return
                return;
            }
    
            if(step === 0){
                //如果电脑当前需要下第一步,因为是第一步所以不需要考虑该位置是否被别人下过的问题
                var proStep = [0,2,6,8,4];  //第一步允许下的地方,四个角与中央,这里用的是从0开始
                var posit = parseInt(Math.random()*5,10);       //从0-4中随机生成一个数,作为proStep的下标,即随机选择一个格子下棋
                group[proStep[posit]] = 1;          //电脑下棋
                $("#span-"+(proStep[posit]+1)).html(cmpt);
    
                judge();
                return;
            }
    
            if(step === 2){
                //如果是电脑下的第二步,分两种情况,分别是电脑第一步下了正中和四个角
                if(group[4] === 1){
                    //如果电脑第一步下的正中,又分两种情况,对方下的是四个角还是中间
                    var corStep = [0,2,6,8];    //四个角在group的索引
                    for(var t = 0; t<4;t++){
                        if(group[corStep[t]] === 2){
                            //如果玩家下的是某一个角,那就下他对角
                            var posit = 0;      //这里表示的是电脑要下的位置
                            if(corStep[t] === 0){
                                posit = 8;
                            }else if(corStep[t] === 8){
                                posit = 0;
                            }else if(corStep[t] === 2){
                                posit = 6;
                            }else if(corStep[t] === 6){
                                posit = 2;
                            }
                            posit = parseInt(posit);
                            group[posit] = 1;   //电脑下棋
                            $("#span-"+(posit+1)).html(cmpt);
                            judge();
                            return;
                        }
                    }
                    //电脑下的不是某个角,而是在每一行或列的中间位置,电脑就下一个靠着它的角
                    var posit_g=[0,0];  //如果下在中间,就会有两个角靠着它,随机选一个
                    var posit = 0;              //这就是随机选择之后的位置
                    if(group[1] === 2){
                        posit_g[0] = 0;
                        posit_g[1] = 2;
                    }else if(group[3] === 2){
                        posit_g[0] = 0;
                        posit_g[1] = 6;
                    }else if(group[5] === 2){
                        posit_g[0] = 2;
                        posit_g[1] = 8;
                    }else if(group[7] === 2){
                        posit_g[0] = 6;
                        posit_g[1] = 8;
                    }
                    posit = posit_g[parseInt(Math.random()*2)];
                    posit = parseInt(posit);
                    group[posit] = 1;   //电脑下棋
                    $("#span-"+(posit+1)).html(cmpt);
                    judge();
                    return;
                }else{
                    //如果电脑第一步下的不是正中,而是四个角
                    //分两种情况,如果对方没下正中,那么就下正中,如果对方下了正中,就下第一步的对角
                    if(group[4] === 0){
                        //玩家没下正中,则下正中
                        group[4] = 1;
                        $("#span-5").html(cmpt);
                        judge();
                        return;
                    }
                    //下第一步的对角
                    var posit = 0;  //记录要下的从0起的位置
                    if(group[0] === 1){
                        posit = 8;
                    }else if(group[8] === 1){
                        posit = 0;
                    }else if(group[2] === 1){
                        posit = 6;
                    }else if(group[6] === 1){
                        posit = 2;
                    }
                    posit = parseInt(posit);
                    group[posit] = 1;   //电脑下棋
                    $("#span-"+(posit+1)).html(cmpt);
                    judge();
                    return;
                }
            }
    
            /*如果是第二步以后,分三步
            * 1、判断自己是否可以三连珠,如果可以就连上就赢了
            * 2、判断对方是否可以三连珠,如果有就堵上,不然就输了
            * 3、遍历剩下的还没下的格子,看下在哪一个,能让自己一条线上存在两粒棋子且都能实现三连珠的  这样的线最多      
            */
    
            //第一步
            var first_arr = checkThree(1,group);
            if(first_arr.length !== 0){
                //如果自己可以三连珠
                var posit = first_arr[0];
                posit = parseInt(posit);
                group[posit] = 1;   //电脑下棋
                $("#span-"+(posit+1)).html(cmpt);
                judge();
                return;
            }
            //如果自己不能三连珠,就第二步,检查对方是否能三连珠
            var second_arr = checkThree(2,group);
            if(second_arr.length !== 0){
                //如果对方可以三连珠
                //console.log(second_arr[0]);
                var posit = second_arr[0];
                posit = parseInt(posit);
                group[posit] = 1;   //电脑下棋
                $("#span-"+(posit+1)).html(cmpt);
                judge();
                return;
            }
            //如果自己和对方都不能三连珠,进入第三步
            var third_posit = 0;
            var third_max = -1;
            for(var temp in group){
                if(group[temp] === 0){
                    if(third_max === -1){
                        third_posit = temp;
                        third_max = 0;
                    }
                    var ttt = [].concat(group);
                    ttt[temp] = 1;
                    var temp_arr = checkThree(1,ttt);
                    if(temp_arr.length > third_max){
                        //如果当前点能让更多个连珠,就决定是它了
                        third_max = temp_arr.length;
                        third_posit = temp;
                    }
                }
            }
            group[third_posit] = 1; //电脑下棋
            //这里必须先转成数字,不然会当成字符串相加,会不对
            var wtf = parseInt(third_posit);
            wtf+=1;
            $("#span-"+wtf).html(cmpt);
            judge();
            return;
        };
    
        //检查是否有一点可以三连珠,参数kind如果是1,检查电脑,参数如果是2,检查玩家,如果检查到下某一点可以三连珠,
        //就返回该格从0开始的下标(数组形式,所有情况都在),如果没找到,返回[]
        //参数gp是参考的数组,正常情况下就是group,但是考虑到第三步需要判断每一个格子的,那时候要传一个临时数组了
        var checkThree = function(kind,gp){
            var situ = [];          //用来记录所有需要返回的值
            var 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(var i in allPossible){
                var x = allPossible[i][0];
                var y = allPossible[i][1];
                var z = allPossible[i][2];
                if((gp[x] === kind && gp[y] === kind && gp[z] === 0) || (gp[x] === 0 && gp[y] === kind && gp[z] === kind) || (gp[x] === kind && gp[y] === 0 && gp[z] === kind)){
                    //迭代判断吧,就不复制粘贴了
                    //如果满足上述条件
                    //console.log("Three:"+allPossible[i]);
                    if(gp[x] === 0){
                        situ.push(x);
                        continue;
                    }else if(gp[y] === 0){
                        situ.push(y);
                        continue;
                    }else if(gp[z] === 0){
                        situ.push(z);
                        continue;
                    }
                }
            }
            return situ;
        };
    
    
        //输赢平的结果显示  state取值1,2,3,1表示电脑赢,2表示玩家赢,3表示平局,a,b,c表示连起来的三格
        var result = function(state,a,b,c){
            if(state === 1){
                console.log('lose');
                $(".loser").html("LOSE!");
    
            }else if(state === 2){
                console.log('win');
                $(".loser").html("WIN!");
    
            }else if(state === 3){
                console.log('tie');
                $(".loser").html("TIE!");
    
            }
            if(state !== 3){
                $("#tic-"+a).css("background-color","#877f6c");
                $("#tic-"+b).css("background-color","#877f6c");
                $("#tic-"+c).css("background-color","#877f6c");
            }
            setTimeout(function(){
                if(state !== 3){
                    $("#tic-"+a).css("background-color","#fff");
                    $("#tic-"+b).css("background-color","#fff");
                    $("#tic-"+c).css("background-color","#fff");
                }
                $(".loser").fadeIn(400,function(){
                        setTimeout(function(){
                        beginAgain();
                    },2000);
                });
            },1500);
    
    
    
        };
    
        //出了结果就重新开始
        var beginAgain= function(){
            for(var yyy = 0; yyy < 9;yyy++){
                group[yyy]=0;
                $("#span-"+(yyy+1)).html("");
            }
            $(".loser").fadeOut(1,function(){
                pcStep();
            });
    
        }
    
        //判断输赢与和棋,一共10种情况,8负1平1还没下完
        var judge = function(){
            if(group[0] === group[1] && group[1] === group[2] && group[0]!== 0){
                //第一行连起来了,这样写是因为格子的编号是从0开始,
                result(group[0],1,2,3);
            }else if(group[3] === group[4] && group[4] === group[5] && group[3] !== 0){
                //第二行
                result(group[3],4,5,6);
            }else if(group[6] === group[7] && group[7] === group[8] && group[6] !== 0){
                //第三行
                result(group[6],7,8,9);
            }else if(group[0] === group[3] && group[3] === group[6] && group[0] !== 0){
                //第一列
                result(group[0],1,4,7);
            }else if(group[1] === group[4] && group[4] === group[7] && group[1] !== 0){
                //第二列
                result(group[1],2,5,8);
            }else if(group[2] === group[5] && group[5] === group[8] && group[2] !== 0){
                //第三列
                result(group[2],3,6,9);
            }else if(group[0] === group[4] && group[4] === group[8] && group[0] !== 0){
                //主对角线
                result(group[0],1,5,9);
            }else if(group[2] === group[4] && group[4] === group[6] && group[2] !== 0){
                //次对角线
                result(group[2],3,5,7);
            }else{
                //没分出胜负
                var isTie = true;
                for(var i = 0; i < 9;i++){
                    if(group[i] === 0){
                        //还有格子没下,表示棋还没下完
                        isTie = false;
                    }
                }
                //平局
                if(isTie){
                    result(3,0,0,0);
                }else{
                    var step = 0;       //记录当前是电脑下的第几步
                    for(var i in group){
                        if(group[i] !== 0){
                        //如果某格已经下过了,step++
                            step++;
                        }
                    }
                    if(step %2 === 0 && step !== 0){
                        //如果用户下了,就电脑下
                        pcStep();   
                    }
                }
            }
        };
    
    
        //初始化棋盘点击事件,闭包以获取对应的o
        var initClick = function(i){
            $("#tic-"+i).on("click",function(){
                var step = 0;       //记录当前是下的第几步
                for(var j in group){
                    if(group[j] !== 0){
                        //如果某格已经下过了,step++
                        step++;
                    }
                }
                if(step %2 === 0){
                    //如果电脑还没下,点了也没用
                    return;
                }
    
    
                if(group[i-1] === 0){
                    //如果没下,那么按了才有效,并将之设置为玩家下的,并判断是否和棋或者赢了
                    //console.log("pressed:"+i);
                    //console.log(group);
                    group[i-1] = 2;
                    $("#span-"+i).html(user);
                    judge();
                }
            });
        };
    
        for(var i=1;i<=9;i++){
            initClick(i);       //初始化棋盘点击事件 
        }
    
    
    
    
    });



    欢迎大家加入QQ群一起交流讨论,「吟游」程序人生——YinyouPoet

  • 相关阅读:
    如何将u盘、移动硬盘转化为活动分区--绝招
    jstl错误排除:According to TLD or attribute directive in tag file, attribute value does not accept any expressions
    eclipse中package explore和project explore 怎么相互切换???
    硬盘知识区
    Sublime Text 3下Emmet使用技巧
    sublime text3中设置Emmet输入标签自动闭合
    window如何分区
    HTTP缓存
    react-router 4实现代码分割(code spliting)
    Vue练手项目(包含typescript版本)
  • 原文地址:https://www.cnblogs.com/yinyoupoet/p/13287570.html
Copyright © 2011-2022 走看看