精华内容
下载资源
问答
  • c语言综合编程训练

    2012-10-17 15:28:27
    一个c语言综合编程训练文档,综合训练的PPT
  • 用 JavaScript 实现一个 TicTacToe 游戏 —— 编程训练

    万次阅读 多人点赞 2020-10-30 08:28:45
    这里我们给大家讲讲一个好玩的编程练习,很多同学想到编程练习就会觉得与算法有关。但是往往在编程的过程中,我们要实现某种逻辑或者是功能的时候,确实是需要用到算法。


    同学们好,我是来自 《技术银河》的 💎 三钻

    这里我们给大家讲讲一个好玩的编程练习,很多同学想到编程练习就会觉得与算法有关。但是往往在编程的过程中,我们要实现某种逻辑或者是功能的时候,确实是需要用到算法。但是我觉得 Winter 老师说的也挺对的。

    编程练习有一部分是与算法和数据结构密切相关的,但是也有一部分是跟语言比较相关的。我们既要知道这个算法我们怎么去写,我们还要跟语言相结合,就是怎么去用我们的语言更好的去表达。不过编程练习的核心还是提升我们编程的能力。

    TicTacToe 是一个非常著名的一个小游戏,国外叫做 TicTacToe,国内我们叫它 “三子棋” 或者 “一条龙”。

    如果我们要实现这个小游戏,我们首先就需要了解这个游戏的规则。如果不懂这个游戏的规则,我们是无法用代码语言来表达的。

    「一」规则

    • 棋盘:3 x 3 方格
    • 双方分别持有 ⭕️ 和 ❌ 两种棋子
    • 双方交替落子
    • 率先连成三子直线的一方获胜
    • 这个直线分别可以是“横”,“竖”,“斜” 三种

    「二」代码实现

    「1」创建棋盘

    这个游戏是基于拥有一个可以放棋子的棋盘,换做我们的程序的话,就是一个存放数据的地方,记录着每个棋子所放在的位置。

    这里我们可以用一个二维数字来存放:

    let parttern = [
      [2, 0, 0],
      [0, 1, 0],
      [0, 0, 0]
    ]
    
    console.log(pattern)
    
    • 0 表示为没有棋子存放在这个棋盘的位置
    • 1 表示为在其面上有 ⭕️ 的棋子
    • 2 表示为在其面上有 ❌ 的棋子

    我们拥有棋盘的数据之后,因为这是一个可以给用户玩的游戏,我们当然需要展示在浏览器上的。所以这里我们就需要加入 HTML 和 CSS。

    <style>
          * {
            box-sizing: border-box;
            background: #0f0e18;
          }
          .container {
            margin: 3rem;
            display: flex;
            justify-content: center;
            align-items: center;
            flex-direction: column;
          }
          h1 {
            color: #ea4dc5;
          }
          #board {
            width: 300px;
            height: 300px;
            display: flex;
            flex-wrap: wrap;
          }
          .cell {
            width: 100px;
            height: 100px;
            background: #2d2f42;
            border: 1px solid #0f0e18;
            cursor: pointer;
            display: flex;
            justify-content: center;
            align-items: center;
            transition: background 500ms ease;
          }
          .cell:hover {
            background: #454966;
          }
          .cell .iconfont {
            background: transparent;
            width: 100%;
            height: 100%;
            font-size: 50px;
            line-height: 100px;
            text-align: center;
            vertical-align: middle;
          }
          .blue {
            color: #7ceefc;
          }
          .purple {
            color: #ea4dc5;
          }
          #tips p {
            color: #dddddd;
          }
    </style>
    
    <div class="container">
      <h1>Tic-tac-toe Simulator</h1>  <!-- 标题 -->
      <div id="board"></div>  <!-- 棋盘 -->
      <div id="tips"></div> <!-- 这里是用于提示的,后面的功能会用到 -->
    </div>
    

    写好了上边的 HTML 和 CSS,我们会发现棋盘上是一个空 div,棋盘上的格子还没有被加上。

    这里我们是需要根据我们的 pattern 中的数据来创建棋盘的。所以我们需要加入 JavaScript ,根据我们的棋盘数据来创建我们棋盘上的格子和棋子。

    // 棋盘
    let pattern = [
      [2, 0, 0],
      [0, 1, 0],
      [0, 0, 0]
    ];
    
    let chess = 1;
    
    /** 渲染棋盘 */
    function build() {
      // 获取棋盘元素
      let board = document.getElementById('board');
    
      board.innerHTML = '';
    
      // 填充棋盘
      for (let y = 0; y < 3; y++) {
        for (let x = 0; x < 3; x++) {
          let cell = document.createElement('div');
          cell.classList.add('cell');
    
          // 创建圆圈棋子
          let circle = document.createElement('i');
          circle.classList.add('iconfont', 'icon-circle', 'blue');
          // 创建叉叉棋子
          let cross = document.createElement('i');
          cross.classList.add('iconfont', 'icon-cross', 'purple');
          // 创建空棋子
          let empty = document.createElement('i');
    
          let chessIcon = pattern[y][x] == 2 ? cross : pattern[y][x] == 1 ? circle : empty;
          cell.appendChild(chessIcon);
          board.appendChild(cell);
        }
      }
    }
    

    创建这个棋盘我们使用了以下思路:

    • 首先循环一遍我们的二维数组 pattern
    • 一个双循环就等同于我们从上到下,从左到右的走了一篇这个棋盘数据了
    • 在循环这个棋盘的同时我们需要把棋子也同时放入棋盘中
    • 首先我们创建一个棋盘格子 div 元素,给予它一个 class 名为 cell
    • 如果我们遇到 1 的时候就放入 ⭕️ 到 cell 里面
    • 如果我们遇到 2 的时候就放入 ❌ 到 cell 里面
    • 如果我们遇到 0 的时候就放入一个 “空” 到 cell 里面
    • 棋子这里我给了一个 i 元素,并且它的 class 用了 iconfont
    • 当然如果我们也是可以用 emoji 替代这部分内容,直接给 cell 元素加入文本 (例如:cell.innerText = '⭕️'
    • 最后把 cell 加入到棋盘 board 里面即可

    这里的代码我使用了 “阿里巴巴” 的 iconfont,当然我们也可以直接用 emoji。跟着我的文章练习的同学,也可以使用我在用的 iconfont。这里我附上我在使用的 iconfont 地址:

    <link rel="stylesheet" href="//at.alicdn.com/t/font_2079768_2oql7pr49rm.css" />
    

    最后显示出来的就是这样的效果:

    「2」落棋子

    我们已经拥有一个 3 x 3 的棋盘了,下来就是实现落棋子的动作的方法。我们想要达到的效果就是让用户点击一个格子的时候,就把棋子落到对应点击的位子。如果该位置已经有棋子了就不生效。

    /**
     * 把棋子放入棋盘
     *
     *   - 先把当前棋子代号给予当前 x,y 位置的元素
     *
     * @param {Number} x x轴
     * @param {Number} y y轴
     */
    function move(x, y) {
      if (pattern[y][x]) return;
    
      pattern[y][x] = chess;
    
      chess = 3 - chess;
    
      build();
    }
    

    这段代码的逻辑很简单:

    • 如果当前 xy 位置已经有棋子,那必然就不是 0 ,如果是 0 就直接返回,推出此方法即可
    • 如果可以落下棋子,就给当前位置赋予棋子的代码 1 就是 ⭕️, 2 就是 ❌
    • 这里我们使用了 1 和 2 的对等特性, 3 − 1 = 2 3-1=2 31=2,同样 3 − 2 = 1 3-2=1 32=1 ,用这样的对等换算我们就可以反正当前棋子了
    • 也就是说上一位玩家的棋子是 1 3 − 当 前 棋 子 = 下 一 位 玩 家 的 棋 子 3-当前棋子=下一位玩家的棋子 3= ,那就是 2
    • 最后调用我们的棋盘构建方法 build 重新构建棋盘即可

    这个方法写了,但是我们发现我们根本没有调用到它,所以在棋盘上点击的时候是无任何效果的。

    所以这里我们要在构建棋盘的时候,就给每一个格子加上一个 “点击 (click)” 事件的监听。

    /** 渲染棋盘 */
    function build() {
      //... 省略了这部分代码
    
      let chessIcon = pattern[y][x] == 2 ? cross : pattern[y][x] == 1 ? circle : empty;
      cell.appendChild(chessIcon);
      cell.addEventListener('click', () => move(x, y)); // 《 == 这里加入监听事件
      board.appendChild(cell);
      
     // ... 省略了这部分代码
    }
    

    这样我们的棋盘就可以点击格子放下棋子了!

    「3」判断输赢

    我们的游戏到这里已经可以开始玩了,但是一个游戏不能没有结局吧,所以我们还需要让它可以判断输赢。

    在了解 TicTacToe 这个游戏的时候,我们知道这个游戏是有几个条件可以胜利的,就是一方的棋子在“”,“”,“”连成一线就可以赢得游戏。所以这里我们就需要分别检测这三种情况。

    通过分析我们就有 4 种情况:

    • 竖行有 3 个棋子都是一样的
    • 横行有 3 个棋子都是一样的
    • 正斜行 “/” 有 3 个棋子都是一样的
    • 反斜行 “\” 有 3 个棋子都是一样的

    那么我们就写一个 check() 方法来检测:

    /**
     * 检查棋盘中的所有棋子
     *
     *  - 找出是否已经有棋子获胜了
     *  - 有三个棋子连成一线就属于赢了
     *
     * @param {Array} pattern 棋盘数据
     * @param {Number} chess 棋子代号
     * @return {Boolean}
     */
    function check(pattern, chess) {
      // 首先检查所有横行
      for (let i = 0; i < 3; i++) {
        let win = true;
        for (let j = 0; j < 3; j++) {
          if (pattern[i][j] !== chess) win = false;
        }
        if (win) return true;
      }
    
      // 检查竖行
      for (let i = 0; i < 3; i++) {
        let win = true;
        for (let j = 0; j < 3; j++) {
          if (pattern[j][i] !== chess) win = false;
        }
        if (win) return true;
      }
    
      // 检查交叉行
      // 这里用花括号 "{}" 可以让 win 变量
      // 变成独立作用域的变量,不受外面的
      // win 变量影响
    
      // "反斜行 \ 检测"
      {
        let win = true;
        for (let j = 0; j < 3; j++) {
          if (pattern[j][j] !== chess) win = false;
        }
        if (win) return true;
      }
    
      // "正斜行 / 检测"
      {
        let win = true;
        for (let j = 0; j < 3; j++) {
          if (pattern[j][2 - j] !== chess) win = false;
        }
        if (win) return true;
      }
    
      return false;
    }
    

    有了这个检测输赢的方法,我们就可以把它放到一个地方让它检测游戏的赢家了。

    我们可以把这个检测放入用户落棋子的时候,在棋子类型反转和重建之前,就检测当前玩家是否胜利了。

    /** 全局变量 —— 是否有赢家了 */
    let hasWinner = false
    
    /**
     * 把棋子放入棋盘
     *
     *   - 先把当前棋子代号给予当前 x,y 位置的元素
     *   - 检测是否有棋子已经赢了
     *
     * @param {Number} x x轴
     * @param {Number} y y轴
     */
    function move(x, y) {
      if (hasWinner || pattern[y][x]) return;
    
      pattern[y][x] = chess;
    
      // 这里加入了胜负判断
      if (check(pattern, chess);) {
        tips(chess == 2 ? '❌ is the winner!' : '⭕️ is the winner!');
      }
    
      chess = 3 - chess;
    
      build();
    }
    

    这里我们需要加入一个 hasWinner 的全局变量,这个是用来记录这个游戏是否已经有赢家了,如果有赢家,就不能让用户在落棋子了。所以在 move 方法的开头就判断了,如果有赢家了就直接返回,退出方法。

    加入这段代码我们就可以判断胜负的,但是我们还需要在页面上提示用户到底是谁赢了才完美嘛。所以这里我们加入了一个提示插入的方法:

    /**
     * 插入提示
     * @param {String} message 提示文案
     */
    function tips(message) {
      let tips = document.getElementById('tips');
    
      tips.innerHTML = '';
    
      let text = document.createElement('p');
      text.innerText = message;
      tips.appendChild(text);
    }
    

    最终的效果如下:

    「三」实现 AI

    现在我们已经拥有了一个可以玩的 “TicTacToe” 游戏了。但是在这个时代,没有一点 AI 支持的程序,怎么能成为一个好的产品呢?所以这里我们来一起给我们的游戏加入一下 AI 的功能。

    「1」预判下一步是否会赢

    我们首先整理一下这个需求,在某一个玩家落棋之后,就可以检测这盘棋的下一个玩家是否即将会赢。

    要判断下一个玩家是否即将会赢,我们就需要模拟下一个玩家落棋子的位置,其实对我们的程序来说,就是把棋子依次放入现在棋盘中空出来的格子,然后判断下一个玩家会不会赢了游戏。

    实现思路:

    • 我们的时机是在上一个玩家落下棋子后,开始模拟下一个玩家所有可能走的位置
    • 这个时候我们可以循环现在的棋盘上的格子,模拟下一个玩家把棋子放入每一个非空的格子的结果
    • 如果遇到有一个格子放入棋子后会赢的话,那下一个玩家就是可以赢了!

    这里我们要注意的是,我们需要模拟下一个玩家在当前局面下走了每一个空格子的结果,这个时候如果我们用原来的 pattern 数据来模拟,就会影响了现在游戏里棋子的位置。所以我们需要不停的克隆现在棋盘的数据来模拟。这样才不会影响当前棋盘的数据。

    实现预测方法:willWin()

    /**
     * 检测当前棋子是否要赢了
     *
     *   - 循环整个棋盘
     *   - 跳过所有已经有棋子的格子
     *   - 克隆棋盘数据(因为我们要让下一个棋子都走一遍所有空位的地方
     *     看看会不会赢,如果直接在原来的棋盘上模拟,就会弄脏了数据)
     *   - 让当前棋子模拟走一下当前循环到的空位子
     *   - 然后检测是否会赢了
     *
     * @param {Array} pattern 棋盘数据
     * @param {Number} chess 棋子代号
     * @return {boolean}
     */
    function willWin(pattern, chess) {
      for (let i = 0; i < 3; i++) {
        for (let j = 0; j < 3; j++) {
          if (pattern[i][j]) continue;
          let tmp = clone(pattern);
          tmp[i][j] = chess;
          if (check(tmp, chess)) {
            return true;
          }
        }
      }
      return false;
    }
    

    克隆方法:clone()

    /**
     * 克隆棋盘数据
     * @param {Array} pattern 棋盘数据
     * @return {Array} 克隆出来的棋盘数据
     */
    function clone(pattern) {
      return JSON.parse(JSON.stringify(pattern));
    }
    

    最后我们需要在上一个玩家落棋,之后加入输赢预判方法:“改装我们的 move() 方法即可”

    /**
     * 把棋子放入棋盘
     *
     *   - 先把当前棋子代号给予当前 x,y 位置的元素
     *   - 检测是否有棋子已经赢了
     *   - 反转上一个棋子的代号,并且重新渲染棋盘
     *
     * @param {Number} x x轴
     * @param {Number} y y轴
     */
    function move(x, y) {
      if (hasWinner || pattern[y][x]) return;
    
      pattern[y][x] = chess;
    
      hasWinner = check(pattern, chess);
      if (hasWinner) {
        tips(chess == 2 ? '❌ is the winner!' : '⭕️ is the winner!');
      }
    
      chess = 3 - chess;
    
      build();
    
      if (hasWinner) return;
    
      // 这里加入了输赢预判
      if (willWin(pattern, chess)) {
        tips(chess == 2 ? '❌ is going to win!' : '⭕️ is going to win!');
      }
    }
    

    这里还加入了一个判断:if(hasWinner) return;,这个是为了如果这步棋有玩家已经赢了,我们就不需要再预判输赢了,可以直接返回了。

    就这样我们就实现了一个,智能的输赢预判功能了,最后的效果如下图:

    「2」预判游戏胜负

    上面我们实现的 AI 只能给我们预判下一步棋是否会赢。但是并没有给我们预判出,以现在的局面最终谁会赢。

    这里我们一起来实现一个更加智能的 AI,让程序在每一个玩家落子之后,判断以现在棋子的局面,最终谁会赢,或者是否结果是和棋。

    实现思路:

    • 首先我们要给我们游戏的最终结果定义好标识
    • 结果是 -1 就是最后会输
    • 结果是 0 就是最后会和
    • 结果是 1 就是最后会赢
    • 这里胜负是正负相反的,这个设计就是为了让我们更好的判断输赢
    • 也可以这么理解,对方的棋子放入了可以赢的位置,那么我们的结果就肯定是输,这个结果就是刚好相反的,所以我们用了正负的标识来表达就非常方便我们用程序来判断
    • 使用我们上面说到的逻辑,我们就可以锁定一个思路,如果我们找到对方要输的棋子的位置,那我们就是会赢的位置,如果我们找到对方要赢的位置,我们就要输
    • 利用这样的逻辑我们可以用一个递归的方法来循环模拟两个玩家的落子动作,并且判断出落棋后的结果,一直深度搜索直到我们找到一个赢家
    • 这个递归最终会模拟两个玩家走了这盘棋的所有情况并且找到一个能赢的局面,就可以结束循环了。这个也叫做“胜负节支”。赢已经是最好的结果了,我们并不需要继续模拟到所有的情况,我们已经找到最佳的情况了。
    • 当然在其他棋盘游戏中,可能有很多胜利的局面,有可能是赢了但是损失了很多,也有赢了但是又快又减少了损失。但是在这个 “TicTacToe” 当中就不需要考虑这些因素了。

    说了那么多,我们来看看代码是怎么实现的,我们先来实现一个寻找最佳结果的方法 bestChoice

    /**
     * 找到最佳结果
     *
     *   - 结果是 -1 就是最后会输
     *   - 结果是  1 就是最后会赢
     *   - 结果是  0 就是最后会和
     *
     * @param {Array} pattern 棋盘数据
     * @param {Number} chess 棋子代号
     */
    function bestChoice(pattern, chess) {
      // 定义可以赢的位置
      let point;
    
      // 如果当前局面,我们已经即将要赢了
      // 我们就可以直接返回结果了
      if ((point = willWin(pattern, chess))) {
        return {
          point: point,
          result: 1,
        };
      }
    
      // 定义一个结果,-2 是要比 -1,0,1 要小
      // 所以是一个最差的局面,我们需要从最差的局面开始
      // 数字变得越高,我们就越接近赢
      let result = -2;
      point = null;
      outer: for (let y = 0; y < 3; y++) {
        for (let x = 0; x < 3; x++) {
          // 跳过所有已经有棋子的地方(因为不能在这些地方放我们的棋子了)
          if (pattern[y][x]) continue;
    
          // 先克隆当前棋盘数据来做预测
          let tmp = clone(pattern);
    
          // 模拟我们的棋子下了这个位置
          tmp[y][x]= chess;
    
          // 找到我们下了这个棋子之后对手的最佳结果
          let opp = bestChoice(tmp, 3 - chess);
    
          // 记录最佳结果
          if (-opp.result >= result) {
            result = -opp.result;
            point = [x, y];
          }
    
          if (result === 1) break outer;
        }
      }
    

    这段代码做了什么?其实就是让我们的程序进行了自我博弈,A 方找到自己可以赢的落子位置,然后 B 方找自己可以赢的落子位置,知道最后进入一个结果,要不两方都赢不了,那就是和局,要不就是一方获胜为止。

    我们会关注到,这里 bestChoice 返回了一个对象,一个属性是 result, 这个就是预判出来这个游戏最后的结果。而另外一个是 point,这个就是当前玩家可以走的位置,也是可以达到最佳结果的位置。这个在我们实现最后一个 AI 功能的时候会用到。这一步我们只需要用到 result 属性来做判断,输出胜负提示即可。

    有了这个更高级的预判 AI,我们就可以把我们的 willWin() 替换下来了。

    这里我们改造一下我们的 move() 方法:

    /**
     * 把棋子放入棋盘
     *
     *   - 先把当前棋子代号给予当前 x,y 位置的元素
     *   - 检测是否有棋子已经赢了
     *   - 反转上一个棋子的代号,并且重新渲染棋盘
     *
     * @param {Number} x x轴
     * @param {Number} y y轴
     */
    function userMove(x, y) {
      if (hasWinner || pattern[y][x]) return;
    
      pattern[y][x] = chess;
    
      if ((hasWinner = check(pattern, chess))) {
        tips(chess == 2 ? '❌ is the winner!' : '⭕️ is the winner!');
      }
    
      chess = 3 - chess;
    
      build();
    
      if (hasWinner) return;
    
      let result = bestChoice(pattern, chess).result;
      let chessMark = chess == 2 ? '❌' : '⭕️';
      tips(
        result == -1
          ? `${chessMark} is going to loss!`
          : result == 0
          ? `This game is going to draw!`
          : `${chessMark} is going to win!`
      );
    }
    

    最后出来的效果就是如此:

    当然这个预判是在预判最好的结果,这里我们假设了两个玩家都是非常优秀的,每一步都是走了最佳的位置。但是如果玩家失误还是有可能反败为胜的哦!

    「3」加入电脑玩家

    我们前面实现的 AI,已经足够让我们实现一个很聪明的 AI 电脑玩家了。

    在上一步我们实现了 bestChoice() 方法的时候,这个方法返回的属性里,有一个 point 属性,这个point 其实就是玩家最佳落子的位置,我们只需要让程序自动落子到这个位置,我们就完成了电脑玩家的功能了。

    实现思路:

    • 上一个玩家落子之后,就可以调用我们电脑玩家落子方法
    • 使用 bestChoice 找到最佳结果的落子位子
    • 给最佳位子放下电脑玩家的棋子
    • 最后继续预测这个游戏的结局

    真的就是那么简单,我们来看看代码怎么实现:

    这里我们需要改造 move() 方法,改为 userMove(),并且创建一个 computerMove()

    /**
     * 把棋子放入棋盘
     *
     *   - 先把当前棋子代号给予当前 x,y 位置的元素
     *   - 检测是否有棋子已经赢了
     *   - 反转上一个棋子的代号,并且重新渲染棋盘
     *
     * @param {Number} x x轴
     * @param {Number} y y轴
     */
    function userMove(x, y) {
      if (hasWinner || pattern[y][x]) return;
    
      pattern[y][x] = chess;
    
      if ((hasWinner = check(pattern, chess))) {
        tips(chess == 2 ? '❌ is the winner!' : '⭕️ is the winner!');
      }
    
      chess = 3 - chess;
    
      build();
    
      if (hasWinner) return;
    
      computerMove();
    }
    
    /** 电脑自动走棋子 */
    function computerMove() {
      let choice = bestChoice(pattern, chess);
    
      if (choice.point) pattern[choice.point[1]][ choice.point[0]] = chess;
    
      if ((hasWinner = check(pattern, chess))) {
        tips(chess == 2 ? '❌ is the winner!' : '⭕️ is the winner!');
      }
    
      chess = 3 - chess;
      build();
    
      if (hasWinner) return;
    
      let result = bestChoice(pattern, chess).result;
      let chessMark = chess == 2 ? '❌' : '⭕️';
      tips(
        result == -1
          ? `${chessMark} is going to loss!`
          : result == 0
          ? `This game is going to draw!`
          : `${chessMark} is going to win!`
      );
    }
    

    就是这样我们就实现了电脑玩家,这样一个单身狗也可以玩 “TicTacToe” 了。 😂😂😂

    开个玩笑哈,说不定玩着玩着你就找到人生另一半的啦!加油哦!💪

    「四」优化

    写到这里,我们已经完成了一个 “TicTacToe” 游戏了。实现完一个功能后,我们都会问自己一个问题,这个程序有没有可以优化的地方呢?

    以我们上面的代码示例,其实是有一个地方可以优化的,那就是我们的棋盘数据。

    示例里面我们的棋盘数据是使用了一个二维数组的,这样在我们克隆的时候需要使用 JSON 转换来克隆,这个过程我们需要用到大量的内存空间。

    如果我们把棋盘的数据改造成一个一维数组的话,我们就可以用 JavaScript 里面的 Object.create(pattern) 来克隆了。这个方法创建了一个新对象,使用现有的对象来提供新创建的对象的 __proto__,这样的方式就能节省大量的内存空间。因为我们使用了原型克隆,而不是整个对象的克隆。

    首先我们改造棋盘数据

    // 棋盘
    let pattern = [
      0, 0, 0, 
      0, 0, 0, 
      0, 0, 0
    ];
    

    现在我们棋盘是一个一位数字,那么我们怎么换行呢?

    用数学去理解的话:当前行数 * 3 + 当前行的指针位置​,当然我们行数在数组是从0开始的。

    所以就是这样一个现象:

    [

    (0 * 3 + 1), (0 * 3 + 2), (0 * 3 + 3),

    ( 1 * 3 + 1), ( 1 * 3 + 2), ( 1 * 3 + 3),

    (2 * 3 + 1), (2 * 3 + 2), (2 * 3 + 3),

    ]

    最后得出的位置就是这样的:

    [

    1, 2, 3,

    4, 5, 6,

    7, 8, 9

    ]

    这样是不是就能找到我们 9 个格子的位置呀?

    所以在代码中,我们只需要把所有的 pattern[y][x] 改为 pattern[y * 3 + x] 即可!

    最后我们可以改造我们的 clone() 方法:

    /**
     * 克隆棋盘数据
     * @param {Array} pattern 棋盘数据
     * @return {Array} 克隆出来的棋盘数据
     */
    function clone(pattern) {
      return Object.create(pattern);
    }
    

    「终」总结

    其实这个 “TicTacToe” 练习的重点在于抽象思路。我们是怎么把一个游戏复杂的逻辑一步一步抽象成我们程序的代码,通过 if else 判断,加上 iteration 循环来实现我们的需求和功能。这个过程其实不单纯的锻炼我们的算法和数学,更多是编程能力。

    这里我又要感叹一下,覃超老师经常说的 “五毒神掌” 了。一切学习和技能不在于我们有多么好的天赋,更多的在于我们有没有反复练习。只有经历过无数遍磨练的知识和技能才会变成我们内力。


    我是来自《技术银河》的三钻:“学习是为了成长,成长是为了不退步。坚持才能成功,失败只是因为没有坚持。同学们加油哦!下期见!”


    推荐专栏

    小伙伴们可以查看或者订阅相关的专栏,从而集中阅读相关知识的文章哦。

    • 📖 《前端进阶》 — 这里包含的文章学习内容需要我们拥有 1-2 年前端开发经验后,选择让自己升级到高级前端工程师的学习内容(这里学习的内容是对应阿里 P6 级别的内容)。

    • 📖 《数据结构与算法》 — 到了如今,如果想成为一个高级开发工程师或者进入大厂,不论岗位是前端、后端还是AI,算法都是重中之重。也无论我们需要进入的公司的岗位是否最后是做算法工程师,前提面试就需要考算法。

    • 📖 《FCC前端集训营》 — 根据FreeCodeCamp的学习课程,一起深入浅出学习前端。稳固前端知识,一起在FreeCodeCamp获得证书

    • 📖 《前端星球》 — 以实战为线索,深入浅出前端多维度的知识点。内含有多方面的前端知识文章,带领不懂前端的童鞋一起学习前端,在前端开发路上童鞋一起燃起心中那团火🔥

    展开全文
  • 编码培训网站 编程训练 c c ++解题 使用单元测试,而不是main函数 web.md译文及链接 web / num.c(num.cpp)题解源码及思路描述 网路
  • CodingBootCampGoLang 硕士学院的GoLang编程训练
  • c语言经典编程训练题筛选适合初学者自我训练,包含很多c语言语法知识
  • Qt简易 C语言编程训练 考试系统 文档在此http://blog.csdn.net/d759378563/article/details/25367033
  • Matlab编程训练 任务书 专 业 学生姓名 班 级 学 号 指导教师 段文勇 完成日期 2018年6月03日 实训要求 实训课时为一周要求在机房上满五天课 成绩评定分为两部分平时50% +作业50;其中平时包括 出勤上机情况最后上机...
  • plc编程训练使用

    2013-09-13 22:37:38
    有利于PLC编程训练,建议想学习PLC的同学们注意
  • 在Web教学课件中进行启发式编程训练对培养学生规范编程和思考问题很有帮助。该编程训练软件利用客户端的 DHTML技术实现与学生的交互和动态显示处理,以教师模板程序为匹配目标,在模板程序中通过特殊标记给出提示和...
  • C语言综合编程训练,新手可以试试~~~~ 都是打基础的,不是很难~~~
  • c++编程训练

    2014-11-04 22:16:02
    编程练习,c++五子棋。没事可以练练手,新手不妨一试啊
  • 编程-Bootcamp-Java 提供给参与者的BRACU编程训练营的代码
  • MATLAB编程训练营 概述 计算机编程技能正在成为进行心理学和神经科学研究的重要工具,并有助于其再现性。 这个为期7周的训练营的目的是介绍使用Matlab(R)编程环境进行计算机编程。 该训练营将包括演讲和“动手”...
  • Shell 教程:1.Shell入门;2.Shell编程之变量详解;3.if条件语句学习;4.使用if条件语句编写MySQL备份脚本;5.IF条件综合Shell实战脚本编写;6等等等。
  • leetcode中国 Programming-Training 个人编程练习题库 C++ : 试题来源:、 、、 相关链接:
  • 最适合编程训练的三大OJ(从易到难)

    万次阅读 多人点赞 2018-09-16 21:06:09
    按照学习的需求来说依次介绍,方便初学者使用: ... 优点:全中文OJ,题目分类,有题解 ...另外还有题解分享,不会的题目可以看看别人的代码,非常如何小白入门学编程以及ACM蓝桥杯训练入门使用。建议入门至少刷300道...

    按照学习的需求来说依次介绍,方便初学者使用:

    1.Dotcpp网www.dotcpp.com
    这里写图片描述
    优点:全中文OJ,题目分类,有题解
    这可能是初学者最好用的OJ甚至没有之一了
    全中文的友好操作,充足的语法基础题、二级C语言题、入门题足够小白入门以及了解OJ的机制了。
    另外还有题解分享,不会的题目可以看看别人的代码,非常如何小白入门学编程以及ACM蓝桥杯训练入门使用。建议入门至少刷300道题起

    不足:
    题目不足、难度偏低,不适合竞赛训练,


    2.杭电OJ
    这里写图片描述
    优点:这应该是每个ACMer必刷的OJ了,由于其充足的题库、各大比赛都在杭电举办,因此非常多的竞赛同学都在这里刷题,甚至给出了要像拿省XX等奖,必须XX道题的建议

    不足:重复题不少,全英文操作,不适合小白入门

    3.UVAhttps://uva.onlinejudge.org/
    这里写图片描述
    优点:权威专业,可以说是国外OJ里面最有名气的了,国内看过刘汝佳经典算法白书的都知道里面的例题都在这个OJ上面。适合搞ICPC或CCPC级别的大神刷题用

    不足:网速慢,国内访问不方便

    这里写图片描述

    展开全文
  • 适合2020年积木机器人比赛的资料,含有比赛规则和实现流程
  • 包含面试编程技能训练合集、C及C++面试笔试题合集共近50个pdf文档和众多图片文档(公司的笔试面试题目),对于提高编程能力和面试笔试水平有很大帮助。
  • 很不错的C程序题目跟例子代码 C等级考试必备题目
  • 《c语言练习题——递归和栈编程训练》 1.题目: 利用递归方法实现一个函数,该函数能够实现n的阶乘,即 n! = n*(n-1)*…*3*2*1; 2.题目:利用字符数组实现一个先入后出的栈结构,并提供栈操作的push和pop的接口 3.题目:...

    C语言相关的其他练习题链接:

    【C语言】c语言练习题【1】
    【C语言】c语言练习题【2】
    【C语言】c语言练习题【3】
    【C语言】c语言练习题【4】
    【C语言】c语言练习题【整数算法训练】
    【C语言】c语言练习题【递归和栈编程训练】
    【C语言】c语言练习题【字符串训练】
    【C语言】c语言练习题【指针和链表训练】


    目录

    1.题目: 利用递归方法实现一个函数,该函数能够实现n的阶乘,即 n! = n*(n-1)*…*3*2*1;

    2.题目:利用字符数组实现一个先入后出的栈结构,并提供栈操作的push和pop的接口

    3.题目:输入一个表达式字符串,如1+3*4-6,输出这个表达式的值.

    4.题目:利用递归函数调用方式,将所输入的n个字符以相反顺序打印出来


    1.题目: 利用递归方法实现一个函数,该函数能够实现n的阶乘,即 n! = n*(n-1)*…*3*2*1;


    2.题目:利用字符数组实现一个先入后出的栈结构,并提供栈操作的push和pop的接口


    3.题目:输入一个表达式字符串,如1+3*4-6,输出这个表达式的值.

    提示:需要建立两个栈结构,一个为整形存放操作数,另一个为字符型,存放运算符,运算符的进栈要和在站顶的元素比较优选级如果低于栈顶元素则进行一次运算,要求至少实现正整数的加减乘除四则运算,如100- 5*4 -50/10 =75


    4.题目:利用递归函数调用方式,将所输入的n个字符以相反顺序打印出来

    展开全文
  • Scratch编程训练——小猫进圈

    千次阅读 2019-05-07 17:57:44
    题目描述:小猫非常听话,能够接受我们的命令在舞台上左右移动,但是他只能跳到相邻的里,不能跑到圆圈外。 小朋友们可以先思考一下,我们应该怎么做?你想到了吗?接下来,我们将手把手的给小朋友讲解清楚。...

    题目描述:小猫非常听话,能够接受我们的命令在舞台上左右移动,但是他只能跳到相邻的里,不能跑到圆圈外。
    在这里插入图片描述
    小朋友们可以先思考一下,我们应该怎么做?你想到了吗?接下来,我们将手把手的给小朋友讲解清楚。

    1. 第一步,当然是打开我们的Scratch软件啦。
    2. 在这里插入图片描述
    3. 我们来讲解下这个界面。

    (1)工具栏

    (2)积木块区/功能模块区

    (3)程序代码区/脚本区

    (4)舞台区/程序运行和预览区

    (5)角色列表区

    1. 填充颜色。参照题目要求,使用“舞台-背景”的造型工具绘制舞台背景。在位图模式下,选择蓝色,“用颜色填充”工具把背景填充成蓝色。

    (1)点击右下家图标,单击“绘制”选项。
    在这里插入图片描述
    (2)进入如下界面,并依次选择这三步,第一步“转换为位图”,第二步颜色设定为蓝色,第三步选择框型背景。
    在这里插入图片描述
    绘制出如下形式:

    在这里插入图片描述
    3. 把小猫拖到舞台左边的第一个圆圈里,在“脚本”区域编辑以下代码。“当绿旗被点击”积木以下的程序,是在主程序开始运行时会运行的。“当按下空格键”积木以下的程序,在检测到空格键被按下的情况下就会运行的,而不管是不是已经点击了绿旗。
    4. 在这里插入图片描述
    5. 在这里插入图片描述
    6. 黄色积木块表示事件;蓝色积木块表示运动。

    事件是一个很复杂的概念,在这里小朋友只需要把事件理解成触发。从上面的代码中,我们可以这样理解,当绿色小旗子被触发的时候,小猫直接初始化到x=-210,y=22的位置;当我们触发空格的时候,小猫就前进80步。
    转自:www.daerhou.cn

    展开全文
  • 常用面试编程训练5大网站!

    千次阅读 2017-05-08 09:05:51
    这个网站同时还提供了Shell编程,AI编程等模块的训练,以及编程基础培训,极大地融合上面所有网站的特点。 5、TopCoder 传送门:www.topcoder.com 为什么要关心算法、数据结构和复杂性? 好吧...
  • function sum(arr) { return arr.reduce(function(prev,curr){ return prev+curr; }); }
  • [新手编程训练项目]005——C语言文件夹文件信息输出002:递归输出指定文件夹中的文件信息 void dir_search(char*file_loc, int layer, char* key) //dir_search { struct _finddata_t fileinfo; //...
  • function indexOf(arr, item) { if(Array.prototype.indexOf){ return arr.indexOf(item); }else{ for(var i = 0; i < arr.length; i++){ if(arr[i] == item){ return i; } } return -1;...}

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 128,321
精华内容 51,328
关键字:

编程训练