///|
/// 示例共用的小世界,支持 1..8 个玩家。
///
/// 每个玩家各有 3 个按键,状态是单个 64 位整数,每帧把所有人的输入按下标
/// 混进同一个数:只要漏掉一帧输入、或者重放顺序不对,状态就会立刻分叉。
/// 示例里看到的回滚与分叉检测因此是真实发生的,不是摆拍。
pub struct Scene {
  mut x : UInt64
}

///|
pub fn Scene::new() -> Scene {
  // 选个非零初值,避免「全 0 状态 + 全 0 输入」把问题掩盖掉。
  { x: 0x243F6A8885A308D3UL, }
}

///|
pub fn Scene::state(self : Scene) -> UInt64 {
  self.x
}

///|
pub impl @session.Sim for Scene with fn save(self) -> Bytes {
  Bytes::makei(8, fn(i) { ((self.x >> (i * 8)) & 0xFFUL).to_int().to_byte() })
}

///|
pub impl @session.Sim for Scene with fn restore(self, bytes : Bytes) -> Unit {
  let mut x = 0UL
  let mut i = 0
  while i < 8 {
    x = x | (bytes[i].to_int().to_uint64() << (i * 8))
    i = i + 1
  }
  self.x = x
}

///|
pub impl @session.Sim for Scene with fn step_players(
  self,
  inputs : Array[UInt64],
) -> Unit {
  // 每个玩家的输入乘一个互不相同的常数再加进状态,最后整体做一次乘法与
  // xor-shift:任何一路缺失都会扩散到全部位,玩家之间也不会互相抵消。
  let mut mixed = self.x
  let mut i = 0
  for bits in inputs {
    let salt = 0x9E3779B97F4A7C15UL >> (i * 8)
    mixed = mixed + (bits & 7UL) * salt
    i = i + 1
  }
  let m = mixed * 0xBF58476D1CE4E5B9UL + 0x94D049BB133111EBUL
  self.x = m ^ (m >> 29)
}

///|
pub impl @session.Sim for Scene with fn hash(self) -> UInt64 {
  @digest.hash_u64(self.x)
}

///|
/// 显式声明「把 `Sim` 的方法提升为 `Scene` 的方法」,消除 W0079。
pub extend Scene with @session.Sim::{save, restore, step_players, hash}

///|
/// 确定性的「玩家操作脚本」:每 6 帧换一次按键组合,各玩家相位错开。
///
/// 示例不接键盘,是为了让输出可以逐位复现——`moon run` 两次结果必须一样。
pub fn scripted_input(frame : Int32, player : Int32) -> UInt64 {
  let phase = frame / 6 + player * 3
  match phase % 5 {
    0 => 0b001UL
    1 => 0b011UL
    2 => 0b111UL
    3 => 0b100UL
    _ => 0b010UL
  }
}