From 98f369bd93ff8f0e53213f17ff3cbb20bf3c0354 Mon Sep 17 00:00:00 2001 From: Austin Smith Date: Sat, 4 Jul 2026 14:55:30 -0700 Subject: [PATCH] base recycle penalty on dealt draw mode Co-Authored-By: Claude Fable 5 --- .../Game/Klondike/GameSessionKlondike.swift | 2 +- .../Game/Shared/GameSession.swift | 2 +- .../Shared/SolitaireViewModelCoreTests.swift | 41 ++++++++++++++++++- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/ComputerSolitaire/Game/Klondike/GameSessionKlondike.swift b/ComputerSolitaire/Game/Klondike/GameSessionKlondike.swift index 4bad037..cf5e6ba 100644 --- a/ComputerSolitaire/Game/Klondike/GameSessionKlondike.swift +++ b/ComputerSolitaire/Game/Klondike/GameSessionKlondike.swift @@ -162,7 +162,7 @@ extension SolitaireViewModel { state.waste.removeAll() setWasteDrawCount(0) incrementMovesCount() - if stockDrawCount == DrawMode.one.rawValue { + if scoringDrawCount == DrawMode.one.rawValue { applyScore(.recycleWasteInDrawOne) } SoundManager.shared.play(.wasteRecycleToStock) diff --git a/ComputerSolitaire/Game/Shared/GameSession.swift b/ComputerSolitaire/Game/Shared/GameSession.swift index 505cf05..9af52f7 100644 --- a/ComputerSolitaire/Game/Shared/GameSession.swift +++ b/ComputerSolitaire/Game/Shared/GameSession.swift @@ -40,7 +40,7 @@ final class SolitaireViewModel { private(set) var finalElapsedSeconds: Int? private var pauseStartedAt: Date? private(set) var stockDrawCount: Int = 3 - private var scoringDrawCount: Int = DrawMode.three.rawValue + private(set) var scoringDrawCount: Int = DrawMode.three.rawValue private var hasStartedTrackedGame = false private var isCurrentGameFinalized = false private var hintRequestsInCurrentGame: Int = 0 diff --git a/ComputerSolitaireTests/Shared/SolitaireViewModelCoreTests.swift b/ComputerSolitaireTests/Shared/SolitaireViewModelCoreTests.swift index 331b299..0c205dd 100644 --- a/ComputerSolitaireTests/Shared/SolitaireViewModelCoreTests.swift +++ b/ComputerSolitaireTests/Shared/SolitaireViewModelCoreTests.swift @@ -65,6 +65,44 @@ final class SolitaireViewModelCoreTests: XCTestCase { XCTAssertTrue(viewModel.state.stock.allSatisfy { !$0.isFaceUp }) } + func testRecyclePenaltyAppliesWhenDealtDrawOneEvenAfterSwitchingToDrawThree() { + var state = GameStateFixtures.validPersistenceState() + state.waste = state.stock.map { card in + var faceUp = card + faceUp.isFaceUp = true + return faceUp + } + state.stock = [] + state.wasteDrawCount = min(1, state.waste.count) + let viewModel = makeViewModel( + restoring: payload(state: state, stockDrawCount: DrawMode.one.rawValue, score: 150) + ) + + viewModel.updateDrawMode(.three) + viewModel.handleStockTap() + + XCTAssertEqual(viewModel.score, 150 + Scoring.delta(for: .recycleWasteInDrawOne)) + } + + func testRecycleHasNoPenaltyWhenDealtDrawThreeEvenAfterSwitchingToDrawOne() { + var state = GameStateFixtures.validPersistenceState() + state.waste = state.stock.map { card in + var faceUp = card + faceUp.isFaceUp = true + return faceUp + } + state.stock = [] + state.wasteDrawCount = min(3, state.waste.count) + let viewModel = makeViewModel( + restoring: payload(state: state, stockDrawCount: DrawMode.three.rawValue, score: 150) + ) + + viewModel.updateDrawMode(.one) + viewModel.handleStockTap() + + XCTAssertEqual(viewModel.score, 150) + } + func testStartDragCanDropAndHandleDropMoveWasteToFoundation() { let aceSpades = TestCards.make(.spades, .ace, isFaceUp: true) let state = makeValidState( @@ -168,6 +206,7 @@ final class SolitaireViewModelCoreTests: XCTestCase { state: GameState, savedAt: Date = DateFixtures.reference, stockDrawCount: Int, + score: Int = 0, gameStartedAt: Date = DateFixtures.reference, pauseStartedAt: Date? = nil ) -> SavedGamePayload { @@ -175,7 +214,7 @@ final class SolitaireViewModelCoreTests: XCTestCase { savedAt: savedAt, state: state, movesCount: 0, - score: 0, + score: score, gameStartedAt: gameStartedAt, pauseStartedAt: pauseStartedAt, hasAppliedTimeBonus: false,