Fair Play
This page describes how Agari shuffles tiles and what information the CPU opponents use when they play. The relevant source code is included so the behavior can be inspected directly.
The shuffle
Every hand begins from the full 136-tile set: the manzu, pinzu, and souzu suits, the four winds, the three dragons, and the red fives (if you have them enabled in settings). That set is shuffled with a Fisher-Yates shuffle, driven by a ChaCha-based cryptographic random number generator that is rooted in your operating system's entropy. Each new game produces an independent, uniform shuffle with no carryover between games. In single-player, Agari additionally records that game's seed, so you can see it and optionally replay the exact same deal.
The function below builds and shuffles the wall, from the game's source:
/// Build a new shuffled wall with the given configuration.
pub fn new<R: Rng>(rng: &mut R, config: &WallConfig) -> Self {
let mut tiles = build_tile_set(config);
tiles.shuffle(rng);
// Last 14 tiles become the dead wall
let dead_tiles: [TileInstance; 14] = tiles[122..136]
.try_into()
.expect("dead wall must be 14 tiles");
let dead = DeadWall::from_tiles(&dead_tiles);
let live = tiles[..122].to_vec();
Self { live, dead }
}build_tile_set assembles the 136 tiles, shuffle performs the Fisher-Yates pass, and the result is divided into the live wall and the dead wall.
The rng passed into that function is created fresh for each game. In multiplayer, it is seeded directly from the operating system:
let mut rng = rand::rngs::StdRng::from_entropy();In single-player, the game first draws a 64-bit master seed from the operating system, then derives the wall's generator from it:
// A fresh master seed from the operating system's entropy:
let master_seed = rand::random::<u64>();
// The wall generator for the deal is derived from that master seed:
let mut rng = rand::rngs::StdRng::seed_from_u64(wall_seed(master_seed, 0));StdRng is the rand crate's standard generator (currently the ChaCha stream cipher). Both from_entropy() and rand::random() seed from your operating system's entropy source, so the shuffle is uniform and unpredictable either way. The single-player master seed is recorded and shown to you, so you can replay the exact same deal by entering its code when you start a new game. On a replay, the master seed comes from that code instead of being drawn fresh. Multiplayer walls are not recorded or shown. A seed reproduces a deal, but a multiplayer game also depends on the live choices of the other human players, which a seed cannot capture, so a recorded seed could not reproduce the match.
What the CPU opponents can see
Beyond its own concealed hand, a CPU reads only public information: the discards, the open melds, the revealed dora indicators, the riichi declarations, the seat winds, and the current scores. The function below builds the picture of visible tiles the AI works from: every discard, every tile in an open meld, and every revealed dora indicator. Apart from the CPU's own hand, this is the entire set of tiles it is able to account for.
pub fn visible_counts(round: &RoundState) -> [u8; 34] {
let mut counts = [0u8; 34];
for player in &round.players {
for discard in &player.discards {
if !discard.was_called {
counts[tile_index(discard.tile.tile)] += 1;
}
}
for meld in &player.melds {
for ti in &meld.tiles {
counts[tile_index(ti.tile)] += 1;
}
}
}
for &t in &round.wall.dead.dora_indicators[..round.wall.dead.revealed_dora] {
counts[tile_index(t.tile)] += 1;
}
counts
}The information the CPUs read about another player is their discards, their open melds, whether they have declared riichi, their seat, and their score, all of which are visible to everyone at the table.
The CPUs never read another player's concealed hand and never read the order of tiles in the wall.
Continue and New Game
Selecting Continue on a single-player match restores the exact saved state, including the wall, so play resumes from where it ended. New Game draws a fresh master seed and generates a new shuffle, unless you enter a seed code, in which case the deal is reproduced from that code.