Four state classes that each answer every event, a machine that only forwards events, and change-making from a limited cash box where greedy gets stuck.
The vending-machine round, as in the lesson: ₹1, ₹2, ₹5, ₹10 coins and ₹20 notes, products in coded slots, change from a limited cash box, and a cancel that returns the very coins that went in. Product, Slot and the base State (which refuses every event politely) are given. You build make_change, the four states and the machine that forwards events to its current state.
make_change(amount, cash): cash maps a denomination to how many the box holds. Return the fewest coins that make amount using only those, as {coin: n}, or None. Greedy fails here: ₹6 from {5: 1, 2: 3} is 2 + 2 + 2, and greedy grabs the 5 first.
VendingMachine(slots, cash) holds the data: slots (code to Slot), cash (coins the machine owns), escrow (coins inserted for this sale), returned (the tray), state. It starts Idle(), or SoldOut() when no slot has stock. balance is the sum of the escrow. insert(coin), select(code), cancel() and item_dropped() forward to the current state (an unknown denomination goes straight to the tray, whatever the state, and answers "unknown coin returned"); refill(code, n) adds stock and leaves SoldOut; take_tray() empties and returns the tray; commit(slot, box, change) does the sale: one less in the slot, the change moved from box to the tray, cash becomes box without zero counts, the escrow cleared.
The states (each returns a message; the base class gives the "not now" answers):
State (name) |
insert |
select(code) |
cancel |
item_dropped |
|---|---|---|---|---|
Idle (IDLE) |
escrow it, go to HasMoney, balance ₹10 |
insert money first |
base | base |
HasMoney (HAS_MONEY) |
escrow it, balance ₹15 |
see below | return the escrow, go Idle, cancelled, coins returned |
base |
Dispensing (DISPENSING) |
base (coin returned) | base | base | go Idle (or SoldOut if no stock anywhere), done, now IDLE |
SoldOut (SOLD_OUT) |
base | sold out |
base | base |
The base answers are "<NAME>: coin returned" (the coin goes to the tray), "<NAME>: can't select now", "<NAME>: nothing to cancel" and "<NAME>: ignored sensor event".
HasMoney.select(code), in order: an unknown or empty slot says "<code> sold out, pick another or cancel"; too little money says "<product name> is ₹<price>, add ₹<missing>" and stays in HasMoney; otherwise work out the change from the cash box plus the customer's own coins (before committing anything): none possible says "can't make change: exact amount only" and keeps the money; else commit, go to Dispensing and say "dispensing <product name>".
JavaScript and TypeScript: camelCase (itemDropped, takeTray, makeChange), cash and the change are plain objects ({ 5: 1, 2: 3 }), slots is a Map, balance is a getter, and a state's name is a field.
Write your code, then run 11 example tests and 3 hidden tests. Each run is limited to 8 seconds: an infinite loop gets stopped and the runtime restarts, your code stays.