Suppose we have `total_information` choices, and we first use them to
encode the answer `x` to a question with `m` answers. That answer is encoded
by the choice we take modulo `m`.
How much "information" do we have left? That depends on the number of
numbers less than `total_information` that are equal to `x` modulo `m`.
Depending on the value of `x`, this is either
`floor(total_information/m)` or `floor(total_information/m) + 1`.
We now use all of this information as opposed to just
`floor(total_information/m)`, at the cost of making our math not a lot
more complicated but pretty confusing.
- If a player discards because nobody else needed a hint, that
information is "transmitted" before they transmit other info through
hat stuff.
- The "card possibility partition" questions now take into account info
learned through earlier questions.
One important change is that now, when deciding which questions to ask, they can see the answer to the last question before asking the next one.
Some design choices:
- Questions now take a BoardState instead of an OwnedGameView.
- When deciding which questions to ask (in ask_questions), we get an immutable public information object
(representing the public information before any questions were asked), and a mutable HandInfo<CardPossibilityTable>
that gets updated as we ask questions. That HandInfo<CardPossibilityTable> was copied instead of taken.
- In ask_questions, we also get some &mut u32 representing "info_remaining" that gets updated for us.
This will later allow for cases where "info_remaining" depends on the answers to previous questions.
- Both get_hint_sum and update_from_hint_sum change the public information object. If you want to compute the
hint sum but aren't sure if you actually want to give the hint, you'll have to clone the public information
object!
- Over time, in the code to decide on a move, we'll be able to build an increasingly complicated tree of
"public information object operations" that will have to be matched exactly in the code to update on a move.
In order to make this less scary, I moved most of the code into
"decide_wrapped" and "update_wrapped". If the call to update_wrapped
(for the player who just made the move) changes the public information
object in different ways than the previous call to decide_wrapped, we
detect this and panic.
This commit should be purely refactoring; all changes to win-rates are
due to bugs.
This has the advantage that for some ModulusInformation object m and
some modulus,
```
a = m.split(modulus);
m.cast_down(x);
```
is equivalent to
```
m.cast_down(x*modulus);
a = m.split(modulus);
```
This means that when using a ModulusInformation object to answer
questions, we can answer the first question without needing to know how
many questions there are.
"Needing a hint" is defined as having a playable card, but not knowing
any specific card is playable. Thus, if a player doesn't know any
playable cards and someone else discards, the player (publicly)
concludes that all their cards are unplayable.
I think most of the gains of this change come from the fact
that now we don't discard any potentially useful cards if we have more
than 4 hints remaining.
- When there are less than 5 players, and we're near the discard threshold, prefer
hinting over discarding, even if there are known useless cards.
- We now ask questions like "what's the first playable card in this list?"
This means that if a playable card is in the asking player's list,
the player will learn that it's playable, and that every card before
it is not playable.
Additionally, if a player doesn't know of any dead cards in their hand
and there is enough information available, we use this mechanism so that
if the player doesn't have a playable card, they will learn about one
dead card in their hand.
(These were two commits that got joined in a rebase accident, sorry.)