From: Geoffrey Allott Date: Wed, 7 Jun 2023 21:43:14 +0000 (+0100) Subject: add ChaCha8 rng with better properties X-Git-Url: https://git.pointlesshacks.com/?a=commitdiff_plain;h=db1f725aa0f063c5656717b2dad8ddac7ca98791;p=pokerwave.git add ChaCha8 rng with better properties --- diff --git a/src/card.rs b/src/card.rs index 7391a73..042b99f 100644 --- a/src/card.rs +++ b/src/card.rs @@ -202,6 +202,11 @@ impl IntoIterator for CardSet { impl Iterator for CardSetIntoIter { type Item = Card; + fn size_hint(&self) -> (usize, Option) { + let len = self.inner.len(); + (len, Some(len)) + } + fn next(&mut self) -> Option { if self.inner.repr == 0 { None diff --git a/src/game/cribbage/mod.rs b/src/game/cribbage/mod.rs index 4c93851..7afbbcd 100644 --- a/src/game/cribbage/mod.rs +++ b/src/game/cribbage/mod.rs @@ -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); + } } diff --git a/src/rng.rs b/src/rng.rs index 5d29227..29fe60e 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -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>(&mut self, elements: I) -> Option { - elements.into_iter().sorted().choose_stable(self) + pub fn choose_from(&mut self, elements: I) -> Option + where + T: Eq + Ord, + I: IntoIterator, + { + 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::() <= 501_000); + assert!(count[50..].iter().sum::() <= 501_000); + } } diff --git a/src/server.rs b/src/server.rs index 796698e..c03d02e 100644 --- a/src/server.rs +++ b/src/server.rs @@ -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) }