Skip to content

Commit 0816810

Browse files
Improvements to dropsim stuff.
1 parent f243fd3 commit 0816810

5 files changed

Lines changed: 350 additions & 337 deletions

File tree

analyze-simulations.js

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
const fs = require('fs');
2+
3+
const ensureDir = (dir) => {
4+
if (!fs.existsSync(dir)) {
5+
fs.mkdirSync(dir, { recursive: true });
6+
}
7+
};
8+
9+
const safeName = (value) => value.toString().replace(/[^A-Za-z0-9._ \-\[\]\(\)]+/g, '_');
10+
11+
const mean = (values) => values.reduce((sum, v) => sum + v, 0) / (values.length || 1);
12+
13+
const stddev = (values, avg) => {
14+
if (values.length < 2) {
15+
return 0;
16+
}
17+
const variance = values.reduce((sum, v) => sum + Math.pow(v - avg, 2), 0) / (values.length - 1);
18+
return Math.sqrt(variance);
19+
};
20+
21+
const normalSample = (avg, deviation) => {
22+
if (deviation <= 0) {
23+
return avg;
24+
}
25+
let u = 0;
26+
let v = 0;
27+
while (u === 0) {
28+
u = Math.random();
29+
}
30+
while (v === 0) {
31+
v = Math.random();
32+
}
33+
const z = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
34+
return avg + deviation * z;
35+
};
36+
37+
const buildDistribution = (values, samplesCount, binCount) => {
38+
const avg = mean(values);
39+
const deviation = stddev(values, avg);
40+
41+
const spread = deviation > 0 ? deviation * 4 : Math.max(1, Math.abs(avg) * 0.1);
42+
const minX = avg - spread;
43+
const maxX = avg + spread;
44+
const bins = new Array(binCount).fill(0);
45+
const step = (maxX - minX) / binCount;
46+
47+
for (let i = 0; i < samplesCount; i++) {
48+
const sample = normalSample(avg, deviation);
49+
const clamped = Math.max(minX, Math.min(maxX, sample));
50+
const idx = Math.min(binCount - 1, Math.max(0, Math.floor((clamped - minX) / step)));
51+
bins[idx] += 1;
52+
}
53+
54+
const distribution = bins.map((count, index) => {
55+
const x = minX + step * (index + 0.5);
56+
const y = count / samplesCount;
57+
return { x, y };
58+
});
59+
60+
const maxY = distribution.reduce((maxValue, point) => Math.max(maxValue, point.y), 0);
61+
62+
return {
63+
avg,
64+
deviation,
65+
minX,
66+
maxX,
67+
minY: 0,
68+
maxY,
69+
distribution
70+
};
71+
};
72+
73+
const formatNumber = (value) => {
74+
if (!Number.isFinite(value)) {
75+
return '0';
76+
}
77+
78+
let digits = Math.log10(Math.abs(value));
79+
80+
if (digits < -2) {
81+
return value.toExponential(2);
82+
}
83+
return value.toFixed(2);
84+
};
85+
86+
const datapoints = {};
87+
const maxseq = {};
88+
const runs = {};
89+
const totalsdir = __dirname + '/simulation-totals/';
90+
const graphsdir = __dirname + '/simulation-graphs/';
91+
92+
ensureDir(totalsdir);
93+
ensureDir(graphsdir);
94+
95+
for (let dir of [totalsdir, graphsdir]) {
96+
for (let file of fs.readdirSync(dir)) {
97+
if (file.endsWith('.json') || file.endsWith('.svg')) {
98+
fs.unlinkSync(dir + file);
99+
}
100+
}
101+
}
102+
103+
for (let file of fs.readdirSync(__dirname + '/simulations/')) {
104+
if (file.endsWith('.json')) {
105+
const data = JSON.parse(fs.readFileSync(__dirname + '/simulations/' + file));
106+
107+
if (data.runs > 0) {
108+
datapoints[data.tc] = datapoints[data.tc] || {};
109+
datapoints[data.tc][data.playermod] = datapoints[data.tc][data.playermod] || {};
110+
maxseq[data.tc] = maxseq[data.tc] || {};
111+
maxseq[data.tc][data.playermod] = maxseq[data.tc][data.playermod] || 0;
112+
maxseq[data.tc][data.playermod] = Math.max(maxseq[data.tc][data.playermod], data.seq);
113+
114+
runs[data.tc] = runs[data.tc] || {};
115+
runs[data.tc][data.playermod] = runs[data.tc][data.playermod] || 0;
116+
runs[data.tc][data.playermod] += data.runs;
117+
118+
for (let item of data.drops) {
119+
let [itc, count, magic, rare, set, unique] = item;
120+
datapoints[data.tc][data.playermod][itc] = datapoints[data.tc][data.playermod][itc] || [];
121+
datapoints[data.tc][data.playermod][itc][data.seq] = count / data.runs;
122+
}
123+
}
124+
}
125+
}
126+
127+
for (let tc in maxseq) {
128+
for (let playermod in maxseq[tc]) {
129+
for (let itc in datapoints[tc][playermod]) {
130+
let data = datapoints[tc][playermod][itc].map(v => v || 0), runcount = runs[tc][playermod];
131+
132+
const samplesCount = Math.max(5000, data.length * 200);
133+
const binCount = 60;
134+
const dist = buildDistribution(data, samplesCount, binCount);
135+
const safeBase = safeName(`${tc} [${playermod}][${itc}]`);
136+
const totalsPath = totalsdir + safeBase + '.json';
137+
const graphPath = graphsdir + safeBase + '.svg';
138+
139+
const totalsPayload = {
140+
tc,
141+
playermod: Number(playermod),
142+
itc,
143+
runs: runcount,
144+
samples: data.length,
145+
mean: dist.avg,
146+
stddev: dist.deviation,
147+
minX: dist.minX,
148+
maxX: dist.maxX,
149+
minY: dist.minY,
150+
maxY: dist.maxY,
151+
distribution: dist.distribution
152+
};
153+
154+
fs.writeFileSync(totalsPath, JSON.stringify(totalsPayload, null, 2));
155+
156+
const plotWidth = 700;
157+
const plotHeight = 350;
158+
const gutterTop = 40;
159+
const gutterBottom = 30;
160+
const gutterX = 80;
161+
const width = gutterX + plotWidth + gutterX;
162+
const height = gutterTop + plotHeight + gutterBottom;
163+
const boxX = -gutterX;
164+
const boxY = -gutterTop;
165+
const rangeX = dist.maxX - dist.minX || 1;
166+
const rangeY = dist.maxY - dist.minY || 1;
167+
168+
const points = dist.distribution.map((point, index) => {
169+
const x = ((point.x - dist.minX) / rangeX) * plotWidth;
170+
const y = plotHeight - ((point.y - dist.minY) / rangeY) * plotHeight;
171+
return `${index === 0 ? 'M' : 'L'} ${x.toFixed(2)} ${y.toFixed(2)}`;
172+
}).join(' ');
173+
174+
const svg = [
175+
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="${boxX} ${boxY} ${width} ${height}">`,
176+
`<rect x="${boxX}" y="${boxY}" width="${width}" height="${height}" fill="#11161c"/>`,
177+
`<rect x="0" y="0" width="${plotWidth}" height="${plotHeight}" fill="#0b0f14" stroke="#2a323d" stroke-width="2"/>`,
178+
`<line x1="0" y1="${plotHeight}" x2="${plotWidth}" y2="${plotHeight}" stroke="#2a323d" stroke-width="2"/>`,
179+
`<line x1="0" y1="0" x2="0" y2="${plotHeight}" stroke="#2a323d" stroke-width="2"/>`,
180+
`<path d="${points}" fill="none" stroke="#8ad1ff" stroke-width="2"/>`,
181+
`<text x="${(width) / 2 - gutterX}" y="${-gutterTop / 2 + 5}" fill="#e6f0ff" font-size="18" text-anchor="middle">${tc} | players ${playermod * 2 - 1} | ${itc} | ${formatNumber(dist.avg)} ± ${formatNumber(dist.deviation)}</text>`,
182+
`<text x="0" y="${plotHeight + gutterBottom / 2 + 5}" fill="#d7e3f2" font-size="14" text-anchor="start">${formatNumber(dist.minX)}</text>`,
183+
`<text x="${plotWidth}" y="${plotHeight + gutterBottom / 2 + 5}" fill="#d7e3f2" font-size="14" text-anchor="end">${formatNumber(dist.maxX)}</text>`,
184+
`<text x="-10" y="${plotHeight + 4}" fill="#d7e3f2" font-size="14" text-anchor="end">${formatNumber(dist.minY)}</text>`,
185+
`<text x="-10" y="4" fill="#d7e3f2" font-size="14" text-anchor="end">${formatNumber(dist.maxY)}</text>`,
186+
`</svg>`
187+
].join('');
188+
189+
fs.writeFileSync(graphPath, svg);
190+
}
191+
}
192+
}

build.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
#!/bin/bash
2-
g++ dropsim.cpp -o dropsim -std=c++17 -O3
2+
g++ dropsim.cpp -o dropsim -std=c++17 -O3
3+
g++ dropsim.cpp -o dropsimbase -D"BASETC=1" -std=c++17 -O3

combine-simulations.js

Lines changed: 0 additions & 59 deletions
This file was deleted.

0 commit comments

Comments
 (0)