Javascript 初学者如何思考才可以把脑中的东西转换成代码写出来?

本人女生,在学 Javascript。之前没有一点这方面的底子,决定想成为一名程序员之前有经过深思熟虑的思考,接触下来也很喜欢,但现在有点迷惘,希望各位前辈指点迷津。 已经自学三个月,主要集中学 Javascript、AngularJS。目前把 W3school、Khanacademy、Codecademy 的 HTML、CSS、Javascript 的课程、练习都全部看完、做完, 还学了基本的Angular 课程。HTML 和 CSS 我觉得没有什么问题。 最困扰我的是,不知道如何运用 Javascr…
关注者
781
被浏览
43963

30 个回答

@vczh 轮子哥时间宝贵。我就来详细的讲一下这个穷举法的意思。

想当初旁听人工智能课程的时候就是讲的井字棋的AI实现。对一般人来说,实现井字棋最难的地方就是这里的这个AI算法了吧。

)——课件找不到了,好在我是认真听课了的!——(

下图这个就是所谓的穷举法的前两层。
……

实际情况下,完全遍历一颗博弈树的层级的节点数是指数级递增的:

层数 节点数
0 1
1 9
2 72
3 504
4 3024
5 15120
6 60480
7 181440
8 362880

那么实际的层数可以不要那么多就好了。

然后你就会问了,就算遍历了,怎么知道要怎样走呢。我们的Minimax算法就在这里出现了,具体介绍可以移步维基 Minimax 。我就简单的说一下,在井字棋的游戏里面,两个人博弈,一个是用X,一个是用O,这里的X是Max,O就是这里的Min。X的策略就是让这个Max-Min的值尽可能的大,而O的策略就是让这个Max-Min的值尽可能的小。Max-Min的这个值就是我们要的分数值。

那么下一个问题,这个分数要怎么就算呢。其实这个也是可以自己总结出来的,数字也可以改,下面是NTU的网站上给的记分方法:
  • 己方在同一条线上有三子,记+100分;
  • 己方在同一条线上有二子,记+10分;
  • 己方在同一条线上有一子,记+1分;
  • 同理,对方在同一条线上有三子,二子,一子,分别记-100, -10, -1分;
  • 其余情况记0分。(在线上没有任一子或者同时有两方的子数)
然后就可以得到下面的遍历的记分。(这个是4层遍历的情况)


这样一个简单的AI就实现了,接下来我们还可以把这个AI再完善一些,加上剪枝,加上一些经验的规则,一些经验的走法,再给AI加上开局库等等。

----------- @vczh 轮子哥简单一句穷举就把上面这些给带过了,也真是醉了-----------

接下来就是代码层面的具体实现了,这里的话就是要把js具体用进去了。前面的前辈们也说到了,真正学写代码还是要自己一个字母一个字母的敲进去才行,用代码实现我给几个简单的hint。

比如每一个棋位用什么表示,用数字,还是用一个矩阵。至于显示的话,比较直观的就是用Canvas来画。比方说对于计算机来说数组[1,4,9]就可以表示获胜。

另外一个小建议,这样的项目可以不用什么lib来实现,这样比较直观,Angular本身也不适合处理这个项目。它本身就是设计来收发json到页面的。

当然如果是要Hack,用什么lib都没有关系。

最后说一句,Codeacademy上的Javascript的课程并不好,我大概两年前就做过了,学完了不过是熟悉了JS的语法而已,真正JS里的重要的地方都没有提到,比如像什么bind, apply, call, __proto__这些特别容易混淆的地方,都是需要重新去看书。如果是学诚心想学Javascript的话,推荐看Javascript: The Good Parts》 这本书,不容错过。


以上,抛砖引玉,还望指正。

参考拓展:
Tic-tac-toe AI
Cut & Paste Tic Tac Toe with AI(具体的JS实现,其实可以在这个基础上做得更漂亮的)
Minimax算法研究(TicTacToe)
——————————————

P.S. 除去机器人的实现,剩余的部分有好的范例啦,参见 @Jasin Yip,代码很干净漂亮^^
既然是个妹子,那我就来教教你(逃

这是游戏截图:

为简化,胜利我直接用 alert 提示了。

首先是妹子你比较熟悉的 HTML、CSS:
<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <link rel="stylesheet" href="http://cdn.bootcss.com/meyer-reset/2.0/reset.css">
    
    <style>
        .container {
            width:340px;
            height:320px;
            margin:10px auto;
            padding-left:10px;
            padding-top:20px;
            border:2px solid #ccc;
        }
        .cells {
            display: block;
            float:left;
            width:80px;
            height:80px;
            margin-left: 20px;
            margin-bottom: 20px;
            border:2px solid #ccc;
            color:#777;
            text-decoration: none;
            font-size: 60px;
            line-height:80px;
            text-align: center;
        }
    </style>
</head>
<body>
    
    <div class="container">
        <a href="javascript:;" class="cells" id="cell-0-0"></a>
        <a href="javascript:;" class="cells" id="cell-0-1"></a>
        <a href="javascript:;" class="cells" id="cell-0-2"></a>
        <a href="javascript:;" class="cells" id="cell-1-0"></a>
        <a href="javascript:;" class="cells" id="cell-1-1"></a>
        <a href="javascript:;" class="cells" id="cell-1-2"></a>
        <a href="javascript:;" class="cells" id="cell-2-0"></a>
        <a href="javascript:;" class="cells" id="cell-2-1"></a>
        <a href="javascript:;" class="cells" id="cell-2-2"></a>
    </div>

    <script src="js/game.js"></script>
</body>
</html>
既然你比较熟悉,我就不解释了。

接下来是关键的 Javascript 部分:

第一步,我们要初始化一些变量:
var TICK = '√';
var CROSS = 'Χ';

var model = []; // 用于存放数据模型,0 表示未点击,1 表示打叉,2 表示打勾
var cells = []; // 用于存放 DOM 中的 a 标签

var flag; // 用于表示轮到谁了

// 初始化二维数组
for (i=0; i<3; i++){
    model[i] = [];
    cells[i] = [];
}

同时,我们每次开始新游戏都需要清空 a 标签中显示的字符,以及重置数据模型 model,所以我将它做成一个函数 newGame():
var newGame = function(){
    for (i=0; i<3; i++)
        for (j=0; j<3; j++){
            model[i][j] = 0;
            cells[i][j].text = ' ';
        }
    flag = 1;
 // 1 表示以 '√' 开始,若为 0 则表示以 'Χ' 开始
}

然后,每次修改数据模型后,我们都要刷新视图:
var freshView = function(){
    var i, j;

    for (i=0; i<3; i++)
        for (j=0; j<3; j++)
            if (model[i][j] == 1)
                cells[i][j].text = CROSS;
            else if (model[i][j] == 2)
                cells[i][j].text = TICK;
}

当然,我们还需要一个判断胜利的函数:
var checkWin = function(x, y){

    // 检查 X 方向
    if (model[x][y] != 0 && model[0][y] == model[1][y] && model[0][y] == model[2][y])
        return 1;

    // 检查 Y 方向
    if (model[x][y] != 0 && model[x][0] == model[x][1] && model[x][0] == model[x][2])
        return 1;

    // 如果在 \ 方向
    if (x == y)
        // 检查 \ 方向
        if (model[0][0] != 0 && model[0][0] == model[1][1] && model[0][0] == model[2][2])
            return 1;

    // 如果在 / 方向
    if ( ( x == 2 && y == 0 ) || ( x == 0 && y == 2 ) || ( x == 1 && y == 1 ) ) 
        // 检查 / 方向
        if (model[2][0] != 0 && model[2][0] == model[1][1] && model[2][0] == model[0][2])
            return 1;

    return 0;
}

还有,我们需要向每个 a 标签绑定一个函数,用来修改数据模型:
 for (i=0; i<3; i++)
        for (j=0; j<3; j++){
            cells[j][i] = document.getElementById('cell-' + i + '-' + j);

            (function(j, i){
                cells[i][j].onclick = function(){
                    model[i][j] = flag + 1;

                    if (flag)
                        flag = 0;
                    else
                        flag = 1;

                    freshView();

                    if (checkWin(i, j)){
                        if (flag)
                            alert("用 " + CROSS + " 的选手胜利!");
                        else
                            alert("用 " + TICK + " 的选手胜利!");
                        newGame();
                    }
                    
                }
            })(i, j);

        }
这里我为了写得优雅一点,我使用了闭包,以便于批量绑定 onclick 事件。

所有 js 写起来就是这样:
var TICK = '√';
var CROSS = 'Χ';

var model = [];
var cells = [];

var flag; // 用于表示轮到谁了

var newGame = function(){
    for (i=0; i<3; i++)
        for (j=0; j<3; j++){
            model[i][j] = 0;
            cells[i][j].text = ' ';
        }
    flag = 1;
}

var freshView = function(){
    var i, j;

    for (i=0; i<3; i++)
        for (j=0; j<3; j++)
            if (model[i][j] == 1)
                cells[i][j].text = CROSS;
            else if (model[i][j] == 2)
                cells[i][j].text = TICK;
}

var checkWin = function(x, y){

    // 检查 X 方向
    if (model[x][y] != 0 && model[0][y] == model[1][y] && model[0][y] == model[2][y])
        return 1;

    // 检查 Y 方向
    if (model[x][y] != 0 && model[x][0] == model[x][1] && model[x][0] == model[x][2])
        return 1;

    // 如果在 \ 方向
    if (x == y)
        // 检查 \ 方向
        if (model[0][0] != 0 && model[0][0] == model[1][1] && model[0][0] == model[2][2])
            return 1;

    // 如果在 / 方向
    if ( ( x == 2 && y == 0 ) || ( x == 0 && y == 2 ) || ( x == 1 && y == 1 ) ) 
        // 检查 / 方向
        if (model[2][0] != 0 && model[2][0] == model[1][1] && model[2][0] == model[0][2])
            return 1;

    return 0;
}



window.onload = function(){
    var i, j;
    
    // 初始化二维数组
    for (i=0; i<3; i++){
        model[i] = [];
        cells[i] = [];
    }

    for (i=0; i<3; i++)
        for (j=0; j<3; j++){
            cells[j][i] = document.getElementById('cell-' + i + '-' + j);

            (function(j, i){
                cells[i][j].onclick = function(){
                    model[i][j] = flag + 1;

                    if (flag)
                        flag = 0;
                    else
                        flag = 1;

                    freshView();

                    if (checkWin(i, j)){
                        if (flag)
                            alert("用 " + CROSS + " 的选手胜利!");
                        else
                            alert("用 " + TICK + " 的选手胜利!");
                        newGame();
                    }
                    
                }
            })(i, j);

        }

    newGame();

}

这个游戏我没有写机器人,因为写那个又会复杂很多,建议妹子先把这些理解掉,再研究写机器人吧!

可能闭包那里对于你现在的水平来说有点超纲,你可以用一些比如 with 之类(自己Google 一下咯)的方法来做批量绑定,当然了,你也可以傻傻地复制粘贴……

最后总结一下,其实你选择做一个游戏来锻炼自己的 Javascript 能力这点是非常正确的,不过可能你有些逻辑关系没有理清楚,下次如果觉得大脑一片空白,不如试着画一下流程图吧~
为什么?