thanks for the formatting tips!!!
let price = 0;
let cid = [
["PENNY", 0],
["NICKEL", 0],
["DIME", 0],
["QUARTER", 0],
["ONE", 0],
["FIVE", 0],
["TEN", 0],
["TWENTY", 0],
["ONE HUNDRED", 0],
];
let cash = 0;
// change { denomination: string, quantity: number }[]
let change = [],
STATUS = "OPEN",
QUANTITIES = {};
// lower case status is deprecated keyword
const cashInput = document.getElementById("cash");
const purchaseButton = document.getElementById("purchase-btn");
const clearButton = document.getElementById("clear-btn");
const exampleButton = document.getElementById("examples-btn");
const changeDue = document.getElementById("change-due");
const loadExampleButton = document.getElementById("load-example-btn");
const exampleSelect = document.getElementById("example-select");
const priceInput = document.getElementById("price");
const currency = {
PENNY: 0.01,
NICKEL: 0.05,
DIME: 0.1,
QUARTER: 0.25,
ONE: 1,
FIVE: 5,
TEN: 10,
TWENTY: 20,
"ONE HUNDRED": 100,
};
// use cents instead of dollars to avoid float rounding errors
const cents = {
PENNY: 1,
NICKEL: 5,
DIME: 10,
QUARTER: 25,
ONE: 100,
FIVE: 500,
TEN: 1000,
TWENTY: 2000,
"ONE HUNDRED": 10000,
};
const currencies = [
"ONE HUNDRED",
"TWENTY",
"TEN",
"FIVE",
"ONE",
"QUARTER",
"DIME",
"NICKEL",
"PENNY",
];
const messages = {
insufficient: "Customer does not have enough money to purchase the item",
exact: "No change due - customer paid with exact cash",
};
const examples = [
{
price: 2,
cash: 1,
cid: [
["PENNY", 0],
["NICKEL", 0],
["DIME", 0],
["QUARTER", 0],
["ONE", 0],
["FIVE", 0],
["TEN", 0],
["TWENTY", 0],
["ONE HUNDRED", 0],
],
},
{
price: 1,
cash: 1,
cid: [
["PENNY", 0],
["NICKEL", 0],
["DIME", 0],
["QUARTER", 0],
["ONE", 0],
["FIVE", 0],
["TEN", 0],
["TWENTY", 0],
["ONE HUNDRED", 0],
],
},
{
price: 19.5,
cash: 20,
cid: [
["PENNY", 1.01],
["NICKEL", 2.05],
["DIME", 3.1],
["QUARTER", 4.25],
["ONE", 90],
["FIVE", 55],
["TEN", 20],
["TWENTY", 60],
["ONE HUNDRED", 100],
],
},
{
price: 3.26,
cash: 100,
cid: [
["PENNY", 1.01],
["NICKEL", 2.05],
["DIME", 3.1],
["QUARTER", 4.25],
["ONE", 90],
["FIVE", 55],
["TEN", 20],
["TWENTY", 60],
["ONE HUNDRED", 100],
],
},
{
price: 19.5,
cash: 20,
cid: [
["PENNY", 0.01],
["NICKEL", 0],
["DIME", 0],
["QUARTER", 0],
["ONE", 0],
["FIVE", 0],
["TEN", 0],
["TWENTY", 0],
["ONE HUNDRED", 0],
],
},
{
price: 19.5,
cash: 20,
cid: [
["PENNY", 0.01],
["NICKEL", 0],
["DIME", 0],
["QUARTER", 0],
["ONE", 1],
["FIVE", 0],
["TEN", 0],
["TWENTY", 0],
["ONE HUNDRED", 0],
],
},
{
price: 19.5,
cash: 20,
cid: [
["PENNY", 0.5],
["NICKEL", 0],
["DIME", 0],
["QUARTER", 0],
["ONE", 0],
["FIVE", 0],
["TEN", 0],
["TWENTY", 0],
["ONE HUNDRED", 0],
],
},
];
const loadExample = () => {
const idx = parseInt(exampleSelect.value);
const example = examples[idx];
cash = cashInput.value = example.cash;
price = priceInput.value = example.price;
// make a copy because counting change would modify by reference
cid = JSON.parse(JSON.stringify(example.cid));
let input;
for (let i = 0; i < cid.length; i++) {
if (cid[i][0] == "ONE HUNDRED") {
input = document.getElementById("drawer-HUNDRED");
} else {
input = document.getElementById(`drawer-${cid[i][0]}`);
}
if (input) input.value = cid[i][1];
}
};
const countChange = (due) => {
change = [];
STATUS = "OPEN";
let remaining = due;
let quantity, inDrawer, max;
filQuantities();
for (let i = 0; i < currencies.length; i++) {
const denomination = currencies[i];
inDrawer = QUANTITIES[denomination] || 0;
if (remaining > cents[denomination] && inDrawer > 0) {
max = Math.floor(remaining / cents[denomination]);
quantity = Math.min(inDrawer, max);
addToChange(denomination, cents[denomination], quantity);
remaining -= quantity * cents[denomination];
}
}
if (remaining > 0) {
STATUS = "INSUFFICIENT_FUNDS";
}
};
const filQuantities = () => {
QUANTITIES = [];
for (let i = 0; i < cid.length; i++) {
QUANTITIES[cid[i][0]] = Math.floor(cid[i][1] / currency[cid[i][0]]);
}
};
const addToChange = (denomination, amount, quantity) => {
// console.log({ denomination, amount, quantity })
const idxD = drawerIndex(denomination);
const drawerCents = Math.floor(cid[idxD][1] * 100) - amount * quantity;
cid[idxD][1] = drawerCents / 100;
const idxC = changeIndex(denomination);
if (idxC == -1) {
const item = { denomination, quantity };
change.push(item);
} else {
// not likely to get here
change[idxC].quantity += quantity;
}
};
const drawerIndex = (denomitation) => {
for (let i = 0; i < cid.length; i++) {
if (cid[i][0] == denomitation) return i;
}
return -1;
};
const changeIndex = (denomitation) => {
for (let i = 0; i < change.length; i++) {
if (change[i].denomination == denomitation) return i;
}
return -1;
};
const countDrawer = () => {
let total = 0;
for (let i = 0; i < cid.length; i++) {
total += Math.floor(cid[i][1] * 100);
}
return total / 100;
};
const adjustDrawer = (name, value) => {
let idx = drawerIndex(name);
if (idx == -1) {
cid.push([name, parseFloat(value)]);
} else {
cid[idx][1] = parseFloat(value);
}
};
const formatChange = () => {
let div, amt;
const wrapper = document.createElement("DIV");
wrapper.classList.add("change");
div = document.createElement("P");
div.innerHTML = `Status: ${STATUS}`;
wrapper.appendChild(div);
for (let i = 0; i < change.length; i++) {
const item = change[i];
div = document.createElement("P");
amt = (item.quantity * cents[item.denomination]) / 100;
div.innerHTML = `${item.denomination}: \$${amt}`;
wrapper.appendChild(div);
}
return wrapper;
};
const formatChangeText = () => {
const parts = [];
parts.push(`Status: ${STATUS}`);
for (let i = 0; i < change.length; i++) {
const item = change[i];
const amt = (item.quantity * cents[item.denomination]) / 100;
parts.push(`${item.denomination}: $${amt}`);
}
return parts.join(" ");
};
const handlePurchase = (cash, price, cid) => {
const response = {
status: "OPEN",
change: [],
};
if (cash < price) {
alert(messages.insufficient);
return;
}
if (cash == price) {
changeDue.innerText = messages.exact;
return messages.exact;
}
QUANTITIES = {};
let totalCents = 0;
const indicees = [];
for (let i = 0; i < cid.length; i++) {
QUANTITIES[cid[i][0]] = Math.floor(cid[i][1] / currency[cid[i][0]]);
totalCents += Math.floor(cid[i][1] * 100);
indicees.push(cid[i][0]);
}
const cashCents = Math.floor(cash * 100);
const priceCents = Math.floor(price * 100);
const dueCents = cashCents - priceCents;
let remaining = dueCents;
let quantity, inDrawer, max, amount, denomination, item, idx;
for (let i = 0; i < currencies.length; i++) {
denomination = currencies[i];
inDrawer = QUANTITIES[denomination] || 0;
if (remaining > cents[denomination] && inDrawer > 0) {
max = Math.floor(remaining / cents[denomination]);
quantity = Math.min(inDrawer, max);
amount = (quantity * cents[denomination]) / 100;
item = [denomination, amount];
response.change.push(item);
remaining -= quantity * cents[denomination];
QUANTITIES[denomination] -= quantity;
idx = indicees.indexOf(denomination);
if (idx != -1 && quantity > 0) {
const offset = parseFloat(
(quantity * currency[denomination]).toFixed(2)
);
cid[idx][1] = parseFloat((cid[idx][1] - offset).toFixed(2));
}
}
}
if (remaining > 0) {
response.status = "INSUFFICIENT_FUNDS";
response.change = [];
}
if (dueCents == totalCents && response.status !== "INSUFFICIENT_FUNDS") {
response.status = "CLOSED";
}
let parts = [];
parts.push(`Status: ${response.status}`);
for (const item of response.change) {
parts.push(`${item[0]}: $${item[1]}`);
}
const textResponse = parts.join(" ");
changeDue.innerText = textResponse;
return response;
};
const formatResponseChange = () => {
const c = [];
for (const item of change) {
const { denomination, quantity } = item;
c.push([denomination, quantity * currency[denomination]]);
}
return c;
};
const handleExamples = () => {
handleClear();
for (const ex of examples) {
price = ex.price;
cash = cashInput.value = ex.cash;
cid = JSON.parse(JSON.stringify(ex.cid));
if (cash < price) {
changeDue.innerHTML += `<div class="change">${messages.insufficient}</div>`;
continue;
}
if (cash == price) {
changeDue.innerHTML += `<div class="change">${messages.exact}</div>`;
continue;
}
const cashCents = Math.floor(cash * 100);
const priceCents = Math.floor(price * 100);
const due = cashCents - priceCents;
const remaining = Math.floor(countDrawer() * 100);
countChange(due);
if (remaining == due && STATUS !== "INSUFFICIENT_FUNDS") {
STATUS = "CLOSED";
}
if (STATUS === "INSUFFICIENT_FUNDS") {
changeDue.innerHTML += `<div class="change">Status: ${STATUS}</div>`;
} else {
const formatted = formatChange();
changeDue.innerHTML += formatted.outerHTML;
}
}
};
const handleClear = () => {
cashInput.value = 0;
priceInput.value = 0;
changeDue.innerHTML = "";
let input;
for (let i = 0; i < cid.length; i++) {
if (cid[i][0] == "ONE HUNDRED") {
input = document.getElementById("drawer-HUNDRED");
} else {
input = document.getElementById(`drawer-${cid[i][0]}`);
}
if (input) input.value = 0;
}
};
purchaseButton.addEventListener("click", (e) => {
e.preventDefault();
handlePurchase(cash, price, cid);
});
clearButton.addEventListener("click", handleClear);
exampleButton.addEventListener("click", handleExamples);
loadExampleButton.addEventListener("click", loadExample);
// window.onload = handleClear;