add ChaCha8 rng with better properties
authorGeoffrey Allott <geoffrey@allott.email>
Wed, 7 Jun 2023 21:43:14 +0000 (22:43 +0100)
committerGeoffrey Allott <geoffrey@allott.email>
Wed, 7 Jun 2023 21:43:14 +0000 (22:43 +0100)
src/card.rs
src/game/cribbage/mod.rs
src/rng.rs
src/server.rs

index 7391a7380cf279872aaaf47976cda61b9e6e61d2..042b99fbe142eb91deb6b6c10254896de44bb02f 100644 (file)
@@ -202,6 +202,11 @@ impl IntoIterator for CardSet {
 impl Iterator for CardSetIntoIter {
     type Item = Card;
 
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        let len = self.inner.len();
+        (len, Some(len))
+    }
+
     fn next(&mut self) -> Option<Self::Item> {
         if self.inner.repr == 0 {
             None
index 4c93851699b13b96ca0692eefaa68e7de26751d0..7afbbcdc5957685a35c3aa482c0b5236b8c10b9b 100644 (file)
@@ -1772,4 +1772,478 @@ mod tests {
 
         test_game(actions, settings, seed);
     }
+
+    #[test]
+    fn complete_cribbage_game_chacha8() {
+        let actions = r#"[
+            {"timestamp":1686172280617,"username":"Geoff","action":{"action":"Join","seat":0,"chips":0}},
+            {"timestamp":1686172298709,"username":"Aga","action":{"action":"Join","seat":1,"chips":0}},
+            {"timestamp":1686172298710,"username":"Geoff","action":{"action":"NextToDeal"}},
+            {"timestamp":1686172298710,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Eight","suit":"Hearts"}}},
+            {"timestamp":1686172298711,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Three","suit":"Spades"}}},
+            {"timestamp":1686172298711,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Jack","suit":"Hearts"}}},
+            {"timestamp":1686172298712,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Seven","suit":"Hearts"}}},
+            {"timestamp":1686172298712,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Six","suit":"Hearts"}}},
+            {"timestamp":1686172298713,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Six","suit":"Diamonds"}}},
+            {"timestamp":1686172298713,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Queen","suit":"Spades"}}},
+            {"timestamp":1686172298714,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Five","suit":"Clubs"}}},
+            {"timestamp":1686172298714,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Nine","suit":"Spades"}}},
+            {"timestamp":1686172298714,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"King","suit":"Clubs"}}},
+            {"timestamp":1686172298715,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Six","suit":"Spades"}}},
+            {"timestamp":1686172298715,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Ten","suit":"Clubs"}}},
+            {"timestamp":1686172298716,"username":"Geoff","action":{"action":"EndDeal"}},
+            {"timestamp":1686172310565,"username":"Geoff","action":{"action":"PutInBox","card":{"rank":"Seven","suit":"Hearts"}}},
+            {"timestamp":1686172311294,"username":"Geoff","action":{"action":"PutInBox","card":{"rank":"Six","suit":"Diamonds"}}},
+            {"timestamp":1686172318125,"username":"Aga","action":{"action":"PutInBox","card":{"rank":"Queen","suit":"Spades"}}},
+            {"timestamp":1686172319158,"username":"Aga","action":{"action":"PutInBox","card":{"rank":"Jack","suit":"Hearts"}}},
+            {"timestamp":1686172319159,"username":"Geoff","action":{"action":"CommunityCard","card":{"rank":"Five","suit":"Spades"}}},
+            {"timestamp":1686172323416,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Nine","suit":"Spades"}}},
+            {"timestamp":1686172326450,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"King","suit":"Clubs"}}},
+            {"timestamp":1686172328985,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Eight","suit":"Hearts"}}},
+            {"timestamp":1686172330772,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Three","suit":"Spades"}}},
+            {"timestamp":1686172332473,"username":"Aga","action":{"action":"Pass"}},
+            {"timestamp":1686172333343,"username":"Geoff","action":{"action":"Pass"}},
+            {"timestamp":1686172333343,"username":"Geoff","action":{"action":"Score","points":1,"reason":"One for a go"}},
+            {"timestamp":1686172338139,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Six","suit":"Spades"}}},
+            {"timestamp":1686172339188,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Ten","suit":"Clubs"}}},
+            {"timestamp":1686172340030,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Six","suit":"Hearts"}}},
+            {"timestamp":1686172340740,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Five","suit":"Clubs"}}},
+            {"timestamp":1686172340741,"username":"Geoff","action":{"action":"Score","points":1,"reason":"One for a go"}},
+            {"timestamp":1686172340741,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Six","suit":"Hearts"}}},
+            {"timestamp":1686172340742,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Six","suit":"Spades"}}},
+            {"timestamp":1686172340743,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Eight","suit":"Hearts"}}},
+            {"timestamp":1686172340744,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Nine","suit":"Spades"}}},
+            {"timestamp":1686172340744,"username":"Aga","action":{"action":"Score","points":6,"reason":"Fifteen two, fifteen four and two is 6"}},
+            {"timestamp":1686172340745,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Three","suit":"Spades"}}},
+            {"timestamp":1686172340746,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Five","suit":"Clubs"}}},
+            {"timestamp":1686172340747,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Ten","suit":"Clubs"}}},
+            {"timestamp":1686172340747,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"King","suit":"Clubs"}}},
+            {"timestamp":1686172340748,"username":"Geoff","action":{"action":"Score","points":10,"reason":"Fifteen two, fifteen four, fifteen six, fifteen eight and two is 10"}},
+            {"timestamp":1686172340748,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Six","suit":"Diamonds"}}},
+            {"timestamp":1686172340749,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Seven","suit":"Hearts"}}},
+            {"timestamp":1686172340749,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Jack","suit":"Hearts"}}},
+            {"timestamp":1686172340749,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Queen","suit":"Spades"}}},
+            {"timestamp":1686172340750,"username":"Geoff","action":{"action":"Score","points":7,"reason":"Fifteen two, fifteen four and three is 7"}},
+            {"timestamp":1686172340750,"username":"Aga","action":{"action":"NextToDeal"}},
+            {"timestamp":1686172340751,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Six","suit":"Clubs"}}},
+            {"timestamp":1686172340751,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Six","suit":"Hearts"}}},
+            {"timestamp":1686172340752,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Queen","suit":"Hearts"}}},
+            {"timestamp":1686172340753,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Three","suit":"Diamonds"}}},
+            {"timestamp":1686172340766,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Jack","suit":"Clubs"}}},
+            {"timestamp":1686172340766,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Seven","suit":"Clubs"}}},
+            {"timestamp":1686172340766,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Four","suit":"Spades"}}},
+            {"timestamp":1686172340767,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Queen","suit":"Diamonds"}}},
+            {"timestamp":1686172340767,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Jack","suit":"Spades"}}},
+            {"timestamp":1686172340767,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Three","suit":"Clubs"}}},
+            {"timestamp":1686172340768,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Five","suit":"Diamonds"}}},
+            {"timestamp":1686172340780,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Three","suit":"Hearts"}}},
+            {"timestamp":1686172340780,"username":"Aga","action":{"action":"EndDeal"}},
+            {"timestamp":1686172377048,"username":"Aga","action":{"action":"PutInBox","card":{"rank":"Seven","suit":"Clubs"}}},
+            {"timestamp":1686172377833,"username":"Aga","action":{"action":"PutInBox","card":{"rank":"Queen","suit":"Diamonds"}}},
+            {"timestamp":1686172381737,"username":"Geoff","action":{"action":"PutInBox","card":{"rank":"Four","suit":"Spades"}}},
+            {"timestamp":1686172382673,"username":"Geoff","action":{"action":"PutInBox","card":{"rank":"Six","suit":"Clubs"}}},
+            {"timestamp":1686172382673,"username":"Aga","action":{"action":"CommunityCard","card":{"rank":"Ace","suit":"Spades"}}},
+            {"timestamp":1686172388053,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Jack","suit":"Spades"}}},
+            {"timestamp":1686172389479,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Three","suit":"Clubs"}}},
+            {"timestamp":1686172390704,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Queen","suit":"Hearts"}}},
+            {"timestamp":1686172393382,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Six","suit":"Hearts"}}},
+            {"timestamp":1686172394732,"username":"Geoff","action":{"action":"Pass"}},
+            {"timestamp":1686172395541,"username":"Aga","action":{"action":"Pass"}},
+            {"timestamp":1686172395542,"username":"Aga","action":{"action":"Score","points":1,"reason":"One for a go"}},
+            {"timestamp":1686172400578,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Jack","suit":"Clubs"}}},
+            {"timestamp":1686172401598,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Three","suit":"Hearts"}}},
+            {"timestamp":1686172402699,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Five","suit":"Diamonds"}}},
+            {"timestamp":1686172403676,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Three","suit":"Diamonds"}}},
+            {"timestamp":1686172403677,"username":"Aga","action":{"action":"Score","points":1,"reason":"One for a go"}},
+            {"timestamp":1686172403677,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Five","suit":"Diamonds"}}},
+            {"timestamp":1686172403678,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Jack","suit":"Clubs"}}},
+            {"timestamp":1686172403679,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Jack","suit":"Spades"}}},
+            {"timestamp":1686172403679,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Queen","suit":"Hearts"}}},
+            {"timestamp":1686172403680,"username":"Geoff","action":{"action":"Score","points":9,"reason":"Fifteen two, fifteen four, fifteen six and two and one for his nob is 9"}},
+            {"timestamp":1686172403681,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Three","suit":"Clubs"}}},
+            {"timestamp":1686172403681,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Three","suit":"Diamonds"}}},
+            {"timestamp":1686172403682,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Three","suit":"Hearts"}}},
+            {"timestamp":1686172403682,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Six","suit":"Hearts"}}},
+            {"timestamp":1686172403683,"username":"Aga","action":{"action":"Score","points":8,"reason":"Fifteen two and six is 8"}},
+            {"timestamp":1686172403683,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Four","suit":"Spades"}}},
+            {"timestamp":1686172403684,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Six","suit":"Clubs"}}},
+            {"timestamp":1686172403684,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Seven","suit":"Clubs"}}},
+            {"timestamp":1686172403685,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Queen","suit":"Diamonds"}}},
+            {"timestamp":1686172403686,"username":"Aga","action":{"action":"Score","points":2,"reason":"Fifteen two, and the rest won't do"}},
+            {"timestamp":1686172403686,"username":"Geoff","action":{"action":"NextToDeal"}},
+            {"timestamp":1686172403687,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Three","suit":"Clubs"}}},
+            {"timestamp":1686172403688,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Eight","suit":"Spades"}}},
+            {"timestamp":1686172403688,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Two","suit":"Clubs"}}},
+            {"timestamp":1686172403689,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Four","suit":"Clubs"}}},
+            {"timestamp":1686172403700,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Eight","suit":"Diamonds"}}},
+            {"timestamp":1686172403700,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Two","suit":"Spades"}}},
+            {"timestamp":1686172403701,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Seven","suit":"Clubs"}}},
+            {"timestamp":1686172403702,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Nine","suit":"Hearts"}}},
+            {"timestamp":1686172403702,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Four","suit":"Hearts"}}},
+            {"timestamp":1686172403703,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Ace","suit":"Diamonds"}}},
+            {"timestamp":1686172403703,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Five","suit":"Clubs"}}},
+            {"timestamp":1686172403704,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Six","suit":"Spades"}}},
+            {"timestamp":1686172403706,"username":"Geoff","action":{"action":"EndDeal"}},
+            {"timestamp":1686172473098,"username":"Geoff","action":{"action":"PutInBox","card":{"rank":"Two","suit":"Spades"}}},
+            {"timestamp":1686172473783,"username":"Geoff","action":{"action":"PutInBox","card":{"rank":"Four","suit":"Clubs"}}},
+            {"timestamp":1686172483642,"username":"Aga","action":{"action":"PutInBox","card":{"rank":"Four","suit":"Hearts"}}},
+            {"timestamp":1686172484594,"username":"Aga","action":{"action":"PutInBox","card":{"rank":"Eight","suit":"Diamonds"}}},
+            {"timestamp":1686172484595,"username":"Geoff","action":{"action":"CommunityCard","card":{"rank":"King","suit":"Clubs"}}},
+            {"timestamp":1686172489733,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Seven","suit":"Clubs"}}},
+            {"timestamp":1686172492010,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Eight","suit":"Spades"}}},
+            {"timestamp":1686172492011,"username":"Geoff","action":{"action":"Score","points":2,"reason":"Fifteen for two"}},
+            {"timestamp":1686172496404,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Five","suit":"Clubs"}}},
+            {"timestamp":1686172498906,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Nine","suit":"Hearts"}}},
+            {"timestamp":1686172500149,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Two","suit":"Clubs"}}},
+            {"timestamp":1686172500150,"username":"Aga","action":{"action":"Score","points":2,"reason":"Thirty-one for two"}},
+            {"timestamp":1686172507623,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Six","suit":"Spades"}}},
+            {"timestamp":1686172508867,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Three","suit":"Clubs"}}},
+            {"timestamp":1686172510009,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Ace","suit":"Diamonds"}}},
+            {"timestamp":1686172510010,"username":"Geoff","action":{"action":"Score","points":1,"reason":"One for a go"}},
+            {"timestamp":1686172510011,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Two","suit":"Clubs"}}},
+            {"timestamp":1686172510011,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Three","suit":"Clubs"}}},
+            {"timestamp":1686172510012,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Five","suit":"Clubs"}}},
+            {"timestamp":1686172510013,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Seven","suit":"Clubs"}}},
+            {"timestamp":1686172510013,"username":"Aga","action":{"action":"Score","points":11,"reason":"Fifteen two, fifteen four, fifteen six and five is 11"}},
+            {"timestamp":1686172510014,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Six","suit":"Spades"}}},
+            {"timestamp":1686172510015,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Eight","suit":"Spades"}}},
+            {"timestamp":1686172510015,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Nine","suit":"Hearts"}}},
+            {"timestamp":1686172510016,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Ace","suit":"Diamonds"}}},
+            {"timestamp":1686172510016,"username":"Geoff","action":{"action":"Score","points":4,"reason":"Fifteen two, fifteen four, look all day, see no more"}},
+            {"timestamp":1686172510017,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Two","suit":"Spades"}}},
+            {"timestamp":1686172510017,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Four","suit":"Clubs"}}},
+            {"timestamp":1686172510018,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Four","suit":"Hearts"}}},
+            {"timestamp":1686172510019,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Eight","suit":"Diamonds"}}},
+            {"timestamp":1686172510020,"username":"Geoff","action":{"action":"Score","points":2,"reason":"Two"}},
+            {"timestamp":1686172510020,"username":"Aga","action":{"action":"NextToDeal"}},
+            {"timestamp":1686172510021,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"King","suit":"Clubs"}}},
+            {"timestamp":1686172510021,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Four","suit":"Hearts"}}},
+            {"timestamp":1686172510022,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"King","suit":"Spades"}}},
+            {"timestamp":1686172510023,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Seven","suit":"Clubs"}}},
+            {"timestamp":1686172510033,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Ten","suit":"Hearts"}}},
+            {"timestamp":1686172510033,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Eight","suit":"Clubs"}}},
+            {"timestamp":1686172510034,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Three","suit":"Hearts"}}},
+            {"timestamp":1686172510035,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Ace","suit":"Diamonds"}}},
+            {"timestamp":1686172510036,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Seven","suit":"Spades"}}},
+            {"timestamp":1686172510036,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Nine","suit":"Clubs"}}},
+            {"timestamp":1686172510037,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Seven","suit":"Hearts"}}},
+            {"timestamp":1686172510038,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Nine","suit":"Spades"}}},
+            {"timestamp":1686172510038,"username":"Aga","action":{"action":"EndDeal"}},
+            {"timestamp":1686172540681,"username":"Aga","action":{"action":"PutInBox","card":{"rank":"Ace","suit":"Diamonds"}}},
+            {"timestamp":1686172541491,"username":"Aga","action":{"action":"PutInBox","card":{"rank":"Four","suit":"Hearts"}}},
+            {"timestamp":1686172546010,"username":"Geoff","action":{"action":"PutInBox","card":{"rank":"Three","suit":"Hearts"}}},
+            {"timestamp":1686172546797,"username":"Geoff","action":{"action":"PutInBox","card":{"rank":"Ten","suit":"Hearts"}}},
+            {"timestamp":1686172546798,"username":"Aga","action":{"action":"CommunityCard","card":{"rank":"Six","suit":"Spades"}}},
+            {"timestamp":1686172555037,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"King","suit":"Clubs"}}},
+            {"timestamp":1686172556354,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Nine","suit":"Spades"}}},
+            {"timestamp":1686172558422,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"King","suit":"Spades"}}},
+            {"timestamp":1686172563036,"username":"Aga","action":{"action":"Pass"}},
+            {"timestamp":1686172566289,"username":"Geoff","action":{"action":"Pass"}},
+            {"timestamp":1686172566290,"username":"Geoff","action":{"action":"Score","points":1,"reason":"One for a go"}},
+            {"timestamp":1686172573453,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Nine","suit":"Clubs"}}},
+            {"timestamp":1686172574530,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Seven","suit":"Hearts"}}},
+            {"timestamp":1686172576040,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Eight","suit":"Clubs"}}},
+            {"timestamp":1686172576041,"username":"Aga","action":{"action":"Score","points":3,"reason":"Three"}},
+            {"timestamp":1686172579776,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Seven","suit":"Spades"}}},
+            {"timestamp":1686172579777,"username":"Geoff","action":{"action":"Score","points":2,"reason":"Thirty-one for two"}},
+            {"timestamp":1686172580822,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Seven","suit":"Clubs"}}},
+            {"timestamp":1686172580823,"username":"Aga","action":{"action":"Score","points":1,"reason":"One for a go"}},
+            {"timestamp":1686172580823,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Seven","suit":"Hearts"}}},
+            {"timestamp":1686172580824,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Seven","suit":"Spades"}}},
+            {"timestamp":1686172580825,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"King","suit":"Clubs"}}},
+            {"timestamp":1686172580825,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"King","suit":"Spades"}}},
+            {"timestamp":1686172580826,"username":"Geoff","action":{"action":"Score","points":4,"reason":"Two and two"}},
+            {"timestamp":1686172580827,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Seven","suit":"Clubs"}}},
+            {"timestamp":1686172580827,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Eight","suit":"Clubs"}}},
+            {"timestamp":1686172580828,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Nine","suit":"Clubs"}}},
+            {"timestamp":1686172580828,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Nine","suit":"Spades"}}},
+            {"timestamp":1686172580829,"username":"Aga","action":{"action":"Score","points":16,"reason":"Fifteen two, fifteen four, fifteen six and ten is 16"}},
+            {"timestamp":1686172580830,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Three","suit":"Hearts"}}},
+            {"timestamp":1686172580830,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Four","suit":"Hearts"}}},
+            {"timestamp":1686172580830,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Ten","suit":"Hearts"}}},
+            {"timestamp":1686172580831,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Ace","suit":"Diamonds"}}},
+            {"timestamp":1686172580832,"username":"Aga","action":{"action":"Score","points":2,"reason":"Fifteen two, and the rest won't do"}},
+            {"timestamp":1686172580832,"username":"Geoff","action":{"action":"NextToDeal"}},
+            {"timestamp":1686172580833,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Five","suit":"Diamonds"}}},
+            {"timestamp":1686172580833,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Nine","suit":"Diamonds"}}},
+            {"timestamp":1686172580834,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Ace","suit":"Diamonds"}}},
+            {"timestamp":1686172580834,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Queen","suit":"Diamonds"}}},
+            {"timestamp":1686172580835,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Ace","suit":"Spades"}}},
+            {"timestamp":1686172580836,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"King","suit":"Clubs"}}},
+            {"timestamp":1686172580848,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Ten","suit":"Diamonds"}}},
+            {"timestamp":1686172580848,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Queen","suit":"Spades"}}},
+            {"timestamp":1686172580849,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Four","suit":"Diamonds"}}},
+            {"timestamp":1686172580849,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Seven","suit":"Clubs"}}},
+            {"timestamp":1686172580850,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Ace","suit":"Hearts"}}},
+            {"timestamp":1686172580850,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Seven","suit":"Diamonds"}}},
+            {"timestamp":1686172580856,"username":"Geoff","action":{"action":"EndDeal"}},
+            {"timestamp":1686172638464,"username":"Aga","action":{"action":"PutInBox","card":{"rank":"Four","suit":"Diamonds"}}},
+            {"timestamp":1686172639697,"username":"Aga","action":{"action":"PutInBox","card":{"rank":"Five","suit":"Diamonds"}}},
+            {"timestamp":1686172647549,"username":"Geoff","action":{"action":"PutInBox","card":{"rank":"Queen","suit":"Spades"}}},
+            {"timestamp":1686172648260,"username":"Geoff","action":{"action":"PutInBox","card":{"rank":"Queen","suit":"Diamonds"}}},
+            {"timestamp":1686172648261,"username":"Geoff","action":{"action":"CommunityCard","card":{"rank":"Seven","suit":"Spades"}}},
+            {"timestamp":1686172655807,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Ace","suit":"Hearts"}}},
+            {"timestamp":1686172657009,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"King","suit":"Clubs"}}},
+            {"timestamp":1686172658594,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Ten","suit":"Diamonds"}}},
+            {"timestamp":1686172660839,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Nine","suit":"Diamonds"}}},
+            {"timestamp":1686172662374,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Ace","suit":"Spades"}}},
+            {"timestamp":1686172662375,"username":"Aga","action":{"action":"Score","points":2,"reason":"Thirty-one for two"}},
+            {"timestamp":1686172668470,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Seven","suit":"Diamonds"}}},
+            {"timestamp":1686172669697,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Ace","suit":"Diamonds"}}},
+            {"timestamp":1686172670614,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Seven","suit":"Clubs"}}},
+            {"timestamp":1686172670615,"username":"Geoff","action":{"action":"Score","points":3,"reason":"Fifteen for two and a go is three"}},
+            {"timestamp":1686172670616,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Ten","suit":"Diamonds"}}},
+            {"timestamp":1686172670616,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Ace","suit":"Diamonds"}}},
+            {"timestamp":1686172670617,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Ace","suit":"Hearts"}}},
+            {"timestamp":1686172670618,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Ace","suit":"Spades"}}},
+            {"timestamp":1686172670618,"username":"Aga","action":{"action":"Score","points":6,"reason":"Six"}},
+            {"timestamp":1686172670619,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Seven","suit":"Clubs"}}},
+            {"timestamp":1686172670619,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Seven","suit":"Diamonds"}}},
+            {"timestamp":1686172670620,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Nine","suit":"Diamonds"}}},
+            {"timestamp":1686172670620,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"King","suit":"Clubs"}}},
+            {"timestamp":1686172670621,"username":"Geoff","action":{"action":"Score","points":6,"reason":"Six"}},
+            {"timestamp":1686172670621,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Four","suit":"Diamonds"}}},
+            {"timestamp":1686172670622,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Five","suit":"Diamonds"}}},
+            {"timestamp":1686172670622,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Queen","suit":"Diamonds"}}},
+            {"timestamp":1686172670623,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Queen","suit":"Spades"}}},
+            {"timestamp":1686172670624,"username":"Geoff","action":{"action":"Score","points":6,"reason":"Fifteen two, fifteen four and two is 6"}},
+            {"timestamp":1686172670624,"username":"Aga","action":{"action":"NextToDeal"}},
+            {"timestamp":1686172670625,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Seven","suit":"Clubs"}}},
+            {"timestamp":1686172670626,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Three","suit":"Spades"}}},
+            {"timestamp":1686172670626,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Four","suit":"Clubs"}}},
+            {"timestamp":1686172670627,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Queen","suit":"Hearts"}}},
+            {"timestamp":1686172670628,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Ace","suit":"Diamonds"}}},
+            {"timestamp":1686172670628,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Jack","suit":"Hearts"}}},
+            {"timestamp":1686172670641,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Nine","suit":"Diamonds"}}},
+            {"timestamp":1686172670641,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Ten","suit":"Hearts"}}},
+            {"timestamp":1686172670642,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Eight","suit":"Spades"}}},
+            {"timestamp":1686172670642,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Four","suit":"Hearts"}}},
+            {"timestamp":1686172670642,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"King","suit":"Hearts"}}},
+            {"timestamp":1686172670643,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Five","suit":"Spades"}}},
+            {"timestamp":1686172670644,"username":"Aga","action":{"action":"EndDeal"}},
+            {"timestamp":1686172703945,"username":"Aga","action":{"action":"PutInBox","card":{"rank":"Four","suit":"Hearts"}}},
+            {"timestamp":1686172705156,"username":"Aga","action":{"action":"PutInBox","card":{"rank":"Three","suit":"Spades"}}},
+            {"timestamp":1686172716669,"username":"Geoff","action":{"action":"PutInBox","card":{"rank":"King","suit":"Hearts"}}},
+            {"timestamp":1686172717783,"username":"Geoff","action":{"action":"PutInBox","card":{"rank":"Ace","suit":"Diamonds"}}},
+            {"timestamp":1686172717784,"username":"Aga","action":{"action":"CommunityCard","card":{"rank":"Ace","suit":"Spades"}}},
+            {"timestamp":1686172721071,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Nine","suit":"Diamonds"}}},
+            {"timestamp":1686172722505,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Jack","suit":"Hearts"}}},
+            {"timestamp":1686172723900,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Eight","suit":"Spades"}}},
+            {"timestamp":1686172728502,"username":"Aga","action":{"action":"Pass"}},
+            {"timestamp":1686172731181,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Four","suit":"Clubs"}}},
+            {"timestamp":1686172731182,"username":"Geoff","action":{"action":"Score","points":2,"reason":"Thirty-one for two"}},
+            {"timestamp":1686172737337,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Queen","suit":"Hearts"}}},
+            {"timestamp":1686172738747,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Seven","suit":"Clubs"}}},
+            {"timestamp":1686172740582,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Ten","suit":"Hearts"}}},
+            {"timestamp":1686172743084,"username":"Aga","action":{"action":"Pass"}},
+            {"timestamp":1686172743085,"username":"Aga","action":{"action":"Score","points":1,"reason":"One for a go"}},
+            {"timestamp":1686172752238,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Five","suit":"Spades"}}},
+            {"timestamp":1686172752240,"username":"Aga","action":{"action":"Score","points":1,"reason":"One for a go"}},
+            {"timestamp":1686172752240,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Four","suit":"Clubs"}}},
+            {"timestamp":1686172752241,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Seven","suit":"Clubs"}}},
+            {"timestamp":1686172752241,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Eight","suit":"Spades"}}},
+            {"timestamp":1686172752242,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Nine","suit":"Diamonds"}}},
+            {"timestamp":1686172752242,"username":"Geoff","action":{"action":"Score","points":5,"reason":"Fifteen two and three is 5"}},
+            {"timestamp":1686172752243,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Five","suit":"Spades"}}},
+            {"timestamp":1686172752244,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Ten","suit":"Hearts"}}},
+            {"timestamp":1686172752244,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Jack","suit":"Hearts"}}},
+            {"timestamp":1686172752245,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Queen","suit":"Hearts"}}},
+            {"timestamp":1686172752245,"username":"Aga","action":{"action":"Score","points":9,"reason":"Fifteen two, fifteen four, fifteen six and three is 9"}},
+            {"timestamp":1686172752246,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Three","suit":"Spades"}}},
+            {"timestamp":1686172752247,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Four","suit":"Hearts"}}},
+            {"timestamp":1686172752247,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"King","suit":"Hearts"}}},
+            {"timestamp":1686172752248,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Ace","suit":"Diamonds"}}},
+            {"timestamp":1686172752248,"username":"Aga","action":{"action":"Score","points":8,"reason":"Fifteen two, fifteen four, fifteen six and two is 8"}},
+            {"timestamp":1686172752249,"username":"Geoff","action":{"action":"NextToDeal"}},
+            {"timestamp":1686172752250,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Seven","suit":"Spades"}}},
+            {"timestamp":1686172752250,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Seven","suit":"Hearts"}}},
+            {"timestamp":1686172752251,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Six","suit":"Hearts"}}},
+            {"timestamp":1686172752251,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Ten","suit":"Hearts"}}},
+            {"timestamp":1686172752252,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Nine","suit":"Hearts"}}},
+            {"timestamp":1686172752255,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Four","suit":"Clubs"}}},
+            {"timestamp":1686172752256,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Seven","suit":"Diamonds"}}},
+            {"timestamp":1686172752256,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Jack","suit":"Diamonds"}}},
+            {"timestamp":1686172752257,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Nine","suit":"Clubs"}}},
+            {"timestamp":1686172752257,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Eight","suit":"Spades"}}},
+            {"timestamp":1686172752258,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Three","suit":"Diamonds"}}},
+            {"timestamp":1686172752259,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Eight","suit":"Hearts"}}},
+            {"timestamp":1686172752259,"username":"Geoff","action":{"action":"EndDeal"}},
+            {"timestamp":1686172804389,"username":"Aga","action":{"action":"PutInBox","card":{"rank":"Six","suit":"Hearts"}}},
+            {"timestamp":1686172805077,"username":"Aga","action":{"action":"PutInBox","card":{"rank":"Three","suit":"Diamonds"}}},
+            {"timestamp":1686172810741,"username":"Geoff","action":{"action":"PutInBox","card":{"rank":"Jack","suit":"Diamonds"}}},
+            {"timestamp":1686172811563,"username":"Geoff","action":{"action":"PutInBox","card":{"rank":"Four","suit":"Clubs"}}},
+            {"timestamp":1686172811564,"username":"Geoff","action":{"action":"CommunityCard","card":{"rank":"Ace","suit":"Clubs"}}},
+            {"timestamp":1686172815987,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Nine","suit":"Clubs"}}},
+            {"timestamp":1686172818505,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Seven","suit":"Hearts"}}},
+            {"timestamp":1686172820225,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Seven","suit":"Diamonds"}}},
+            {"timestamp":1686172820226,"username":"Aga","action":{"action":"Score","points":2,"reason":"Two"}},
+            {"timestamp":1686172823553,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Eight","suit":"Hearts"}}},
+            {"timestamp":1686172823554,"username":"Geoff","action":{"action":"Score","points":2,"reason":"Thirty-one for two"}},
+            {"timestamp":1686172830125,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Nine","suit":"Hearts"}}},
+            {"timestamp":1686172831542,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Eight","suit":"Spades"}}},
+            {"timestamp":1686172832677,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Seven","suit":"Spades"}}},
+            {"timestamp":1686172832678,"username":"Aga","action":{"action":"Score","points":3,"reason":"Three"}},
+            {"timestamp":1686172835911,"username":"Geoff","action":{"action":"Pass"}},
+            {"timestamp":1686172835912,"username":"Aga","action":{"action":"Score","points":1,"reason":"One for a go"}},
+            {"timestamp":1686172839051,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Ten","suit":"Hearts"}}},
+            {"timestamp":1686172839052,"username":"Geoff","action":{"action":"Score","points":1,"reason":"One for a go"}},
+            {"timestamp":1686172839053,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Seven","suit":"Diamonds"}}},
+            {"timestamp":1686172839054,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Seven","suit":"Spades"}}},
+            {"timestamp":1686172839054,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Nine","suit":"Clubs"}}},
+            {"timestamp":1686172839055,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Nine","suit":"Hearts"}}},
+            {"timestamp":1686172839055,"username":"Aga","action":{"action":"Score","points":6,"reason":"Fifteen two and two and two is 6"}},
+            {"timestamp":1686172839056,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Seven","suit":"Hearts"}}},
+            {"timestamp":1686172839056,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Eight","suit":"Hearts"}}},
+            {"timestamp":1686172839057,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Eight","suit":"Spades"}}},
+            {"timestamp":1686172839057,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Ten","suit":"Hearts"}}},
+            {"timestamp":1686172839058,"username":"Geoff","action":{"action":"Score","points":6,"reason":"Fifteen two, fifteen four and two is 6"}},
+            {"timestamp":1686172839058,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Three","suit":"Diamonds"}}},
+            {"timestamp":1686172839059,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Four","suit":"Clubs"}}},
+            {"timestamp":1686172839060,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Six","suit":"Hearts"}}},
+            {"timestamp":1686172839060,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Jack","suit":"Diamonds"}}},
+            {"timestamp":1686172839061,"username":"Geoff","action":{"action":"Score","points":2,"reason":"Fifteen two, and the rest won't do"}},
+            {"timestamp":1686172839061,"username":"Aga","action":{"action":"NextToDeal"}},
+            {"timestamp":1686172839062,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"King","suit":"Hearts"}}},
+            {"timestamp":1686172839062,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Eight","suit":"Diamonds"}}},
+            {"timestamp":1686172839063,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Six","suit":"Clubs"}}},
+            {"timestamp":1686172839063,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Jack","suit":"Clubs"}}},
+            {"timestamp":1686172839064,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Eight","suit":"Clubs"}}},
+            {"timestamp":1686172839064,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Ten","suit":"Clubs"}}},
+            {"timestamp":1686172839065,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Two","suit":"Clubs"}}},
+            {"timestamp":1686172839078,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Seven","suit":"Clubs"}}},
+            {"timestamp":1686172839079,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Ace","suit":"Spades"}}},
+            {"timestamp":1686172839079,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Ten","suit":"Spades"}}},
+            {"timestamp":1686172839080,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Five","suit":"Spades"}}},
+            {"timestamp":1686172839080,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"King","suit":"Spades"}}},
+            {"timestamp":1686172839081,"username":"Aga","action":{"action":"EndDeal"}},
+            {"timestamp":1686172873017,"username":"Geoff","action":{"action":"PutInBox","card":{"rank":"King","suit":"Hearts"}}},
+            {"timestamp":1686172874829,"username":"Geoff","action":{"action":"PutInBox","card":{"rank":"Eight","suit":"Clubs"}}},
+            {"timestamp":1686172876946,"username":"Aga","action":{"action":"PutInBox","card":{"rank":"Seven","suit":"Clubs"}}},
+            {"timestamp":1686172877713,"username":"Aga","action":{"action":"PutInBox","card":{"rank":"Eight","suit":"Diamonds"}}},
+            {"timestamp":1686172877714,"username":"Aga","action":{"action":"CommunityCard","card":{"rank":"Four","suit":"Diamonds"}}},
+            {"timestamp":1686172884451,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Six","suit":"Clubs"}}},
+            {"timestamp":1686172885879,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"King","suit":"Spades"}}},
+            {"timestamp":1686172901318,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Five","suit":"Spades"}}},
+            {"timestamp":1686172902477,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Ten","suit":"Spades"}}},
+            {"timestamp":1686172902479,"username":"Aga","action":{"action":"Score","points":2,"reason":"Thirty-one for two"}},
+            {"timestamp":1686172908224,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Two","suit":"Clubs"}}},
+            {"timestamp":1686172909870,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Ten","suit":"Clubs"}}},
+            {"timestamp":1686172910995,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Ace","suit":"Spades"}}},
+            {"timestamp":1686172912113,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Jack","suit":"Clubs"}}},
+            {"timestamp":1686172912114,"username":"Aga","action":{"action":"Score","points":1,"reason":"One for a go"}},
+            {"timestamp":1686172912114,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Two","suit":"Clubs"}}},
+            {"timestamp":1686172912115,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Five","suit":"Spades"}}},
+            {"timestamp":1686172912115,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Six","suit":"Clubs"}}},
+            {"timestamp":1686172912116,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Ace","suit":"Spades"}}},
+            {"timestamp":1686172912117,"username":"Geoff","action":{"action":"Score","points":5,"reason":"Fifteen two and three is 5"}},
+            {"timestamp":1686172912117,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Ten","suit":"Clubs"}}},
+            {"timestamp":1686172912118,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Ten","suit":"Spades"}}},
+            {"timestamp":1686172912118,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Jack","suit":"Clubs"}}},
+            {"timestamp":1686172912119,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"King","suit":"Spades"}}},
+            {"timestamp":1686172912119,"username":"Aga","action":{"action":"Score","points":2,"reason":"Two"}},
+            {"timestamp":1686172912120,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Seven","suit":"Clubs"}}},
+            {"timestamp":1686172912121,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Eight","suit":"Clubs"}}},
+            {"timestamp":1686172912121,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Eight","suit":"Diamonds"}}},
+            {"timestamp":1686172912122,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"King","suit":"Hearts"}}},
+            {"timestamp":1686172912122,"username":"Aga","action":{"action":"Score","points":6,"reason":"Fifteen two, fifteen four and two is 6"}},
+            {"timestamp":1686172912123,"username":"Geoff","action":{"action":"NextToDeal"}},
+            {"timestamp":1686172912123,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Ten","suit":"Hearts"}}},
+            {"timestamp":1686172912124,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Seven","suit":"Hearts"}}},
+            {"timestamp":1686172912124,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Ten","suit":"Clubs"}}},
+            {"timestamp":1686172912125,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Ace","suit":"Spades"}}},
+            {"timestamp":1686172912125,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Queen","suit":"Hearts"}}},
+            {"timestamp":1686172912126,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Five","suit":"Clubs"}}},
+            {"timestamp":1686172912130,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Three","suit":"Spades"}}},
+            {"timestamp":1686172912130,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Nine","suit":"Clubs"}}},
+            {"timestamp":1686172912131,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Nine","suit":"Hearts"}}},
+            {"timestamp":1686172912132,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Ace","suit":"Diamonds"}}},
+            {"timestamp":1686172912132,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Six","suit":"Clubs"}}},
+            {"timestamp":1686172912133,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Two","suit":"Spades"}}},
+            {"timestamp":1686172912134,"username":"Geoff","action":{"action":"EndDeal"}},
+            {"timestamp":1686172944010,"username":"Aga","action":{"action":"PutInBox","card":{"rank":"Three","suit":"Spades"}}},
+            {"timestamp":1686172944603,"username":"Aga","action":{"action":"PutInBox","card":{"rank":"Six","suit":"Clubs"}}},
+            {"timestamp":1686172951266,"username":"Geoff","action":{"action":"PutInBox","card":{"rank":"Nine","suit":"Clubs"}}},
+            {"timestamp":1686172952751,"username":"Geoff","action":{"action":"PutInBox","card":{"rank":"Seven","suit":"Hearts"}}},
+            {"timestamp":1686172952752,"username":"Geoff","action":{"action":"CommunityCard","card":{"rank":"Seven","suit":"Clubs"}}},
+            {"timestamp":1686172957298,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Queen","suit":"Hearts"}}},
+            {"timestamp":1686172960107,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Five","suit":"Clubs"}}},
+            {"timestamp":1686172960108,"username":"Geoff","action":{"action":"Score","points":2,"reason":"Fifteen for two"}},
+            {"timestamp":1686172966247,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Nine","suit":"Hearts"}}},
+            {"timestamp":1686172969125,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Two","suit":"Spades"}}},
+            {"timestamp":1686172971961,"username":"Aga","action":{"action":"Pass"}},
+            {"timestamp":1686172974106,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Ace","suit":"Diamonds"}}},
+            {"timestamp":1686173011382,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Ace","suit":"Spades"}}},
+            {"timestamp":1686173011383,"username":"Geoff","action":{"action":"Score","points":2,"reason":"Two"}},
+            {"timestamp":1686173015507,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Ten","suit":"Hearts"}}},
+            {"timestamp":1686173016640,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Ten","suit":"Clubs"}}},
+            {"timestamp":1686173016641,"username":"Aga","action":{"action":"Score","points":3,"reason":"Two and a go is three"}},
+            {"timestamp":1686173016642,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Nine","suit":"Hearts"}}},
+            {"timestamp":1686173016642,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Ten","suit":"Clubs"}}},
+            {"timestamp":1686173016643,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Ten","suit":"Hearts"}}},
+            {"timestamp":1686173016643,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Queen","suit":"Hearts"}}},
+            {"timestamp":1686173016644,"username":"Aga","action":{"action":"Score","points":2,"reason":"Two"}},
+            {"timestamp":1686173016645,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Two","suit":"Spades"}}},
+            {"timestamp":1686173016645,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Five","suit":"Clubs"}}},
+            {"timestamp":1686173016646,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Ace","suit":"Diamonds"}}},
+            {"timestamp":1686173016646,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Ace","suit":"Spades"}}},
+            {"timestamp":1686173016647,"username":"Geoff","action":{"action":"Score","points":6,"reason":"Fifteen two, fifteen four and two is 6"}},
+            {"timestamp":1686173016647,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Three","suit":"Spades"}}},
+            {"timestamp":1686173016648,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Six","suit":"Clubs"}}},
+            {"timestamp":1686173016649,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Seven","suit":"Hearts"}}},
+            {"timestamp":1686173016649,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Nine","suit":"Clubs"}}},
+            {"timestamp":1686173016650,"username":"Geoff","action":{"action":"Score","points":4,"reason":"Fifteen two and two is 4"}},
+            {"timestamp":1686173016651,"username":"Aga","action":{"action":"NextToDeal"}},
+            {"timestamp":1686173016651,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Queen","suit":"Clubs"}}},
+            {"timestamp":1686173016652,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Ten","suit":"Hearts"}}},
+            {"timestamp":1686173016652,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Six","suit":"Diamonds"}}},
+            {"timestamp":1686173016653,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Five","suit":"Spades"}}},
+            {"timestamp":1686173016654,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Seven","suit":"Spades"}}},
+            {"timestamp":1686173016673,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Five","suit":"Diamonds"}}},
+            {"timestamp":1686173016673,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Ace","suit":"Hearts"}}},
+            {"timestamp":1686173016674,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"King","suit":"Hearts"}}},
+            {"timestamp":1686173016675,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Four","suit":"Spades"}}},
+            {"timestamp":1686173016675,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Seven","suit":"Diamonds"}}},
+            {"timestamp":1686173016676,"username":"Geoff","action":{"action":"ReceiveCard","card":{"rank":"Six","suit":"Clubs"}}},
+            {"timestamp":1686173016686,"username":"Aga","action":{"action":"ReceiveCard","card":{"rank":"Queen","suit":"Spades"}}},
+            {"timestamp":1686173016687,"username":"Aga","action":{"action":"EndDeal"}},
+            {"timestamp":1686173053339,"username":"Aga","action":{"action":"PutInBox","card":{"rank":"Seven","suit":"Diamonds"}}},
+            {"timestamp":1686173056894,"username":"Aga","action":{"action":"PutInBox","card":{"rank":"King","suit":"Hearts"}}},
+            {"timestamp":1686173062224,"username":"Geoff","action":{"action":"PutInBox","card":{"rank":"Four","suit":"Spades"}}},
+            {"timestamp":1686173063357,"username":"Geoff","action":{"action":"PutInBox","card":{"rank":"Queen","suit":"Clubs"}}},
+            {"timestamp":1686173063359,"username":"Aga","action":{"action":"CommunityCard","card":{"rank":"Jack","suit":"Diamonds"}}},
+            {"timestamp":1686173063359,"username":"Aga","action":{"action":"Score","points":2,"reason":"Two for his heels"}},
+            {"timestamp":1686173071916,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Seven","suit":"Spades"}}},
+            {"timestamp":1686173073787,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Ten","suit":"Hearts"}}},
+            {"timestamp":1686173075269,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Six","suit":"Clubs"}}},
+            {"timestamp":1686173077856,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Five","suit":"Diamonds"}}},
+            {"timestamp":1686173084378,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Ace","suit":"Hearts"}}},
+            {"timestamp":1686173085696,"username":"Aga","action":{"action":"Pass"}},
+            {"timestamp":1686173086888,"username":"Geoff","action":{"action":"Pass"}},
+            {"timestamp":1686173086889,"username":"Geoff","action":{"action":"Score","points":1,"reason":"One for a go"}},
+            {"timestamp":1686173090533,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Queen","suit":"Spades"}}},
+            {"timestamp":1686173091527,"username":"Geoff","action":{"action":"PlayCard","card":{"rank":"Six","suit":"Diamonds"}}},
+            {"timestamp":1686173092402,"username":"Aga","action":{"action":"PlayCard","card":{"rank":"Five","suit":"Spades"}}},
+            {"timestamp":1686173092403,"username":"Aga","action":{"action":"Score","points":1,"reason":"One for a go"}},
+            {"timestamp":1686173092404,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Six","suit":"Clubs"}}},
+            {"timestamp":1686173092404,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Six","suit":"Diamonds"}}},
+            {"timestamp":1686173092405,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Seven","suit":"Spades"}}},
+            {"timestamp":1686173092405,"username":"Geoff","action":{"action":"RevealCard","card":{"rank":"Ace","suit":"Hearts"}}},
+            {"timestamp":1686173092406,"username":"Geoff","action":{"action":"Score","points":2,"reason":"Two"}},
+            {"timestamp":1686173092407,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Five","suit":"Diamonds"}}},
+            {"timestamp":1686173092407,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Five","suit":"Spades"}}},
+            {"timestamp":1686173092408,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Ten","suit":"Hearts"}}},
+            {"timestamp":1686173092408,"username":"Aga","action":{"action":"RevealCard","card":{"rank":"Queen","suit":"Spades"}}},
+            {"timestamp":1686173092409,"username":"Aga","action":{"action":"Score","points":17,"reason":"Fifteen two, fifteen four, fifteen six, fifteen eight, fifteen ten, fifteen twelve and two and three is 17"}},
+            {"timestamp":1686173092409,"username":"Aga","action":{"action":"WinGame"}}
+        ]"#;
+
+        let actions = serde_json::from_str(actions).unwrap();
+
+        let settings = r#"{"format":"Cribbage","title":"Incredible Festivity","max_players":2,"target_score":121,"start_time":null}"#;
+        let settings = serde_json::from_str(settings).unwrap();
+        let seed = r#"{"rng":"ChaCha8","seed":"ec78f745442d4ffd6d55ddf9bc38ec4de2c100c10585a2bac225258548c51d80"}"#;
+        let seed = serde_json::from_str(seed).unwrap();
+
+        test_game(actions, settings, seed);
+    }
 }
index 5d2922778242f74256707e3b4708fc6247cd4b39..29fe60eba8f4f43a8a14c12bce5e0fafd0b8e0e4 100644 (file)
@@ -3,11 +3,15 @@ use std::cmp::{Eq, Ord};
 use getrandom::getrandom;
 use itertools::Itertools;
 use rand::{seq::IteratorRandom, CryptoRng, Error, Rng, RngCore, SeedableRng};
-use rand_chacha::ChaCha20Rng;
+use rand_chacha::{ChaCha8Rng, ChaCha20Rng};
 
 #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
 #[serde(tag = "rng")]
 pub enum Seed {
+    ChaCha8 {
+        #[serde(with = "hex")]
+        seed: [u8; 32],
+    },
     ChaCha20 {
         #[serde(with = "hex")]
         seed: [u8; 32],
@@ -15,14 +19,15 @@ pub enum Seed {
 }
 
 impl Seed {
-    pub fn cha_cha_20_from_entropy() -> Self {
+    pub fn cha_cha_8_from_entropy() -> Self {
         let mut seed = [0u8; 32];
         getrandom(&mut seed).expect("getrandom failed when generating random seed");
-        Self::ChaCha20 { seed }
+        Self::ChaCha8 { seed }
     }
 
     pub fn into_rng(self) -> WaveRng {
         match self {
+            Seed::ChaCha8 { seed } => WaveRng::ChaCha8(ChaCha8Rng::from_seed(seed)),
             Seed::ChaCha20 { seed } => WaveRng::ChaCha20(ChaCha20Rng::from_seed(seed)),
         }
     }
@@ -30,42 +35,117 @@ impl Seed {
 
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub enum WaveRng {
+    ChaCha8(ChaCha8Rng),
     ChaCha20(ChaCha20Rng),
 }
 
 impl WaveRng {
     pub fn advance(&mut self) {
-        let _: [u8; 32] = match self {
-            WaveRng::ChaCha20(rng) => rng.gen(),
+        match self {
+            WaveRng::ChaCha8(rng) => {
+                rng.next_u32();
+                rng.next_u32();
+                rng.next_u32();
+                rng.next_u32();
+            }
+            WaveRng::ChaCha20(rng) => {
+                let _: [u8; 32] = rng.gen();
+            }
         };
     }
 
-    pub fn choose_from<T: Eq + Ord, I: IntoIterator<Item = T>>(&mut self, elements: I) -> Option<T> {
-        elements.into_iter().sorted().choose_stable(self)
+    pub fn choose_from<T, I>(&mut self, elements: I) -> Option<T>
+        where
+            T: Eq + Ord,
+            I: IntoIterator<Item = T>,
+    {
+        let mut vec = Vec::from_iter(elements);
+        let len = vec.len() as u32;
+        if len == 0 {
+            return None;
+        }
+        let mut i = 0;
+        match self {
+            WaveRng::ChaCha8(rng) => {
+                // Prevent bias by restricting the generated range
+                // to one less than a multiple of len.
+                let zone = u32::MAX - (u32::MAX - len + 1) % len;
+                // Prevent the rng from being called more than 4
+                // times for one random choice. This adds a tiny
+                // bias, but beyond 4 the sequence would be biased
+                // anyway since advance() only moves forward by 4.
+                for _ in 0..4 {
+                    i = rng.next_u32();
+                    if i <= zone { break; }
+                }
+                i %= len
+            },
+            WaveRng::ChaCha20(rng) => {
+                // Legacy implementation to match the `choose_stable`
+                // function on a sorted iterator. This calls rng.gen
+                // too many times so that calling
+                // clone().choose_from(), advance(), choose_from()
+                // will get results that are autocorrelated.
+                // Choose 1 with probability 1/1,
+                // 2 with probability 1/2,
+                // 3 with probability 1/3 and so on.
+                // Taking the highest such chosen means every number
+                // will have a probability of 1/len of being chosen.
+                // Then subtract 1.
+                for j in 1..=len {
+                    // For power-of-two j (e.g. 1, 2, 4) this restricts
+                    // the zone to 50% of the range of u32, meaning in
+                    // the loop below we take an average of 2 iterations
+                    // and potentially many more than that.
+                    // Especially for j == 1 (i.e. 100% probability),
+                    // this seems especially stupid.
+                    let zone = (j << j.leading_zeros()) - 1;
+                    loop {
+                        let v = rng.next_u32() as u64 * j as u64;
+                        // Exclude the result if the low bits are outside of 0..=zone
+                        // to prevent bias in the result.
+                        if v as u32 <= zone {
+                            // The high bits are in the range 0..j
+                            // Compare equal to zero to select the result 1/j times
+                            if v >> 32 == 0 {
+                                i = j - 1;
+                            }
+                            break;
+                        }
+                    }
+                }
+            },
+        }
+        vec.select_nth_unstable(i as usize);
+        Some(vec.swap_remove(i as usize))
     }
 }
 
 impl RngCore for WaveRng {
     fn next_u32(&mut self) -> u32 {
         match self {
+            WaveRng::ChaCha8(rng) => rng.next_u32(),
             WaveRng::ChaCha20(rng) => rng.next_u32(),
         }
     }
 
     fn next_u64(&mut self) -> u64 {
         match self {
+            WaveRng::ChaCha8(rng) => rng.next_u64(),
             WaveRng::ChaCha20(rng) => rng.next_u64(),
         }
     }
 
     fn fill_bytes(&mut self, dest: &mut [u8]) {
         match self {
+            WaveRng::ChaCha8(rng) => rng.fill_bytes(dest),
             WaveRng::ChaCha20(rng) => rng.fill_bytes(dest),
         }
     }
 
     fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
         match self {
+            WaveRng::ChaCha8(rng) => rng.try_fill_bytes(dest),
             WaveRng::ChaCha20(rng) => rng.try_fill_bytes(dest),
         }
     }
@@ -118,4 +198,42 @@ mod tests {
 
         assert_eq!(Some(44), rng.choose_from(set));
     }
+
+    #[test]
+    fn stable_set_values_chacha8() {
+        let seed_json = r#"{"rng":"ChaCha8","seed":"ec78f745442d4ffd6d55ddf9bc38ec4de2c100c10585a2bac225258548c51d81"}"#;
+        let seed: Seed = serde_json::from_str(seed_json).unwrap();
+        let mut rng = seed.into_rng();
+
+        let mut set = HashSet::new();
+        for i in 0..100 {
+            set.insert(i);
+        }
+
+        assert_eq!(Some(4), rng.choose_from(set));
+    }
+
+    #[test]
+    fn chacha8_free_from_bias() {
+        let seed_json = r#"{"rng":"ChaCha8","seed":"ec78f745442d4ffd6d55ddf9bc38ec4de2c100c10585a2bac225258548c51d82"}"#;
+        let seed: Seed = serde_json::from_str(seed_json).unwrap();
+        let mut rng = seed.into_rng();
+        let mut count = [0; 100];
+
+        let mut set = HashSet::new();
+        for i in 0..100 {
+            set.insert(i);
+        }
+
+        for _ in 0..1_000_000 {
+            count[*rng.choose_from(&set).unwrap()] += 1;
+        }
+
+        for x in &count[..] {
+            assert!(9_500 <= *x && *x <= 10_500);
+        }
+
+        assert!(count[..50].iter().sum::<u32>() <= 501_000);
+        assert!(count[50..].iter().sum::<u32>() <= 501_000);
+    }
 }
index 796698e9c8c1ad7be03febb1f68ccedd360dcd7c..c03d02e13cf3e6608a17a5d8cd633dbb393b08e6 100644 (file)
@@ -128,7 +128,7 @@ impl ServerState {
         self.redis.hset(&status_key, "completed", AsJson(completed)).await?;
         self.redis.hset(&status_key, "players", AsJson(players)).await?;
         self.redis.hset(&status_key, "winner", AsJson(winner)).await?;
-        self.redis.set(game_seed_key(id), AsJson(Seed::cha_cha_20_from_entropy())).await?;
+        self.redis.set(game_seed_key(id), AsJson(Seed::cha_cha_8_from_entropy())).await?;
         self.redis.rpush("game:list", id).await?;
         Ok(id)
     }