The Spirit-Circle of Kelmorrow Tarn
Before every expedition into the marshes, the summoners of the Ashgrove Conclave chalk a binding-circle on the shore of Kelmorrow Tarn. The circle can hold only so many spirits at once. The Conclave offers a bounty to any keeper who can say, at every moment, which spirits remain bound and which are lost.
Implement the class SpiritCircle. Each spirit has a unique name (a non-empty string) and a rank (an integer). A bound spirit may also be sealed.
Methods
- constructor(capacity):
capacityis a non-negative integer, the maximum number of spirits bound at once. The circle starts empty. - summon(name, rank) returns a string or null.
- If
nameis already bound, its rank becomesrankand this counts as a use. It stays sealed or unsealed as it was. Return null. This case never evicts anyone. - Otherwise, if fewer than
capacityspirits are bound, bind it unsealed with rankrank. This counts as a use. Return null. - Otherwise the circle is full. The candidate is the unsealed bound spirit with the lowest rank. If several share that lowest rank, the candidate is the one whose most recent use is earliest.
- If no bound spirit is unsealed, or
rankis lower than the candidate's rank, the newcomer is turned away. Nothing changes, and the method returnsname(the newcomer's own name). - Otherwise, remove the candidate and bind the newcomer unsealed. This counts as a use. Return the candidate's name.
- call(name) returns an integer.
- If
nameis bound, return its rank. This counts as a use, and the call removes the spirit's seal if it had one. - If
nameis not bound, return -1 and change nothing. - seal(name) returns a boolean.
- If
nameis bound, it becomes sealed (it stays sealed if it already was), and the method returns true. - Otherwise, return false.
- dismiss(name) returns a boolean.
- If
nameis bound, remove it whether sealed or not, and return true. - Otherwise, return false.
- roster() returns a list of the names currently bound, sorted in ascending order by character code.
Recency
Only the summon and call outcomes marked above as counting as a use update a spirit's most recent use. Each such use is later than every use before it. Sealing, dismissing, listing the roster, a turned-away summon and a call on an unbound name are not uses. A spirit that is dismissed or removed and later summoned again is a fresh binding: unsealed, with its new rank.
examples
- case 1
capacity 2; summon ash (rank 3), summon brine (rank 5), summon cinder (rank 4)the third summon evicts ash and returns "ash"; roster is ["brine", "cinder"] - case 2
capacity 2; summon quill (rank 1), summon rook (rank 9), seal quill, summon sorrow (rank 3)sorrow is turned away and summon returns "sorrow": the only unsealed spirit, rook, outranks it, and the weaker quill is sealed - case 3
capacity 2; summon yarrow (rank 2), summon zephyr (rank 8), seal yarrow, call yarrow, summon alder (rank 5)call returns 2 and breaks the seal, so summon alder evicts yarrow and returns "yarrow" - case 4
capacity 0; summon flint (rank 7)"flint" (turned away); roster stays []
- reward
- 540 XP
- signature
SpiritCircle
TypeScript, run as ES2022 — types are stripped, not checked. If a JS engine would run it, so will we.
- lowest rank is evicted
- equal ranks fall to least recently used
- rank outweighs recency
- weaker newcomer is turned away, equal rank displaces
- sealed spirit is ignored when choosing the candidate
- all sealed turns every newcomer away
- calling breaks the seal
- re-summon updates rank and recency but keeps seal
- capacity zero holds nothing
- dismiss frees a place and rebinding is fresh