feat(core): add klondike v0.2.0 dep and KlondikeAdapter (integration steps 1, 3, 4)
Build and Deploy / build-and-push (push) Failing after 1m0s
Build and Deploy / build-and-push (push) Failing after 1m0s
Step 1 — Cargo & registry: - Add .cargo/config.toml with Quaternions sparse registry (https://git.aleshym.co/api/packages/Quaternions/cargo/) - Add klondike = "0.2.0" to workspace deps (+ card_game v0.3.0, arrayvec v0.7.6 as transitives via the Quaternions registry) - Add klondike as a solitaire_core dep Step 3 — KlondikeConfig / MoveFromFoundationConfig: - KlondikeAdapter::new(draw_mode, take_from_foundation) builds a KlondikeConfig with the correct DrawStockConfig and MoveFromFoundationConfig (Allowed/Disallowed); exposes it via klondike_config() for future solver and pile-mapping steps Step 4 — Scoring via ScoringConfig: - GameState.adapter (serde(skip)) owns the authoritative KlondikeConfig with ScoringConfig::DEFAULT (WXP values) - score_for_move/flip/undo/recycle replace direct scoring.rs calls; scoring.rs retained for reference and future deletion - score_for_recycle implements the WXP free-recycle allowance rule that ScoringConfig::recycle cannot express (flat delta) - PartialEq/Eq for KlondikeAdapter compare draw_stock and move_from_foundation only (scoring is always DEFAULT) All 192 solitaire_core tests pass; clippy -D warnings clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
[registries.Quaternions]
|
||||||
|
index = "sparse+https://git.aleshym.co/api/packages/Quaternions/cargo/"
|
||||||
Generated
+65
-12
@@ -364,6 +364,12 @@ version = "0.7.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayvec"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "sparse+https://git.aleshym.co/api/packages/Quaternions/cargo/"
|
||||||
|
checksum = "813440870d646c57c222c1d713dc4e3ddcb2919c3801564d767d85d7bf2afee4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "as-raw-xcb-connection"
|
name = "as-raw-xcb-connection"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@@ -901,7 +907,7 @@ version = "0.18.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c9cf7a3ee41342dd7b5a5d82e200d0e8efb933169247fce853b4ad633d51e87d"
|
checksum = "c9cf7a3ee41342dd7b5a5d82e200d0e8efb933169247fce853b4ad633d51e87d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"bevy_ecs_macros",
|
"bevy_ecs_macros",
|
||||||
"bevy_platform",
|
"bevy_platform",
|
||||||
"bevy_ptr",
|
"bevy_ptr",
|
||||||
@@ -1138,7 +1144,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e931fa969f89c83498b22c97432383afe90e90fd1a5e04fa07be8da4d3bcac84"
|
checksum = "e931fa969f89c83498b22c97432383afe90e90fd1a5e04fa07be8da4d3bcac84"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"approx",
|
"approx",
|
||||||
"arrayvec",
|
"arrayvec 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"bevy_reflect",
|
"bevy_reflect",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"glam 0.30.10",
|
"glam 0.30.10",
|
||||||
@@ -1703,7 +1709,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce"
|
checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayref",
|
"arrayref",
|
||||||
"arrayvec",
|
"arrayvec 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"cc",
|
"cc",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"constant_time_eq",
|
"constant_time_eq",
|
||||||
@@ -1879,6 +1885,15 @@ dependencies = [
|
|||||||
"wayland-client",
|
"wayland-client",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "card_game"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "sparse+https://git.aleshym.co/api/packages/Quaternions/cargo/"
|
||||||
|
checksum = "38b68e4fb32f8a1f92edf8488c012f6d8af71491a2f9f8a855362d7eaf1a2d0c"
|
||||||
|
dependencies = [
|
||||||
|
"arrayvec 0.7.6 (sparse+https://git.aleshym.co/api/packages/Quaternions/cargo/)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cbc"
|
name = "cbc"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@@ -1939,6 +1954,17 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "18758054972164c3264f7c8386f5fc6da6114cb46b619fd365d4e3b2dc3ae487"
|
checksum = "18758054972164c3264f7c8386f5fc6da6114cb46b619fd365d4e3b2dc3ae487"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chacha20"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures 0.3.0",
|
||||||
|
"rand_core 0.10.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.44"
|
version = "0.4.44"
|
||||||
@@ -4326,13 +4352,23 @@ dependencies = [
|
|||||||
"triple_buffer",
|
"triple_buffer",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "klondike"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "sparse+https://git.aleshym.co/api/packages/Quaternions/cargo/"
|
||||||
|
checksum = "0bce541f9b14e9d9d8c9b17d5df40bd0a017709b61d9be8ad5bab7b19a1a0152"
|
||||||
|
dependencies = [
|
||||||
|
"card_game",
|
||||||
|
"rand 0.10.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kurbo"
|
name = "kurbo"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7564e90fe3c0d5771e1f0bc95322b21baaeaa0d9213fa6a0b61c99f8b17b3bfb"
|
checksum = "7564e90fe3c0d5771e1f0bc95322b21baaeaa0d9213fa6a0b61c99f8b17b3bfb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"euclid",
|
"euclid",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
@@ -4740,7 +4776,7 @@ version = "27.0.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "066cf25f0e8b11ee0df221219010f213ad429855f57c494f995590c861a9a7d8"
|
checksum = "066cf25f0e8b11ee0df221219010f213ad429855f57c494f995590c861a9a7d8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"bit-set",
|
"bit-set",
|
||||||
"bitflags 2.11.1",
|
"bitflags 2.11.1",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
@@ -5947,6 +5983,16 @@ dependencies = [
|
|||||||
"rand_core 0.9.5",
|
"rand_core 0.9.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
|
||||||
|
dependencies = [
|
||||||
|
"chacha20",
|
||||||
|
"rand_core 0.10.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_chacha"
|
name = "rand_chacha"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@@ -5985,6 +6031,12 @@ dependencies = [
|
|||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_distr"
|
name = "rand_distr"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@@ -6980,6 +7032,7 @@ dependencies = [
|
|||||||
name = "solitaire_core"
|
name = "solitaire_core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"klondike",
|
||||||
"rand 0.9.4",
|
"rand 0.9.4",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
@@ -7502,7 +7555,7 @@ version = "0.5.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ea00cc4f79b7f6bb7ff87eddc065a1066f3a43fe1875979056672c9ef948c2af"
|
checksum = "ea00cc4f79b7f6bb7ff87eddc065a1066f3a43fe1875979056672c9ef948c2af"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
@@ -7601,7 +7654,7 @@ version = "0.9.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "41ba83ebaf2954d31d05d67340fd46cebe99da2b7133b0dd68d70c65473a437b"
|
checksum = "41ba83ebaf2954d31d05d67340fd46cebe99da2b7133b0dd68d70c65473a437b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"grid",
|
"grid",
|
||||||
"serde",
|
"serde",
|
||||||
"slotmap",
|
"slotmap",
|
||||||
@@ -7870,7 +7923,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
|
checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayref",
|
"arrayref",
|
||||||
"arrayvec",
|
"arrayvec 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"log",
|
"log",
|
||||||
@@ -7884,7 +7937,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "47ffee5eaaf5527f630fb0e356b90ebdec84d5d18d937c5e440350f88c5a91ea"
|
checksum = "47ffee5eaaf5527f630fb0e356b90ebdec84d5d18d937c5e440350f88c5a91ea"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayref",
|
"arrayref",
|
||||||
"arrayvec",
|
"arrayvec 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"log",
|
"log",
|
||||||
@@ -9044,7 +9097,7 @@ version = "27.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bfe68bac7cde125de7a731c3400723cadaaf1703795ad3f4805f187459cd7a77"
|
checksum = "bfe68bac7cde125de7a731c3400723cadaaf1703795ad3f4805f187459cd7a77"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"bitflags 2.11.1",
|
"bitflags 2.11.1",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
@@ -9068,7 +9121,7 @@ version = "27.0.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "27a75de515543b1897b26119f93731b385a19aea165a1ec5f0e3acecc229cae7"
|
checksum = "27a75de515543b1897b26119f93731b385a19aea165a1ec5f0e3acecc229cae7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"bit-set",
|
"bit-set",
|
||||||
"bit-vec",
|
"bit-vec",
|
||||||
"bitflags 2.11.1",
|
"bitflags 2.11.1",
|
||||||
@@ -9118,7 +9171,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "5b21cb61c57ee198bc4aff71aeadff4cbb80b927beb912506af9c780d64313ce"
|
checksum = "5b21cb61c57ee198bc4aff71aeadff4cbb80b927beb912506af9c780d64313ce"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android_system_properties",
|
"android_system_properties",
|
||||||
"arrayvec",
|
"arrayvec 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"ash",
|
"ash",
|
||||||
"bit-set",
|
"bit-set",
|
||||||
"bitflags 2.11.1",
|
"bitflags 2.11.1",
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ solitaire_core = { path = "solitaire_core" }
|
|||||||
solitaire_sync = { path = "solitaire_sync" }
|
solitaire_sync = { path = "solitaire_sync" }
|
||||||
solitaire_data = { path = "solitaire_data" }
|
solitaire_data = { path = "solitaire_data" }
|
||||||
solitaire_engine = { path = "solitaire_engine" }
|
solitaire_engine = { path = "solitaire_engine" }
|
||||||
|
klondike = { version = "0.2.0", registry = "Quaternions" }
|
||||||
|
|
||||||
# Bevy with `default-features = false` to avoid the unused
|
# Bevy with `default-features = false` to avoid the unused
|
||||||
# `bevy_audio → rodio + symphonia + cpal 0.15 + alsa 0.9` chain.
|
# `bevy_audio → rodio + symphonia + cpal 0.15 + alsa 0.9` chain.
|
||||||
|
|||||||
@@ -8,3 +8,4 @@ edition.workspace = true
|
|||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
|
klondike = { workspace = true }
|
||||||
|
|||||||
@@ -3,10 +3,8 @@ use crate::deck::{Deck, deal_klondike};
|
|||||||
use crate::error::MoveError;
|
use crate::error::MoveError;
|
||||||
use crate::pile::{Pile, PileType};
|
use crate::pile::{Pile, PileType};
|
||||||
use crate::rules::{can_place_on_foundation, can_place_on_tableau, is_valid_tableau_sequence};
|
use crate::rules::{can_place_on_foundation, can_place_on_tableau, is_valid_tableau_sequence};
|
||||||
use crate::scoring::{
|
use crate::klondike_adapter::KlondikeAdapter;
|
||||||
compute_time_bonus as scoring_time_bonus, score_flip, score_move, score_recycle,
|
use crate::scoring::compute_time_bonus as scoring_time_bonus;
|
||||||
score_undo as scoring_undo,
|
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
|
||||||
@@ -166,6 +164,8 @@ pub struct GameState {
|
|||||||
#[serde(default = "schema_v1")]
|
#[serde(default = "schema_v1")]
|
||||||
pub schema_version: u32,
|
pub schema_version: u32,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
|
pub adapter: KlondikeAdapter,
|
||||||
|
#[serde(skip)]
|
||||||
undo_stack: VecDeque<StateSnapshot>,
|
undo_stack: VecDeque<StateSnapshot>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,6 +208,7 @@ impl GameState {
|
|||||||
recycle_count: 0,
|
recycle_count: 0,
|
||||||
take_from_foundation: true,
|
take_from_foundation: true,
|
||||||
schema_version: GAME_STATE_SCHEMA_VERSION,
|
schema_version: GAME_STATE_SCHEMA_VERSION,
|
||||||
|
adapter: KlondikeAdapter::new(draw_mode, true),
|
||||||
undo_stack: VecDeque::new(),
|
undo_stack: VecDeque::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -276,7 +277,7 @@ impl GameState {
|
|||||||
self.recycle_count = self.recycle_count.saturating_add(1);
|
self.recycle_count = self.recycle_count.saturating_add(1);
|
||||||
if self.mode != GameMode::Zen {
|
if self.mode != GameMode::Zen {
|
||||||
let penalty =
|
let penalty =
|
||||||
score_recycle(self.recycle_count, self.draw_mode == DrawMode::DrawThree);
|
KlondikeAdapter::score_for_recycle(self.recycle_count, self.draw_mode == DrawMode::DrawThree);
|
||||||
self.score = (self.score + penalty).max(0);
|
self.score = (self.score + penalty).max(0);
|
||||||
}
|
}
|
||||||
self.move_count = self.move_count.saturating_add(1);
|
self.move_count = self.move_count.saturating_add(1);
|
||||||
@@ -413,7 +414,7 @@ impl GameState {
|
|||||||
let score_delta = if self.mode == GameMode::Zen {
|
let score_delta = if self.mode == GameMode::Zen {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
score_move(&from, &to)
|
self.adapter.score_for_move(&from, &to)
|
||||||
};
|
};
|
||||||
self.push_snapshot();
|
self.push_snapshot();
|
||||||
|
|
||||||
@@ -446,7 +447,7 @@ impl GameState {
|
|||||||
.append(&mut moved);
|
.append(&mut moved);
|
||||||
|
|
||||||
let flip_bonus = if flipped && self.mode != GameMode::Zen {
|
let flip_bonus = if flipped && self.mode != GameMode::Zen {
|
||||||
score_flip()
|
self.adapter.score_for_flip()
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
@@ -480,7 +481,7 @@ impl GameState {
|
|||||||
self.score = if self.mode == GameMode::Zen {
|
self.score = if self.mode == GameMode::Zen {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
(snapshot.score + scoring_undo()).max(0)
|
(snapshot.score + KlondikeAdapter::score_for_undo()).max(0)
|
||||||
};
|
};
|
||||||
self.move_count = snapshot.move_count;
|
self.move_count = snapshot.move_count;
|
||||||
self.is_won = false;
|
self.is_won = false;
|
||||||
@@ -726,6 +727,7 @@ impl GameState {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::card::{Card, Rank, Suit};
|
use crate::card::{Card, Rank, Suit};
|
||||||
|
use crate::klondike_adapter::KlondikeAdapter;
|
||||||
|
|
||||||
fn new_game() -> GameState {
|
fn new_game() -> GameState {
|
||||||
GameState::new(42, DrawMode::DrawOne)
|
GameState::new(42, DrawMode::DrawOne)
|
||||||
@@ -1126,7 +1128,7 @@ mod tests {
|
|||||||
let score_before = g.score;
|
let score_before = g.score;
|
||||||
g.draw().unwrap();
|
g.draw().unwrap();
|
||||||
g.undo().unwrap();
|
g.undo().unwrap();
|
||||||
let expected = (score_before + scoring_undo()).max(0);
|
let expected = (score_before + KlondikeAdapter::score_for_undo()).max(0);
|
||||||
assert_eq!(g.score, expected);
|
assert_eq!(g.score, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
//! Adapter bridging `solitaire_core` types to the upstream `klondike` crate.
|
||||||
|
//!
|
||||||
|
//! # Current scope (integration steps 1–4)
|
||||||
|
//!
|
||||||
|
//! [`KlondikeAdapter`] owns the authoritative [`KlondikeConfig`] and exposes
|
||||||
|
//! scoring helpers backed by [`ScoringConfig::DEFAULT`] (Windows XP Standard
|
||||||
|
//! values). [`GameState`] delegates scoring here so that klondike remains the
|
||||||
|
//! single source of truth for scoring constants.
|
||||||
|
//!
|
||||||
|
//! # Not yet implemented
|
||||||
|
//!
|
||||||
|
//! - Live [`klondike::Klondike`] shadow state (requires pile-mapping, step 2).
|
||||||
|
//! - Move validation via klondike's rule engine (step 2).
|
||||||
|
//! - DFS solver via [`klondike::KlondikeState`] (step 6).
|
||||||
|
|
||||||
|
use klondike::{DrawStockConfig, KlondikeConfig, MoveFromFoundationConfig, ScoringConfig};
|
||||||
|
|
||||||
|
use crate::game_state::DrawMode;
|
||||||
|
use crate::pile::PileType;
|
||||||
|
|
||||||
|
/// Bridges `solitaire_core` game config and scoring to the upstream `klondike` crate.
|
||||||
|
///
|
||||||
|
/// Holds a [`KlondikeConfig`] reflecting the current game settings and exposes
|
||||||
|
/// scoring helpers that read from [`ScoringConfig::DEFAULT`] (WXP values).
|
||||||
|
/// [`GameState`] uses this instead of calling `scoring.rs` functions directly.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct KlondikeAdapter {
|
||||||
|
config: KlondikeConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for KlondikeAdapter {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.config.draw_stock == other.config.draw_stock
|
||||||
|
&& self.config.move_from_foundation == other.config.move_from_foundation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Eq for KlondikeAdapter {}
|
||||||
|
|
||||||
|
impl Default for KlondikeAdapter {
|
||||||
|
/// Returns an adapter with Draw-1 and `take_from_foundation = true`,
|
||||||
|
/// matching `GameState`'s own defaults. Used by `#[serde(skip)]`
|
||||||
|
/// field initialisation on deserialisation.
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new(DrawMode::DrawOne, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KlondikeAdapter {
|
||||||
|
/// Create an adapter from the game's draw mode and foundation house-rule setting.
|
||||||
|
///
|
||||||
|
/// `take_from_foundation = true` maps to [`MoveFromFoundationConfig::Allowed`];
|
||||||
|
/// `false` maps to [`MoveFromFoundationConfig::Disallowed`].
|
||||||
|
pub fn new(draw_mode: DrawMode, take_from_foundation: bool) -> Self {
|
||||||
|
let config = KlondikeConfig {
|
||||||
|
draw_stock: match draw_mode {
|
||||||
|
DrawMode::DrawOne => DrawStockConfig::DrawOne,
|
||||||
|
DrawMode::DrawThree => DrawStockConfig::DrawThree,
|
||||||
|
},
|
||||||
|
move_from_foundation: if take_from_foundation {
|
||||||
|
MoveFromFoundationConfig::Allowed
|
||||||
|
} else {
|
||||||
|
MoveFromFoundationConfig::Disallowed
|
||||||
|
},
|
||||||
|
scoring: ScoringConfig::DEFAULT,
|
||||||
|
};
|
||||||
|
Self { config }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the underlying [`KlondikeConfig`].
|
||||||
|
///
|
||||||
|
/// Used by the solver and pile-mapping code added in later integration steps.
|
||||||
|
pub fn klondike_config(&self) -> &KlondikeConfig {
|
||||||
|
&self.config
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the foundation house-rule flag, keeping [`KlondikeConfig`] in sync.
|
||||||
|
pub fn set_take_from_foundation(&mut self, allowed: bool) {
|
||||||
|
self.config.move_from_foundation = if allowed {
|
||||||
|
MoveFromFoundationConfig::Allowed
|
||||||
|
} else {
|
||||||
|
MoveFromFoundationConfig::Disallowed
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Scoring helpers ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Score delta for a card move.
|
||||||
|
///
|
||||||
|
/// Reads from [`ScoringConfig`] (WXP Standard values):
|
||||||
|
/// - Any pile → Foundation: +10
|
||||||
|
/// - Waste → Tableau: +5
|
||||||
|
/// - Foundation → Tableau: −15
|
||||||
|
/// - All other moves: 0
|
||||||
|
pub fn score_for_move(&self, from: &PileType, to: &PileType) -> i32 {
|
||||||
|
let sc = &self.config.scoring;
|
||||||
|
match (from, to) {
|
||||||
|
(_, PileType::Foundation(_)) => sc.move_to_foundation,
|
||||||
|
(PileType::Waste, PileType::Tableau(_)) => sc.move_to_tableau,
|
||||||
|
(PileType::Foundation(_), PileType::Tableau(_)) => sc.move_from_foundation,
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Score delta for exposing a face-down tableau card: +5.
|
||||||
|
pub fn score_for_flip(&self) -> i32 {
|
||||||
|
self.config.scoring.flip_up_bonus
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Score delta for undo: −15.
|
||||||
|
///
|
||||||
|
/// [`card_game::Session`] handles this via `SessionConfig::undo_penalty`
|
||||||
|
/// (default −15). We mirror the constant here so `GameState` can apply it
|
||||||
|
/// in its snapshot-based undo path without owning a `Session`.
|
||||||
|
pub const fn score_for_undo() -> i32 {
|
||||||
|
-15
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Score delta for recycling waste → stock.
|
||||||
|
///
|
||||||
|
/// [`ScoringConfig::recycle`] is a flat delta (default 0 = always free).
|
||||||
|
/// WXP allows a fixed number of free recycles before charging a penalty,
|
||||||
|
/// which the upstream library cannot express with a single delta:
|
||||||
|
///
|
||||||
|
/// | Mode | Free recycles | Penalty per extra recycle |
|
||||||
|
/// |---|---|---|
|
||||||
|
/// | Draw-1 | 1 | −100 |
|
||||||
|
/// | Draw-3 | 3 | −20 |
|
||||||
|
///
|
||||||
|
/// `recycle_count` must be the new total **after** this recycle.
|
||||||
|
pub fn score_for_recycle(recycle_count: u32, is_draw_three: bool) -> i32 {
|
||||||
|
if is_draw_three {
|
||||||
|
if recycle_count > 3 { -20 } else { 0 }
|
||||||
|
} else if recycle_count > 1 {
|
||||||
|
-100
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ pub mod card;
|
|||||||
pub mod deck;
|
pub mod deck;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod game_state;
|
pub mod game_state;
|
||||||
|
pub mod klondike_adapter;
|
||||||
pub mod pile;
|
pub mod pile;
|
||||||
pub mod rules;
|
pub mod rules;
|
||||||
pub mod scoring;
|
pub mod scoring;
|
||||||
|
|||||||
Reference in New Issue
Block a user