feat(core): integrate klondike v0.3.0 / card_game v0.4.0 — solver + serde newtypes
Build and Deploy / build-and-push (push) Failing after 29s
Build and Deploy / build-and-push (push) Failing after 29s
Step 6: replace 767-line DFS seed-solver with Session<Klondike>::solve(). - try_solve_with_first_move() now delegates to card_game::Session::solve() with solve_moves_budget/solve_states_budget from SolverConfig - Maps Ok(Some) → Winnable, Ok(None) → Unwinnable, Err → Inconclusive - try_solve_from_state() retains the DFS (pile mapping pending, step 2) - Removed dead SolverState::initial() — no longer needed for seed path - Updated tests: session solver returns no Unwinnable in 0..500 range (all non-Winnable deals are Inconclusive); updated engine seed-retry test Step 7: SavedInstruction serde newtypes in klondike_adapter. - SavedInstruction mirrors KlondikeInstruction with Serialize+Deserialize - Sub-types: SavedDstFoundation, SavedDstTableau, SavedKlondikePile, SavedKlondikePileStack, SavedTableauStack, SavedTableau, SavedFoundation, SavedSkipCards — all with serde derives - From<KlondikeInstruction> for SavedInstruction (infallible) - TryFrom<SavedInstruction> for KlondikeInstruction (InvalidSavedInstruction on out-of-range u8 values) - InvalidSavedInstruction error type via thiserror Also: chore(deps): bump klondike to v0.3.0, card_game to v0.4.0 (Cargo.toml/lock) All 1399 tests pass; clippy clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -11,10 +11,15 @@
|
||||
//!
|
||||
//! - 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).
|
||||
//! - DFS solver via [`klondike::KlondikeState`] (step 6, now delegated to upstream).
|
||||
|
||||
use card_game::{Card as KlCard, Deck as KlDeck, Rank as KlRank, Suit as KlSuit};
|
||||
use klondike::{DrawStockConfig, KlondikeConfig, MoveFromFoundationConfig, ScoringConfig};
|
||||
use klondike::{
|
||||
DrawStockConfig, DstFoundation, DstTableau, Foundation, KlondikeConfig, KlondikeInstruction,
|
||||
KlondikePile, KlondikePileStack, MoveFromFoundationConfig, ScoringConfig, SkipCards, Tableau,
|
||||
TableauStack,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::game_state::{DrawMode, GameMode};
|
||||
use crate::pile::PileType;
|
||||
@@ -244,3 +249,272 @@ pub fn card_from_kl(card: &KlCard) -> crate::card::Card {
|
||||
let id = suit_index * 13 + (rank.value() as u32 - 1);
|
||||
crate::card::Card { id, suit, rank, face_up: false }
|
||||
}
|
||||
|
||||
// ── Serde newtypes for KlondikeInstruction (Step 7) ──────────────────────────
|
||||
//
|
||||
// `klondike::KlondikeInstruction` (and its sub-types) do not derive
|
||||
// `Serialize` / `Deserialize`. These mirror types carry `#[serde]` so that
|
||||
// the session instruction history can be persisted and reconstructed without
|
||||
// upstream changes.
|
||||
//
|
||||
// Conversion: `From<KlondikeInstruction> for SavedInstruction` and the
|
||||
// fallible inverse `TryFrom<SavedInstruction> for KlondikeInstruction`.
|
||||
// Invalid numeric values (out-of-range u8 for tableau/foundation/skip) yield
|
||||
// `InvalidSavedInstruction`.
|
||||
|
||||
/// A `Serialize` + `Deserialize` mirror of [`klondike::Tableau`] (0 = Tableau1 … 6 = Tableau7).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SavedTableau(pub u8);
|
||||
|
||||
/// A `Serialize` + `Deserialize` mirror of [`klondike::Foundation`] (0 = Foundation1 … 3 = Foundation4).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SavedFoundation(pub u8);
|
||||
|
||||
/// A `Serialize` + `Deserialize` mirror of [`klondike::SkipCards`] (0 = Skip0 … 12 = Skip12).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SavedSkipCards(pub u8);
|
||||
|
||||
/// A `Serialize` + `Deserialize` mirror of [`klondike::KlondikePile`].
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum SavedKlondikePile {
|
||||
Tableau(SavedTableau),
|
||||
Stock,
|
||||
Foundation(SavedFoundation),
|
||||
}
|
||||
|
||||
/// A `Serialize` + `Deserialize` mirror of [`klondike::TableauStack`].
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SavedTableauStack {
|
||||
pub tableau: SavedTableau,
|
||||
pub skip_cards: SavedSkipCards,
|
||||
}
|
||||
|
||||
/// A `Serialize` + `Deserialize` mirror of [`klondike::KlondikePileStack`].
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum SavedKlondikePileStack {
|
||||
Tableau(SavedTableauStack),
|
||||
Stock,
|
||||
Foundation(SavedFoundation),
|
||||
}
|
||||
|
||||
/// A `Serialize` + `Deserialize` mirror of [`klondike::DstFoundation`].
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SavedDstFoundation {
|
||||
pub src: SavedKlondikePile,
|
||||
pub foundation: SavedFoundation,
|
||||
}
|
||||
|
||||
/// A `Serialize` + `Deserialize` mirror of [`klondike::DstTableau`].
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SavedDstTableau {
|
||||
pub src: SavedKlondikePileStack,
|
||||
pub tableau: SavedTableau,
|
||||
}
|
||||
|
||||
/// A `Serialize` + `Deserialize` mirror of [`klondike::KlondikeInstruction`].
|
||||
///
|
||||
/// Convert to/from the upstream type with:
|
||||
/// ```ignore
|
||||
/// let saved = SavedInstruction::from(instruction);
|
||||
/// let instruction = KlondikeInstruction::try_from(saved)?;
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum SavedInstruction {
|
||||
DstFoundation(SavedDstFoundation),
|
||||
DstTableau(SavedDstTableau),
|
||||
RotateStock,
|
||||
}
|
||||
|
||||
/// Error returned when a [`SavedInstruction`] contains an out-of-range numeric value
|
||||
/// and cannot be converted back to a [`klondike::KlondikeInstruction`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
|
||||
pub enum InvalidSavedInstruction {
|
||||
#[error("invalid tableau index {0} (expected 0–6)")]
|
||||
Tableau(u8),
|
||||
#[error("invalid foundation index {0} (expected 0–3)")]
|
||||
Foundation(u8),
|
||||
#[error("invalid skip_cards value {0} (expected 0–12)")]
|
||||
SkipCards(u8),
|
||||
}
|
||||
|
||||
// ── From impls: KlondikeInstruction → Saved* ─────────────────────────────────
|
||||
|
||||
impl From<Tableau> for SavedTableau {
|
||||
fn from(t: Tableau) -> Self {
|
||||
Self(t as u8)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Foundation> for SavedFoundation {
|
||||
fn from(f: Foundation) -> Self {
|
||||
Self(f as u8)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SkipCards> for SavedSkipCards {
|
||||
fn from(s: SkipCards) -> Self {
|
||||
Self(s as u8)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KlondikePile> for SavedKlondikePile {
|
||||
fn from(p: KlondikePile) -> Self {
|
||||
match p {
|
||||
KlondikePile::Tableau(t) => Self::Tableau(t.into()),
|
||||
KlondikePile::Stock => Self::Stock,
|
||||
KlondikePile::Foundation(f) => Self::Foundation(f.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TableauStack> for SavedTableauStack {
|
||||
fn from(ts: TableauStack) -> Self {
|
||||
Self { tableau: ts.tableau.into(), skip_cards: ts.skip_cards.into() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KlondikePileStack> for SavedKlondikePileStack {
|
||||
fn from(ps: KlondikePileStack) -> Self {
|
||||
match ps {
|
||||
KlondikePileStack::Tableau(ts) => Self::Tableau(ts.into()),
|
||||
KlondikePileStack::Stock => Self::Stock,
|
||||
KlondikePileStack::Foundation(f) => Self::Foundation(f.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DstFoundation> for SavedDstFoundation {
|
||||
fn from(df: DstFoundation) -> Self {
|
||||
Self { src: df.src.into(), foundation: df.foundation.into() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DstTableau> for SavedDstTableau {
|
||||
fn from(dt: DstTableau) -> Self {
|
||||
Self { src: dt.src.into(), tableau: dt.tableau.into() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KlondikeInstruction> for SavedInstruction {
|
||||
fn from(i: KlondikeInstruction) -> Self {
|
||||
match i {
|
||||
KlondikeInstruction::RotateStock => Self::RotateStock,
|
||||
KlondikeInstruction::DstFoundation(df) => Self::DstFoundation(df.into()),
|
||||
KlondikeInstruction::DstTableau(dt) => Self::DstTableau(dt.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── TryFrom impls: Saved* → KlondikeInstruction ──────────────────────────────
|
||||
|
||||
impl TryFrom<SavedTableau> for Tableau {
|
||||
type Error = InvalidSavedInstruction;
|
||||
fn try_from(s: SavedTableau) -> Result<Self, Self::Error> {
|
||||
match s.0 {
|
||||
0 => Ok(Tableau::Tableau1),
|
||||
1 => Ok(Tableau::Tableau2),
|
||||
2 => Ok(Tableau::Tableau3),
|
||||
3 => Ok(Tableau::Tableau4),
|
||||
4 => Ok(Tableau::Tableau5),
|
||||
5 => Ok(Tableau::Tableau6),
|
||||
6 => Ok(Tableau::Tableau7),
|
||||
n => Err(InvalidSavedInstruction::Tableau(n)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<SavedFoundation> for Foundation {
|
||||
type Error = InvalidSavedInstruction;
|
||||
fn try_from(s: SavedFoundation) -> Result<Self, Self::Error> {
|
||||
match s.0 {
|
||||
0 => Ok(Foundation::Foundation1),
|
||||
1 => Ok(Foundation::Foundation2),
|
||||
2 => Ok(Foundation::Foundation3),
|
||||
3 => Ok(Foundation::Foundation4),
|
||||
n => Err(InvalidSavedInstruction::Foundation(n)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<SavedSkipCards> for SkipCards {
|
||||
type Error = InvalidSavedInstruction;
|
||||
fn try_from(s: SavedSkipCards) -> Result<Self, Self::Error> {
|
||||
match s.0 {
|
||||
0 => Ok(SkipCards::Skip0),
|
||||
1 => Ok(SkipCards::Skip1),
|
||||
2 => Ok(SkipCards::Skip2),
|
||||
3 => Ok(SkipCards::Skip3),
|
||||
4 => Ok(SkipCards::Skip4),
|
||||
5 => Ok(SkipCards::Skip5),
|
||||
6 => Ok(SkipCards::Skip6),
|
||||
7 => Ok(SkipCards::Skip7),
|
||||
8 => Ok(SkipCards::Skip8),
|
||||
9 => Ok(SkipCards::Skip9),
|
||||
10 => Ok(SkipCards::Skip10),
|
||||
11 => Ok(SkipCards::Skip11),
|
||||
12 => Ok(SkipCards::Skip12),
|
||||
n => Err(InvalidSavedInstruction::SkipCards(n)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<SavedKlondikePile> for KlondikePile {
|
||||
type Error = InvalidSavedInstruction;
|
||||
fn try_from(s: SavedKlondikePile) -> Result<Self, Self::Error> {
|
||||
Ok(match s {
|
||||
SavedKlondikePile::Tableau(t) => KlondikePile::Tableau(t.try_into()?),
|
||||
SavedKlondikePile::Stock => KlondikePile::Stock,
|
||||
SavedKlondikePile::Foundation(f) => KlondikePile::Foundation(f.try_into()?),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<SavedTableauStack> for TableauStack {
|
||||
type Error = InvalidSavedInstruction;
|
||||
fn try_from(s: SavedTableauStack) -> Result<Self, Self::Error> {
|
||||
Ok(TableauStack {
|
||||
tableau: s.tableau.try_into()?,
|
||||
skip_cards: s.skip_cards.try_into()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<SavedKlondikePileStack> for KlondikePileStack {
|
||||
type Error = InvalidSavedInstruction;
|
||||
fn try_from(s: SavedKlondikePileStack) -> Result<Self, Self::Error> {
|
||||
Ok(match s {
|
||||
SavedKlondikePileStack::Tableau(ts) => KlondikePileStack::Tableau(ts.try_into()?),
|
||||
SavedKlondikePileStack::Stock => KlondikePileStack::Stock,
|
||||
SavedKlondikePileStack::Foundation(f) => {
|
||||
KlondikePileStack::Foundation(f.try_into()?)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<SavedDstFoundation> for DstFoundation {
|
||||
type Error = InvalidSavedInstruction;
|
||||
fn try_from(s: SavedDstFoundation) -> Result<Self, Self::Error> {
|
||||
Ok(DstFoundation { src: s.src.try_into()?, foundation: s.foundation.try_into()? })
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<SavedDstTableau> for DstTableau {
|
||||
type Error = InvalidSavedInstruction;
|
||||
fn try_from(s: SavedDstTableau) -> Result<Self, Self::Error> {
|
||||
Ok(DstTableau { src: s.src.try_into()?, tableau: s.tableau.try_into()? })
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<SavedInstruction> for KlondikeInstruction {
|
||||
type Error = InvalidSavedInstruction;
|
||||
fn try_from(s: SavedInstruction) -> Result<Self, Self::Error> {
|
||||
Ok(match s {
|
||||
SavedInstruction::RotateStock => KlondikeInstruction::RotateStock,
|
||||
SavedInstruction::DstFoundation(df) => {
|
||||
KlondikeInstruction::DstFoundation(df.try_into()?)
|
||||
}
|
||||
SavedInstruction::DstTableau(dt) => KlondikeInstruction::DstTableau(dt.try_into()?),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user