chiark / gitweb /
addpieces command
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 26 Jul 2020 21:25:49 +0000 (22:25 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 26 Jul 2020 21:25:49 +0000 (22:25 +0100)
12 files changed:
src/cmdlistener.rs
src/commands.rs
src/error.rs
src/gamestate.rs
src/global.rs
src/imports.rs
src/lib.rs
src/pieces.rs
src/spec.rs
src/sse.rs
src/updates.rs
src/utils.rs [new file with mode: 0644]

index 1159d0138e5aee27d08abdd28037eb0850f4a03b..e38f7fc18aa2d50366aaa5597446de24b72c1630 100644 (file)
@@ -207,6 +207,7 @@ fn execute(cs: &mut CommandStream, cmd: MgmtCommand) -> MgmtResponse {
         players : Default::default(),
         log : Default::default(),
         gen : Generation(0),
+        max_z: ZCoord(0.),
       };
 
       let name = InstanceName {
@@ -226,18 +227,8 @@ fn execute(cs: &mut CommandStream, cmd: MgmtCommand) -> MgmtResponse {
       Fine { }
     },
 
-    
+//      let game = cs.lookup_game(&game)?;
 
-    /*
-    AddPiece(game, { pos,count,name,info }) => {
-      let game = cs.lookup_game(&game)?;
-      let count = spec.count.unwrap_or(1);
-      let pos = spec.ok_or(XXU("missing piece pos"))?;
-      let _xxx_name = spec.name;
-      let pc = info.load()?;
-      
-    }
-    }, // xxx*/
   }
 }
 
@@ -343,12 +334,47 @@ fn execute_for_game(cs: &CommandStream, ig: &mut InstanceGuard,
   response
 }
 
+const XXX_START_POS : Pos = [20,20];
+const XXX_DEFAULT_POSD : Pos = [5,5];
+
+const CREATE_PIECES_MAX : u32 = 300;
+
 #[throws(ME)]
-fn execute_game_insn(_gs: &mut GameState, update: MgmtGameInstruction)
+fn execute_game_insn(gs: &mut GameState, update: MgmtGameInstruction)
                      -> (Vec<(PieceId,PieceUpdateOp<()>)>, Vec<LogEntry>) {
   use MgmtGameInstruction::*;
   match update {
     Noop { } => (vec![], vec![]),
+
+    AddPiece(PiecesSpec{ pos,posd,count,face,info }) => {
+      let count = count.unwrap_or(1);
+      if count > CREATE_PIECES_MAX { throw!(LimitExceeded) }
+      let posd = posd.unwrap_or(XXX_DEFAULT_POSD);
+      let face = info.resolve_spec_face(face)?;
+
+      let mut updates = Vec::with_capacity(count as usize);
+      let mut pos = pos.unwrap_or(XXX_START_POS);
+      for _ in 0..count {
+        let p = info.load()?;
+        let z = ZCoord(gs.max_z.0 + (count + 1) as f64);
+        let pc = PieceState {
+          held: None,
+          zlevel: ZLevel { z, zg: gs.gen },
+          lastclient: Default::default(),
+          gen_before_lastclient: Generation(0),
+          gen: gs.gen,
+          pos, p, face,
+        };
+        let piece = gs.pieces.insert(pc);
+        updates.push((piece, PieceUpdateOp::Insert(())));
+        pos[0] += posd[0];
+        pos[1] += posd[1];
+      }
+
+      (updates, vec![ LogEntry {
+        html: format!("The facilitaror added {} pieces", count),
+      }])
+    },
   }
 }
 
index 3a411005c27220f4e5815cb648d87c6d9e8b66e4..a308c2fab4bf4bc5a72ba69394a673438a903592 100644 (file)
@@ -11,7 +11,7 @@ pub enum MgmtCommand {
 #[derive(Debug,Serialize,Deserialize)]
 pub enum MgmtGameInstruction {
   Noop { },
-  //  AddPiece(Box<dyn PieceSpec>),
+  AddPiece(PiecesSpec),
 }
 
 #[derive(Debug,Serialize,Deserialize)]
@@ -35,7 +35,9 @@ pub enum MgmtError {
   AlreadyExists,
   GameBeingDestroyed,
   GameCorrupted,
+  LimitExceeded,
   SVGProcessingFailed(#[from] SVGProcessingError),
+  GameError(#[from] GameError),
 }
 display_as_debug!{MgmtError}
 
index e62837dfdcea86e6c47f2d56602779c101c805a2..5d2a71b9672b025f64a1993c7e47682da2298d22 100644 (file)
@@ -1,12 +1,13 @@
 
 use crate::imports::*;
 
-#[derive(Error,Debug)]
+#[derive(Error,Debug,Serialize,Deserialize)]
 #[error("operation error {:?}",self)]
 pub enum GameError {
   Conflict,
   PieceGone,
   PieceHeld,
+  FaceNotFound,
   InternalErrorSVG(#[from] SVGProcessingError),
 }
 
index ce6633e3336c2a961f6f3fddca6cd7b08f87deb9..7e7459c45d3308dd099aead11259881aa4849c09 100644 (file)
@@ -10,6 +10,7 @@ slotmap::new_key_type!{
 }
 
 define_index_type! {
+  #[derive(Default)]
   pub struct FaceId = u8;
 }
 
@@ -24,7 +25,7 @@ visible_slotmap_key!{ VisiblePieceId('.') }
 #[derive(Serialize,Deserialize)]
 #[serde(into="f64")]
 #[serde(try_from="f64")]
-pub struct ZCoord(f64);
+pub struct ZCoord(pub f64);
 
 // ---------- general data types ----------
 
@@ -42,6 +43,7 @@ pub struct GameState {
   pub players : DenseSlotMap<PlayerId,PlayerState>,
   pub gen : Generation,
   pub log : Vec<(Generation,Arc<LogEntry>)>,
+  pub max_z : ZCoord,
 }
 
 #[derive(Debug,Serialize,Deserialize)]
@@ -96,7 +98,9 @@ pub struct PieceRenderInstructions {
 
 #[typetag::serde(tag="type")]
 pub trait PieceSpec : Debug {
-  fn load(self) -> Result<Box<dyn Piece>,SE>;
+  fn load(&self) -> Result<Box<dyn Piece>,SE>;
+  fn resolve_spec_face(&self, face : Option<FaceId>)
+                       -> Result<FaceId,GameError>;
 }
 
 // ========== implementations ==========
@@ -202,5 +206,6 @@ pub fn xxx_gamestate_init() -> GameState {
     pieces.insert(pr);
   }
   GameState { pieces, gen, players : Default::default(),
+              max_z: ZCoord(0.),
               log : Default::default(), }
 }
index 9bfdced9f864b25cb0912eae6163ac533a157aeb..0956a56f8770eaee79560ed5b118adf64c3bb15e 100644 (file)
@@ -11,7 +11,7 @@ const SAVE_DIRECTORY : &str = "save";
 
 visible_slotmap_key!{ ClientId('C') }
 
-#[derive(Clone,Debug,Eq,PartialEq,Ord,PartialOrd,Hash,Deserialize)]
+#[derive(Clone,Debug,Eq,PartialEq,Ord,PartialOrd,Hash,Serialize,Deserialize)]
 #[serde(transparent)]
 pub struct RawToken (pub String);
 
index 4013313b9b191561f6c71ed1c3285a93eafa77e3..4d6f239f344d7ade1d9583af846bf534fd253a29 100644 (file)
@@ -51,7 +51,7 @@ pub use rand::distributions::Alphanumeric;
 
 pub use slotmap::dense::DenseSlotMap;
 pub type SecondarySlotMap<K,V> = slotmap::secondary::SecondaryMap<K,V>;
-pub use index_vec::{define_index_type,index_vec,IndexVec};
+pub use index_vec::{define_index_type,index_vec,IndexVec,IndexSlice};
 
 pub use vecdeque_stableix::StableIndexVecDeque;
 
@@ -65,7 +65,9 @@ pub use crate::error::*;
 pub use crate::commands::*;
 pub use crate::slotmap_slot_idx::*;
 pub use crate::cmdlistener::*;
+pub use crate::spec::*;
 pub use crate::api::{Lens,TransparentLens};
+pub use crate::utils::OrdExt;
 
 pub use libc::uid_t;
 
index 728c7888eaa35ad4dbfcc9c393309b504ea8c43e..5fc4ccad96a2d6168d631f3ca667f8c0b766d66f 100644 (file)
@@ -15,4 +15,5 @@ pub mod api;
 pub mod spec;
 pub mod cmdlistener;
 pub mod commands;
+pub mod utils;
 #[path="slotmap-slot-idx.rs"] pub mod slotmap_slot_idx;
index b0ddddd4d7ca12ff933eb45efbad19463358b6de..de532ef39be9e5c7409b7c7cc31838d89527425c 100644 (file)
@@ -110,11 +110,12 @@ impl Piece for SimpleShape {
 
 impl SimpleShape {
   fn new_from_path(desc: String, path: String, approx_dia: Coord,
-                   mut faces: Vec<ColourSpec>) -> Result<Box<dyn Piece>,SE> {
+                   faces: &IndexVec<FaceId,ColourSpec>)
+                   -> Result<Box<dyn Piece>,SE> {
     let scaled_path = svg_rescale_path(&path, SELECT_SCALE)?;
     let colours = faces
-      .iter_mut()
-      .map(|s| mem::take(s).try_into())
+      .iter()
+      .map(|s| s.try_into())
       .collect::<Result<_,SE>>()?;
     Ok(Box::new(SimpleShape {
       scaled_path, desc, approx_dia, path, colours,
@@ -127,45 +128,58 @@ impl SimpleShape {
 #[repr(transparent)]
 struct ColourSpec(String);
 
-impl TryFrom<ColourSpec> for Colour {
+impl TryFrom<&ColourSpec> for Colour {
   type Error = SE;
   #[throws(SE)]
-  fn try_from(spec: ColourSpec) -> Colour {
+  fn try_from(spec: &ColourSpec) -> Colour {
     // xxx check syntax
-    spec.0
+    spec.0.clone()
   }
 }
 
 #[derive(Debug,Serialize,Deserialize)]
 struct Disc {
   diam : Coord,
-  faces : Vec<ColourSpec>,
+  faces : IndexVec<FaceId,ColourSpec>,
 }
 
 #[derive(Debug,Serialize,Deserialize)]
 struct Square {
   size : Vec<Coord>,
-  faces : Vec<ColourSpec>,
+  faces : IndexVec<FaceId,ColourSpec>,
+}
+
+#[throws(GameError)]
+fn simple_resolve_spec_face(faces: &IndexSlice<FaceId,[ColourSpec]>,
+                            face: Option<FaceId>)
+                            -> FaceId {
+  let face = face.unwrap_or_default();
+  faces.get(face).ok_or(GameError::FaceNotFound)?;
+  face
 }
 
 #[typetag::serde]
 impl PieceSpec for Disc {
   #[throws(SE)]
-  fn load(self) -> Box<dyn Piece> {
+  fn load(&self) -> Box<dyn Piece> {
     let unit_path =
       "M 0 1  a 1 1 0 1 0 0 -2 \
               a 1 1 0 1 0 0  2  z";
     let scale = (self.diam as f64) * 0.5;
     let path = svg_rescale_path(&unit_path, scale)?;
     SimpleShape::new_from_path("circle".to_owned(), path, self.diam,
-                               self.faces)?
+                               &self.faces)?
+  }
+  #[throws(GameError)]
+  fn resolve_spec_face(&self, face: Option<FaceId>) -> FaceId {
+    simple_resolve_spec_face(&self.faces, face)?
   }
 }
 
 #[typetag::serde]
 impl PieceSpec for Square {
   #[throws(SE)]
-  fn load(self) -> Box<dyn Piece> {
+  fn load(&self) -> Box<dyn Piece> {
     let (x, y) = match self.size.as_slice() {
       &[s,] => (s,s),
       &[x, y] => (x,y),
@@ -173,7 +187,12 @@ impl PieceSpec for Square {
     };
     let path = format!("M {} {} h {} v {} h {} z",
                        -(x as f64)*0.5, -(y as f64)*0.5, x, y, -x);
-    SimpleShape::new_from_path("square".to_owned(), path, (x+y+1)/2, self.faces)?
+    SimpleShape::new_from_path("square".to_owned(), path, (x+y+1)/2,
+                               &self.faces)?
+  }
+  #[throws(GameError)]
+  fn resolve_spec_face(&self, face: Option<FaceId>) -> FaceId {
+    simple_resolve_spec_face(&self.faces, face)?
   }
 } 
 
@@ -182,12 +201,12 @@ pub fn xxx_make_pieces() -> Result<Vec<(Pos, Box<dyn Piece>)>,SE> {
     ([ 90, 80 ],
      Disc {
        diam : 20,
-       faces : vec![ ColourSpec("red".to_string()), ColourSpec("grey".to_string()) ],
+       faces : index_vec![ ColourSpec("red".to_string()), ColourSpec("grey".to_string()) ],
      }.load()?),
     ([ 90, 60 ],
      Square {
        size : vec![20],
-       faces : vec![ ColourSpec("blue".to_string()), ColourSpec("grey".to_string()) ],
+       faces : index_vec![ ColourSpec("blue".to_string()), ColourSpec("grey".to_string()) ],
      }.load()?),
   ])
 }
index aba45f4555a64a0faa6be2cdaf4b57a3bf58dcc1..fb284e9802120800297933292ef681536fabba63 100644 (file)
@@ -3,37 +3,38 @@
 
 use crate::imports::*;
 
-#[derive(Debug,Deserialize)]
-struct GameSpec {
-  table : Pos,
-  players : Vec<PlayerSpec>,
-  pieces : Vec<PiecesSpec>,
+#[derive(Debug,Serialize,Deserialize)]
+pub struct GameSpec {
+  pub table : Pos,
+  pub players : Vec<PlayerSpec>,
+  pub pieces : Vec<PiecesSpec>,
 }
 
-#[derive(Debug,Deserialize)]
-struct PlayerSpec {
-  nick: String,
+#[derive(Debug,Serialize,Deserialize)]
+pub struct PlayerSpec {
+  pub nick: String,
   #[serde(flatten)]
-  access: Box<dyn PlayerAccessSpec>,
+  pub access: Box<dyn PlayerAccessSpec>,
 }
 
-#[derive(Debug,Deserialize)]
-struct PiecesSpec {
-  pos : Option<Pos>,
-  count : Option<u32>,
-  name : Option<String>,
+#[derive(Debug,Serialize,Deserialize)]
+pub struct PiecesSpec {
+  pub pos : Option<Pos>,
+  pub posd : Option<Pos>,
+  pub count : Option<u32>,
+  pub face : Option<FaceId>,
   #[serde(flatten)]
-  info : Box<dyn PieceSpec>,
+  pub info : Box<dyn PieceSpec>,
 }
 
-#[typetag::deserialize(tag="access")]
-trait PlayerAccessSpec : Debug {
+#[typetag::serde(tag="access")]
+pub trait PlayerAccessSpec : Debug {
   #[throws(OE)]
   fn make_token(&self) -> RawToken { RawToken::new_random()? }
   fn deliver_token(&mut self) -> Result<(),OE>;
 }
 
-#[typetag::deserialize]
+#[typetag::serde]
 impl PlayerAccessSpec for RawToken {
   #[throws(OE)]
   fn make_token(&self) -> RawToken { self.clone() }
index 6e4442de99917dc4d78720b14567bef36734b420..f4d0b2812d604ad538c55f8ce83af14087348342 100644 (file)
@@ -57,6 +57,7 @@ impl Read for UpdateReader {
       if next_len > buf.len() { break }
 
       let tu = next.for_transmit(self.client);
+      // xxx handle overflow by allocating
       write!(buf, "data: ")?;
       serde_json::to_writer(&mut buf, &tu)?;
       write!(buf, "\n\
index b7ba42d3887af941bbe1fbb7b4f6534069baf040..c157b55005e0921cee89e5899d5e6b5ba21f50c9 100644 (file)
@@ -184,8 +184,12 @@ impl<'r> PrepareUpdatesBuffer<'r> {
                       lens: &dyn Lens) {
     let gs = &mut self.g.gs;
 
+    // xxx check pos is within range,  everywhere
+
     let (update, piece) = match gs.pieces.byid_mut(piece) {
       Ok(pc) => {
+        gs.max_z.update_max(pc.zlevel.z);
+
         if self.by_client != pc.lastclient {
           pc.gen_before_lastclient = pc.gen;
           pc.lastclient = self.by_client;
diff --git a/src/utils.rs b/src/utils.rs
new file mode 100644 (file)
index 0000000..55117cb
--- /dev/null
@@ -0,0 +1,8 @@
+
+pub trait OrdExt : Ord + Sized {
+  fn update_max(&mut self, new: Self) {
+    if new > *self { *self = new }
+  }
+}
+impl<T> OrdExt for T where T : Ord + Sized {
+}