通过我对羊了个羊的研发,发现它只是一个概率的数学问题,你能不能过关与智商没有关系。
当时为什么你有时候总觉得自己可以过呢? 如果想知道原因,请听听我的分析。
如果不想看下面分析,可以直接去看b站视频
开局体验
开局的情况 玩到最后无解的情况:
分析:
经过多次试完:我们发现这个游戏有如下特点:
- 卡牌的种类有十五种
- 每种卡牌约有6到7组,我们已6组来算,约一共有270张。
- 开始可供选择的卡牌有29张,玩到过关可供选择的卡牌估算位13张,(包括卡槽的位置)
- 游戏越到最后,可提供选择的卡牌就越少。
接下来我们通过计算机,模拟可以通过的概率
代码分析
1. 定义需要控制的变量
- 卡组的种类
- 每种卡组的数量
- 最大可供选择数,最小可供选择数
- 抽到几张相同的可以删除
- 循环测试的次数
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.模拟人进行玩游戏
游戏规则:
- 开始给出 postion张牌,这里默认给出29张
- 在29张牌中,进行三三消除。
- 根据当前可用的卡位,进行动态计算,重新得到新的postion张牌。
- 返回第一步。
失败: 如果给出的卡位,不存在三张一样的表示失败。
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}%`
);