Skip to content

Instantly share code, notes, and snippets.

@lukegb
Last active May 15, 2021 18:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lukegb/1f65dbf4c98c6891a7a1d89545299a5d to your computer and use it in GitHub Desktop.
Save lukegb/1f65dbf4c98c6891a7a1d89545299a5d to your computer and use it in GitHub Desktop.
shiptoasting
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.total-money {
font-weight: bold;
}
.container td {
padding: 0.2rem 0.5rem;
}
.slider {
width: 10rem;
}
.slider-text {
width: 3rem;
}
.diff-expl {
text-align: right;
}
</style>
<title>How much do I need to Contribute To The Economy</title>
<h1>How much do I need to Contribute To The Economy</h1>
<table class="container"></table>
<p>Based on some recent <a href="https://www.standard.co.uk/news/uk/brits-drink-124-pints-each-struggling-pubs-covid-lockdown-b935186.html">news</a> and this <a href="https://www.companydebt.com/features/124-pints-to-save-the-pub/">source</a>.</p>
<template id="slider-template">
<tr class="slider-holder">
<td><input type="range" class="slider"></td>
<td><input type="number" class="slider-text"></td>
<td><span class="slider-value"></span></td>
<td>@ <span class="slider-each"></span>/ea</td>
<td><span class="slider-money"></span></td>
</tr>
</template>
<template id="total-template">
<tr style="display:none"><!-- this is really just for debugging -->
<td colspan="3"></td>
<td class="diff-expl">Difference:</td>
<td><span class="diff-money"></span></td>
</tr>
<tr>
<td colspan="4"></td>
<td><span class="total-money"></span></td>
</tr>
</template>
<script type="module">
const sum = (it, f) => it.reduce((acc, v) => acc + f(v), 0);
const i = (v) => parseInt(v, 10);
const MONEY_FORMAT = new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP' });
const container = document.querySelector('.container');
const tmpl = document.querySelector('#slider-template');
const PUB_THINGS = [
{
name: "Pints of Beer",
singularName: "Pint of Beer",
defaultValue: 124,
each: 394,
},
{
name: "Glasses of Wine",
singularName: "Glass of Wine",
defaultValue: 0,
each: 400,
},
{
name: "Roast Dinners",
singularName: "Roast Dinner",
defaultValue: 0,
each: 1220,
},
{
name: "Packs of Crisps",
singularName: "Pack of Crisps",
defaultValue: 0,
each: 50,
}
];
const TOTAL = sum(PUB_THINGS, (v) => v.defaultValue*v.each);
const ELEMENTS = PUB_THINGS.map((v) => {
const content = tmpl.content.cloneNode(true);
const slider = content.querySelector('.slider');
const text = content.querySelector('.slider-text');
text.min = slider.min = 0;
text.max = slider.max = Math.ceil(TOTAL / v.each);
text.value = slider.value = v.defaultValue;
const textValue = content.querySelector('.slider-value');
const money = content.querySelector('.slider-money');
content.querySelector('.slider-each').textContent = MONEY_FORMAT.format(v.each / 100);
container.appendChild(content);
slider.addEventListener('change', () => {
text.value = slider.value;
updateElements(v.name);
});
text.addEventListener('change', () => {
slider.value = text.value;
updateElements(v.name);
});
return {
slider,
text,
textValue,
money,
...v,
}
});
const {totalEl, diffEl} = (() => {
const content = document.querySelector('#total-template').content.cloneNode(true);
const totalEl = content.querySelector('.total-money');
const diffEl = content.querySelector('.diff-money');
container.appendChild(content);
return {totalEl, diffEl};
})();
const updateElements = (changedName) => {
let total = 0;
ELEMENTS.forEach((v) => {
v.textValue.textContent = v.slider.value == 1 ? v.singularName : v.name;
v.money.textContent = MONEY_FORMAT.format(v.each * i(v.slider.value) / 100);
total += v.each * i(v.slider.value);
});
totalEl.textContent = MONEY_FORMAT.format(total / 100);
diffEl.textContent = MONEY_FORMAT.format((TOTAL - total) / 100);
if (changedName === null) return;
// If we know which slider was changed, try to bring all other non-zero sliders up to sync.
// If all other sliders are zero, then only increase the last one as a fallback.
// This is to ensure that we don't increase things that people have set to zero on purpose (e.g. because they don't like roast dinners or wine).
let epsilon = null;
let canBeAdjusted = [];
for (const e of ELEMENTS) {
if (e.name === changedName) continue;
if (e.slider.value == 0) continue;
canBeAdjusted.push(e);
if (epsilon === null || epsilon.each > e.each) {
epsilon = e;
}
}
if (epsilon === null) {
if (changedName === 'Packs of Crisps') {
// If everything is 0 other than packs of crisps, use beer.
epsilon = ELEMENTS[0];
canBeAdjusted.push(ELEMENTS[0]);
} else {
// Always just adjust packs of crisps.
epsilon = ELEMENTS[ELEMENTS.length-1];
canBeAdjusted.push(ELEMENTS[ELEMENTS.length-1]);
}
}
let totalOfAll = sum(canBeAdjusted, (v) => v.each);
let maxCanBeRemoved = canBeAdjusted.reduce((t, v) => Math.min(t, v.slider.value), TOTAL);
let difference = TOTAL - sum(ELEMENTS, (v) => v.slider.value*v.each);
console.log(`we're ${difference/100} short!`, epsilon.each);
// If we're *less* than TOTAL, we always adjust.
if (Math.abs(difference) < (epsilon.each/2) && difference < 0) return; // Nothing to do.
if (Math.abs(difference) >= (totalOfAll/2)) {
const amountToAddToAll = Math.max(-maxCanBeRemoved, Math.floor(difference / totalOfAll));
canBeAdjusted.forEach((v) => {
v.slider.value = v.text.value = i(v.slider.value) + amountToAddToAll;
});
difference -= Math.floor(difference / totalOfAll);
}
difference = TOTAL - sum(ELEMENTS, (v) => v.slider.value*v.each)
console.log(`Remaining difference: ${difference}; epsilon is ${epsilon.each}`);
// Mess with epsilon to make up the difference.
if (Math.abs(difference) >= (epsilon.each/2) || difference > 0) {
difference += epsilon.each * i(epsilon.slider.value);
epsilon.slider.value = epsilon.text.value = Math.max(0, Math.ceil(difference / epsilon.each));
}
updateElements(null);
};
updateElements(null);
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment