From fc57ce4a077f31e2ef0b3ca790fe58495cbc1d23 Mon Sep 17 00:00:00 2001 From: rowboat1 Date: Fri, 28 Jun 2024 21:09:01 +1000 Subject: [PATCH 1/3] Enhancing distance function The tooClose function will always hit if you draw too many early cards in the 1800-2020 range When the tooClose function rules out all cards, the game defaults to drawing a random card from the deck without any other failsafes. One of the failsafes in tooClose is to ensure cards are at least 1 year away from any existing card. Without this failsafe, we can have ties (which fail on a coinflip) and in some cases even a duplicate card being drawn. We also lose all the other balancing effects of avoidPeople and splitting cards into three different periods. --- lib/items.ts | 61 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/lib/items.ts b/lib/items.ts index b459d69..5db4fb0 100644 --- a/lib/items.ts +++ b/lib/items.ts @@ -10,6 +10,7 @@ export function getRandomItem(deck: Item[], played: Item[]): Item { const [fromYear, toYear] = periods[Math.floor(Math.random() * periods.length)]; const avoidPeople = Math.random() > 0.5; + const periodWillFail = checkPeriodWillFail(played, fromYear, toYear); const candidates = deck.filter((candidate) => { if (avoidPeople && candidate.instance_of.includes("human")) { return false; @@ -17,7 +18,7 @@ export function getRandomItem(deck: Item[], played: Item[]): Item { if (candidate.year < fromYear || candidate.year > toYear) { return false; } - if (tooClose(candidate, played)) { + if (tooClose(candidate, played, periodWillFail)) { return false; } return true; @@ -29,10 +30,62 @@ export function getRandomItem(deck: Item[], played: Item[]): Item { return deck[Math.floor(Math.random() * deck.length)]; } -function tooClose(item: Item, played: Item[]) { +function getIdealDistance(played: Item[]) { + return 110 - 10 * played.length; +} + +/** + * This function checks that the played cards won't cause tooClose to return + * true for all years in a period (possible for 1800-2020) + */ +function checkPeriodWillFail( + played: Item[], + fromYear: number, + toYear: number +): boolean { + if (played.length > 11 || played.length === 0) { + return false; + } + + const distance = getIdealDistance(played); + + const playedYears = played.map(({ year }) => year).sort(); + const interestingYears = playedYears.filter((year) => + fromYear - distance < year && year < toYear + distance + ); + + if (interestingYears.length === 0) { + return false; + } + + // If there is room at either end of the period, then we can proceed safely. + if (interestingYears[0] - fromYear >= distance) { + return false; + } + + if (toYear - interestingYears[-1] >= distance) { + return false; + } + + // If there is room in between cards, we can proceed safely. + for (let i = 0; i < interestingYears.length - 1; i++) { + if (interestingYears[i+1] - interestingYears[i] >= 2 * distance) { + return false; + } + } + + return true; +} + +function tooClose( + item: Item, + played: Item[], + periodWillFail: boolean +): boolean { let distance = (played.length < 40) ? 5 : 1; - if (played.length < 11) - distance = 110 - 10 * played.length; + if (!periodWillFail && played.length < 11) { + distance = getIdealDistance(played); + } return played.some((p) => Math.abs(item.year - p.year) < distance); } From 216f2c1abd13ec5d759256f694e73ab33ddf094d Mon Sep 17 00:00:00 2001 From: rowboat1 Date: Fri, 28 Jun 2024 22:17:02 +1000 Subject: [PATCH 2/3] Can't draw from invalid period --- lib/items.ts | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/items.ts b/lib/items.ts index 5db4fb0..64074dc 100644 --- a/lib/items.ts +++ b/lib/items.ts @@ -7,10 +7,12 @@ export function getRandomItem(deck: Item[], played: Item[]): Item { [1000, 1800], [1800, 2020], ]; + const validPeriods = periods.filter( + ([fromYear, toYear]) => checkPeriodWillFail(played, fromYear, toYear) + ); const [fromYear, toYear] = - periods[Math.floor(Math.random() * periods.length)]; + validPeriods[Math.floor(Math.random() * validPeriods.length)]; const avoidPeople = Math.random() > 0.5; - const periodWillFail = checkPeriodWillFail(played, fromYear, toYear); const candidates = deck.filter((candidate) => { if (avoidPeople && candidate.instance_of.includes("human")) { return false; @@ -18,7 +20,7 @@ export function getRandomItem(deck: Item[], played: Item[]): Item { if (candidate.year < fromYear || candidate.year > toYear) { return false; } - if (tooClose(candidate, played, periodWillFail)) { + if (tooClose(candidate, played)) { return false; } return true; @@ -39,9 +41,9 @@ function getIdealDistance(played: Item[]) { * true for all years in a period (possible for 1800-2020) */ function checkPeriodWillFail( - played: Item[], - fromYear: number, - toYear: number + played: Item[], + fromYear: number, + toYear: number, ): boolean { if (played.length > 11 || played.length === 0) { return false; @@ -77,13 +79,9 @@ function checkPeriodWillFail( return true; } -function tooClose( - item: Item, - played: Item[], - periodWillFail: boolean -): boolean { +function tooClose(item: Item, played: Item[]): boolean { let distance = (played.length < 40) ? 5 : 1; - if (!periodWillFail && played.length < 11) { + if (played.length < 11) { distance = getIdealDistance(played); } From d624cf297bee3f2727f805c86647db3cc3180907 Mon Sep 17 00:00:00 2001 From: rowboat1 Date: Fri, 28 Jun 2024 22:18:26 +1000 Subject: [PATCH 3/3] reversing a check --- lib/items.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/items.ts b/lib/items.ts index 64074dc..f49daef 100644 --- a/lib/items.ts +++ b/lib/items.ts @@ -8,7 +8,7 @@ export function getRandomItem(deck: Item[], played: Item[]): Item { [1800, 2020], ]; const validPeriods = periods.filter( - ([fromYear, toYear]) => checkPeriodWillFail(played, fromYear, toYear) + ([fromYear, toYear]) => !checkPeriodWillFail(played, fromYear, toYear) ); const [fromYear, toYear] = validPeriods[Math.floor(Math.random() * validPeriods.length)];