fix(core): enable take-from-foundation by default (closes #3)
Build and Deploy / build-and-push (push) Successful in 4m55s

Standard Klondike allows returning the top card of a foundation pile to a
compatible tableau column. The flag was previously off by default, making the
move impossible for all players.

Changes:
- GameState::new_with_mode: take_from_foundation now initialises to true
- Settings: default changed to true; custom serde default function ensures
  older settings.json files without the key also resolve to true
- Tests: rename "blocked_by_default" → "allowed_by_default" (asserts the
  move now succeeds); add "blocked_when_disabled" to cover the flag=false path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-05-14 17:57:22 -07:00
parent 8fe0891866
commit c35c045f08
2 changed files with 23 additions and 9 deletions
+13 -3
View File
@@ -191,7 +191,7 @@ impl GameState {
is_auto_completable: false,
undo_count: 0,
recycle_count: 0,
take_from_foundation: false,
take_from_foundation: true,
schema_version: GAME_STATE_SCHEMA_VERSION,
undo_stack: VecDeque::new(),
}
@@ -1306,9 +1306,18 @@ mod tests {
}
#[test]
fn take_from_foundation_blocked_by_default() {
fn take_from_foundation_allowed_by_default() {
let mut g = setup_take_from_foundation_game();
assert!(!g.take_from_foundation);
assert!(g.take_from_foundation, "standard Klondike allows take-from-foundation by default");
g.move_cards(PileType::Foundation(0), PileType::Tableau(0), 1).unwrap();
assert_eq!(g.piles[&PileType::Foundation(0)].cards.len(), 1);
assert_eq!(g.piles[&PileType::Tableau(0)].cards.len(), 2);
}
#[test]
fn take_from_foundation_blocked_when_disabled() {
let mut g = setup_take_from_foundation_game();
g.take_from_foundation = false;
let err = g
.move_cards(PileType::Foundation(0), PileType::Tableau(0), 1)
.unwrap_err();
@@ -1321,6 +1330,7 @@ mod tests {
#[test]
fn take_from_foundation_allowed_when_enabled() {
let mut g = setup_take_from_foundation_game();
// already true by default; explicit set confirms the behaviour holds
g.take_from_foundation = true;
g.move_cards(PileType::Foundation(0), PileType::Tableau(0), 1).unwrap();
// Foundation slot 0 should now hold only the Ace.
+10 -6
View File
@@ -239,11 +239,11 @@ pub struct Settings {
/// field existed deserialize cleanly to `None` via `#[serde(default)]`.
#[serde(default)]
pub leaderboard_display_name: Option<String>,
/// When `true`, the player may drag the top card of a completed foundation
/// pile back onto a compatible tableau column — a non-standard house rule.
/// Off by default. Older `settings.json` files deserialize cleanly to
/// `false` via `#[serde(default)]`.
#[serde(default)]
/// When `true`, the player may drag the top card of a foundation pile back
/// onto a compatible tableau column. Enabled by default (standard Klondike
/// rules). Older `settings.json` files without this key deserialize to
/// `true` via the custom serde default.
#[serde(default = "default_take_from_foundation")]
pub take_from_foundation: bool,
/// When `true`, anonymous game-play events (game start, game won, etc.)
/// are sent to the configured Matomo instance. Opt-in; defaults to `false`.
@@ -332,6 +332,10 @@ fn default_matomo_site_id() -> u32 {
1
}
fn default_take_from_foundation() -> bool {
true
}
/// Lower bound of the player-tunable replay-playback per-move interval,
/// in seconds. Below this the cards barely register visually before
/// the next move fires; the cap keeps the playback legible.
@@ -384,7 +388,7 @@ impl Default for Settings {
replay_move_interval_secs: default_replay_move_interval_secs(),
last_difficulty: None,
leaderboard_display_name: None,
take_from_foundation: false,
take_from_foundation: true,
analytics_enabled: false,
matomo_url: None,
matomo_site_id: default_matomo_site_id(),