仿《自私的基因》,傻瓜、斤斤计较者、骗子策略模拟echarts 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: '仿《自私的基因》,傻瓜、斤斤计较者、骗子策略模拟配置项内容和展示

假设有一种非常令人厌恶的蜱寄生在某种小鸟身上,而这种蜱又带有某种危险的病菌,所以必须尽早消灭这些蜱。 一般说来,小鸟用嘴梳理自己的羽毛时能够把蜱剔除掉,可是有一个鸟嘴达不到的地方——它的头顶。 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);

    
截图如下