羊了个羊过不了,真的与你的智商有关?

1,869 阅读3分钟

通过我对羊了个羊的研发,发现它只是一个概率的数学问题,你能不能过关与智商没有关系

当时为什么你有时候总觉得自己可以过呢? 如果想知道原因,请听听我的分析。

如果不想看下面分析,可以直接去看b站视频

开局体验

开局的情况 玩到最后无解的情况: image.pngimage.png

分析:

经过多次试完:我们发现这个游戏有如下特点:

  1. 卡牌的种类有十五种
  2. 每种卡牌约有6到7组,我们已6组来算,约一共有270张。
  3. 开始可供选择的卡牌有29张,玩到过关可供选择的卡牌估算位13张,(包括卡槽的位置)
  4. 游戏越到最后,可提供选择的卡牌就越少。

接下来我们通过计算机,模拟可以通过的概率

代码分析

1. 定义需要控制的变量

  1. 卡组的种类
  2. 每种卡组的数量
  3. 最大可供选择数,最小可供选择数
  4. 抽到几张相同的可以删除
  5. 循环测试的次数

let type = 15;  // 卡组的种类 
let count = 3*6; // 每种卡组的数量
let rate = 1000; 
const postion = 29; // 最大可供选择数
const minpostion = 13; // 最小可供选择数
let removeCount = 3; // 抽到几张相同的可以删除
let allCount = 1000*500; // 循环测试的次数

2. 洗牌

通过随机hash的方式,让牌进入不同的位置,保证乱序。

let cardMap = {};
      for (let i = 1; i <= type; i++) {
        for (let j = 0; j < count; j++) {
          let p = -1;
          while (!cardMap[p]) {
            p = Math.floor(Math.random() * type * count * rate);
            cardMap[p] = i;
          }
        }
      }
let cards = Object.values(cardMap);

3. 可选择牌数

可选择牌数,我们按照线性的方式进行减小,(也可以按照其他方式进行减少,这里我们只是做一个简单的预算)

// 使用数学的点斜式进行计算
let k = (minpostion - postion) / (type * count);
let b = k * 0 + postion;

4.模拟人进行玩游戏

游戏规则:

  1. 开始给出 postion张牌,这里默认给出29张
  2. 在29张牌中,进行三三消除。
  3. 根据当前可用的卡位,进行动态计算,重新得到新的postion张牌。
  4. 返回第一步。

失败: 如果给出的卡位,不存在三张一样的表示失败。

  function plan() {
        let myCards = [];
        let offsetIndex = postion;
        let p = postion;
        for (let i = 0; i < p; i++) {
          myCards[i] = cards[i];
        }
        while (offsetIndex < cards.length) {
          let tem = {};
          // 如果卡牌等于三张,就删除三张;
          for (let i = 0; i < myCards.length; i++) {
            if (!tem[myCards[i]]) {
              tem[myCards[i]] = 0;
            }
            tem[myCards[i]]++;
            if (tem[myCards[i]] === removeCount) {
              for (let j = 0; j <= i; j++) {
                if (myCards[j] === myCards[i]) {
                  myCards[j] = undefined;
                }
              }
            }
          }

          // 收拢数组    
          let temCards = [];
          for (let i = 0; i < myCards.length; i++) {
            if (myCards[i] === undefined) {
            }else{
              temCards.push(myCards[i])
            
            }
          }
          if (temCards.length === myCards.length) {
            return false;
          } else {
            let newPosion = Math.floor(k * offsetIndex + b);
            while(temCards.length < newPosion){
              if(offsetIndex >= cards.length){
                return true;
              }
              temCards.push(cards[offsetIndex++]);
            }
            myCards = temCards;
           
          }
        }
        return true;
}

5. 进行多次尝试玩游戏

let type = 15;
let count = 3*6;
let rate = 1000;
const postion = 29;
const minpostion = 13;
let removeCount = 3;
let allCount = 1000*1000;

我们设置卡牌的种类为15中,每种卡牌的数量为18张,开始提供可以选的卡为29张,最终提供可选择的卡牌为13张,测试100万次,电脑通关的概率如下: 时间:81.022s,总次数:100万,失败率:99.9996%,成功率:0.00040000000000040004%;约为百万分之四,跟彩票中一等奖的概率差不多。

接下来解释下:

当时为什么你有时候总觉得自己可以过呢?

因为开始的可提供选择的卡牌特别的多,所以进行消除的概率特别的大,后面可提供的卡牌变少了,所以消除的概率就变小了。

这个是我的解题思路,也是近似的算出可以过的概率,并不一定准确。不喜欢勿喷,如果觉得有改进的地方可以一起讨论下。

实现代码:


    // 15个种类,6到7组
    let type = 15;
    let count = 3*6;
    let rate = 1000;
    const postion = 29;
    const minpostion = 13;
    let removeCount = 3;
    let allCount = 1000*1000;

    function getRate(type, count, rate, postion, removeCount) {
      let cardMap = {};
      for (let i = 1; i <= type; i++) {
        for (let j = 0; j < count; j++) {
          let p = -1;
          while (!cardMap[p]) {
            p = Math.floor(Math.random() * type * count * rate);
            cardMap[p] = i;
          }
        }
      }
      let cards = Object.values(cardMap);
      let k = (minpostion - postion) / (type * count);
      let b = k * 0 + postion;

      return plan();
      function plan() {
        let myCards = [];
        let offsetIndex = postion;
        let p = postion;
        for (let i = 0; i < p; i++) {
          myCards[i] = cards[i];
        }
        while (offsetIndex < cards.length) {
          let tem = {};
          // 如果卡牌等于三张,就删除三张;
          for (let i = 0; i < myCards.length; i++) {
            if (!tem[myCards[i]]) {
              tem[myCards[i]] = 0;
            }
            tem[myCards[i]]++;
            if (tem[myCards[i]] === removeCount) {
              for (let j = 0; j <= i; j++) {
                if (myCards[j] === myCards[i]) {
                  myCards[j] = undefined;
                }
              }
            }
          }

          // 收拢数组    
          let temCards = [];
          for (let i = 0; i < myCards.length; i++) {
            if (myCards[i] === undefined) {
            }else{
              temCards.push(myCards[i])
            
            }
          }
          if (temCards.length === myCards.length) {
            return false;
          } else {
            let newPosion = Math.floor(k * offsetIndex + b);
            while(temCards.length < newPosion){
              if(offsetIndex >= cards.length){
                return true;
              }
              temCards.push(cards[offsetIndex++]);
            }
            // console.log(newPosion,offsetIndex)
            myCards = temCards;
           
          }
        }
        return true;
      }
    }

    // 计算概率
    let failCount = 0;
    let starTime = new Date().getTime();
    for (let i = 0; i < allCount; i++) {
      if (!getRate(type, count, rate, postion, removeCount)) {
        failCount++;
      }
    }
   
    console.log(
      `时间:${((new Date().getTime()-starTime)/1000)}s,总次数:${allCount / 10000}万,失败率:${
        (failCount / allCount) * 100
      }%,成功率:${(1 - failCount / allCount) * 100}%`
    );