JavaScript 50行代码 写一个数独生成器

最初是用react写的,但核心代码完全是原生js,做些小的改动还能完美移植到小程序,核心生成数独代码只有50行,实现效果如下

case.gif

基本思路

按照我解数独的思路,使用暴力生成的方式。即,比如从数字1开始,第一行,随机一个位置放1,第二行起,除了确定当前纵列没有1,每一个小九宫格也不应有1。数字1在每一行放置完后从第一行起放置2。如图示:

image.png

代码实现

1,入口函数,在react的componentWillMount处调用

  handleGenerateSudoku(){
    if(this.state.btnDisabled){
      return
    }
    // 重点是这一行和下面的while循环
    this.generateSudokuSuccess = false
    this.setState({
      btnDisabled: true,
      array: null
    })
    let result = null

    // 当数独未生成成功时,持续调用
    while(!this.generateSudokuSuccess){
      result = this.generateSudoku()
      // console.log(result)
    }   
    this.setState({
      btnDisabled: false,
      array: result
    })
  }

2,下面开始为每一个数字寻找合适位置

  • 当前行: 正在处理数独哪一行
  • 当前数字: 正在为1-9哪个数字分配位置索引
   generateSudoku(){

    /**
     * 首先创建一个9x9二位数组array,如[[undefined, undefined, ...], [..], [..],...[..]]
     * array代表整个数独容器
     * array的第一个索引array[x]代表数独的每一行,array[x][y]表示每一个单元格,填充单个数字。
     */
    let array = new Array(9)

    // new Array创建的数组默认填充undefined,使用map等函数式方法无法遍历
    for(let i=0; i<9; i++){
      array[i] = new Array(9)
    }

    /**
     * 时间戳的作用是避免某一数字在各行索引生成失败而长时间循环下去。
     * 在实际应用中,如果前面的数字随机的不合理,会导致数独无解
     * 设置一个时间戳,当一定时间后,这里是1000ms后,如果数独仍未生成成功,返回这个函数
     * 在while内部的else if 代码块中如果超时,返回到上面第1步从第一行重新生成数独
     * 这种处理方法不好,有待优化
     */
    let time = new Date().getTime()

    // 1-9共9个数字,所以循环9次,为每个数字寻找合适的位置索引
    for(let j=0; j<9; j++){
      // 存放当前数字合法位置索引的地方,比如,上图中数字1(当前数字)的合法索引为[2, 5, 7, 6, 1, 4, 3, 8, 0]
      let idxInList
      // 当前数字的索引是否分配完毕,默认没有
      let notComplete = true
      while (notComplete) {
        idxInList = []
        // 循环数独每一行,即在每一行为当前数字寻找索引
        // 将数独当前行的数字填充情况、当前第几行和当前数字的合法索引位置传进去处理
        // 返回一个索引位置,表示当前数字在当前行应填充的位置
        for(let k=0; k<9; k++){
          let avalibIdx = this.avalibleIdx(array[k], k, idxInList)
          // 如果返回的位置有效,更新当前数字的索引位置
          if(avalibIdx !== undefined){
            idxInList.push(avalibIdx)
          }
        }

        // for循环完成后

        // 如果当前数字在每一行的9个索引位置全部查找完毕,完成,退出while循环
        if (idxInList.length === 9){
          notComplete = false
        } else if (new Date().getTime() - time > 1000){
          // 超时的话返回这个函数,从1重新开始生成数独
          return
        }
        // 否则继续while循环,将当前数字从第一行重新安排位置
      }

      // 要return,不map
      // 如果while循环出来,说明当前数字的位置索引完成,更新array
      for(let n=0; n<idxInList.length; n++){
        array[n][idxInList[n]] = j+1
        if(j===8 && n===8){
          // 如果是第8个数字,即9,说明所有数字的位置更新完成。整个数独生成完毕,返回array结果
          this.generateSudokuSuccess = true
          return array
        }
      }
    }
  }

3, 下面是为当前数字分配一个索引位置的操作,这里处理的是当前数字在当前行(具体的某一行)的可能性

  // 每行分为3块,即三个九宫格,同一数字在三行内不处于同一块
  /**
   * 
   * @param {Array} rowList - 数独当前行的数字填充情况
   * @param {Number} idxOfRowList - 数独第几行
   * @param {Array} idxInList - 当前数字在每行所处位置,即位置索引
   */
  avalibleIdx(rowList, idxOfRowList, idxInList){
    // 存储当前数字在当前行所有可能的存储位置
    // 比如数独第一行时,应有9个位置
    // 第二行时,除去一个九宫格,有6个可能的位置
    // 第三行只有3个合法位置
    // 但是第四行,去除九宫格限制,减去不能同列的位置,有6个合法位置
    // ...
    let avalibleList = []
    // 循环数独当前行的每一个单元格,只有是undefined(没有数字填充)并且在已有的位置索引记录中不同于该位置(即排除相同列)继续
    for(let m=0; m<9; m++){
      if(rowList[m] === undefined && idxInList.indexOf(m)===-1){
        // 在1,4,7行,即array[0], array[3], array[6] 这三行不受九宫格限制
        // 只要是undefined就把该位置的索引记录下来
        if(idxOfRowList % 3 === 0){
          avalibleList.push(m)
        } else {

          // 在2,3,5,6,8,9行,受九宫格限制,需排除掉
          // 找出当前数字在上一行的索引位置,如果与当前行正在循环的索引位置处于同一个九宫格区间,跳过
          // 2,5,8行比对的是九宫格第一行;3,6,9比对第二行
          // 如果不在同一个九宫格区间,且不是第3, 6, 9行,记录该位置
          let blockLastIndex = idxInList[idxInList.length - 1]
          if(( blockLastIndex < 3 && m < 3) || ( (blockLastIndex>=3 && blockLastIndex<6) && (m>=3 && m<6) ) || ( blockLastIndex >= 6 && m >= 6) ) {
            continue
          } else {
            // 在3,6,9行
            // 找出当前数字在上上一行(即九宫格第一行)的索引位置,如果与当前行正在循环的索引位置处于同一个九宫格区间,跳过
            // 否则记录该位置
            if(idxOfRowList % 3 === 2){
              let blockAheadIdx = idxInList[idxInList.length - 2]
              if(( blockAheadIdx < 3 && m < 3) || ( (blockAheadIdx>=3 && blockAheadIdx<6) && (m>=3 && m<6) ) || ( blockAheadIdx >= 6 && m >= 6) ) {
                continue
              }
            }
            avalibleList.push(m)
          }
        }
      }
    }
    
    // 去重记录的合法位置索引,并随机一个返回,作为当前数字在当前行的合法位置记录在idxInList数组
    let resultList = Array.from(new Set(avalibleList))
    return resultList[Math.floor(Math.random() * resultList.length)]

  }

完整代码在这里

移植到小程序

代码经过补充,具备一个基本数独小游戏的功能,已移植到小程序,详见sudoLite

sudoLite

sudoLite是一款轻巧、趣萌、界面精美,具备统计、排行(即将推出),开源、免费、无广告的数独小程序

优点

  • 以上

  • 统共一百多kB,有微信就可以愉快地耍,还可将成绩分享给小伙伴炫耀

  • 作者是个偶尔勤劳、积极进取、有奇思妙想、视产品如孩子的人,sudoLite的后大半生就放心交给我辣

  • 作者还是个民主的人,欢迎任何形式、内容的反馈 (虽然不一定会听)

  • 总之,你会在使用过程中发现sudoLite在不断进步,且一如既往的具有以上优点 [鞠躬]

缺点

  • 性能较差,部分安卓机会卡

  • 未知bug

截图

主页

image.png

统计

image.png

排行

image.png

ENJOY

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,413评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,591评论 18 139
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • 电话沟通虽然见不到面,说话的激情和能量一样可以传递给客户。但是仅仅有激情不够,还需要掌握节奏、准备方法应对各种变化...
    木山精进阅读 1,250评论 0 1
  • 我的外婆,那是我老的样子,我的妈妈,那是我中年的样子,妈妈说外婆长寿,我应该也长寿,以后你也肯定长寿,我笑了,外婆...
    海贝_82fd阅读 281评论 0 1