假设有一种非常令人厌恶的蜱寄生在某种小鸟身上,而这种蜱又带有某种危险的病菌,所以必须尽早消灭这些蜱。
一般说来,小鸟用嘴梳理自己的羽毛时能够把蜱剔除掉,可是有一个鸟嘴达不到的地方——它的头顶。
A.傻瓜策略:无论如何都帮助对方清理蜱
B.骗子策略:无论如何都不帮助对方清理蜱
C.斤斤计较策略:只帮助从未欺骗过自己的小鸟清理蜱
配置项如下
const seriesLayoutBy = 'row';
const seriesType = 'scatter';
const baseConfig = {
moveSpeed: 1,
childThreshold: 200,
maxAge: 200,
childCost: 60,
illLost: 30,
clearCost: 0.05,
selfRecoveryIncrease: 10,
balancePopulation: 1000,
illPR: 0.3,
distance: 1,
baseCount: 200,
rateA: 1,
rateB: 1,
rateC: 0.001,
};
function init(rateA, rateB, rateC) {
const baseCount = baseConfig.baseCount;
let ret = {
A: [],
B: [],
C: [],
};
let planA = {
clear: true,
ill: false,
child: 0,
age: 0,
};
let planB = {
clear: false,
ill: false,
child: 0,
age: 0,
};
let planC = {
clear: true,
ill: false,
child: 0,
age: 0,
except: {},
};
for (let i = 0; i < baseCount * rateA; i++) {
ret.A.push(['傻瓜', Math.random() * 10, Math.random() * 10, 100, 'A' + i, Object.assign({}, planA)]);
}
for (let i = 0; i < baseCount * rateB; i++) {
ret.B.push(['骗子', Math.random() * 10, Math.random() * 10, 100, 'B' + i, Object.assign({}, planB)]);
}
for (let i = 0; i < baseCount * rateC; i++) {
ret.C.push(['斤斤计较者', Math.random() * 10, Math.random() * 10, 100, 'C' + i, Object.assign({}, planC)]);
}
// console.log(ret);
return ret;
}
function emulate(src) {
let dst = {
A: [],
B: [],
C: [],
};
let srcArr = src.A.concat(src.B).concat(src.C);
// console.log(srcArr.length);
for (let i = srcArr.length - 1; i >= 0; i--) {
// 随机移动
srcArr[i][1] += (Math.random() - 0.5) * 2 * baseConfig.moveSpeed;
srcArr[i][2] += (Math.random() - 0.5) * 2 * baseConfig.moveSpeed;
// 移动超出范围则折返
srcArr[i][1] < 0
? (srcArr[i][1] = 0 - srcArr[i][1])
: srcArr[i][1] > 10
? (srcArr[i][1] = 20 - srcArr[i][1])
: null;
srcArr[i][2] < 0
? (srcArr[i][2] = 0 - srcArr[i][2])
: srcArr[i][2] > 10
? (srcArr[i][2] = 20 - srcArr[i][2])
: null;
// 随机生病
if (Math.random() <= baseConfig.illPR) {
// console.log('some one ill');
srcArr[i][5].ill = true;
}
// 生病减少生命值
if (srcArr[i][5].ill) {
tmpHealth = srcArr[i][3];
srcArr[i][3] -= baseConfig.illLost;
// console.log(`some one illcost: ${tmpHealth} -> ${srcArr[i][3]}(${baseConfig.illLost})`);
}
//寿命计算
srcArr[i][5].age < baseConfig.maxAge ? srcArr[i][5].age++ : (srcArr[i][3] = 0);
// 生命值归零则丢弃去掉
if (srcArr[i][3] <= 0) {
srcArr.splice(i, 1);
// 生命值大于阈值,则生育
} else if (srcArr[i][3] > baseConfig.childThreshold) {
srcArr[i][3] -= baseConfig.childCost;
srcArr[i][5].child += 1;
tmpPlan = Object.assign({}, srcArr[i][5]);
tmpPlan.ill = false;
tmpPlan.child = 0;
tmpPlan.age = 0;
tmpPlan.except ? (tmpPlan.except = {}) : null;
srcArr.push([srcArr[i][0], srcArr[i][1], srcArr[i][2], 100, `${srcArr[i][4]}_${srcArr[i][5].child}`, tmpPlan]);
}
}
// console.log(srcArr.length);
for (let i = 0; i < srcArr.length; i++) {
for (let j = i + 1; j < srcArr.length; j++) {
let dx = srcArr[i][1] - srcArr[j][1] > 0 ? srcArr[i][1] - srcArr[j][1] : srcArr[j][1] - srcArr[i][1];
let dy = srcArr[i][2] - srcArr[j][2] > 0 ? srcArr[i][2] - srcArr[j][2] : srcArr[j][2] - srcArr[i][2];
if (dx + dy > baseConfig.distance) {
continue; // 未达到相互作用距离(为了简化运算,未使用平方和来判断)
}
// 判断、清理、消耗、记仇(斤斤计较者未被清理)
srcArr[i][5].clear && !(srcArr[i][5].except && srcArr[i][5].except[srcArr[j][4]] > 0)
? ((srcArr[j][5].ill = false), (srcArr[i][3] -= baseConfig.clearCost))
: typeof srcArr[j][5].except === 'undefined'
? null
: (srcArr[j][5].except[srcArr[i][4]] = 1);
srcArr[j][5].clear && !(srcArr[j][5].except && srcArr[j][5].except[srcArr[i][4]] > 0)
? ((srcArr[i][5].ill = false), (srcArr[j][3] -= baseConfig.clearCost))
: typeof srcArr[i][5].except === 'undefined'
? null
: (srcArr[i][5].except[srcArr[j][4]] = 1);
}
// 正常恢复(个体数量超过平衡数量后,恢复值将与个体数量呈反比)
srcArr[i][3] +=
srcArr.length < baseConfig.balancePopulation
? baseConfig.selfRecoveryIncrease
: Math.round((baseConfig.selfRecoveryIncrease * baseConfig.balancePopulation) / srcArr.length);
// 计算完毕,存入 dst
srcArr[i][4].startsWith('A')
? dst.A.push(srcArr[i])
: srcArr[i][4].startsWith('B')
? dst.B.push(srcArr[i])
: dst.C.push(srcArr[i]);
}
// console.log(dst);
return dst;
}
let dataAll = init(baseConfig.rateA, baseConfig.rateB, baseConfig.rateC);
option = {
title: {
show: true,
text: '仿《自私的基因》,傻瓜、斤斤计较者、骗子策略模拟',
subtext:
'假设有一种非常令人厌恶的蜱寄生在某种小鸟身上,而这种蜱又带有某种危险的病菌,所以必须尽早消灭这些蜱。\n一般说来,小鸟用嘴梳理自己的羽毛时能够把蜱剔除掉,可是有一个鸟嘴达不到的地方——它的头顶。\n\nA.傻瓜策略:无论如何都帮助对方清理蜱\nB.骗子策略:无论如何都不帮助对方清理蜱\nC.斤斤计较策略:只帮助从未欺骗过自己的小鸟清理蜱',
},
dataset: [
{
sourceHeader: true,
// dimensions: ['seriesName', 'x', 'y', 'value', 'idx', 'obj'],
source: dataAll.A,
},
{
sourceHeader: true,
// dimensions: ['seriesName', 'x', 'y', 'value', 'idx', 'obj'],
source: dataAll.B,
},
{
sourceHeader: true,
// dimensions: ['seriesName', 'x', 'y', 'value', 'idx', 'obj'],
source: dataAll.C,
},
{
// dimensions: ['seriesName', 'count'],
source: (function () {
let sourceStats = [];
for (let key in dataAll) {
sourceStats.push([dataAll[key][0][0], dataAll[key].length]);
}
return sourceStats;
})(),
},
],
grid: {
top: '20%',
bottom: '30%',
},
tooltip: {
formatter: (params) => {
if (params.seriesIndex === 3) {
return params.name + ':' + params.data[1];
}
// console.log(params);
return `${params.seriesName}<br />${params.data[4]}(${params.data[3]})<br />蜱:${params.data[5].ill}${
typeof params.data[5].except === 'undefined' || true
? ''
: '<br />黑名单:' + Object.keys(params.data[5].except).join('<br/>')
}`;
},
},
legend: {
right: '5%',
top: '5%',
},
xAxis: {
max: 10,
},
yAxis: {
max: 10,
},
animation: false,
series: (function () {
let seriesList = [];
for (let i = 0; i < 3; i++) {
seriesList.push({
type: seriesType,
datasetIndex: i,
encode: {
seriesName: 0,
x: 1,
y: 2,
value: 3,
},
});
}
seriesList.push({
type: 'pie',
datasetIndex: 3,
xAxisIndex: 1,
yAxisIndex: 1,
seriesLayoutBy: 'column',
center: ['50%', '85%'],
startAngle: 0,
radius: [0, '20%'],
itemStyle: {
color: (params) => {
return params.name === '傻瓜' ? '#5470c6' : params.name === '骗子' ? '#91cc75' : '#fac858';
},
},
label: {
formatter: '{b}:{d}%',
alignTo: 'edge',
},
labelLayout: {
alignTo: 'edge',
draggable: true,
},
});
return seriesList;
})(),
};
let timer = setInterval(function () {
// console.log(dataAll);
dataAll = emulate(dataAll);
console.log('emulate');
myChart.setOption({
dataset: [
{
source: dataAll.A,
},
{
source: dataAll.B,
},
{
source: dataAll.C,
},
{
source: (function () {
let sourceStats = [];
for (let key in dataAll) {
if (dataAll[key].length > 0) {
sourceStats.push([dataAll[key][0][0], dataAll[key].length]);
}
}
return sourceStats;
})(),
},
],
});
if (dataAll.A.length === 0 && dataAll.B.length === 0 && dataAll.C.length === 0) {
clearInterval(timer);
}
}, 100);
// clearInterval(timer);