chiark / gitweb /
ilk mixing: Support non-mixing pieces
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 17 Apr 2022 17:11:44 +0000 (18:11 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 17 Apr 2022 18:15:04 +0000 (19:15 +0100)
Allow us to handle an pieces which are occultable but which don't
participate in shuffling and displacement.

I. Introduce IOccultIlk enum.  The old approach is the variant Mix.
This allows us to introduce a new approach Distinct.

II. Make Notch optional in Passive Replace the unconditional
OccultIlkOwningId in IPiece with a new enum IOccultIlk, and the
OccultIlkName in the return value from PieceSpec::load with a new enum
LOccultIlk.

III. Immediately use the new approach for dice, dropping all handling
of ilks.  (Combining this into this commit avoids writing a bunch of
daft code that will be deleted right away.)

Consequences and ancillary changes:

 * We need a new compatibility arrangement so we can load old
   savefiles.  This is a bit fiddly and involves some macro
   trickery (which we will be moved to another file in due course).

 * OccultIlks gets two new methods from_iilk and dispose_iilk for
   handling IOccultIlk, rather than merely OccultIlkName.
   (We're going to move creation in here too, shortly.)

 * All existing pieces other than dice are still mixed by ilk.
   Allowing this not to be the case ia a question for the future.

 * Rename Passive.notch to Passive.permute_notch, for clarity
   of its purpose.

Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
daemon/cmdlistener.rs
src/dice.rs
src/gamestate.rs
src/global.rs
src/hidden.rs
src/occultilks.rs
src/prelude.rs
src/shapelib.rs
src/vpid.rs

index ebf38ee03bfbd2fa453ecc8e36fce013b89fce5a..c55116d7e0ec454d2e43824f3bbed35d7a636d72 100644 (file)
@@ -1042,7 +1042,7 @@ fn execute_game_insn<'cs, 'igr, 'ig: 'igr>(
         ipc.p.into_inner().delete_hook(&gpc, gs);
       }
       if let Some(occilk) = ipc.occilk {
-        ig.ioccults.ilks.dispose(occilk);
+        ig.ioccults.ilks.dispose_iilk(occilk);
       }
       (U{ pcs: vec![(piece, PieceUpdateOp::Delete())],
           log: vec![ LogEntry {
@@ -1122,8 +1122,12 @@ fn execute_game_insn<'cs, 'igr, 'ig: 'igr>(
         let p = IPieceTraitObj::new(p);
         (||{
           let ilks = &mut ig.ioccults.ilks;
-          let occilk = occultable.map(|(ilkname, p_occ)| {
-            ilks.create(ilkname, OccultIlkData { p_occ })
+          let occilk = occultable.map(|(lilk, p_occ)| {
+            let data = OccultIlkData { p_occ };
+            match lilk {
+              LOI::Distinct => IOI::Distinct(data),
+              LOI::Mix(ilkname) => IOI::Mix(ilks.create(ilkname, data)),
+            }
           });
           ig.ipieces.as_mut(modperm).insert(piece, IPiece {
             p, occilk,
index 9101e520dc0a18a437b959d0a01d00e0071a834a..1e05dea1f286c19c54bcc522eabe0fb569672af1 100644 (file)
@@ -194,35 +194,16 @@ impl PieceSpec for Spec {
           throw!(SpecError::UnoccultableButRichImageForOccultation)
         }
         let occ_label = occ_label(occ);
-        Some((image.into(), "bad-ilk-mixing-todo".into(), occ_label))
+        Some((image.into(), occ_label))
       },
-      (Some((image_occ_ilk, image_occ_image)), occ) => {
+      (Some((_, image_occ_image)), occ) => {
         let default_occ = default();
         let occ = occ.as_ref().unwrap_or(&default_occ);
         let occ_label = occ_label(occ);
-
-        let our_ilk =
-          // We need to invent an ilk to allow coalescing of similar
-          // objects.  Here "similar" includes dice with the same
-          // occulted image, but possibly different sets of faces
-          // (ie, different labels).
-          //
-          // We also disregard the cooldown timer parameters, so
-          // similar-looking dice with different cooldowns can be
-          // mixed.  Such things are pathological anyway.
-          //
-          // But we don't want to get mixed up with some other things
-          // that aren't dice.  That would be mad even if they look a
-          // bit like us.
-          format!("die.{}.{}", nfaces, &image_occ_ilk);
-
-        Some((image_occ_image, our_ilk, occ_label))
+        Some((image_occ_image, occ_label))
       },
-    }.map(|(occ_image, occ_ilk, occ_label)| {
-      let occ_ilk = GoodItemName::try_from(occ_ilk)
-        .map_err(|e| internal_error_bydebug(&e))?
-        .into();
-
+    }.map(|(occ_image, occ_label)| {
+      let occ_ilk = LOI::Distinct;
       let our_occ_image = Arc::new(Die {
         nfaces, cooldown_time, cooldown_radius, surround_outline,
         itemname: itemname.clone(),
index cf92b98535391710e8ad1e2b33da2a70cfc41d7c..e8c7fffb0c25bc5ba3e056f4508963fb4e97314c 100644 (file)
@@ -289,7 +289,7 @@ pub struct SpecLoaded<PT: ?Sized> {
 }
 pub type PieceSpecLoaded = SpecLoaded<dyn PieceTrait>;
 pub type PieceSpecLoadedOccultable =
-  Option<(OccultIlkName, Arc<dyn InertPieceTrait>)>;
+  Option<(LOccultIlk, Arc<dyn InertPieceTrait>)>;
 
 #[typetag::serde(tag="type")]
 pub trait PieceSpec: Debug + Sync + Send + 'static {
index 5b70f336d5eb9c4e1759d570dd8fd08ab33872f5..18cccd6dfd534f93623eb68f8a859c5a69ccf0ec 100644 (file)
@@ -79,7 +79,7 @@ pub struct IPlayer { // usual variable: ipl
 #[derive(Debug,Serialize,Deserialize)]
 pub struct IPiece {
   pub p: IPieceTraitObj,
-  pub occilk: Option<OccultIlkOwningId>,
+  pub occilk: Option<IOccultIlk>,
 }
 deref_to_field!{IPiece, IPieceTraitObj, p}
 
index 5a0e68778c97fcfb0e8d56d0bd5bb7497630f33c..a89264348c651d161600b1221e7e91fae465ee86 100644 (file)
@@ -44,7 +44,8 @@ pub struct PieceOccult {
 #[derive(Clone,Copy,Debug,Serialize,Deserialize,Eq,PartialEq)]
 struct Passive {
   occid: OccId,
-  notch: Notch,
+  // If None, this piece does not participate in mixing (IOI::Distinct)
+  permute_notch: Option<Notch>,
 }
 
 #[derive(Clone,Debug,Serialize,Deserialize)]
@@ -151,7 +152,8 @@ impl PieceOccult {
   pub fn passive_delete_hook(&self, goccults: &mut GameOccults,
                              piece: PieceId) {
     if_chain! {
-      if let Some(Passive { occid, notch }) = self.passive;
+      if let Some(Passive { occid, permute_notch }) = self.passive;
+      if let Some(notch) = permute_notch;
       if let Some(occ) = goccults.occults.get_mut(occid);
       then {
         occ.notches.remove(piece, notch)
@@ -287,32 +289,46 @@ pub fn piece_pri(
 {
   let mut occk_dbg = None;
   let occulted = if_chain! {
-    if let Some(Passive { occid, notch }) = gpc.occult.passive;
+    if let Some(Passive { occid, permute_notch }) = gpc.occult.passive;
     if let Some(occ) = occults.occults.get(occid);
-    if let Some(zg) = occ.notch_zg(notch);
-    then {
-      let occk = occ.views.get_kind(player)
-        .map_displaced(|(displace, z)| {
-          let notch: NotchNumber = notch.into();
-          let pos = displace.place(occ.ppiece_use_size, notch);
-          let z = z.plus_offset(notch)
-            .unwrap_or_else(|e| { // eek!
-              error!("z coordinate overflow ({:?}), bodging! {:?} {:?}",
-                     e, piece, &z);
-              z.clone()
-            });
-          (pos, ZLevel { z, zg })
-        });
-
-      occk_dbg = Some(occk.clone());
-      match occk.pri_occulted() {
-        Some(o) => o,
-        None => {
-          trace_dbg!("piece_pri", player, piece, occk_dbg, gpc);
-          return None;
+    then { if_chain!{
+      if let Some(notch) = permute_notch;
+      if let Some(zg) = occ.notch_zg(notch);
+      then {
+        let occk = occ.views.get_kind(player)
+          .map_displaced(|(displace, z)| {
+            let notch: NotchNumber = notch.into();
+            let pos = displace.place(occ.ppiece_use_size, notch);
+            let z = z.plus_offset(notch)
+              .unwrap_or_else(|e| { // eek!
+                error!("z coordinate overflow ({:?}), bodging! {:?} {:?}",
+                       e, piece, &z);
+                z.clone()
+              });
+            (pos, ZLevel { z, zg })
+          });
+
+        occk_dbg = Some(occk.clone());
+        match occk.pri_occulted() {
+          Some(o) => o,
+          None => {
+            trace_dbg!("piece_pri", player, piece, occk_dbg, gpc);
+            return None;
+          }
         }
       }
-    }
+      else { // No notch, not mixing (so not displacing)
+        match occ.views.get_kind(player) {
+          OccKG::Invisible          => {
+            trace_dbg!("piece_pri", player, piece, gpc);
+            return None;
+          }
+          OccKG::Visible      => PriOG::Visible(ShowUnocculted(())),
+          OccKG::Scrambled    => PriOG::Occulted,
+          OccKG::Displaced(_) => PriOG::Occulted,
+        }
+      }
+    } }
     else {
       PriOG::Visible(ShowUnocculted(()))
     }
@@ -401,7 +417,7 @@ impl IPieceTraitObj {
 
 impl IPiece {
   #[throws(IE)]
-  pub fn show_or_instead<'p>(&self, ioccults: &'p IOccults,
+  pub fn show_or_instead<'p>(&'p self, ioccults: &'p IOccults,
                          y: Option<ShowUnocculted>)
           -> Either<ShowUnocculted, /*occulted*/ &'p dyn InertPieceTrait> {
     match y {
@@ -410,7 +426,7 @@ impl IPiece {
         let occilk = self.occilk.as_ref()
           .ok_or_else(|| internal_logic_error(format!(
             "occulted non-occultable {:?}", self)))?;
-        let occ_data = ioccults.ilks.get(occilk)
+        let occ_data = ioccults.ilks.from_iilk(occilk)
           .ok_or_else(|| internal_logic_error(format!(
             "occulted ilk vanished {:?} {:?}", self, occilk)))?;
         occ_data.p_occ.as_ref()
@@ -471,7 +487,7 @@ pub fn vpiece_decode(
   let piece: Option<PieceId> = if_chain! {
     if let Some(p) = piece;
     if let Some(gpc) = gs.pieces.get(p);
-    if let Some(Passive { occid, notch:_ }) = gpc.occult.passive;
+    if let Some(Passive { occid, permute_notch:_ }) = gpc.occult.passive;
     if let Some(occ) = gs.occults.occults.get(occid);
     let kind = occ.views.get_kind(player);
     if ! kind.at_all_visible();
@@ -508,7 +524,7 @@ fn recalculate_occultation_general<
 {
   #[derive(Debug,Copy,Clone)]
   struct OldNewOcculteds<O> {
-    old: Option<(O, Notch)>,
+    old: Option<(O, Option<Notch>)>,
     new: Option<O>,
   }
   impl<O> OldNewOcculteds<O> {
@@ -541,13 +557,13 @@ fn recalculate_occultation_general<
 
     let occulteds = OldNewOcculteds {
       old:
-        gpc.occult.passive.map(|Passive { occid, notch }| Ok::<_,IE>((
+        gpc.occult.passive.map(|Passive { occid, permute_notch }| Ok::<_,IE>((
           Occulted {
             occid,
             occ: goccults.occults.get(occid).ok_or_else(
               || internal_logic_error("uccultation vanished"))?,
           },
-          notch,
+          permute_notch,
         ))).transpose()?,
 
       new:
@@ -686,7 +702,7 @@ fn recalculate_occultation_general<
       to_recalculate.mark_dirty(occid);
       goccults.occults.get_mut(occid).unwrap()
     };
-    if let Some((occid, old_notch)) = occulteds.old {
+    if let Some((occid, Some(old_notch))) = occulteds.old {
       occultation(goccults, occid)
         .notches
         .remove(piece, old_notch)
@@ -698,15 +714,22 @@ fn recalculate_occultation_general<
       let occ = occultation(goccults, occid);
       if let Some(ilk) = wants!( ipc.occilk.as_ref() );
       then {
-        if_chain!{
-          if occ.notches.is_empty();
-          if let Some(ilk) = wants!( ioccults.ilks.get(ilk) );
-          if let Some(bbox) = want!( Ok = ilk.p_occ.bbox_approx() );
-          if let Some(size) = want!( Ok = bbox.br() - bbox.tl(), ?(bbox) );
-          then { occ.ppiece_use_size = size; }
+        let permute_notch = match ilk {
+          IOI::Distinct(_) => None,
+          IOI::Mix(ilk) => {
+            if_chain!{
+              if occ.notches.is_empty();
+              if let Some(ilk) = wants!( ioccults.ilks.get(ilk) );
+              if let Some(bbox) = want!( Ok = ilk.p_occ.bbox_approx() );
+              if let Some(size) = want!( Ok = bbox.br() - bbox.tl(), ?(bbox) );
+              then { occ.ppiece_use_size = size; }
+            };
+
+            let notch = occ.notches.insert(zg, piece);
+            Some(notch)
+          }
         };
-        let notch = occ.notches.insert(zg, piece);
-        Some(Passive { occid, notch })
+        Some(Passive { occid, permute_notch })
       }
       else {
         None
index f7ac81a76b73a9f0070466f0cf934fcab57291ac..bdce5f68cf22bdd924468a641ca35452433a92fa 100644 (file)
@@ -18,6 +18,59 @@ pub struct OccultIlkData {
   pub p_occ: Arc<dyn InertPieceTrait>,
 }
 
+#[derive(Debug,Clone)]
+pub enum LOccultIlk {
+  /// Pieces does not participate in ilk-based mixing.
+  ///
+  /// Such a piece remains distinguishable from all other pieces, and
+  /// trackable, by all players, even when occulted (unless made
+  /// totally invisible, in which case it will still be trackable
+  /// when it returns).
+  Distinct,
+
+  /// Ilk-based mixing
+  ///
+  /// Pieces with the same OccultIlkName will be mixed together when
+  /// occulted, so that players who see the occulted view cannot track
+  /// the individual piece identities.  This supports deck-shuffling
+  /// for pickup decks (and for hands etc. hides what the player is
+  /// doing with their own cards, from the other players).
+  Mix(OccultIlkName),
+}
+
+macro_rules! serde_with_compat { {
+  [ #[ $($attrs:meta)* ] ] [ $vis:vis ] [ $($intro:tt)* ]
+    $main:ident=$main_s:literal $new:ident $compat_s:literal
+  [ $($body:tt)* ]
+} => {
+  $(#[ $attrs ])* 
+  #[serde(try_from=$compat_s)]
+  $vis $($intro)* $main $($body)*
+
+  #[allow(non_camel_case_types)]
+  $(#[ $attrs ])* 
+  #[serde(remote=$main_s)]
+  $($intro)* $new $($body)*
+} }
+
+serde_with_compat!{
+  [ #[derive(Debug,Serialize,Deserialize)] ]
+  [ pub ][ enum ] IOccultIlk="IOccultIlk" IOccultIlk_New "IOccultIlk_Compat" [
+    {
+      Distinct(OccultIlkData),
+      Mix(OccultIlkOwningId),
+    }
+  ]
+}
+
+#[derive(Debug,Deserialize)]
+#[serde(untagged)]
+#[allow(non_camel_case_types)]
+enum IOccultIlk_Compat {
+  V1(OccultIlkOwningId), // Otter 1.0.0
+  V2(#[serde(with="IOccultIlk_New")] IOccultIlk),
+}
+  
 type Id = OccultIlkId;
 type OId = OccultIlkOwningId;
 type K = OccultIlkName;
@@ -37,12 +90,31 @@ pub struct Data {
   refcount: Refcount,
 }
 
+impl TryFrom<IOccultIlk_Compat> for IOccultIlk {
+  type Error = Infallible;
+  #[throws(Infallible)]
+  fn try_from(compat: IOccultIlk_Compat) -> IOccultIlk { match compat {
+    IOccultIlk_Compat::V1(oioi) => IOI::Mix(oioi),
+    IOccultIlk_Compat::V2(ioi) => ioi,
+  } }
+}
+
 impl OccultIlks {
   #[throws(as Option)]
   pub fn get<I: Borrow<Id>>(&self, id: &I) -> &V {
     &self.table.get(*id.borrow())?.v
   }
 
+  #[throws(as Option)]
+  pub fn from_iilk<'r>(&'r self, iilk: &'r IOccultIlk) -> &'r V { match iilk {
+    IOI::Distinct(data) => data,
+    IOI::Mix(id) => self.get(id)?,
+  } }
+  pub fn dispose_iilk(&mut self, iilk: IOccultIlk) { match iilk {
+    IOI::Distinct(_data) => { },
+    IOI::Mix(id) => self.dispose(id),
+  } }
+
   pub fn create(&mut self, k: K, v: V) -> OId {
     let OccultIlks { lookup, table } = self;
     let id = *lookup
index ef2c9a6a45eef973a7197217e5fc217424a88ee7..c9ea63c4db88f8f8cec6ffeb4f869679482a7a29 100644 (file)
@@ -218,6 +218,10 @@ pub type OccK = OccultationKind;
 pub use OccultationKindGeneral as OccKG;
 pub use OccultationKindAlwaysOk as OccKA;
 
+// occultilks.rs
+pub type LOI = LOccultIlk;
+pub type IOI = IOccultIlk;
+
 // pcrender.rs
 pub use PriOccultedGeneral as PriOG;
 
index b38ee2939af12710e30c4a7340ab47a2b3c71a8e..b075722cee80e8e94a7855177eac0b0872f58e74 100644 (file)
@@ -583,7 +583,7 @@ impl Contents {
       OccData::Back(ilk) => {
         if let Some(back) = &back {
           let back = back.clone();
-          Some((ilk.clone(), back))
+          Some((LOI::Mix(ilk.clone()), back))
         } else {
           None // We got AliasNotFound, ah well
         }
@@ -604,7 +604,7 @@ impl Contents {
           desc: occ.desc.clone(),
           outline: occ.outline.clone(),
         }) as Arc<dyn InertPieceTrait>;
-        Some((occ_name.into_inner(), it))
+        Some((LOI::Mix(occ_name.into_inner()), it))
       },
     };
 
index bb5f3d1975a4bcd71ac37f759b9df2488f60de09..d463908bd1ec2a5677cf5ed2789ea376c15d7f77 100644 (file)
@@ -301,6 +301,7 @@ pub fn permute(occid: OccId,
         error!("{}", internal_error_bydebug(&(occid, &occ, &nr, piece)));
         continue;
       }}
+      if_let!{ IOI::Mix(occilk) = occilk; else continue; }
       let (notches, pieces) = ilks.entry(*occilk.borrow()).or_default();
       notches.push(notch);
       pieces.push(piece);
@@ -327,8 +328,8 @@ pub fn permute(occid: OccId,
         new_notches[notch] = NR::Piece(new_piece);
         gpieces.get_mut(new_piece).unwrap()
           .occult.passive.as_mut().unwrap()
-          .notch
-          = notch;
+          .permute_notch
+          = Some(notch);
       }
     }
 
@@ -400,9 +401,11 @@ pub fn consistency_check(
       assert_eq!(&gpc.occult.passive, &None);
     }
 
-    if let Some(Passive { occid, notch }) = gpc.occult.passive {
+    if let Some(Passive { occid, permute_notch }) = gpc.occult.passive {
       let occ = goccults.occults.get(occid).unwrap();
-      assert_eq!(occ.notches.table[notch], NR::Piece(piece));
+      if let Some(notch) = permute_notch {
+        assert_eq!(occ.notches.table[notch], NR::Piece(piece));
+      }
     }
   }
 
@@ -416,7 +419,7 @@ pub fn consistency_check(
       let pgpc = gpieces.get(ppiece).unwrap();
       let passive = pgpc.occult.passive.as_ref().unwrap();
       assert_eq!(passive.occid, occid);
-      assert_eq!(passive.notch, notch);
+      assert_eq!(passive.permute_notch.unwrap(), notch);
     }
 
     let nfree1 = occ.notches.table.iter()