chiark / gitweb /
organise: Introduce new functionality
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Fri, 2 Apr 2021 22:35:38 +0000 (23:35 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 3 Apr 2021 09:35:15 +0000 (10:35 +0100)
It still has some bugs.

Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
src/error.rs
src/hand.rs
src/hidden.rs
src/lib.rs
src/organise.rs [new file with mode: 0644]
src/prelude.rs

index 1d1da32b0f70c8deb802ffc1757fc73a711b4f86..59ce5061d66f8d26834d386527f4757fab6f652b 100644 (file)
@@ -59,6 +59,8 @@ pub enum InternalError {
   PartialPlayerData,
   #[error("Coordinate overflow")]
   CoordinateOverflow(#[from] CoordinateOverflow),
+  #[error("Organised placement not possible")]
+  OrganisedPlacementFailure,
   #[error("Z Coordinate overflow (game is too crufty?)")]
   ZCoordinateOverflow(#[from] zcoord::Overflow),
   #[error("Multiple errors occurred where only one could be reported")]
@@ -177,6 +179,7 @@ pub enum PieceOpError {
   Conflict,
   PosOffTable,
   PieceGone,
+  Occultation,
 }
 display_as_debug!{PieceOpError}
 
index 827772219a3387ccb24ccfb4e6daff7f4dbbec75..7aff7793d896d48792535ac8b100833c275905e5 100644 (file)
@@ -206,13 +206,25 @@ impl PieceTrait for Hand {
         desc: self.behaviour.claim_desc().into(),
         wrc: WRC::Unpredictable,
       }}
-    })
+    });
+
+    organise::add_ui_operations(upd, &self.shape.outline.rect(gpc.pos)?)?;
   }
 
   #[throws(ApiPieceOpError)]
-  fn ui_operation(&self, a: ApiPieceOpArgs<'_>,
+  fn ui_operation(&self, mut a: ApiPieceOpArgs<'_>,
                   opname: &str, wrc: WhatResponseToClientOp)
                   -> UpdateFromOpComplex {
+    if let Some(r) = {
+      let gpc = a.gs.pieces.byid_mut(a.piece)?;
+      let rect = self.shape.outline.rect(gpc.pos)
+        .map_err(|CoordinateOverflow|
+                 internal_error_bydebug(&(&gpc.pos, &self.shape)))?;
+      organise::ui_operation(&mut a, opname, wrc, &rect)?                             
+    } {
+      return r;
+    }
+
     let ApiPieceOpArgs { gs,player,piece,ipieces,ioccults,to_recalculate,.. } = a;
     let gen = &mut gs.gen;
     let gplayers = &mut gs.players;
index cf709cd706cb6fe08456ec968da0cddef3add2c6..5cd7f4a9eca3aadd0ee3a9a73bfb41695fcb2b84 100644 (file)
@@ -250,6 +250,18 @@ impl GameOccults {
     let kind = occ.get_kind(player);
     kind
   }
+
+  #[throws(IE)]
+  pub fn pos_occulter(&self, goccults: &GameOccults, pos: Pos)
+                      -> Option<PieceId> {
+    goccults.occults.iter().find_map(|(_occid, occ)| {
+      if occ.in_region(pos) {
+        Some(occ.occulter)
+      } else {
+        None
+      }
+    })
+  }
 }
 
 // ========== public entrypoints ==========
index 5ddbdc100633de61338e6f65ee8cb4a88803cc99..f2109a9d9a824ed641d57af1a6882d3b156822e1 100644 (file)
@@ -24,6 +24,7 @@ pub mod keydata;
 pub mod mgmtchannel;
 pub mod nwtemplates;
 pub mod occultilks;
+pub mod organise;
 pub mod pcrender;
 pub mod pieces;
 pub mod shapelib;
diff --git a/src/organise.rs b/src/organise.rs
new file mode 100644 (file)
index 0000000..369be67
--- /dev/null
@@ -0,0 +1,197 @@
+// Copyright 2020-2021 Ian Jackson and contributors to Otter
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// There is NO WARRANTY.
+
+use crate::prelude::*;
+
+const MARGIN_INSIDE: Coord = 1;
+const HANG_INSIDE:   Coord = 2;
+
+#[throws(InternalError)]
+pub fn add_ui_operations(upd: &mut Vec<UoDescription>,
+                         region: &Rect) {
+  if (region.br() - region.tl())?.coords.iter().any(
+    |&c| c < HANG_INSIDE*2
+  ) {
+    // too small!
+    return;
+  }
+
+  upd.push(UoDescription {
+    kind: UoKind::Piece,
+    def_key: 'o',
+    opname: "organise".to_string(),
+    desc: Html::lit("Organise").into(),
+    wrc: WRC::Predictable,
+  });
+}
+
+define_index_type!{ struct InHand = usize; }
+
+#[derive(Copy,Clone,Debug,Ord,PartialOrd,Eq,PartialEq)]
+#[derive(EnumIter)]
+enum Attempt {
+  Nonoverlap,
+  Inside,
+  Abut,
+  Hanging,
+}
+use Attempt as A;
+
+impl Attempt {
+  #[throws(CoordinateOverflow)]
+  fn tl(self, bbox: &Rect) -> Pos {
+    let cnr = bbox.tl();
+    match self {
+      A::Nonoverlap |
+      A::Inside     => (cnr - PosC::both(MARGIN_INSIDE))?,
+      A::Abut       =>  cnr,
+      A::Hanging    =>  PosC::both(-HANG_INSIDE),
+    }
+  }
+
+  #[throws(CoordinateOverflow)]
+  fn br_real(self, bbox: &Rect) -> Pos {
+    let cnr = bbox.br();
+    match self {
+      A::Nonoverlap |
+      A::Inside     => (cnr + PosC::both(MARGIN_INSIDE))?,
+      A::Abut       =>  cnr,
+      A::Hanging    =>  PosC::both(HANG_INSIDE),
+    }
+  }
+
+  #[throws(CoordinateOverflow)]
+  fn br_tile(self, bbox: &Rect) -> Pos {
+    let cnr = bbox.br();
+    match self {
+      A::Nonoverlap => (cnr + PosC::both(MARGIN_INSIDE))?,
+      A::Inside     |
+      A::Abut       => (bbox.tl() - bbox.tl().map(|v| v/3))?,
+      A::Hanging    => PosC::both(HANG_INSIDE),
+    }
+  }
+}     
+
+#[throws(InternalError)]
+fn try_layout(region: &Rect,
+              pieces: &IndexVec<InHand, (PieceId, Rect)>,
+              att: Attempt)
+              -> Option<IndexVec<InHand, Pos>> {
+  let mut out = default();
+  if pieces.is_empty() { return Some(out) }
+
+  let mut cur = region.tl();
+  let mut n_y = region.tl().y();
+  // Invariant:
+  // Everything below n_y is overwriteable
+  // Everything below and to the right of cur is overwriteable
+
+  for (_piece, bbox) in pieces {
+    let place = 'placed: loop {
+      for _ in 0..3 {
+        let place = (cur - att.tl(&bbox)?)?;
+        let br_real = (place + att.br_real(&bbox)?)?;
+        if br_real.x() > region.br().x() {
+          cur = PosC::new(
+            region.tl().x(),
+            n_y,
+          );
+        } else if br_real.y() > region.br().y() {
+          if ! matches!(att, A::Hanging) { return None }
+          cur = PosC::new(
+            region.tl().x(),
+            region.br().y().checked_sub(HANG_INSIDE)
+              .ok_or(CoordinateOverflow)?,
+          );
+          n_y = cur.y();
+          continue;
+        } else {
+          break 'placed place;
+        }
+      }
+      throw!(IE::OrganisedPlacementFailure);
+    };
+    let br_tile = (place + att.br_tile(&bbox)?)?;
+    cur.coords[0] = br_tile.x();
+    n_y = max(n_y, br_tile.y());
+    out.push(place);
+  }
+  Some(out)
+}
+
+
+#[throws(ApiPieceOpError)]
+pub fn ui_operation(a: &mut ApiPieceOpArgs<'_>, opname: &str,
+                    wrc: WhatResponseToClientOp, region: &Rect)
+                    -> Option<UpdateFromOpComplex> {
+  let _do_sort = match opname {
+    "organise" => (),
+    _ => return None,
+  };
+  let ApiPieceOpArgs { ref mut gs, player,ipieces,ioccults,.. } = *a;
+  let apiece = a.piece;
+  let agpc = gs.pieces.byid(apiece)?;
+  let aipc = ipieces.get(apiece).ok_or(internal_error_bydebug(&apiece))?;
+  let gpl = gs.players.byid(player)?;
+  let log = log_did_to_piece(ioccults, &gs.occults, gpl,agpc,aipc,
+                             "oranised")?;
+
+  let pieces = gs.pieces.iter().filter_map(|(piece, gpc)| if_chain!{
+    if region.contains(gpc.pos);
+    if gpc.held.is_none();
+    if ! gpc.pinned;
+    if let PieceMoveable::Yes = gpc.moveable();
+    if let Some(ipc) = wants!( ipieces.get(piece), ?piece );
+    if let Some(vis) = gpc.fully_visible_to(&gs.occults, player);
+    if let Some(bbox) = want!( Ok = ipc.show(vis).bbox_approx(), ?piece );
+    then {
+      Some((piece, bbox))
+    }
+    else {
+      None
+    }
+  }).collect::<IndexVec<InHand, (PieceId, Rect)>>();
+
+  let layout = 'laid_out: loop {
+    for att in Attempt::iter() {
+      if let Some(layout) = try_layout(region, &pieces, att)? {
+        break 'laid_out layout;
+      }
+    }
+    throw!(internal_error_bydebug(region));
+  };
+
+  for &pos in &layout {
+    // Some sanity checks
+    if pos.clamped(gs.table_size).is_err() {
+      throw!(APOE::ReportViaUpdate(POE::PosOffTable))
+    }
+    match gs.occults.pos_occulter(&gs.occults, pos)? {
+      None => {},
+      Some(occulter) if occulter == apiece => {},
+      Some(_) => throw!(APOE::ReportViaUpdate(POE::Occultation)),
+    };
+  }
+
+  // point of no return
+  (||{
+    let updates = {
+      let mut updates = Vec::with_capacity(pieces.len());
+
+      for ((piece, _bbox), pos) in izip!(pieces, layout) {
+        want_let!{ Some(gpc) = gs.pieces.get_mut(piece); else continue; }
+        gpc.pos = pos;
+        updates.push((piece, PUOs::Simple(PUO::Move(pos))));
+      }
+
+      updates
+    };
+
+    Some((PieceUpdate {
+      wrc,
+      log,
+      ops: PUOs::PerPlayer(default()),
+    }, updates.into_unprepared_nc()))
+  })() // <- no ?, shows it's infallible
+}
index caeeaf04d52119ce02514ca9e14f7da27a3f4206..933de8a171112376a99f391ca30d2634468dffb6 100644 (file)
@@ -134,6 +134,7 @@ pub use crate::keydata::*;
 pub use crate::mgmtchannel::*;
 pub use crate::nwtemplates;
 pub use crate::occultilks::*;
+pub use crate::organise;
 pub use crate::pcrender::*;
 pub use crate::pieces::*;
 pub use crate::shapelib;