1
0
Fork 0

fix(eod): unlock custom minefields after clearing two sectors

This commit is contained in:
Jason Fraley 2026-09-09 18:55:00 -04:00
parent fdf6da5c5b
commit 0dd9673690
4 changed files with 22 additions and 17 deletions

View file

@ -34,7 +34,6 @@ export function EodArena({
const clearedSectors = [ const clearedSectors = [
initialStats.bestTrainingTime, initialStats.bestTrainingTime,
initialStats.bestPerimeterTime, initialStats.bestPerimeterTime,
initialStats.bestSupplyRoadTime,
].filter((time) => time > 0).length; ].filter((time) => time > 0).length;
useEffect(() => { useEffect(() => {
@ -55,8 +54,8 @@ export function EodArena({
{!customUnlocked && ( {!customUnlocked && (
<UnlockNotice <UnlockNotice
title="custom minefields" title="custom minefields"
requirement="you have cleared Training Pad, FOB Perimeter, and Main Supply Road" requirement="you have cleared Training Pad and FOB Perimeter"
progress={`${clearedSectors} of 3 sectors cleared`} progress={`${clearedSectors} of 2 sectors cleared`}
/> />
)} )}
<MinesweeperBoard <MinesweeperBoard

View file

@ -20,6 +20,7 @@ import { PROFILE_UPDATE_EVENT } from "@/hooks/useGameTick";
import { useSessionExpired } from "@/hooks/useSessionExpired"; import { useSessionExpired } from "@/hooks/useSessionExpired";
import { LoginLink } from "@/components/frontend/auth/LoginLink"; import { LoginLink } from "@/components/frontend/auth/LoginLink";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { isEodUnlocked } from "@/lib/minigames/custom/eod";
import { playClear, playExplosion, playFlag, playReveal, playXp } from "./sounds"; import { playClear, playExplosion, playFlag, playReveal, playXp } from "./sounds";
type Phase = "ready" | "playing" | "over"; type Phase = "ready" | "playing" | "over";
@ -227,8 +228,11 @@ export function MinesweeperBoard({
const longPressTimerRef = useRef<number | null>(null); const longPressTimerRef = useRef<number | null>(null);
const baseConfig: GameConfig = difficulty === "custom" ? customConfig : DIFFICULTIES[difficulty]; const baseConfig: GameConfig = difficulty === "custom" ? customConfig : DIFFICULTIES[difficulty];
const config = isMobile ? getMobileConfig(baseConfig, isLandscape) : baseConfig; const config = isMobile ? getMobileConfig(baseConfig, isLandscape) : baseConfig;
const customUnlocked = const customUnlocked = isEodUnlocked({
stats.bestTrainingTime > 0 && stats.bestPerimeterTime > 0 && stats.bestSupplyRoadTime > 0; bestTrainingTime: stats.bestTrainingTime,
bestPerimeterTime: stats.bestPerimeterTime,
bestSupplyRoadTime: stats.bestSupplyRoadTime,
});
const isLargeCustomGame = const isLargeCustomGame =
difficulty === "custom" && config.rows * config.cols > CUSTOM_FULLSCREEN_CELL_THRESHOLD; difficulty === "custom" && config.rows * config.cols > CUSTOM_FULLSCREEN_CELL_THRESHOLD;
const flaggedCount = board.reduce((count, cell) => count + (cell.flagged ? 1 : 0), 0); const flaggedCount = board.reduce((count, cell) => count + (cell.flagged ? 1 : 0), 0);
@ -812,7 +816,7 @@ export function MinesweeperBoard({
</div> </div>
) : !customUnlocked ? ( ) : !customUnlocked ? (
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
Clear all three standard locations to unlock custom minefields. Clear the Training Pad and FOB Perimeter to unlock custom minefields.
</p> </p>
) : null} ) : null}
</fieldset> </fieldset>

View file

@ -23,13 +23,9 @@ export interface EodUnlockStats {
bestSupplyRoadTime: number; bestSupplyRoadTime: number;
} }
/** A player unlocks the custom minefield editor once they cleared all three standard difficulties. */ /** A player unlocks the custom minefield editor once they cleared Training Pad and FOB Perimeter. */
export function isEodUnlocked(stats: EodUnlockStats): boolean { export function isEodUnlocked(stats: EodUnlockStats): boolean {
return ( return stats.bestTrainingTime > 0 && stats.bestPerimeterTime > 0;
stats.bestTrainingTime > 0 &&
stats.bestPerimeterTime > 0 &&
stats.bestSupplyRoadTime > 0
);
} }
export function validateMinefieldName(name: unknown): { error?: string; name: string } { export function validateMinefieldName(name: unknown): { error?: string; name: string } {

View file

@ -465,7 +465,7 @@ describe("Custom minefields", () => {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
describe("Unlock gating", () => { describe("Unlock gating", () => {
it("locks custom minefields until a win is recorded on all three difficulties", () => { it("locks custom minefields until Training Pad and FOB Perimeter are cleared", () => {
expect( expect(
isEodUnlocked({ bestTrainingTime: 0, bestPerimeterTime: 0, bestSupplyRoadTime: 0 }), isEodUnlocked({ bestTrainingTime: 0, bestPerimeterTime: 0, bestSupplyRoadTime: 0 }),
).toBe(false); ).toBe(false);
@ -473,18 +473,24 @@ describe("Custom minefields", () => {
isEodUnlocked({ bestTrainingTime: 45, bestPerimeterTime: 0, bestSupplyRoadTime: 0 }), isEodUnlocked({ bestTrainingTime: 45, bestPerimeterTime: 0, bestSupplyRoadTime: 0 }),
).toBe(false); ).toBe(false);
expect( expect(
isEodUnlocked({ bestTrainingTime: 45, bestPerimeterTime: 60, bestSupplyRoadTime: 0 }), isEodUnlocked({ bestTrainingTime: 0, bestPerimeterTime: 60, bestSupplyRoadTime: 0 }),
).toBe(false); ).toBe(false);
}); });
it("unlocks once every difficulty has a best time above zero", () => { it("unlocks once Training Pad and FOB Perimeter have best times above zero", () => {
expect( expect(
isEodUnlocked({ bestTrainingTime: 1, bestPerimeterTime: 1, bestSupplyRoadTime: 1 }), isEodUnlocked({ bestTrainingTime: 1, bestPerimeterTime: 1, bestSupplyRoadTime: 0 }),
).toBe(true); ).toBe(true);
expect( expect(
isEodUnlocked({ bestTrainingTime: 45, bestPerimeterTime: 60, bestSupplyRoadTime: 120 }), isEodUnlocked({ bestTrainingTime: 45, bestPerimeterTime: 60, bestSupplyRoadTime: 0 }),
).toBe(true); ).toBe(true);
}); });
it("does not let a Main Supply Road win substitute for the required sectors", () => {
expect(
isEodUnlocked({ bestTrainingTime: 0, bestPerimeterTime: 0, bestSupplyRoadTime: 120 }),
).toBe(false);
});
}); });
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------